From fcf8d544d6ca8891cea5f5d299098c7a9eecf5c9 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Sun, 24 Sep 2023 18:49:20 +0300 Subject: [PATCH 01/24] feat: class based rules api --- .../Rules/Contracts/StringBasedRule.php | 23 ++++++++ .../Rules/Contracts/TypeBasedRule.php | 13 +++++ .../RulesExtractor/Rules/EnumRule.php | 43 ++++++++++++++ .../RulesExtractor/Rules/InRule.php | 57 +++++++++++++++++++ .../RulesExtractor/Rules/StringRule.php | 20 +++++++ .../RequestBodyExtensionTest.php | 4 ++ 6 files changed, 160 insertions(+) create mode 100644 src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/StringBasedRule.php create mode 100644 src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/TypeBasedRule.php create mode 100644 src/Support/OperationExtensions/RulesExtractor/Rules/EnumRule.php create mode 100644 src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php create mode 100644 src/Support/OperationExtensions/RulesExtractor/Rules/StringRule.php diff --git a/src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/StringBasedRule.php b/src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/StringBasedRule.php new file mode 100644 index 00000000..903e2aff --- /dev/null +++ b/src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/StringBasedRule.php @@ -0,0 +1,23 @@ +validate(['field' => 'in:foo,bar']) + * ``` + * + * `shouldHandle` will receive `'in'` as `$rule` argument. + * `handle` will receive `'in'` as `$rule` and `['foo', 'bar']` as `$parameters`. + */ +interface StringBasedRule +{ + public function shouldHandle(string $rule): bool; + + public function handle(OpenApiType $previousType, string $rule, array $parameters): OpenApiType; +} diff --git a/src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/TypeBasedRule.php b/src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/TypeBasedRule.php new file mode 100644 index 00000000..a884e845 --- /dev/null +++ b/src/Support/OperationExtensions/RulesExtractor/Rules/Contracts/TypeBasedRule.php @@ -0,0 +1,13 @@ +isInstanceOf(Enum::class); + } + + public function handle(OpenApiType $previousType, Type $rule): OpenApiType + { + $enumName = $this->getEnumName($rule); + + return $this->openApiTransformer->transform( + new ObjectType($enumName) + ); + } + + protected function getEnumName(Type $rule) + { + $enumTypePropertyType = $rule->getPropertyType('type'); + + if (!$enumTypePropertyType instanceof LiteralStringType) { + throw new \RuntimeException('Unexpected enum value type'); + } + + return $enumTypePropertyType->value; + } +} diff --git a/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php b/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php new file mode 100644 index 00000000..8afd75e0 --- /dev/null +++ b/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php @@ -0,0 +1,57 @@ +isInstanceOf(In::class); + } + + public function handle(OpenApiType $previousType, string|Type $rule, array $parameters = []): OpenApiType + { + $allowedValues = $this->getNormalizedValues($rule, $parameters); + + return $previousType->enum( + collect($allowedValues) + ->mapInto(Stringable::class) + ->map(fn (Stringable $v) => (string) $v->trim('"')->replace('""', '"')) + ->values() + ->all() + ); + } + + private function getNormalizedValues(Type|string $rule, array $parameters) + { + if (is_string($rule)) { + return $parameters; + } + + $valueType = $rule->getPropertyType('value'); + if (!$valueType instanceof ArrayType) { + throw new \RuntimeException('Invalid value type'); + } + + return collect($valueType->items) + ->map(fn (ArrayItemType_ $itemType) => $itemType->value) + ->filter(fn (Type $t) => $t instanceof LiteralStringType) + ->map(fn (LiteralStringType $t) => $t->value) + ->values() + ->all(); + } +} diff --git a/src/Support/OperationExtensions/RulesExtractor/Rules/StringRule.php b/src/Support/OperationExtensions/RulesExtractor/Rules/StringRule.php new file mode 100644 index 00000000..7e813faa --- /dev/null +++ b/src/Support/OperationExtensions/RulesExtractor/Rules/StringRule.php @@ -0,0 +1,20 @@ +validate(['foo' => 'file']); } } + +it('falls back to static analysis when cannot evaluate rules', function () { + +}); From a967b2b8ecdf10f74f52c684d01ce701320b53a4 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Sun, 24 Sep 2023 21:27:59 +0300 Subject: [PATCH 02/24] feat: ability to get type of any node from a method (maybe to be changed to indexing) --- src/Infer/Analyzer/MethodAnalysisResult.php | 16 +++++++++++ src/Infer/Analyzer/MethodAnalyzer.php | 11 +++++--- src/Infer/Definition/ClassDefinition.php | 14 +++++++++- src/Infer/Scope/ScopeTypeResolver.php | 27 +++++++++++++++++++ .../RequestBodyExtension.php | 10 +++---- .../RulesExtractor/ValidateCallExtractor.php | 11 +++++++- src/Support/RouteInfo.php | 20 ++++++++++++++ 7 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 src/Infer/Analyzer/MethodAnalysisResult.php create mode 100644 src/Infer/Scope/ScopeTypeResolver.php diff --git a/src/Infer/Analyzer/MethodAnalysisResult.php b/src/Infer/Analyzer/MethodAnalysisResult.php new file mode 100644 index 00000000..8fe70416 --- /dev/null +++ b/src/Infer/Analyzer/MethodAnalysisResult.php @@ -0,0 +1,16 @@ +traverseClassMethod( + $scope = $this->traverseClassMethod( [$this->getClassReflector()->getMethod($methodDefinition->type->name)->getAstNode()], $methodDefinition, ); @@ -38,7 +38,10 @@ public function analyze(FunctionLikeDefinition $methodDefinition) $methodDefinition->isFullyAnalyzed = true; - return $methodDefinition; + return new MethodAnalysisResult( + scope: $scope, + definition: $methodDefinition, + ); } private function getClassReflector(): ClassReflector @@ -55,7 +58,7 @@ private function traverseClassMethod(array $nodes, FunctionLikeDefinition $metho $traverser->addVisitor(new TypeInferer( $this->index, $nameResolver, - new Scope($this->index, new NodeTypesResolver(), new ScopeContext($this->classDefinition), $nameResolver), + $scope = new Scope($this->index, new NodeTypesResolver(), new ScopeContext($this->classDefinition), $nameResolver), Context::getInstance()->extensionsBroker->extensions, )); @@ -67,6 +70,6 @@ private function traverseClassMethod(array $nodes, FunctionLikeDefinition $metho $traverser->traverse(Arr::wrap($node)); - return $node; + return $scope; } } diff --git a/src/Infer/Definition/ClassDefinition.php b/src/Infer/Definition/ClassDefinition.php index db903c02..34c8aedc 100644 --- a/src/Infer/Definition/ClassDefinition.php +++ b/src/Infer/Definition/ClassDefinition.php @@ -20,6 +20,8 @@ class ClassDefinition { + private array $methodsScopes = []; + public function __construct( // FQ name public string $name, @@ -52,10 +54,13 @@ public function getMethodDefinition(string $name, Scope $scope = new GlobalScope $methodDefinition = $this->methods[$name]; if (! $methodDefinition->isFullyAnalyzed()) { - $this->methods[$name] = (new MethodAnalyzer( + $result = (new MethodAnalyzer( $scope->index, $this ))->analyze($methodDefinition); + + $this->methodsScopes[$name] = $result->scope; + $this->methods[$name] = $result->definition; } $methodScope = new Scope( @@ -111,4 +116,11 @@ private function replaceTemplateInType(Type $type, array $templateTypesMap) return $type; } + + public function getMethodScope(string $methodName) + { + $this->getMethodDefinition($methodName); + + return $this->methodsScopes[$methodName] ?? null; + } } diff --git a/src/Infer/Scope/ScopeTypeResolver.php b/src/Infer/Scope/ScopeTypeResolver.php new file mode 100644 index 00000000..85eb4638 --- /dev/null +++ b/src/Infer/Scope/ScopeTypeResolver.php @@ -0,0 +1,27 @@ +scope->getType($node); + + if (! $type) { + return null; + } + + return (new ReferenceTypeResolver($this->scope->index)) + ->resolve($this->scope, $type) + ->mergeAttributes($type->attributes()); + } +} diff --git a/src/Support/OperationExtensions/RequestBodyExtension.php b/src/Support/OperationExtensions/RequestBodyExtension.php index 424c6803..d6737243 100644 --- a/src/Support/OperationExtensions/RequestBodyExtension.php +++ b/src/Support/OperationExtensions/RequestBodyExtension.php @@ -25,7 +25,7 @@ public function handle(Operation $operation, RouteInfo $routeInfo) $description = Str::of($routeInfo->phpDoc()->getAttribute('description')); try { - $bodyParams = $this->extractParamsFromRequestValidationRules($routeInfo->route, $routeInfo->methodNode()); + $bodyParams = $this->extractParamsFromRequestValidationRules($routeInfo->route, $routeInfo->methodNode(), $routeInfo); $mediaType = $this->getMediaType($operation, $routeInfo, $bodyParams); @@ -88,14 +88,14 @@ protected function hasBinary($bodyParams): bool }); } - protected function extractParamsFromRequestValidationRules(Route $route, ?ClassMethod $methodNode) + protected function extractParamsFromRequestValidationRules(Route $route, ?ClassMethod $methodNode, $routeInfo) { - [$rules, $nodesResults] = $this->extractRouteRequestValidationRules($route, $methodNode); + [$rules, $nodesResults] = $this->extractRouteRequestValidationRules($route, $methodNode, $routeInfo); return (new RulesToParameters($rules, $nodesResults, $this->openApiTransformer))->handle(); } - protected function extractRouteRequestValidationRules(Route $route, $methodNode) + protected function extractRouteRequestValidationRules(Route $route, $methodNode, $routeInfo) { $rules = []; $nodesResults = []; @@ -109,7 +109,7 @@ protected function extractRouteRequestValidationRules(Route $route, $methodNode) } if (($validateCallExtractor = new ValidateCallExtractor($methodNode))->shouldHandle()) { - if ($validateCallRules = $validateCallExtractor->extract()) { + if ($validateCallRules = $validateCallExtractor->extract($routeInfo)) { $rules = array_merge($rules, $validateCallRules); $nodesResults[] = $validateCallExtractor->node(); } diff --git a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php index 9198801a..99bafaf2 100644 --- a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php +++ b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php @@ -2,7 +2,12 @@ namespace Dedoc\Scramble\Support\OperationExtensions\RulesExtractor; +use Dedoc\Scramble\Infer; +use Dedoc\Scramble\Infer\Scope\GlobalScope; +use Dedoc\Scramble\Infer\Scope\Scope; +use Dedoc\Scramble\Support\RouteInfo; use Illuminate\Http\Request; +use Illuminate\Routing\Route; use PhpParser\Node; use PhpParser\NodeFinder; use PhpParser\PrettyPrinter\Standard; @@ -73,11 +78,15 @@ public function node(): ?ValidationNodesResult ); } - public function extract() + public function extract(RouteInfo $routeInfo) { $methodNode = $this->handle; $validationRules = $this->node()->node ?? null; + if ($validationRules) { + $type = $routeInfo->getMethodScopeTypeResolver()->getType($validationRules); + } + if ($validationRules) { $printer = new Standard(); $validationRulesCode = $printer->prettyPrint([$validationRules]); diff --git a/src/Support/RouteInfo.php b/src/Support/RouteInfo.php index 4ac197ed..9f088ab5 100644 --- a/src/Support/RouteInfo.php +++ b/src/Support/RouteInfo.php @@ -4,6 +4,7 @@ use Dedoc\Scramble\Infer; use Dedoc\Scramble\Infer\Reflector\MethodReflector; +use Dedoc\Scramble\Infer\Scope\ScopeTypeResolver; use Dedoc\Scramble\Infer\Services\FileParser; use Dedoc\Scramble\PhpDoc\PhpDocTypeHelper; use Dedoc\Scramble\Support\Type\BooleanType; @@ -220,4 +221,23 @@ public function getMethodType(): ?FunctionType return $this->methodType; } + + /** + * Returns a scope type resolver which allows to get the type of any + * node from the method body. + * + * @internal - not sure if this is here to stick + */ + public function getMethodScopeTypeResolver(): ?ScopeTypeResolver + { + if (!$this->isClassBased() || !$this->getMethodType()) { + return null; + } + + $methodScope = $this->infer + ->analyzeClass($this->className()) + ->getMethodScope($this->methodName()); + + return new ScopeTypeResolver($methodScope); + } } From b498847c37830d23d3530720ce972722273cc031 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Sun, 24 Sep 2023 22:33:30 +0300 Subject: [PATCH 03/24] feat: added concated strings type --- src/Infer/Handler/ConcatHandler.php | 29 +++++++++++++++++++ src/Infer/TypeInferer.php | 2 ++ .../RulesExtractor/ValidateCallExtractor.php | 7 ++--- src/Support/Type/ConcatenatedStringType.php | 28 ++++++++++++++++++ src/Support/Type/TypeHelper.php | 11 +++++++ tests/Infer/Scope/ScopeTest.php | 11 +++++++ 6 files changed, 84 insertions(+), 4 deletions(-) create mode 100644 src/Infer/Handler/ConcatHandler.php create mode 100644 src/Support/Type/ConcatenatedStringType.php diff --git a/src/Infer/Handler/ConcatHandler.php b/src/Infer/Handler/ConcatHandler.php new file mode 100644 index 00000000..73094b61 --- /dev/null +++ b/src/Infer/Handler/ConcatHandler.php @@ -0,0 +1,29 @@ +setType( + $node, + new ConcatenatedStringType(TypeHelper::flattenStringConcatTypes([ + $scope->getType($node->left), + $scope->getType($node->right), + ])), + ); + } +} diff --git a/src/Infer/TypeInferer.php b/src/Infer/TypeInferer.php index e0a442bb..1cd84902 100644 --- a/src/Infer/TypeInferer.php +++ b/src/Infer/TypeInferer.php @@ -9,6 +9,7 @@ use Dedoc\Scramble\Infer\Handler\ArrayItemHandler; use Dedoc\Scramble\Infer\Handler\AssignHandler; use Dedoc\Scramble\Infer\Handler\ClassHandler; +use Dedoc\Scramble\Infer\Handler\ConcatHandler; use Dedoc\Scramble\Infer\Handler\CreatesScope; use Dedoc\Scramble\Infer\Handler\ExceptionInferringExtensions; use Dedoc\Scramble\Infer\Handler\ExpressionTypeInferringExtensions; @@ -41,6 +42,7 @@ public function __construct( $this->handlers = [ new FunctionLikeHandler(), new AssignHandler(), + new ConcatHandler(), new ClassHandler(), new PropertyHandler(), new ArrayHandler(), diff --git a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php index 99bafaf2..7bda6e7e 100644 --- a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php +++ b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php @@ -2,12 +2,8 @@ namespace Dedoc\Scramble\Support\OperationExtensions\RulesExtractor; -use Dedoc\Scramble\Infer; -use Dedoc\Scramble\Infer\Scope\GlobalScope; -use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\RouteInfo; use Illuminate\Http\Request; -use Illuminate\Routing\Route; use PhpParser\Node; use PhpParser\NodeFinder; use PhpParser\PrettyPrinter\Standard; @@ -85,6 +81,9 @@ public function extract(RouteInfo $routeInfo) if ($validationRules) { $type = $routeInfo->getMethodScopeTypeResolver()->getType($validationRules); +// dump([ +// $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), +// ]); } if ($validationRules) { diff --git a/src/Support/Type/ConcatenatedStringType.php b/src/Support/Type/ConcatenatedStringType.php new file mode 100644 index 00000000..af976bae --- /dev/null +++ b/src/Support/Type/ConcatenatedStringType.php @@ -0,0 +1,28 @@ + $p->toString(), $this->parts)).')'; + } +} diff --git a/src/Support/Type/TypeHelper.php b/src/Support/Type/TypeHelper.php index 686481d5..0f23922e 100644 --- a/src/Support/Type/TypeHelper.php +++ b/src/Support/Type/TypeHelper.php @@ -146,4 +146,15 @@ public static function createTypeFromValue(mixed $value) return null; // @todo: object } + + /** + * @param Type[] $parts + */ + public static function flattenStringConcatTypes(array $parts): array + { + return collect($parts) + ->flatMap(fn ($t) => $t instanceof ConcatenatedStringType ? $t->parts : [$t]) + ->values() + ->all(); + } } diff --git a/tests/Infer/Scope/ScopeTest.php b/tests/Infer/Scope/ScopeTest.php index 64c47c8d..d1d24c31 100644 --- a/tests/Infer/Scope/ScopeTest.php +++ b/tests/Infer/Scope/ScopeTest.php @@ -11,3 +11,14 @@ function getStatementTypeForScopeTest(string $statement, array $extensions = []) ['$foo->bar', 'unknown'], ['$foo->bar->{"baz"}', 'unknown'], ]); + +it('infers concat string type', function ($code, $expectedTypeString) { + expect(getStatementTypeForScopeTest($code)->toString())->toBe($expectedTypeString); +})->with([ + ['"a"."b"."c"', 'string(string(a), string(b), string(c))'], +]); +it('infers concat string type with unknowns', function ($code, $expectedTypeString) { + expect(getStatementTypeForScopeTest($code)->toString())->toBe($expectedTypeString); +})->with([ + ['"a"."b".auth()->user()->id', 'string(string(a), string(b), unknown)'], +]); From 91024ef38245f7777cba77c33cb4b7417beb1ff5 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Tue, 26 Sep 2023 22:57:36 +0300 Subject: [PATCH 04/24] feat: static methods call support --- .../Event/Concerns/ArgumentTypesAware.php | 16 ++++++ .../Extensions/Event/MethodCallEvent.php | 3 + .../Event/StaticMethodCallEvent.php | 29 ++++++++++ src/Infer/Extensions/ExtensionsBroker.php | 16 ++++++ .../StaticMethodReturnTypeExtension.php | 13 +++++ src/Infer/Scope/Scope.php | 39 ++++++++++++- src/Infer/Services/ReferenceTypeResolver.php | 57 +++++++++++++++++-- src/ScrambleServiceProvider.php | 2 + src/Support/InferExtensions/RuleExtension.php | 44 ++++++++++++++ .../StaticMethodCallReferenceType.php | 36 ++++++++++++ .../Services/ReferenceTypeResolverTest.php | 40 +++++++++++++ tests/InferExtensions/RuleExtensionTest.php | 17 ++++++ 12 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php create mode 100644 src/Infer/Extensions/Event/StaticMethodCallEvent.php create mode 100644 src/Infer/Extensions/StaticMethodReturnTypeExtension.php create mode 100644 src/Support/InferExtensions/RuleExtension.php create mode 100644 src/Support/Type/Reference/StaticMethodCallReferenceType.php create mode 100644 tests/Infer/Services/ReferenceTypeResolverTest.php create mode 100644 tests/InferExtensions/RuleExtensionTest.php diff --git a/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php b/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php new file mode 100644 index 00000000..31bda728 --- /dev/null +++ b/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php @@ -0,0 +1,16 @@ +arguments[$name] ?? $this->arguments[$position] ?? $default; + } +} diff --git a/src/Infer/Extensions/Event/MethodCallEvent.php b/src/Infer/Extensions/Event/MethodCallEvent.php index 7d6be8f4..ac1cdab6 100644 --- a/src/Infer/Extensions/Event/MethodCallEvent.php +++ b/src/Infer/Extensions/Event/MethodCallEvent.php @@ -2,11 +2,14 @@ namespace Dedoc\Scramble\Infer\Extensions\Event; +use Dedoc\Scramble\Infer\Extensions\Event\Concerns\ArgumentTypesAware; use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\ObjectType; class MethodCallEvent { + use ArgumentTypesAware; + public function __construct( public readonly ObjectType $instance, public readonly string $name, diff --git a/src/Infer/Extensions/Event/StaticMethodCallEvent.php b/src/Infer/Extensions/Event/StaticMethodCallEvent.php new file mode 100644 index 00000000..7ae82196 --- /dev/null +++ b/src/Infer/Extensions/Event/StaticMethodCallEvent.php @@ -0,0 +1,29 @@ +scope->index->getClassDefinition($this->callee); + } + + public function getName() + { + return $this->callee; + } +} diff --git a/src/Infer/Extensions/ExtensionsBroker.php b/src/Infer/Extensions/ExtensionsBroker.php index 2de4263c..f5d6e8be 100644 --- a/src/Infer/Extensions/ExtensionsBroker.php +++ b/src/Infer/Extensions/ExtensionsBroker.php @@ -40,4 +40,20 @@ public function getMethodReturnType($event) return null; } + + public function getStaticMethodReturnType($event) + { + $extensions = array_filter($this->extensions, function ($e) use ($event) { + return $e instanceof StaticMethodReturnTypeExtension + && $e->shouldHandle($event->getName()); + }); + + foreach ($extensions as $extension) { + if ($propertyType = $extension->getStaticMethodReturnType($event)) { + return $propertyType; + } + } + + return null; + } } diff --git a/src/Infer/Extensions/StaticMethodReturnTypeExtension.php b/src/Infer/Extensions/StaticMethodReturnTypeExtension.php new file mode 100644 index 00000000..4d7a96af --- /dev/null +++ b/src/Infer/Extensions/StaticMethodReturnTypeExtension.php @@ -0,0 +1,13 @@ +name instanceof Node\Identifier) { + return $type; + } + + // Only string class names support. + if (! $node->class instanceof Node\Name) { + return $type; + } + + return $this->setType( + $node, + new StaticMethodCallReferenceType($node->class->toString(), $node->name->name, $this->getArgsTypes($node->args)), + ); + } + if ($node instanceof Node\Expr\PropertyFetch) { // Only string prop names support. if (! $name = ($node->name->name ?? null)) { @@ -152,8 +172,23 @@ private function getArgsTypes(array $args) { return collect($args) ->filter(fn ($arg) => $arg instanceof Node\Arg) - ->keyBy(fn (Node\Arg $arg, $index) => $arg->name ? $arg->name->name : $index) - ->map(fn (Node\Arg $arg) => $this->getType($arg->value)) + ->mapWithKeys(function (Node\Arg $arg, $index) { + $type = $this->getType($arg->value); + + if (! $arg->unpack) { + return [$arg->name ? $arg->name->name : $index => $type]; + } + + if (! $type instanceof ArrayType) { + return [$arg->name ? $arg->name->name : $index => $type]; // falling back, but not sure if we should. Maybe some DTO is needed to represent unpacked arg type? + } + + return collect($type->items) + ->mapWithKeys(fn (ArrayItemType_ $item, $i) => [ + $item->key ?: $index + $i => $item->value, + ]) + ->all(); + }) ->toArray(); } diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index 75cb04ed..e98793ea 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -5,6 +5,9 @@ use Dedoc\Scramble\Infer\Analyzer\ClassAnalyzer; use Dedoc\Scramble\Infer\Definition\ClassDefinition; use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; +use Dedoc\Scramble\Infer\Extensions\Event\MethodCallEvent; +use Dedoc\Scramble\Infer\Extensions\Event\StaticMethodCallEvent; +use Dedoc\Scramble\Infer\Extensions\ExtensionsBroker; use Dedoc\Scramble\Infer\Scope\Index; use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\CallableStringType; @@ -20,6 +23,7 @@ use Dedoc\Scramble\Support\Type\Reference\MethodCallReferenceType; use Dedoc\Scramble\Support\Type\Reference\NewCallReferenceType; use Dedoc\Scramble\Support\Type\Reference\PropertyFetchReferenceType; +use Dedoc\Scramble\Support\Type\Reference\StaticMethodCallReferenceType; use Dedoc\Scramble\Support\Type\SelfType; use Dedoc\Scramble\Support\Type\SideEffects\SelfTemplateDefinition; use Dedoc\Scramble\Support\Type\TemplateType; @@ -121,6 +125,10 @@ private function doResolve(Type $t, Type $type, Scope $scope) return $this->resolveMethodCallReferenceType($scope, $t); } + if ($t instanceof StaticMethodCallReferenceType) { + return $this->resolveStaticMethodCallReferenceType($scope, $t); + } + if ($t instanceof CallableCallReferenceType) { return $this->resolveCallableCallReferenceType($scope, $t); } @@ -194,6 +202,48 @@ private function resolveMethodCallReferenceType(Scope $scope, MethodCallReferenc return $this->getFunctionCallResult($methodDefinition, $type->arguments, $calleeType); } + private function resolveStaticMethodCallReferenceType(Scope $scope, StaticMethodCallReferenceType $type) + { + // (#self).listTableDetails() + // (#Doctrine\DBAL\Schema\Table).listTableDetails() + // (#TName).listTableDetails() + + $type->arguments = array_map( + // @todo: fix resolving arguments when deep arg is reference + fn ($t) => $t instanceof AbstractReferenceType ? $this->resolve($scope, $t) : $t, + $type->arguments, + ); + + // Assuming callee here can be only string of known name. Reality is more complex than + // that, but it is fine for now. + + // Attempting extensions broker before potentially giving up on type inference + if ($returnType = app(ExtensionsBroker::class)->getStaticMethodReturnType(new StaticMethodCallEvent( + callee: $type->callee, + name: $type->methodName, + scope: $scope, + arguments: $type->arguments, + ))) { + return $returnType; + } + + if ( + ! array_key_exists($type->callee, $this->index->classesDefinitions) + && ! $this->resolveUnknownClassResolver($type->callee) + ) { + return new UnknownType(); + } + + /** @var ClassDefinition $calleeDefinition */ + $calleeDefinition = $this->index->getClassDefinition($type->callee); + + if (! $methodDefinition = $calleeDefinition->getMethodDefinition($type->methodName, $scope)) { + return new UnknownType("Cannot get a method type [$type->methodName] on type [$type->callee]"); + } + + return $this->getFunctionCallResult($methodDefinition, $type->arguments); + } + private function resolveUnknownClassResolver(string $className): ?ClassDefinition { try { @@ -311,7 +361,7 @@ private function resolvePropertyFetchReferenceType(Scope $scope, PropertyFetchRe private function getFunctionCallResult( FunctionLikeDefinition $callee, array $arguments, - /* When this is a handling for method call */ + /* When this is a handling for non-static method call */ ObjectType|SelfType $calledOnType = null, ) { $returnType = $callee->type->getReturnType(); @@ -410,13 +460,10 @@ private function prepareArguments(?FunctionLikeDefinition $callee, array $realAr return $realArguments; } - /* - * @todo $realArguments for now is considered only by index, not by names. - */ return collect($callee->type->arguments) ->keys() ->map(function (string $name, int $index) use ($callee, $realArguments) { - return $realArguments[$index] ?? $callee->argumentsDefaults[$name] ?? null; + return $realArguments[$name] ?? $realArguments[$index] ?? $callee->argumentsDefaults[$name] ?? null; }) ->filter() ->values() diff --git a/src/ScrambleServiceProvider.php b/src/ScrambleServiceProvider.php index a15f5537..8bc84f76 100644 --- a/src/ScrambleServiceProvider.php +++ b/src/ScrambleServiceProvider.php @@ -23,6 +23,7 @@ use Dedoc\Scramble\Support\InferExtensions\PossibleExceptionInfer; use Dedoc\Scramble\Support\InferExtensions\ResourceCollectionTypeInfer; use Dedoc\Scramble\Support\InferExtensions\ResponseFactoryTypeInfer; +use Dedoc\Scramble\Support\InferExtensions\RuleExtension; use Dedoc\Scramble\Support\InferExtensions\ValidatorTypeInfer; use Dedoc\Scramble\Support\OperationBuilder; use Dedoc\Scramble\Support\OperationExtensions\ErrorResponsesExtension; @@ -73,6 +74,7 @@ public function configurePackage(Package $package): void $inferExtensionsClasses = array_merge([ ModelExtension::class, + RuleExtension::class, ], $inferExtensionsClasses); return array_merge( diff --git a/src/Support/InferExtensions/RuleExtension.php b/src/Support/InferExtensions/RuleExtension.php new file mode 100644 index 00000000..d79fad06 --- /dev/null +++ b/src/Support/InferExtensions/RuleExtension.php @@ -0,0 +1,44 @@ + match ($event->name) { + 'in' => new Generic(In::class, [ + $event->getArg('values', 0), + ]), + 'enum' => new Generic(Enum::class, [ + $event->getArg('type', 0), + ]), + 'unique' => new Generic(Unique::class, [ + $event->getArg('table', 0), + $event->getArg('column', 1, new LiteralStringType('NULL')), + ]), + 'exists' => new Generic(Exists::class, [ + $event->getArg('table', 0), + $event->getArg('column', 1, new LiteralStringType('NULL')), + ]), + default => null, + }); + } +} diff --git a/src/Support/Type/Reference/StaticMethodCallReferenceType.php b/src/Support/Type/Reference/StaticMethodCallReferenceType.php new file mode 100644 index 00000000..e8212a60 --- /dev/null +++ b/src/Support/Type/Reference/StaticMethodCallReferenceType.php @@ -0,0 +1,36 @@ + $t->toString(), $this->arguments), + ); + + return "(#{$this->callee})::{$this->methodName}($argsTypes)"; + } + + public function dependencies(): array + { + return [ + new MethodDependency($this->callee, $this->methodName), + ]; + } +} diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php new file mode 100644 index 00000000..c568c61c --- /dev/null +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -0,0 +1,40 @@ +getExpressionType("Foo::foo('wow')"); + + expect($type->toString())->toBe('string(wow)'); +}); + +it('infers static method call type with named args', function () { + $type = analyzeFile(<<<'EOD' +getExpressionType("Foo::foo(a: 'wow')"); + + expect($type->toString())->toBe('string(wow)'); +}); + +it('infers static method call type with named unpacked args', function () { + $type = analyzeFile(<<<'EOD' +getExpressionType("Foo::foo(...['a' => 'wow'])"); + + expect($type->toString())->toBe('string(wow)'); +}); diff --git a/tests/InferExtensions/RuleExtensionTest.php b/tests/InferExtensions/RuleExtensionTest.php new file mode 100644 index 00000000..09ed04f3 --- /dev/null +++ b/tests/InferExtensions/RuleExtensionTest.php @@ -0,0 +1,17 @@ +toString())->toBe($expectedType); +})->with([ + ['Illuminate\Validation\Rule::in(...["values" => ["foo", "bar"]])', 'Illuminate\Validation\Rules\In'], + ['Illuminate\Validation\Rule::in(values: ["foo", "bar"])', 'Illuminate\Validation\Rules\In'], + ['Illuminate\Validation\Rule::in(["foo", "bar"])', 'Illuminate\Validation\Rules\In'], +]); From f20ef4a0beb7acb69d1d349425ea072b9262b017 Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Tue, 26 Sep 2023 20:09:47 +0000 Subject: [PATCH 05/24] Fix styling --- src/Infer/Analyzer/MethodAnalysisResult.php | 3 +-- .../Extensions/Event/Concerns/ArgumentTypesAware.php | 2 +- src/Infer/Handler/ConcatHandler.php | 2 -- src/Infer/Services/ReferenceTypeResolver.php | 1 - src/Support/InferExtensions/RuleExtension.php | 1 - .../RulesExtractor/Rules/EnumRule.php | 2 +- .../RulesExtractor/Rules/InRule.php | 2 +- .../RulesExtractor/ValidateCallExtractor.php | 6 +++--- src/Support/RouteInfo.php | 2 +- src/Support/Type/ConcatenatedStringType.php | 2 +- .../Type/Reference/StaticMethodCallReferenceType.php | 2 -- src/Support/Type/TypeHelper.php | 2 +- tests/InferExtensions/RuleExtensionTest.php | 10 +++++----- 13 files changed, 15 insertions(+), 22 deletions(-) diff --git a/src/Infer/Analyzer/MethodAnalysisResult.php b/src/Infer/Analyzer/MethodAnalysisResult.php index 8fe70416..8f662274 100644 --- a/src/Infer/Analyzer/MethodAnalysisResult.php +++ b/src/Infer/Analyzer/MethodAnalysisResult.php @@ -10,7 +10,6 @@ class MethodAnalysisResult public function __construct( public Scope $scope, public FunctionLikeDefinition $definition, - ) - { + ) { } } diff --git a/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php b/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php index 31bda728..82aef1e6 100644 --- a/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php +++ b/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php @@ -7,7 +7,7 @@ trait ArgumentTypesAware { - public function getArg(string $name, int $position, ?Type $default = null) + public function getArg(string $name, int $position, Type $default = null) { $default ??= new UnknownType(); diff --git a/src/Infer/Handler/ConcatHandler.php b/src/Infer/Handler/ConcatHandler.php index 73094b61..a33c0793 100644 --- a/src/Infer/Handler/ConcatHandler.php +++ b/src/Infer/Handler/ConcatHandler.php @@ -4,8 +4,6 @@ use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\ConcatenatedStringType; -use Dedoc\Scramble\Support\Type\SideEffects\SelfTemplateDefinition; -use Dedoc\Scramble\Support\Type\TemplateType; use Dedoc\Scramble\Support\Type\TypeHelper; use PhpParser\Node; diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index e98793ea..baa66196 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -5,7 +5,6 @@ use Dedoc\Scramble\Infer\Analyzer\ClassAnalyzer; use Dedoc\Scramble\Infer\Definition\ClassDefinition; use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; -use Dedoc\Scramble\Infer\Extensions\Event\MethodCallEvent; use Dedoc\Scramble\Infer\Extensions\Event\StaticMethodCallEvent; use Dedoc\Scramble\Infer\Extensions\ExtensionsBroker; use Dedoc\Scramble\Infer\Scope\Index; diff --git a/src/Support/InferExtensions/RuleExtension.php b/src/Support/InferExtensions/RuleExtension.php index d79fad06..a4fa1505 100644 --- a/src/Support/InferExtensions/RuleExtension.php +++ b/src/Support/InferExtensions/RuleExtension.php @@ -7,7 +7,6 @@ use Dedoc\Scramble\Support\Type\Generic; use Dedoc\Scramble\Support\Type\Literal\LiteralStringType; use Dedoc\Scramble\Support\Type\Type; -use Dedoc\Scramble\Support\Type\TypeHelper; use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\Enum; use Illuminate\Validation\Rules\Exists; diff --git a/src/Support/OperationExtensions/RulesExtractor/Rules/EnumRule.php b/src/Support/OperationExtensions/RulesExtractor/Rules/EnumRule.php index 0c2d994a..fe130440 100644 --- a/src/Support/OperationExtensions/RulesExtractor/Rules/EnumRule.php +++ b/src/Support/OperationExtensions/RulesExtractor/Rules/EnumRule.php @@ -34,7 +34,7 @@ protected function getEnumName(Type $rule) { $enumTypePropertyType = $rule->getPropertyType('type'); - if (!$enumTypePropertyType instanceof LiteralStringType) { + if (! $enumTypePropertyType instanceof LiteralStringType) { throw new \RuntimeException('Unexpected enum value type'); } diff --git a/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php b/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php index 8afd75e0..262332d4 100644 --- a/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php +++ b/src/Support/OperationExtensions/RulesExtractor/Rules/InRule.php @@ -43,7 +43,7 @@ private function getNormalizedValues(Type|string $rule, array $parameters) } $valueType = $rule->getPropertyType('value'); - if (!$valueType instanceof ArrayType) { + if (! $valueType instanceof ArrayType) { throw new \RuntimeException('Invalid value type'); } diff --git a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php index 7bda6e7e..9805868e 100644 --- a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php +++ b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php @@ -81,9 +81,9 @@ public function extract(RouteInfo $routeInfo) if ($validationRules) { $type = $routeInfo->getMethodScopeTypeResolver()->getType($validationRules); -// dump([ -// $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), -// ]); + // dump([ + // $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), + // ]); } if ($validationRules) { diff --git a/src/Support/RouteInfo.php b/src/Support/RouteInfo.php index 9f088ab5..1c29f382 100644 --- a/src/Support/RouteInfo.php +++ b/src/Support/RouteInfo.php @@ -230,7 +230,7 @@ public function getMethodType(): ?FunctionType */ public function getMethodScopeTypeResolver(): ?ScopeTypeResolver { - if (!$this->isClassBased() || !$this->getMethodType()) { + if (! $this->isClassBased() || ! $this->getMethodType()) { return null; } diff --git a/src/Support/Type/ConcatenatedStringType.php b/src/Support/Type/ConcatenatedStringType.php index af976bae..12e20f90 100644 --- a/src/Support/Type/ConcatenatedStringType.php +++ b/src/Support/Type/ConcatenatedStringType.php @@ -5,7 +5,7 @@ class ConcatenatedStringType extends StringType { /** - * @param Type[] $parts Types of parts being concatenated. + * @param Type[] $parts Types of parts being concatenated. */ public function __construct(public array $parts) { diff --git a/src/Support/Type/Reference/StaticMethodCallReferenceType.php b/src/Support/Type/Reference/StaticMethodCallReferenceType.php index e8212a60..bcf43abe 100644 --- a/src/Support/Type/Reference/StaticMethodCallReferenceType.php +++ b/src/Support/Type/Reference/StaticMethodCallReferenceType.php @@ -2,9 +2,7 @@ namespace Dedoc\Scramble\Support\Type\Reference; -use Dedoc\Scramble\Support\Type\ObjectType; use Dedoc\Scramble\Support\Type\Reference\Dependency\MethodDependency; -use Dedoc\Scramble\Support\Type\SelfType; use Dedoc\Scramble\Support\Type\Type; class StaticMethodCallReferenceType extends AbstractReferenceType diff --git a/src/Support/Type/TypeHelper.php b/src/Support/Type/TypeHelper.php index 0f23922e..d040f0fd 100644 --- a/src/Support/Type/TypeHelper.php +++ b/src/Support/Type/TypeHelper.php @@ -148,7 +148,7 @@ public static function createTypeFromValue(mixed $value) } /** - * @param Type[] $parts + * @param Type[] $parts */ public static function flattenStringConcatTypes(array $parts): array { diff --git a/tests/InferExtensions/RuleExtensionTest.php b/tests/InferExtensions/RuleExtensionTest.php index 09ed04f3..b76ef8f1 100644 --- a/tests/InferExtensions/RuleExtensionTest.php +++ b/tests/InferExtensions/RuleExtensionTest.php @@ -1,11 +1,11 @@ Date: Wed, 27 Sep 2023 20:18:28 +0300 Subject: [PATCH 06/24] feat: late static binding, class const fetch --- src/Infer/Analyzer/ClassAnalyzer.php | 3 +- .../Definition/FunctionLikeDefinition.php | 1 + src/Infer/Scope/Scope.php | 17 +++++++- src/Infer/Services/ConstFetchTypeGetter.php | 19 +++++++++ src/Infer/Services/ReferenceTypeResolver.php | 29 +++++++++++++ .../ClassConstFetchTypeGetter.php | 11 +++++ .../Reference/ConstFetchReferenceType.php | 24 +++++++++++ .../Type/Reference/StaticReference.php | 41 +++++++++++++++++++ .../Services/ReferenceTypeResolverTest.php | 39 ++++++++++++++++++ .../Infer/Services/StaticCallsClasses/Bar.php | 11 +++++ .../Infer/Services/StaticCallsClasses/Foo.php | 21 ++++++++++ 11 files changed, 213 insertions(+), 3 deletions(-) create mode 100644 src/Infer/Services/ConstFetchTypeGetter.php create mode 100644 src/Support/Type/Reference/ConstFetchReferenceType.php create mode 100644 src/Support/Type/Reference/StaticReference.php create mode 100644 tests/Infer/Services/StaticCallsClasses/Bar.php create mode 100644 tests/Infer/Services/StaticCallsClasses/Foo.php diff --git a/src/Infer/Analyzer/ClassAnalyzer.php b/src/Infer/Analyzer/ClassAnalyzer.php index 193058c0..13a67c09 100644 --- a/src/Infer/Analyzer/ClassAnalyzer.php +++ b/src/Infer/Analyzer/ClassAnalyzer.php @@ -69,7 +69,8 @@ public function analyze(string $name): ClassDefinition $reflectionMethod->name, arguments: [], returnType: new UnknownType, - ) + ), + definingClassName: $name, ); } diff --git a/src/Infer/Definition/FunctionLikeDefinition.php b/src/Infer/Definition/FunctionLikeDefinition.php index d29814b2..4350e6e3 100644 --- a/src/Infer/Definition/FunctionLikeDefinition.php +++ b/src/Infer/Definition/FunctionLikeDefinition.php @@ -16,6 +16,7 @@ public function __construct( public FunctionType $type, public array $sideEffects = [], public array $argumentsDefaults = [], + public ?string $definingClassName = null, ) { } diff --git a/src/Infer/Scope/Scope.php b/src/Infer/Scope/Scope.php index 25518adb..967e166e 100644 --- a/src/Infer/Scope/Scope.php +++ b/src/Infer/Scope/Scope.php @@ -14,6 +14,7 @@ use Dedoc\Scramble\Support\Type\ArrayType; use Dedoc\Scramble\Support\Type\CallableStringType; use Dedoc\Scramble\Support\Type\ObjectType; +use Dedoc\Scramble\Support\Type\Reference\AbstractReferenceType; use Dedoc\Scramble\Support\Type\Reference\CallableCallReferenceType; use Dedoc\Scramble\Support\Type\Reference\MethodCallReferenceType; use Dedoc\Scramble\Support\Type\Reference\NewCallReferenceType; @@ -52,11 +53,23 @@ public function getType(Node $node): Type } if ($node instanceof Node\Expr\ConstFetch) { - return (new ConstFetchTypeGetter)($node); + $type = (new ConstFetchTypeGetter)($node); + + if (! $type instanceof AbstractReferenceType) { + return $type; + } + + return $this->setType($node, $type); } if ($node instanceof Node\Expr\ClassConstFetch) { - return (new ClassConstFetchTypeGetter)($node, $this); + $type = (new ClassConstFetchTypeGetter)($node, $this); + + if (! $type instanceof AbstractReferenceType) { + return $type; + } + + return $this->setType($node, $type); } if ($node instanceof Node\Expr\BooleanNot) { diff --git a/src/Infer/Services/ConstFetchTypeGetter.php b/src/Infer/Services/ConstFetchTypeGetter.php new file mode 100644 index 00000000..5f59fccb --- /dev/null +++ b/src/Infer/Services/ConstFetchTypeGetter.php @@ -0,0 +1,19 @@ +resolveConstFetchReferenceType($scope, $t); + } + if ($t instanceof MethodCallReferenceType) { return $this->resolveMethodCallReferenceType($scope, $t); } @@ -154,6 +161,28 @@ private function doResolve(Type $t, Type $type, Scope $scope) return $this->resolve($scope, $resolved); } + private function resolveConstFetchReferenceType(Scope $scope, ConstFetchReferenceType $type) + { + $analyzedType = clone $type; + + if ($type->callee instanceof StaticReference) { + $contextualCalleeName = match ($type->callee->keyword) { + StaticReference::SELF => $scope->context->functionDefinition?->definingClassName, + StaticReference::STATIC => $scope->context->classDefinition?->name, + StaticReference::PARENT => $scope->context->classDefinition?->parentFqn, + }; + + // This can only happen if any of static reserved keyword used in non-class context – hence considering not possible for now. + if (! $contextualCalleeName) { + return new UnknownType("Cannot properly analyze [{$type->toString()}] reference type as static keyword used in non-class context, or current class scope has no parent."); + } + + $analyzedType->callee = $contextualCalleeName; + } + + return (new ConstFetchTypeGetter)($scope, $analyzedType->callee, $analyzedType->constName); + } + private function resolveMethodCallReferenceType(Scope $scope, MethodCallReferenceType $type) { // (#self).listTableDetails() diff --git a/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php b/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php index 97938ba7..b0f5a7fd 100644 --- a/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php +++ b/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php @@ -5,7 +5,9 @@ use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\Literal\LiteralStringType; use Dedoc\Scramble\Support\Type\ObjectType; +use Dedoc\Scramble\Support\Type\Reference\ConstFetchReferenceType; use Dedoc\Scramble\Support\Type\Reference\NewCallReferenceType; +use Dedoc\Scramble\Support\Type\Reference\StaticReference; use Dedoc\Scramble\Support\Type\StringType; use Dedoc\Scramble\Support\Type\Type; use Dedoc\Scramble\Support\Type\UnknownType; @@ -13,9 +15,18 @@ class ClassConstFetchTypeGetter { + const STATIC_KEYWORDS = ['self', 'static', 'parent']; + public function __invoke(Node\Expr\ClassConstFetch $node, Scope $scope): Type { if ($node->name->toString() === 'class') { + if ($node->class instanceof Node\Name && in_array($node->class->toString(), static::STATIC_KEYWORDS)) { + return new ConstFetchReferenceType( + new StaticReference($node->class->toString()), + 'class', + ); + } + if ($node->class instanceof Node\Name) { return new LiteralStringType($node->class->toString()); } diff --git a/src/Support/Type/Reference/ConstFetchReferenceType.php b/src/Support/Type/Reference/ConstFetchReferenceType.php new file mode 100644 index 00000000..28a3fa2d --- /dev/null +++ b/src/Support/Type/Reference/ConstFetchReferenceType.php @@ -0,0 +1,24 @@ +callee) ? $this->callee : $this->callee->toString(); + + return "(#{$callee})::{$this->constName}"; + } + + public function dependencies(): array + { + return []; + } +} diff --git a/src/Support/Type/Reference/StaticReference.php b/src/Support/Type/Reference/StaticReference.php new file mode 100644 index 00000000..3adeeee0 --- /dev/null +++ b/src/Support/Type/Reference/StaticReference.php @@ -0,0 +1,41 @@ +keyword, self::KEYWORDS)) { + throw new \InvalidArgumentException("[$this->keyword] keyword must be one of possible values."); + } + } + + public function isStatic() + { + return $this->keyword === static::STATIC; + } + + public function isSelf() + { + return $this->keyword === static::SELF; + } + + public function isParent() + { + return $this->keyword === static::SELF; + } + + public function toString() + { + return $this->keyword; + } +} diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index c568c61c..f1a7dee3 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -1,5 +1,44 @@ index = app(Index::class); + + $this->classAnalyzer = new ClassAnalyzer($this->index); + + $this->resolver = new ReferenceTypeResolver($this->index); +}); + +/* + * Late static binding + */ +it('infers static keywords const fetches on parent class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfClassFetch', 'string(Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo)'], + ['staticClassFetch', 'string(Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo)'], +]); + +it('infers static keywords const fetches on child class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfClassFetch', 'string(Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo)'], + ['staticClassFetch', 'string(Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar)'], + ['parentClassFetch', 'string(Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo)'], +]); + +/* + * Static method calls + */ it('infers static method call type', function () { $type = analyzeFile(<<<'EOD' Date: Wed, 27 Sep 2023 17:19:14 +0000 Subject: [PATCH 07/24] Fix styling --- src/Infer/Services/ReferenceTypeResolver.php | 1 - src/Support/Type/Reference/StaticReference.php | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index 9e1cc240..2307935f 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -9,7 +9,6 @@ use Dedoc\Scramble\Infer\Extensions\ExtensionsBroker; use Dedoc\Scramble\Infer\Scope\Index; use Dedoc\Scramble\Infer\Scope\Scope; -use Dedoc\Scramble\Infer\SimpleTypeGetters\ClassConstFetchTypeGetter; use Dedoc\Scramble\Support\Type\CallableStringType; use Dedoc\Scramble\Support\Type\FunctionType; use Dedoc\Scramble\Support\Type\Generic; diff --git a/src/Support/Type/Reference/StaticReference.php b/src/Support/Type/Reference/StaticReference.php index 3adeeee0..b1c2f40a 100644 --- a/src/Support/Type/Reference/StaticReference.php +++ b/src/Support/Type/Reference/StaticReference.php @@ -14,7 +14,7 @@ class StaticReference public function __construct(public readonly string $keyword) { - if (!in_array($this->keyword, self::KEYWORDS)) { + if (! in_array($this->keyword, self::KEYWORDS)) { throw new \InvalidArgumentException("[$this->keyword] keyword must be one of possible values."); } } From 5ed1fc6b3ba236d5930d823c45ebcf065329c724 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Wed, 27 Sep 2023 22:40:34 +0300 Subject: [PATCH 08/24] feat: added keywords support to new call --- src/Infer/Services/ConstFetchTypeGetter.php | 14 +++++ src/Infer/Services/ReferenceTypeResolver.php | 20 +++++++ .../ClassConstFetchTypeGetter.php | 30 ++++++----- src/Support/Type/TypeHelper.php | 14 +++++ .../Services/ConstFetchTypeGetterTest.php | 13 +++++ .../Services/ReferenceTypeResolverTest.php | 52 +++++++++++++++++++ .../Infer/Services/StaticCallsClasses/Bar.php | 12 +++++ .../Infer/Services/StaticCallsClasses/Foo.php | 22 ++++++++ 8 files changed, 164 insertions(+), 13 deletions(-) create mode 100644 tests/Infer/Services/ConstFetchTypeGetterTest.php diff --git a/src/Infer/Services/ConstFetchTypeGetter.php b/src/Infer/Services/ConstFetchTypeGetter.php index 5f59fccb..e14d6169 100644 --- a/src/Infer/Services/ConstFetchTypeGetter.php +++ b/src/Infer/Services/ConstFetchTypeGetter.php @@ -4,6 +4,7 @@ use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\Literal\LiteralStringType; +use Dedoc\Scramble\Support\Type\TypeHelper; use Dedoc\Scramble\Support\Type\UnknownType; class ConstFetchTypeGetter @@ -14,6 +15,19 @@ public function __invoke(Scope $scope, string $className, string $constName) return new LiteralStringType($className); } + try { + $constantReflection = new \ReflectionClassConstant($className, $constName); + $constantValue = $constantReflection->getValue(); + + $type = TypeHelper::createTypeFromValue($constantValue); + + if ($type) { + return $type; + } + } catch (\ReflectionException $e) { + return new UnknownType('Cannot get const value'); + } + return new UnknownType('ConstFetchTypeGetter is not yet implemented fully for non-class const fetches.'); } } diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index 2307935f..e69674d1 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -318,6 +318,13 @@ private function resolveNewCallReferenceType(Scope $scope, NewCallReferenceType $type->arguments, ); + $contextualClassName = $this->resolveClassName($scope, $type->name); + if (! $contextualClassName) { + return new UnknownType(); + } + + $type->name = $contextualClassName; + if ( ! array_key_exists($type->name, $this->index->classesDefinitions) && ! $this->resolveUnknownClassResolver($type->name) @@ -528,4 +535,17 @@ private function resolveTypesTemplatesFromArguments($templates, $templatedArgume ]; }, $templates))); } + + private function resolveClassName(Scope $scope, string $name): ?string + { + if (!in_array($name, StaticReference::KEYWORDS)) { + return $name; + } + + return match ($name) { + StaticReference::SELF => $scope->context->functionDefinition?->definingClassName, + StaticReference::STATIC => $scope->context->classDefinition?->name, + StaticReference::PARENT => $scope->context->classDefinition?->parentFqn, + }; + } } diff --git a/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php b/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php index b0f5a7fd..88359ea6 100644 --- a/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php +++ b/src/Infer/SimpleTypeGetters/ClassConstFetchTypeGetter.php @@ -15,28 +15,32 @@ class ClassConstFetchTypeGetter { - const STATIC_KEYWORDS = ['self', 'static', 'parent']; - public function __invoke(Node\Expr\ClassConstFetch $node, Scope $scope): Type { - if ($node->name->toString() === 'class') { - if ($node->class instanceof Node\Name && in_array($node->class->toString(), static::STATIC_KEYWORDS)) { - return new ConstFetchReferenceType( - new StaticReference($node->class->toString()), - 'class', - ); - } - - if ($node->class instanceof Node\Name) { - return new LiteralStringType($node->class->toString()); - } + if ( + $node->class instanceof Node\Name + && $node->name instanceof Node\Identifier + ) { + $className = in_array($node->class->toString(), StaticReference::KEYWORDS) + ? new StaticReference($node->class->toString()) + : $node->class->toString(); + + return new ConstFetchReferenceType( + $className, + $node->name->toString(), + ); + } + if ($node->name->toString() === 'class') { $type = $scope->getType($node->class); if ($type instanceof ObjectType || $type instanceof NewCallReferenceType) { return new LiteralStringType($type->name); } + // @todo Should be totally possible to return ConstFetchReferenceType here so any reference types can be + // resolved and the most accurate type retrieved. + return new StringType(); } diff --git a/src/Support/Type/TypeHelper.php b/src/Support/Type/TypeHelper.php index d040f0fd..bce95acc 100644 --- a/src/Support/Type/TypeHelper.php +++ b/src/Support/Type/TypeHelper.php @@ -144,6 +144,20 @@ public static function createTypeFromValue(mixed $value) return new LiteralBooleanType($value); } + if (is_array($value)) { + return new ArrayType( + collect($value) + ->map(function ($value, $key) { + return new ArrayItemType_( + is_string($key) ? $key : null, + static::createTypeFromValue($value), + ); + }) + ->values() + ->all() + ); + } + return null; // @todo: object } diff --git a/tests/Infer/Services/ConstFetchTypeGetterTest.php b/tests/Infer/Services/ConstFetchTypeGetterTest.php new file mode 100644 index 00000000..22b76ed8 --- /dev/null +++ b/tests/Infer/Services/ConstFetchTypeGetterTest.php @@ -0,0 +1,13 @@ +toString())->toBe('array{0: string(foo), 1: string(bar)}'); +}); +class ConstFetchTypeGetterTest_Foo { + const ARRAY = ['foo', 'bar']; +} diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index f1a7dee3..519a05f5 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -15,6 +15,10 @@ /* * Late static binding */ + +/* + * Class' `class` const fetch + */ it('infers static keywords const fetches on parent class', function (string $method, string $expectedType) { $methodDef = $this->classAnalyzer ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo::class) @@ -36,6 +40,54 @@ ['parentClassFetch', 'string(Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo)'], ]); +/* + * Class const fetch + */ +it('infers static keywords some consts fetches on parent class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfConstFetch', 'int(42)'], + ['staticConstFetch', 'int(42)'], +]); + +it('infers static keywords some consts fetches on child class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfConstFetch', 'int(42)'], + ['staticConstFetch', 'int(21)'], + ['parentConstFetch', 'int(42)'], +]); + +/* + * New calls + */ +it('infers new calls on parent class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['newSelfCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], + ['newStaticCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], +]); + +it('infers new calls on child class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['newSelfCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], + ['newStaticCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar'], + ['newParentCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], +]); + /* * Static method calls */ diff --git a/tests/Infer/Services/StaticCallsClasses/Bar.php b/tests/Infer/Services/StaticCallsClasses/Bar.php index 1d8966e8..e28b0c28 100644 --- a/tests/Infer/Services/StaticCallsClasses/Bar.php +++ b/tests/Infer/Services/StaticCallsClasses/Bar.php @@ -4,8 +4,20 @@ class Bar extends Foo { + const SOME = 21; + public function parentClassFetch() { return parent::class; } + + public function parentConstFetch() + { + return parent::SOME; + } + + public function newParentCall() + { + return new parent; + } } diff --git a/tests/Infer/Services/StaticCallsClasses/Foo.php b/tests/Infer/Services/StaticCallsClasses/Foo.php index 14d98877..df2ac744 100644 --- a/tests/Infer/Services/StaticCallsClasses/Foo.php +++ b/tests/Infer/Services/StaticCallsClasses/Foo.php @@ -4,6 +4,8 @@ class Foo { + const SOME = 42; + public function selfClassFetch() { return self::class; @@ -18,4 +20,24 @@ public function staticClassFetch() { return static::class; } + + public function selfConstFetch() + { + return self::SOME; + } + + public function staticConstFetch() + { + return static::SOME; + } + + public function newSelfCall() + { + return new self; + } + + public function newStaticCall() + { + return new static; + } } From 3aed7a751ea7dd68ef745f447674bd8163ffc11d Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Wed, 27 Sep 2023 23:04:50 +0300 Subject: [PATCH 09/24] feat: improved default prop types inference --- src/Infer/Services/ReferenceTypeResolver.php | 13 ++++++++++++- .../RulesExtractor/ValidateCallExtractor.php | 6 +++--- tests/Infer/Services/ReferenceTypeResolverTest.php | 9 ++++++++- tests/Infer/Services/StaticCallsClasses/Bar.php | 2 ++ tests/Infer/Services/StaticCallsClasses/Foo.php | 5 +++++ 5 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index e69674d1..de3cd8b3 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -4,6 +4,7 @@ use Dedoc\Scramble\Infer\Analyzer\ClassAnalyzer; use Dedoc\Scramble\Infer\Definition\ClassDefinition; +use Dedoc\Scramble\Infer\Definition\ClassPropertyDefinition; use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; use Dedoc\Scramble\Infer\Extensions\Event\StaticMethodCallEvent; use Dedoc\Scramble\Infer\Extensions\ExtensionsBroker; @@ -34,6 +35,7 @@ use Dedoc\Scramble\Support\Type\Union; use Dedoc\Scramble\Support\Type\UnknownType; use Illuminate\Support\Str; +use function Pest\Laravel\instance; class ReferenceTypeResolver { @@ -338,12 +340,21 @@ private function resolveNewCallReferenceType(Scope $scope, NewCallReferenceType return new ObjectType($type->name); } - $inferredTemplates = collect($this->resolveTypesTemplatesFromArguments( + $propertyDefaultTemplateTypes = collect($classDefinition->properties) + ->filter(fn (ClassPropertyDefinition $definition) => $definition->type instanceof TemplateType && !! $definition->defaultType) + ->mapWithKeys(fn (ClassPropertyDefinition $definition) => [ + $definition->type->name => $definition->defaultType, + ]); + + $inferredConstructorParamTemplates = collect($this->resolveTypesTemplatesFromArguments( $classDefinition->templateTypes, $classDefinition->getMethodDefinition('__construct', $scope)->type->arguments ?? [], $this->prepareArguments($classDefinition->getMethodDefinition('__construct', $scope), $type->arguments), ))->mapWithKeys(fn ($searchReplace) => [$searchReplace[0]->name => $searchReplace[1]]); + $inferredTemplates = $propertyDefaultTemplateTypes + ->merge($inferredConstructorParamTemplates); + return new Generic( $classDefinition->name, collect($classDefinition->templateTypes) diff --git a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php index 9805868e..afa53296 100644 --- a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php +++ b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php @@ -81,9 +81,9 @@ public function extract(RouteInfo $routeInfo) if ($validationRules) { $type = $routeInfo->getMethodScopeTypeResolver()->getType($validationRules); - // dump([ - // $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), - // ]); +// dump([ +// $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), +// ]); } if ($validationRules) { diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index 519a05f5..b32f1e89 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -84,10 +84,17 @@ expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); })->with([ ['newSelfCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], - ['newStaticCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar'], + ['newStaticCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar'], ['newParentCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], ]); + +it('complex static call and property fetch', function () { + $type = getStatementType('Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::wow()'); + + expect($type->toString())->toBe('string(foo)'); +}); + /* * Static method calls */ diff --git a/tests/Infer/Services/StaticCallsClasses/Bar.php b/tests/Infer/Services/StaticCallsClasses/Bar.php index e28b0c28..ebf7dace 100644 --- a/tests/Infer/Services/StaticCallsClasses/Bar.php +++ b/tests/Infer/Services/StaticCallsClasses/Bar.php @@ -6,6 +6,8 @@ class Bar extends Foo { const SOME = 21; + public string $prop = 'foo'; + public function parentClassFetch() { return parent::class; diff --git a/tests/Infer/Services/StaticCallsClasses/Foo.php b/tests/Infer/Services/StaticCallsClasses/Foo.php index df2ac744..1aa9f8c2 100644 --- a/tests/Infer/Services/StaticCallsClasses/Foo.php +++ b/tests/Infer/Services/StaticCallsClasses/Foo.php @@ -40,4 +40,9 @@ public function newStaticCall() { return new static; } + + public function wow() + { + return (new static)->prop; + } } From 75bad2d0fd00cbe10fc89847ec0cee0c7d780a14 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Wed, 27 Sep 2023 23:12:53 +0300 Subject: [PATCH 10/24] feat: if template type has some constraints, they will be used to determine operation types --- src/Infer/Scope/Scope.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Infer/Scope/Scope.php b/src/Infer/Scope/Scope.php index 967e166e..348a33b4 100644 --- a/src/Infer/Scope/Scope.php +++ b/src/Infer/Scope/Scope.php @@ -113,11 +113,11 @@ public function getType(Node $node): Type $calleeType = $this->getType($node->var); if ($calleeType instanceof TemplateType) { - // @todo - // if ($calleeType->is instanceof ObjectType) { - // $calleeType = $calleeType->is; - // } - return $this->setType($node, new UnknownType("Cannot infer type of method [{$node->name->name}] call on template type: not supported yet.")); + if ($calleeType->is instanceof ObjectType) { + $calleeType = $calleeType->is; + } else { + return $this->setType($node, new UnknownType("Cannot infer type of method [{$node->name->name}] call on template type: not supported yet.")); + } } return $this->setType( From c539639385b10944f6b2336961b9dc94ab77290e Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Wed, 27 Sep 2023 20:14:03 +0000 Subject: [PATCH 11/24] Fix styling --- src/Infer/Services/ReferenceTypeResolver.php | 5 ++--- .../RulesExtractor/ValidateCallExtractor.php | 6 +++--- tests/Infer/Services/ConstFetchTypeGetterTest.php | 3 ++- tests/Infer/Services/ReferenceTypeResolverTest.php | 1 - 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index de3cd8b3..0d31e67a 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -35,7 +35,6 @@ use Dedoc\Scramble\Support\Type\Union; use Dedoc\Scramble\Support\Type\UnknownType; use Illuminate\Support\Str; -use function Pest\Laravel\instance; class ReferenceTypeResolver { @@ -341,7 +340,7 @@ private function resolveNewCallReferenceType(Scope $scope, NewCallReferenceType } $propertyDefaultTemplateTypes = collect($classDefinition->properties) - ->filter(fn (ClassPropertyDefinition $definition) => $definition->type instanceof TemplateType && !! $definition->defaultType) + ->filter(fn (ClassPropertyDefinition $definition) => $definition->type instanceof TemplateType && (bool) $definition->defaultType) ->mapWithKeys(fn (ClassPropertyDefinition $definition) => [ $definition->type->name => $definition->defaultType, ]); @@ -549,7 +548,7 @@ private function resolveTypesTemplatesFromArguments($templates, $templatedArgume private function resolveClassName(Scope $scope, string $name): ?string { - if (!in_array($name, StaticReference::KEYWORDS)) { + if (! in_array($name, StaticReference::KEYWORDS)) { return $name; } diff --git a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php index afa53296..5af14725 100644 --- a/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php +++ b/src/Support/OperationExtensions/RulesExtractor/ValidateCallExtractor.php @@ -81,9 +81,9 @@ public function extract(RouteInfo $routeInfo) if ($validationRules) { $type = $routeInfo->getMethodScopeTypeResolver()->getType($validationRules); -// dump([ -// $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), -// ]); + // dump([ + // $routeInfo->className().'@'.$routeInfo->methodName() => $type->toString(), + // ]); } if ($validationRules) { diff --git a/tests/Infer/Services/ConstFetchTypeGetterTest.php b/tests/Infer/Services/ConstFetchTypeGetterTest.php index 22b76ed8..aace32d4 100644 --- a/tests/Infer/Services/ConstFetchTypeGetterTest.php +++ b/tests/Infer/Services/ConstFetchTypeGetterTest.php @@ -8,6 +8,7 @@ expect($type->toString())->toBe('array{0: string(foo), 1: string(bar)}'); }); -class ConstFetchTypeGetterTest_Foo { +class ConstFetchTypeGetterTest_Foo +{ const ARRAY = ['foo', 'bar']; } diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index b32f1e89..97afffe9 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -88,7 +88,6 @@ ['newParentCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], ]); - it('complex static call and property fetch', function () { $type = getStatementType('Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::wow()'); From 9f48d7dd610974522a502541b20c1a32b8564c16 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Wed, 27 Sep 2023 23:18:35 +0300 Subject: [PATCH 12/24] test: test parameters type retrieval --- tests/Infer/Scope/ScopeTest.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/tests/Infer/Scope/ScopeTest.php b/tests/Infer/Scope/ScopeTest.php index d1d24c31..d2bdc56c 100644 --- a/tests/Infer/Scope/ScopeTest.php +++ b/tests/Infer/Scope/ScopeTest.php @@ -1,5 +1,7 @@ getExpressionType($statement); @@ -22,3 +24,23 @@ function getStatementTypeForScopeTest(string $statement, array $extensions = []) })->with([ ['"a"."b".auth()->user()->id', 'string(string(a), string(b), unknown)'], ]); + +it('analyzes call type of param properly', function () { + $foo = app(ClassAnalyzer::class) + ->analyze(ScopeTest_Foo::class) + ->getMethodDefinition('foo'); + + expect($foo->type->getReturnType()->toString())->toBe('int(42)'); +}); +class ScopeTest_Foo { + public function foo(ScopeTest_Bar $bar) + { + return $bar->getAnswer(); + } +} +class ScopeTest_Bar { + public function getAnswer() + { + return 42; + } +} From 64250df21f311a5bd7efb70fec5af1a542eb7eaa Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Wed, 27 Sep 2023 20:19:06 +0000 Subject: [PATCH 13/24] Fix styling --- tests/Infer/Scope/ScopeTest.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Infer/Scope/ScopeTest.php b/tests/Infer/Scope/ScopeTest.php index d2bdc56c..e894db01 100644 --- a/tests/Infer/Scope/ScopeTest.php +++ b/tests/Infer/Scope/ScopeTest.php @@ -32,13 +32,15 @@ function getStatementTypeForScopeTest(string $statement, array $extensions = []) expect($foo->type->getReturnType()->toString())->toBe('int(42)'); }); -class ScopeTest_Foo { +class ScopeTest_Foo +{ public function foo(ScopeTest_Bar $bar) { return $bar->getAnswer(); } } -class ScopeTest_Bar { +class ScopeTest_Bar +{ public function getAnswer() { return 42; From 7104b0ab212f46791e1f29227b49d317f005adb9 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Thu, 28 Sep 2023 23:14:27 +0300 Subject: [PATCH 14/24] feat: static methods and prop fetch support --- src/Infer/Analyzer/ClassAnalyzer.php | 23 ++++-- src/Infer/Scope/Scope.php | 23 ++++-- src/Infer/Services/ReferenceTypeResolver.php | 44 ++++++++++- .../StaticPropertyFetchReferenceType.php | 28 +++++++ tests/Infer/Scope/ScopeTest.php | 6 ++ .../Services/ReferenceTypeResolverTest.php | 78 +++++++++++++++++++ .../Infer/Services/StaticCallsClasses/Bar.php | 17 ++++ .../Infer/Services/StaticCallsClasses/Foo.php | 27 +++++++ 8 files changed, 232 insertions(+), 14 deletions(-) create mode 100644 src/Support/Type/Reference/StaticPropertyFetchReferenceType.php diff --git a/src/Infer/Analyzer/ClassAnalyzer.php b/src/Infer/Analyzer/ClassAnalyzer.php index 13a67c09..75ce6075 100644 --- a/src/Infer/Analyzer/ClassAnalyzer.php +++ b/src/Infer/Analyzer/ClassAnalyzer.php @@ -49,14 +49,21 @@ public function analyze(string $name): ClassDefinition continue; } - $classDefinition->properties[$reflectionProperty->name] = new ClassPropertyDefinition( - type: $t = new TemplateType('T'.Str::studly($reflectionProperty->name)), - defaultType: $reflectionProperty->hasDefaultValue() - ? TypeHelper::createTypeFromValue($reflectionProperty->getDefaultValue()) - : null, - ); - - $classDefinition->templateTypes[] = $t; + if ($reflectionProperty->isStatic()) { + $classDefinition->properties[$reflectionProperty->name] = new ClassPropertyDefinition( + type: $reflectionProperty->hasDefaultValue() + ? (TypeHelper::createTypeFromValue($reflectionProperty->getDefaultValue()) ?: new UnknownType) + : new UnknownType, + ); + } else { + $classDefinition->properties[$reflectionProperty->name] = new ClassPropertyDefinition( + type: $t = new TemplateType('T'.Str::studly($reflectionProperty->name)), + defaultType: $reflectionProperty->hasDefaultValue() + ? TypeHelper::createTypeFromValue($reflectionProperty->getDefaultValue()) + : null, + ); + $classDefinition->templateTypes[] = $t; + } } foreach ($classReflection->getMethods() as $reflectionMethod) { diff --git a/src/Infer/Scope/Scope.php b/src/Infer/Scope/Scope.php index 348a33b4..8a8b1a3d 100644 --- a/src/Infer/Scope/Scope.php +++ b/src/Infer/Scope/Scope.php @@ -20,6 +20,7 @@ use Dedoc\Scramble\Support\Type\Reference\NewCallReferenceType; use Dedoc\Scramble\Support\Type\Reference\PropertyFetchReferenceType; use Dedoc\Scramble\Support\Type\Reference\StaticMethodCallReferenceType; +use Dedoc\Scramble\Support\Type\Reference\StaticPropertyFetchReferenceType; use Dedoc\Scramble\Support\Type\SelfType; use Dedoc\Scramble\Support\Type\TemplateType; use Dedoc\Scramble\Support\Type\Type; @@ -143,6 +144,23 @@ public function getType(Node $node): Type ); } + if ($node instanceof Node\Expr\StaticPropertyFetch) { + // Only string method names support. + if (! $node->name instanceof Node\Identifier) { + return $type; + } + + // Only string class names support. + if (! $node->class instanceof Node\Name) { + return $type; + } + + return $this->setType( + $node, + new StaticPropertyFetchReferenceType($node->class->toString(), $node->name->name), + ); + } + if ($node instanceof Node\Expr\PropertyFetch) { // Only string prop names support. if (! $name = ($node->name->name ?? null)) { @@ -291,11 +309,6 @@ private function getVariableType(Node\Expr\Variable $node) return $type; } - public function getMethodCallType(Type $calledOn, string $methodName, array $arguments = []): Type - { - - } - public function getPropertyFetchType(Type $calledOn, string $propertyName): Type { return (new ReferenceTypeResolver($this->index))->resolve($this, new PropertyFetchReferenceType($calledOn, $propertyName)); diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index 0d31e67a..67555efe 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -25,6 +25,7 @@ use Dedoc\Scramble\Support\Type\Reference\NewCallReferenceType; use Dedoc\Scramble\Support\Type\Reference\PropertyFetchReferenceType; use Dedoc\Scramble\Support\Type\Reference\StaticMethodCallReferenceType; +use Dedoc\Scramble\Support\Type\Reference\StaticPropertyFetchReferenceType; use Dedoc\Scramble\Support\Type\Reference\StaticReference; use Dedoc\Scramble\Support\Type\SelfType; use Dedoc\Scramble\Support\Type\SideEffects\SelfTemplateDefinition; @@ -135,6 +136,10 @@ private function doResolve(Type $t, Type $type, Scope $scope) return $this->resolveStaticMethodCallReferenceType($scope, $t); } + if ($t instanceof StaticPropertyFetchReferenceType) { + return $this->resolveStaticPropertyFetchReferenceType($scope, $t); + } + if ($t instanceof CallableCallReferenceType) { return $this->resolveCallableCallReferenceType($scope, $t); } @@ -183,6 +188,38 @@ private function resolveConstFetchReferenceType(Scope $scope, ConstFetchReferenc return (new ConstFetchTypeGetter)($scope, $analyzedType->callee, $analyzedType->constName); } + private function resolveStaticPropertyFetchReferenceType(Scope $scope, StaticPropertyFetchReferenceType $type) + { + $analyzedType = clone $type; + + $contextualClassName = $this->resolveClassName($scope, $type->callee); + if (! $contextualClassName) { + return new UnknownType("Cannot properly analyze [{$type->toString()}] reference type as static keyword used in non-class context, or current class scope has no parent."); + } + $type->callee = $contextualClassName; + + if ( + ! array_key_exists($type->callee, $this->index->classesDefinitions) + && ! $this->resolveUnknownClassResolver($type->callee) + ) { + return new UnknownType(); + } + + /** @var ClassDefinition $calleeDefinition */ + $calleeDefinition = $this->index->getClassDefinition($type->callee); + + if (! $propertyDefinition = $calleeDefinition->getPropertyDefinition($type->propertyName)) { + return new UnknownType("Cannot get a static property type [$type->propertyName] on type [$type->callee]"); + } + + $propertyType = $propertyDefinition->type; + if (! $propertyType || $propertyType instanceof TemplateType) { + return new UnknownType("Cannot get a static property type [$type->propertyName] on type [$type->callee]"); + } + + return $propertyType; + } + private function resolveMethodCallReferenceType(Scope $scope, MethodCallReferenceType $type) { // (#self).listTableDetails() @@ -242,6 +279,12 @@ private function resolveStaticMethodCallReferenceType(Scope $scope, StaticMethod $type->arguments, ); + $contextualClassName = $this->resolveClassName($scope, $type->callee); + if (! $contextualClassName) { + return new UnknownType(); + } + $type->callee = $contextualClassName; + // Assuming callee here can be only string of known name. Reality is more complex than // that, but it is fine for now. @@ -323,7 +366,6 @@ private function resolveNewCallReferenceType(Scope $scope, NewCallReferenceType if (! $contextualClassName) { return new UnknownType(); } - $type->name = $contextualClassName; if ( diff --git a/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php b/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php new file mode 100644 index 00000000..cacc39ca --- /dev/null +++ b/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php @@ -0,0 +1,28 @@ +callee})::\${$this->propertyName}"; + } + + public function dependencies(): array + { + return [ + new PropertyDependency($this->callee, $this->propertyName), + ]; + } +} diff --git a/tests/Infer/Scope/ScopeTest.php b/tests/Infer/Scope/ScopeTest.php index e894db01..640c72fc 100644 --- a/tests/Infer/Scope/ScopeTest.php +++ b/tests/Infer/Scope/ScopeTest.php @@ -14,6 +14,12 @@ function getStatementTypeForScopeTest(string $statement, array $extensions = []) ['$foo->bar->{"baz"}', 'unknown'], ]); +it('infers static property fetch nodes types', function ($code, $expectedTypeString) { + expect(getStatementType($code)->toString())->toBe($expectedTypeString); +})->with([ + ['parent::$bar', 'unknown'], +]); + it('infers concat string type', function ($code, $expectedTypeString) { expect(getStatementTypeForScopeTest($code)->toString())->toBe($expectedTypeString); })->with([ diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index 97afffe9..7a3a0c46 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -88,6 +88,84 @@ ['newParentCall', 'Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo'], ]); +/* + * Static method calls (should work the same for both static and non-static methods) + */ +it('infers static method calls on parent class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfMethodCall', 'string(foo)'], + ['staticMethodCall', 'string(foo)'], +]); + +it('infers static method calls on child class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfMethodCall', 'string(foo)'], + ['staticMethodCall', 'string(bar)'], + ['parentMethodCall', 'string(foo)'], +]); + +/* + * Property fetches + */ +it('infers property fetches on parent class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Foo::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfPropertyFetch', 'string(foo)'], + ['staticPropertyFetch', 'string(foo)'], +]); + +it('infers property fetches on child class', function (string $method, string $expectedType) { + $methodDef = $this->classAnalyzer + ->analyze(\Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::class) + ->getMethodDefinition($method); + expect($methodDef->type->getReturnType()->toString())->toBe($expectedType); +})->with([ + ['selfPropertyFetch', 'string(foo)'], + ['staticPropertyFetch', 'string(bar)'], + ['parentPropertyFetch', 'string(foo)'], +]); + +/* + * Complex static calls + */ + +it('infers type of static method call and instance property fetch', function () { + $this->classAnalyzer->analyze(ReferenceTypeResolverTest_Foo::class); + $this->classAnalyzer->analyze(ReferenceTypeResolverTest_Bar::class); + + $type = getStatementType('(new ReferenceTypeResolverTest_Bar)->test()'); + + expect($type->toString())->toBe('array{0: array{0: string(bar), 1: int(21)}, 1: array{0: string(foo), 1: int(21)}}'); +}); +class ReferenceTypeResolverTest_Foo { + public $prop = 42; + public function oreo() { + return ['foo', $this->prop]; + } + public function test() { + return [static::oreo(), self::oreo()]; + } +} +class ReferenceTypeResolverTest_Bar extends ReferenceTypeResolverTest_Foo { + public $prop = 21; + public function oreo() { + return ['bar', $this->prop]; + } +} +$a = (new ReferenceTypeResolverTest_Bar)->test(); + + it('complex static call and property fetch', function () { $type = getStatementType('Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::wow()'); diff --git a/tests/Infer/Services/StaticCallsClasses/Bar.php b/tests/Infer/Services/StaticCallsClasses/Bar.php index ebf7dace..477e5ac6 100644 --- a/tests/Infer/Services/StaticCallsClasses/Bar.php +++ b/tests/Infer/Services/StaticCallsClasses/Bar.php @@ -8,6 +8,8 @@ class Bar extends Foo public string $prop = 'foo'; + public static $staticProp = 'bar'; + public function parentClassFetch() { return parent::class; @@ -22,4 +24,19 @@ public function newParentCall() { return new parent; } + + public function someMethod() + { + return 'bar'; + } + + public function parentMethodCall() + { + return parent::someMethod(); + } + + public function parentPropertyFetch() + { + return parent::$staticProp; + } } diff --git a/tests/Infer/Services/StaticCallsClasses/Foo.php b/tests/Infer/Services/StaticCallsClasses/Foo.php index 1aa9f8c2..7f22d844 100644 --- a/tests/Infer/Services/StaticCallsClasses/Foo.php +++ b/tests/Infer/Services/StaticCallsClasses/Foo.php @@ -6,6 +6,8 @@ class Foo { const SOME = 42; + public static $staticProp = 'foo'; + public function selfClassFetch() { return self::class; @@ -45,4 +47,29 @@ public function wow() { return (new static)->prop; } + + public function someMethod() + { + return 'foo'; + } + + public function selfMethodCall() + { + return self::someMethod(); + } + + public function staticMethodCall() + { + return static::someMethod(); + } + + public function selfPropertyFetch() + { + return self::$staticProp; + } + + public function staticPropertyFetch() + { + return static::$staticProp; + } } From 7cd7c4239b95b8e4ae92ed31b0322495eb0eb747 Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Thu, 28 Sep 2023 20:16:02 +0000 Subject: [PATCH 15/24] Fix styling --- .../StaticPropertyFetchReferenceType.php | 2 -- .../Services/ReferenceTypeResolverTest.php | 19 +++++++++++++------ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php b/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php index cacc39ca..363afeb3 100644 --- a/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php +++ b/src/Support/Type/Reference/StaticPropertyFetchReferenceType.php @@ -2,9 +2,7 @@ namespace Dedoc\Scramble\Support\Type\Reference; -use Dedoc\Scramble\Support\Type\Reference\Dependency\MethodDependency; use Dedoc\Scramble\Support\Type\Reference\Dependency\PropertyDependency; -use Dedoc\Scramble\Support\Type\Type; class StaticPropertyFetchReferenceType extends AbstractReferenceType { diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index 7a3a0c46..45a3fa9b 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -148,24 +148,31 @@ expect($type->toString())->toBe('array{0: array{0: string(bar), 1: int(21)}, 1: array{0: string(foo), 1: int(21)}}'); }); -class ReferenceTypeResolverTest_Foo { +class ReferenceTypeResolverTest_Foo +{ public $prop = 42; - public function oreo() { + + public function oreo() + { return ['foo', $this->prop]; } - public function test() { + + public function test() + { return [static::oreo(), self::oreo()]; } } -class ReferenceTypeResolverTest_Bar extends ReferenceTypeResolverTest_Foo { +class ReferenceTypeResolverTest_Bar extends ReferenceTypeResolverTest_Foo +{ public $prop = 21; - public function oreo() { + + public function oreo() + { return ['bar', $this->prop]; } } $a = (new ReferenceTypeResolverTest_Bar)->test(); - it('complex static call and property fetch', function () { $type = getStatementType('Dedoc\Scramble\Tests\Infer\Services\StaticCallsClasses\Bar::wow()'); From cd65e6d377952505711bf645b6c811e33496cf14 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Fri, 6 Oct 2023 11:02:51 +0300 Subject: [PATCH 16/24] feat: array_keys support --- .../Extensions/Event/FunctionCallEvent.php | 29 +++++++++ src/Infer/Extensions/ExtensionsBroker.php | 18 ++++++ .../FunctionReturnTypeExtension.php | 13 ++++ src/Infer/Services/ReferenceTypeResolver.php | 24 ++++++- src/ScrambleServiceProvider.php | 2 + .../ArrayKeysReturnTypeExtension.php | 62 +++++++++++++++++++ src/Support/Type/ObjectType.php | 3 +- .../ArrayKeysReturnTypeExtensionTest.php | 15 +++++ 8 files changed, 164 insertions(+), 2 deletions(-) create mode 100644 src/Infer/Extensions/Event/FunctionCallEvent.php create mode 100644 src/Infer/Extensions/FunctionReturnTypeExtension.php create mode 100644 src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php create mode 100644 tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php diff --git a/src/Infer/Extensions/Event/FunctionCallEvent.php b/src/Infer/Extensions/Event/FunctionCallEvent.php new file mode 100644 index 00000000..9c321e9c --- /dev/null +++ b/src/Infer/Extensions/Event/FunctionCallEvent.php @@ -0,0 +1,29 @@ +scope->index->getFunctionDefinition($this->name); + } + + public function getName() + { + return $this->name; + } +} diff --git a/src/Infer/Extensions/ExtensionsBroker.php b/src/Infer/Extensions/ExtensionsBroker.php index f5d6e8be..1a40dcf9 100644 --- a/src/Infer/Extensions/ExtensionsBroker.php +++ b/src/Infer/Extensions/ExtensionsBroker.php @@ -2,6 +2,8 @@ namespace Dedoc\Scramble\Infer\Extensions; +use Dedoc\Scramble\Support\Type\FunctionType; + class ExtensionsBroker { public function __construct( @@ -41,6 +43,22 @@ public function getMethodReturnType($event) return null; } + public function getFunctionReturnType($event) + { + $extensions = array_filter($this->extensions, function ($e) use ($event) { + return $e instanceof FunctionReturnTypeExtension + && $e->shouldHandle($event->getName()); + }); + + foreach ($extensions as $extension) { + if ($propertyType = $extension->getFunctionReturnType($event)) { + return $propertyType; + } + } + + return null; + } + public function getStaticMethodReturnType($event) { $extensions = array_filter($this->extensions, function ($e) use ($event) { diff --git a/src/Infer/Extensions/FunctionReturnTypeExtension.php b/src/Infer/Extensions/FunctionReturnTypeExtension.php new file mode 100644 index 00000000..c93a0628 --- /dev/null +++ b/src/Infer/Extensions/FunctionReturnTypeExtension.php @@ -0,0 +1,13 @@ +getStaticMethodReturnType(new StaticMethodCallEvent( + if ($returnType = Context::getInstance()->extensionsBroker->getStaticMethodReturnType(new StaticMethodCallEvent( callee: $type->callee, name: $type->methodName, scope: $scope, @@ -333,6 +335,26 @@ private function resolveUnknownClassResolver(string $className): ?ClassDefinitio private function resolveCallableCallReferenceType(Scope $scope, CallableCallReferenceType $type) { + if ($type->callee instanceof CallableStringType) { + $analyzedType = clone $type; + + $analyzedType->arguments = array_map( + // @todo: fix resolving arguments when deep arg is reference + fn ($t) => $t instanceof AbstractReferenceType ? $this->resolve($scope, $t) : $t, + $type->arguments, + ); + + $returnType = Context::getInstance()->extensionsBroker->getFunctionReturnType(new FunctionCallEvent( + name: $analyzedType->callee->name, + scope: $scope, + arguments: $analyzedType->arguments, + )); + + if ($returnType) { + return $returnType; + } + } + $calleeType = $type->callee instanceof CallableStringType ? $this->index->getFunctionDefinition($type->callee->name) : $this->resolve($scope, $type->callee); diff --git a/src/ScrambleServiceProvider.php b/src/ScrambleServiceProvider.php index 8bc84f76..f554f4b7 100644 --- a/src/ScrambleServiceProvider.php +++ b/src/ScrambleServiceProvider.php @@ -16,6 +16,7 @@ use Dedoc\Scramble\Support\Generator\Components; use Dedoc\Scramble\Support\Generator\TypeTransformer; use Dedoc\Scramble\Support\InferExtensions\AbortHelpersExceptionInfer; +use Dedoc\Scramble\Support\InferExtensions\ArrayKeysReturnTypeExtension; use Dedoc\Scramble\Support\InferExtensions\JsonResourceCallsTypeInfer; use Dedoc\Scramble\Support\InferExtensions\JsonResourceCreationInfer; use Dedoc\Scramble\Support\InferExtensions\JsonResourceTypeInfer; @@ -75,6 +76,7 @@ public function configurePackage(Package $package): void $inferExtensionsClasses = array_merge([ ModelExtension::class, RuleExtension::class, + ArrayKeysReturnTypeExtension::class, ], $inferExtensionsClasses); return array_merge( diff --git a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php new file mode 100644 index 00000000..46755729 --- /dev/null +++ b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php @@ -0,0 +1,62 @@ +getArg('array', 0); + + if (!$argType instanceof ArrayType) { + return null; + } + + $keys = collect($argType->items)->map(fn (ArrayItemType_ $item) => $item->key); + + // assuming it is a list for now + if ($keys->count() === 1) { + return new ArrayType([ + new ArrayItemType_( + key: null, + value: Union::wrap([ + new StringType(), + new IntegerType(), + ]) + ) + ]); + } + + $numIndex = 0; + return new ArrayType( + array_map(function ($key) use (&$numIndex) { + if ($key === null || is_numeric($key)) { + return new ArrayItemType_( + key: null, + value: TypeHelper::createTypeFromValue($numIndex++), + ); + } + + return new ArrayItemType_( + key: null, + value: TypeHelper::createTypeFromValue($key), + ); + }, $keys->toArray()) + ); + } +} diff --git a/src/Support/Type/ObjectType.php b/src/Support/Type/ObjectType.php index 08ad8cd4..d95411e4 100644 --- a/src/Support/Type/ObjectType.php +++ b/src/Support/Type/ObjectType.php @@ -2,6 +2,7 @@ namespace Dedoc\Scramble\Support\Type; +use Dedoc\Scramble\Infer\Context; use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; use Dedoc\Scramble\Infer\Extensions\Event\MethodCallEvent; use Dedoc\Scramble\Infer\Extensions\Event\PropertyFetchEvent; @@ -54,7 +55,7 @@ public function getMethodDefinition(string $methodName, Scope $scope = new Globa public function getMethodReturnType(string $methodName, array $arguments = [], Scope $scope = new GlobalScope): ?Type { - if ($returnType = app(ExtensionsBroker::class)->getMethodReturnType(new MethodCallEvent( + if ($returnType = Context::getInstance()->extensionsBroker->getMethodReturnType(new MethodCallEvent( instance: $this, name: $methodName, scope: $scope, diff --git a/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php b/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php new file mode 100644 index 00000000..c3b90db3 --- /dev/null +++ b/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php @@ -0,0 +1,15 @@ + 1, "bar" => 2])', [new ArrayKeysReturnTypeExtension]); + + expect($type->toString())->toBe('array{0: string(foo), 1: string(bar)}'); +}); + +it('infers all array keys return type from non-string keys', function () { + $type = getStatementType('array_keys([1, "foo", "bar" => 42])', [new ArrayKeysReturnTypeExtension]); + + expect($type->toString())->toBe('array{0: int(0), 1: int(1), 2: string(bar)}'); +}); From 34a3a92de73be9cd9504cd4b697e550aa8da34a3 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Sat, 2 Dec 2023 12:34:49 +0200 Subject: [PATCH 17/24] wip --- src/Support/Type/ObjectType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Support/Type/ObjectType.php b/src/Support/Type/ObjectType.php index d95411e4..e7def718 100644 --- a/src/Support/Type/ObjectType.php +++ b/src/Support/Type/ObjectType.php @@ -29,7 +29,7 @@ public function isSame(Type $type) public function getPropertyType(string $propertyName, Scope $scope = new GlobalScope): Type { - if ($propertyType = app(ExtensionsBroker::class)->getPropertyType(new PropertyFetchEvent( + if ($propertyType = Context::getInstance()->extensionsBroker->getPropertyType(new PropertyFetchEvent( instance: $this, name: $propertyName, scope: $scope, From 4368d7b3277a65d8b72b6267168c85223395ed65 Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Sat, 2 Dec 2023 10:35:21 +0000 Subject: [PATCH 18/24] Fix styling --- src/Infer/Extensions/Event/FunctionCallEvent.php | 1 - src/Infer/Extensions/ExtensionsBroker.php | 2 -- src/Infer/Services/ReferenceTypeResolver.php | 1 - src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php | 5 +++-- src/Support/Type/ObjectType.php | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Infer/Extensions/Event/FunctionCallEvent.php b/src/Infer/Extensions/Event/FunctionCallEvent.php index 9c321e9c..bd5e0ecb 100644 --- a/src/Infer/Extensions/Event/FunctionCallEvent.php +++ b/src/Infer/Extensions/Event/FunctionCallEvent.php @@ -4,7 +4,6 @@ use Dedoc\Scramble\Infer\Extensions\Event\Concerns\ArgumentTypesAware; use Dedoc\Scramble\Infer\Scope\Scope; -use Dedoc\Scramble\Support\Type\ObjectType; class FunctionCallEvent { diff --git a/src/Infer/Extensions/ExtensionsBroker.php b/src/Infer/Extensions/ExtensionsBroker.php index 1a40dcf9..93a9bb66 100644 --- a/src/Infer/Extensions/ExtensionsBroker.php +++ b/src/Infer/Extensions/ExtensionsBroker.php @@ -2,8 +2,6 @@ namespace Dedoc\Scramble\Infer\Extensions; -use Dedoc\Scramble\Support\Type\FunctionType; - class ExtensionsBroker { public function __construct( diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index b1858ace..ee2b92b3 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -9,7 +9,6 @@ use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; use Dedoc\Scramble\Infer\Extensions\Event\FunctionCallEvent; use Dedoc\Scramble\Infer\Extensions\Event\StaticMethodCallEvent; -use Dedoc\Scramble\Infer\Extensions\ExtensionsBroker; use Dedoc\Scramble\Infer\Scope\Index; use Dedoc\Scramble\Infer\Scope\Scope; use Dedoc\Scramble\Support\Type\CallableStringType; diff --git a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php index 46755729..e9f3355b 100644 --- a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php +++ b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php @@ -23,7 +23,7 @@ public function getFunctionReturnType(FunctionCallEvent $event): ?Type { $argType = $event->getArg('array', 0); - if (!$argType instanceof ArrayType) { + if (! $argType instanceof ArrayType) { return null; } @@ -38,11 +38,12 @@ public function getFunctionReturnType(FunctionCallEvent $event): ?Type new StringType(), new IntegerType(), ]) - ) + ), ]); } $numIndex = 0; + return new ArrayType( array_map(function ($key) use (&$numIndex) { if ($key === null || is_numeric($key)) { diff --git a/src/Support/Type/ObjectType.php b/src/Support/Type/ObjectType.php index e7def718..d2580baa 100644 --- a/src/Support/Type/ObjectType.php +++ b/src/Support/Type/ObjectType.php @@ -6,7 +6,6 @@ use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; use Dedoc\Scramble\Infer\Extensions\Event\MethodCallEvent; use Dedoc\Scramble\Infer\Extensions\Event\PropertyFetchEvent; -use Dedoc\Scramble\Infer\Extensions\ExtensionsBroker; use Dedoc\Scramble\Infer\Scope\GlobalScope; use Dedoc\Scramble\Infer\Scope\Scope; From 14290a9cd030d3eb39d56a54802a486f433cc34c Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Mon, 4 Mar 2024 06:39:06 +0000 Subject: [PATCH 19/24] Fix styling --- src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php | 2 +- src/Support/Type/ConcatenatedStringType.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php b/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php index 82aef1e6..31bda728 100644 --- a/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php +++ b/src/Infer/Extensions/Event/Concerns/ArgumentTypesAware.php @@ -7,7 +7,7 @@ trait ArgumentTypesAware { - public function getArg(string $name, int $position, Type $default = null) + public function getArg(string $name, int $position, ?Type $default = null) { $default ??= new UnknownType(); diff --git a/src/Support/Type/ConcatenatedStringType.php b/src/Support/Type/ConcatenatedStringType.php index 12e20f90..d4890c87 100644 --- a/src/Support/Type/ConcatenatedStringType.php +++ b/src/Support/Type/ConcatenatedStringType.php @@ -5,7 +5,7 @@ class ConcatenatedStringType extends StringType { /** - * @param Type[] $parts Types of parts being concatenated. + * @param Type[] $parts Types of parts being concatenated. */ public function __construct(public array $parts) { From 8f742b0facabe966ce640c26f6ae60f4a9a01521 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Mon, 4 Mar 2024 09:12:22 +0200 Subject: [PATCH 20/24] fix attempt of making non-instantiable model --- src/Support/ResponseExtractor/ModelInfo.php | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Support/ResponseExtractor/ModelInfo.php b/src/Support/ResponseExtractor/ModelInfo.php index a3a785c7..d4d4ce04 100644 --- a/src/Support/ResponseExtractor/ModelInfo.php +++ b/src/Support/ResponseExtractor/ModelInfo.php @@ -54,6 +54,16 @@ public function handle() { $class = $this->qualifyModel($this->class); + $reflectionClass = new ReflectionClass($class); + if (! $reflectionClass->isInstantiable()) { + return collect([ + 'instance' => null, + 'class' => $class, + 'attributes' => collect(), + 'relations' => collect(), + ]); + } + /** @var Model $model */ $model = app()->make($class); From a71b627670ab63d09cc486db83ade9f63c490902 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Sun, 31 Mar 2024 20:09:30 +0300 Subject: [PATCH 21/24] fix tests --- src/Infer/Scope/Scope.php | 11 ++++- .../ArrayKeysReturnTypeExtension.php | 45 ++++++------------- src/Support/Type/TypeHelper.php | 6 ++- .../Services/ConstFetchTypeGetterTest.php | 2 +- .../Services/ReferenceTypeResolverTest.php | 2 +- tests/InferExtensions/RuleExtensionTest.php | 6 +-- .../ArrayKeysReturnTypeExtensionTest.php | 4 +- 7 files changed, 36 insertions(+), 40 deletions(-) diff --git a/src/Infer/Scope/Scope.php b/src/Infer/Scope/Scope.php index 5e966daf..a8456417 100644 --- a/src/Infer/Scope/Scope.php +++ b/src/Infer/Scope/Scope.php @@ -13,6 +13,7 @@ use Dedoc\Scramble\Support\Type\ArrayItemType_; use Dedoc\Scramble\Support\Type\ArrayType; use Dedoc\Scramble\Support\Type\CallableStringType; +use Dedoc\Scramble\Support\Type\KeyedArrayType; use Dedoc\Scramble\Support\Type\ObjectType; use Dedoc\Scramble\Support\Type\Reference\AbstractReferenceType; use Dedoc\Scramble\Support\Type\Reference\CallableCallReferenceType; @@ -218,7 +219,15 @@ private function getArgsTypes(array $args) return [$arg->name ? $arg->name->name : $index => $type]; } - if (! $type instanceof ArrayType) { + if (! $type instanceof ArrayType && ! $type instanceof KeyedArrayType) { + return [$arg->name ? $arg->name->name : $index => $type]; // falling back, but not sure if we should. Maybe some DTO is needed to represent unpacked arg type? + } + + if ($type instanceof ArrayType) { + /* + * For example, when passing something that is array, but shape is unknown + * $a = foo(...array_keys($bar)); + */ return [$arg->name ? $arg->name->name : $index => $type]; // falling back, but not sure if we should. Maybe some DTO is needed to represent unpacked arg type? } diff --git a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php index e9f3355b..271ed773 100644 --- a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php +++ b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php @@ -6,11 +6,9 @@ use Dedoc\Scramble\Infer\Extensions\FunctionReturnTypeExtension; use Dedoc\Scramble\Support\Type\ArrayItemType_; use Dedoc\Scramble\Support\Type\ArrayType; -use Dedoc\Scramble\Support\Type\IntegerType; -use Dedoc\Scramble\Support\Type\StringType; +use Dedoc\Scramble\Support\Type\KeyedArrayType; use Dedoc\Scramble\Support\Type\Type; use Dedoc\Scramble\Support\Type\TypeHelper; -use Dedoc\Scramble\Support\Type\Union; class ArrayKeysReturnTypeExtension implements FunctionReturnTypeExtension { @@ -23,41 +21,26 @@ public function getFunctionReturnType(FunctionCallEvent $event): ?Type { $argType = $event->getArg('array', 0); - if (! $argType instanceof ArrayType) { + if ( + ! $argType instanceof ArrayType + && ! $argType instanceof KeyedArrayType + ) { return null; } - $keys = collect($argType->items)->map(fn (ArrayItemType_ $item) => $item->key); - - // assuming it is a list for now - if ($keys->count() === 1) { - return new ArrayType([ - new ArrayItemType_( - key: null, - value: Union::wrap([ - new StringType(), - new IntegerType(), - ]) - ), - ]); + if ($argType instanceof ArrayType) { + return new ArrayType(value: $argType->key); } - $numIndex = 0; - - return new ArrayType( - array_map(function ($key) use (&$numIndex) { - if ($key === null || is_numeric($key)) { - return new ArrayItemType_( - key: null, - value: TypeHelper::createTypeFromValue($numIndex++), - ); - } - + $index = 0; + return new KeyedArrayType(array_map( + function (ArrayItemType_ $item) use (&$index) { return new ArrayItemType_( key: null, - value: TypeHelper::createTypeFromValue($key), + value: TypeHelper::createTypeFromValue($item->key === null ? $index++ : $item->key), ); - }, $keys->toArray()) - ); + }, + $argType->items, + )); } } diff --git a/src/Support/Type/TypeHelper.php b/src/Support/Type/TypeHelper.php index 305118e7..246b4ad8 100644 --- a/src/Support/Type/TypeHelper.php +++ b/src/Support/Type/TypeHelper.php @@ -145,7 +145,7 @@ public static function createTypeFromValue(mixed $value) } if (is_array($value)) { - return new ArrayType( + return new KeyedArrayType( collect($value) ->map(function ($value, $key) { return new ArrayItemType_( @@ -158,6 +158,10 @@ public static function createTypeFromValue(mixed $value) ); } + if (is_null($value)) { + return new NullType; + } + return null; // @todo: object } diff --git a/tests/Infer/Services/ConstFetchTypeGetterTest.php b/tests/Infer/Services/ConstFetchTypeGetterTest.php index aace32d4..5a85cc4a 100644 --- a/tests/Infer/Services/ConstFetchTypeGetterTest.php +++ b/tests/Infer/Services/ConstFetchTypeGetterTest.php @@ -6,7 +6,7 @@ it('gets const type from array value', function () { $type = (new ConstFetchTypeGetter)(new GlobalScope, ConstFetchTypeGetterTest_Foo::class, 'ARRAY'); - expect($type->toString())->toBe('array{0: string(foo), 1: string(bar)}'); + expect($type->toString())->toBe('list{string(foo), string(bar)}'); }); class ConstFetchTypeGetterTest_Foo { diff --git a/tests/Infer/Services/ReferenceTypeResolverTest.php b/tests/Infer/Services/ReferenceTypeResolverTest.php index 45a3fa9b..c3a06068 100644 --- a/tests/Infer/Services/ReferenceTypeResolverTest.php +++ b/tests/Infer/Services/ReferenceTypeResolverTest.php @@ -146,7 +146,7 @@ $type = getStatementType('(new ReferenceTypeResolverTest_Bar)->test()'); - expect($type->toString())->toBe('array{0: array{0: string(bar), 1: int(21)}, 1: array{0: string(foo), 1: int(21)}}'); + expect($type->toString())->toBe('list{list{string(bar), int(21)}, list{string(foo), int(21)}}'); }); class ReferenceTypeResolverTest_Foo { diff --git a/tests/InferExtensions/RuleExtensionTest.php b/tests/InferExtensions/RuleExtensionTest.php index b76ef8f1..b897fbcc 100644 --- a/tests/InferExtensions/RuleExtensionTest.php +++ b/tests/InferExtensions/RuleExtensionTest.php @@ -11,7 +11,7 @@ expect($type->toString())->toBe($expectedType); })->with([ - ['Illuminate\Validation\Rule::in(...["values" => ["foo", "bar"]])', 'Illuminate\Validation\Rules\In'], - ['Illuminate\Validation\Rule::in(values: ["foo", "bar"])', 'Illuminate\Validation\Rules\In'], - ['Illuminate\Validation\Rule::in(["foo", "bar"])', 'Illuminate\Validation\Rules\In'], + ['Illuminate\Validation\Rule::in(...["values" => ["foo", "bar"]])', 'Illuminate\Validation\Rules\In'], + ['Illuminate\Validation\Rule::in(values: ["foo", "bar"])', 'Illuminate\Validation\Rules\In'], + ['Illuminate\Validation\Rule::in(["foo", "bar"])', 'Illuminate\Validation\Rules\In'], ]); diff --git a/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php b/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php index c3b90db3..0f1adfec 100644 --- a/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php +++ b/tests/Support/InferExtensions/ArrayKeysReturnTypeExtensionTest.php @@ -5,11 +5,11 @@ it('infers string array keys return type from literal string keys', function () { $type = getStatementType('array_keys(["foo" => 1, "bar" => 2])', [new ArrayKeysReturnTypeExtension]); - expect($type->toString())->toBe('array{0: string(foo), 1: string(bar)}'); + expect($type->toString())->toBe('list{string(foo), string(bar)}'); }); it('infers all array keys return type from non-string keys', function () { $type = getStatementType('array_keys([1, "foo", "bar" => 42])', [new ArrayKeysReturnTypeExtension]); - expect($type->toString())->toBe('array{0: int(0), 1: int(1), 2: string(bar)}'); + expect($type->toString())->toBe('list{int(0), int(1), string(bar)}'); }); From ba2e1d1be650f23e89d412bbbb6fd8ae3654f521 Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Sun, 31 Mar 2024 17:10:01 +0000 Subject: [PATCH 22/24] Fix styling --- src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php index 271ed773..c99876db 100644 --- a/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php +++ b/src/Support/InferExtensions/ArrayKeysReturnTypeExtension.php @@ -33,6 +33,7 @@ public function getFunctionReturnType(FunctionCallEvent $event): ?Type } $index = 0; + return new KeyedArrayType(array_map( function (ArrayItemType_ $item) use (&$index) { return new ArrayItemType_( From f7ab03ebcfcc77954fc775c27e28646dc98f2432 Mon Sep 17 00:00:00 2001 From: Roman Lytvynenko Date: Mon, 20 May 2024 17:03:51 +0300 Subject: [PATCH 23/24] resolving default --- src/Support/IndexBuilders/RequestParametersBuilder.php | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Support/IndexBuilders/RequestParametersBuilder.php b/src/Support/IndexBuilders/RequestParametersBuilder.php index ca514cbf..404ae108 100644 --- a/src/Support/IndexBuilders/RequestParametersBuilder.php +++ b/src/Support/IndexBuilders/RequestParametersBuilder.php @@ -3,6 +3,7 @@ namespace Dedoc\Scramble\Support\IndexBuilders; use Dedoc\Scramble\Infer\Scope\Scope; +use Dedoc\Scramble\Infer\Services\ReferenceTypeResolver; use Dedoc\Scramble\Support\Generator\MissingExample; use Dedoc\Scramble\Support\Generator\Parameter; use Dedoc\Scramble\Support\Generator\Schema; @@ -151,7 +152,13 @@ private function makeStringParameter(Scope $scope, Node $node) private function makeEnumParameter(Scope $scope, Node $node) { - if (! $className = TypeHelper::getArgType($scope, $node->args, ['default', 1])->value ?? null) { + if (! $argType = TypeHelper::getArgType($scope, $node->args, ['default', 1])) { + return [null, null]; + } + + $argType = ReferenceTypeResolver::getInstance()->resolve($scope, $argType); + + if (! $className = $argType->value ?? null) { return [null, null]; } From 3d4631184bc5c94ad1570054787c068f9a04df5a Mon Sep 17 00:00:00 2001 From: romalytvynenko Date: Fri, 24 May 2024 14:29:45 +0000 Subject: [PATCH 24/24] Fix styling --- src/Infer/Services/ReferenceTypeResolver.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infer/Services/ReferenceTypeResolver.php b/src/Infer/Services/ReferenceTypeResolver.php index 255a1695..a5c3dbbd 100644 --- a/src/Infer/Services/ReferenceTypeResolver.php +++ b/src/Infer/Services/ReferenceTypeResolver.php @@ -7,8 +7,8 @@ use Dedoc\Scramble\Infer\Definition\ClassDefinition; use Dedoc\Scramble\Infer\Definition\ClassPropertyDefinition; use Dedoc\Scramble\Infer\Definition\FunctionLikeDefinition; -use Dedoc\Scramble\Infer\Extensions\Event\MethodCallEvent; use Dedoc\Scramble\Infer\Extensions\Event\FunctionCallEvent; +use Dedoc\Scramble\Infer\Extensions\Event\MethodCallEvent; use Dedoc\Scramble\Infer\Extensions\Event\StaticMethodCallEvent; use Dedoc\Scramble\Infer\Scope\Index; use Dedoc\Scramble\Infer\Scope\Scope;