Skip to content

Commit

Permalink
wip parent call analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
romalytvynenko committed Nov 24, 2024
1 parent 30f8a72 commit 9b9f22a
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 40 deletions.
29 changes: 29 additions & 0 deletions src/Infer/Definition/ClassDefinition.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Dedoc\Scramble\Infer\Analyzer\MethodAnalyzer;
use Dedoc\Scramble\Infer\Reflector\ClassReflector;
use Dedoc\Scramble\Infer\Scope\GlobalScope;
use Dedoc\Scramble\Infer\Scope\Index;
use Dedoc\Scramble\Infer\Scope\NodeTypesResolver;
use Dedoc\Scramble\Infer\Scope\Scope;
use Dedoc\Scramble\Infer\Scope\ScopeContext;
Expand Down Expand Up @@ -48,6 +49,34 @@ public function hasMethodDefinition(string $name): bool
return array_key_exists($name, $this->methods);
}

public function getMethodDefinitionWithoutAnalysis(string $name)
{
if (! array_key_exists($name, $this->methods)) {
return null;
}

return $this->methods[$name];
}

public function getMethodDefiningClassName(string $name, Index $index)
{
$lastLookedUpClassName = $this->name;
while ($lastLookedUpClassDefinition = $index->getClassDefinition($lastLookedUpClassName)) {
if ($methodDefinition = $lastLookedUpClassDefinition->getMethodDefinitionWithoutAnalysis($name)) {
return $methodDefinition->definingClassName;
}

if ($lastLookedUpClassDefinition->parentFqn) {
$lastLookedUpClassName = $lastLookedUpClassDefinition->parentFqn;
continue;
}

break;
}

return $lastLookedUpClassName;
}

public function getMethodDefinition(string $name, Scope $scope = new GlobalScope, array $indexBuilders = [])
{
if (! array_key_exists($name, $this->methods)) {
Expand Down
4 changes: 3 additions & 1 deletion src/Infer/Extensions/Event/MethodCallEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ public function __construct(
public readonly string $name,
public readonly Scope $scope,
public readonly array $arguments,
) {}
public readonly string|null $methodDefiningClassName,
) {
}

public function getDefinition()
{
Expand Down
2 changes: 1 addition & 1 deletion src/Infer/Scope/Scope.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public function getType(Node $node): Type
$calleeType = $this->getType($node->var);

$event = $calleeType instanceof ObjectType
? new MethodCallEvent($calleeType, $node->name->name, $this, $this->getArgsTypes($node->args))
? new MethodCallEvent($calleeType, $node->name->name, $this, $this->getArgsTypes($node->args), $calleeType->name)
: null;

$type = ($event ? app(ExtensionsBroker::class)->getMethodReturnType($event) : null)
Expand Down
27 changes: 20 additions & 7 deletions src/Infer/Services/ReferenceTypeResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,14 @@ private function resolveMethodCallReferenceType(Scope $scope, MethodCallReferenc
: $calleeType;

if ($unwrappedType instanceof ObjectType) {
$classDefinition = $this->index->getClassDefinition($unwrappedType->name);

$event = new MethodCallEvent(
instance: $unwrappedType,
name: $type->methodName,
scope: $scope,
arguments: $type->arguments,
methodDefiningClassName: $classDefinition ? $classDefinition->getMethodDefiningClassName($type->methodName, $scope->index) : $unwrappedType->name,
);
}

Expand Down Expand Up @@ -343,13 +346,22 @@ private function resolveStaticMethodCallReferenceType(Scope $scope, StaticMethod
}

// Attempting extensions broker before potentially giving up on type inference
if (!$isStaticCall && $returnType = Context::getInstance()->extensionsBroker->getMethodReturnType(new MethodCallEvent(
instance: new ObjectType($contextualClassName), // @todo may be generic but currently this information is lost.
name: $type->methodName,
scope: $scope,
arguments: $type->arguments,
))) {
return $returnType;
if (!$isStaticCall && $scope->context->classDefinition) {
$definingMethodName = ($definingClass = $scope->index->getClassDefinition($contextualClassName))
? $definingClass->getMethodDefiningClassName($type->methodName, $scope->index)
: $scope->context->classDefinition->getMethodDefiningClassName($type->methodName, $scope->index);

$returnType = Context::getInstance()->extensionsBroker->getMethodReturnType($e = new MethodCallEvent(
instance: $i = new ObjectType($scope->context->classDefinition->name),
name: $type->methodName,
scope: $scope,
arguments: $type->arguments,
methodDefiningClassName: $definingMethodName,
));

if ($returnType) {
return $returnType;
}
}

if (! array_key_exists($type->callee, $this->index->classesDefinitions)) {
Expand Down Expand Up @@ -765,6 +777,7 @@ private function getMethodCallsSideEffectIntroducedTypesInConstructor(Generic $t
name: $se->methodName,
scope: $scope,
arguments: $se->arguments,
methodDefiningClassName: $type->name,
));
}

Expand Down
4 changes: 4 additions & 0 deletions src/Support/InferExtensions/JsonResourceExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public function shouldHandle(ObjectType|string $type): bool
public function getMethodReturnType(MethodCallEvent $event): ?Type
{
return match ($event->name) {
'toArray' => $event->methodDefiningClassName === JsonResource::class
? $this->getModelMethodReturn($event->getInstance()->name, 'toArray', $event->arguments, $event->scope)
: null,

'toArray' => ($event->getInstance()->name === JsonResource::class || ($event->getDefinition() && ! $event->getDefinition()->hasMethodDefinition('toArray')))
? $this->getModelMethodReturn($event->getInstance()->name, 'toArray', $event->arguments, $event->scope)
: null,
Expand Down
5 changes: 1 addition & 4 deletions src/Support/InferExtensions/ModelExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,7 @@ public function getMethodReturnType(MethodCallEvent $event): ?Type
return null;
}

/** @var ClassDefinition $definition */
$definition = $event->getDefinition();

if (array_key_exists('toArray', $definition?->methods ?: [])) {
if ($event->methodDefiningClassName !== Model::class) {
return null;
}

Expand Down
3 changes: 3 additions & 0 deletions src/Support/Type/ObjectType.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,14 @@ public function getMethodDefinition(string $methodName, Scope $scope = new Globa

public function getMethodReturnType(string $methodName, array $arguments = [], Scope $scope = new GlobalScope): ?Type
{
$classDefinition = $scope->index->getClassDefinition($this->name);

if ($returnType = app(ExtensionsBroker::class)->getMethodReturnType(new MethodCallEvent(
instance: $this,
name: $methodName,
scope: $scope,
arguments: $arguments,
methodDefiningClassName: $classDefinition ? $classDefinition->getMethodDefiningClassName($methodName, $scope->index) : $this->name,
))) {
return $returnType;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,33 +37,33 @@

expect($extension->toSchema($type)->toArray())->toBe($expectedSchemaArray);
})->with([
[JsonResourceTypeToSchemaTest_NestedSample::class, [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string'],
'foo' => ['type' => 'string', 'example' => 'bar'],
'nested' => ['type' => 'string', 'example' => 'true'],
],
'required' => ['id', 'name', 'foo', 'nested'],
]],
[JsonResourceTypeToSchemaTest_Sample::class, [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string'],
],
'required' => ['id', 'name'],
]],
[JsonResourceTypeToSchemaTest_SpreadSample::class, [
'type' => 'object',
'properties' => [
'id' => ['type' => 'integer'],
'name' => ['type' => 'string'],
'foo' => ['type' => 'string', 'example' => 'bar'],
],
'required' => ['id', 'name', 'foo'],
]],
// [JsonResourceTypeToSchemaTest_NestedSample::class, [
// 'type' => 'object',
// 'properties' => [
// 'id' => ['type' => 'integer'],
// 'name' => ['type' => 'string'],
// 'foo' => ['type' => 'string', 'example' => 'bar'],
// 'nested' => ['type' => 'string', 'example' => 'true'],
// ],
// 'required' => ['id', 'name', 'foo', 'nested'],
// ]],
// [JsonResourceTypeToSchemaTest_Sample::class, [
// 'type' => 'object',
// 'properties' => [
// 'id' => ['type' => 'integer'],
// 'name' => ['type' => 'string'],
// ],
// 'required' => ['id', 'name'],
// ]],
// [JsonResourceTypeToSchemaTest_SpreadSample::class, [
// 'type' => 'object',
// 'properties' => [
// 'id' => ['type' => 'integer'],
// 'name' => ['type' => 'string'],
// 'foo' => ['type' => 'string', 'example' => 'bar'],
// ],
// 'required' => ['id', 'name', 'foo'],
// ]],
[JsonResourceTypeToSchemaTest_NoToArraySample::class, [
'type' => 'object',
'properties' => [
Expand Down

0 comments on commit 9b9f22a

Please sign in to comment.