8000 [TypeInfo] Fix type alias resolving · symfony/symfony@1bcb588 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1bcb588

Browse files
committed
[TypeInfo] Fix type alias resolving
1 parent b2a1379 commit 1bcb588

File tree

3 files changed

+87
-13
lines changed

3 files changed

+87
-13
lines changed

src/Symfony/Component/TypeInfo/Tests/Fixtures/DummyWithTypeAliases.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@
1212
namespace Symfony\Component\TypeInfo\Tests\Fixtures;
1313

1414
/**
15+
* @phpstan-type CustomArray = array{0: CustomInt, 1: CustomString, 2: bool}
1516
* @phpstan-type CustomString = string
1617
* @phpstan-import-type CustomInt from DummyWithPhpDoc
1718
* @phpstan-import-type CustomInt from DummyWithPhpDoc as AliasedCustomInt
1819
*
20+
* @psalm-type PsalmCustomArray = array{0: PsalmCustomInt, 1: PsalmCustomString, 2: bool}
1921
* @psalm-type PsalmCustomString = string
2022
* @psalm-import-type PsalmCustomInt from DummyWithPhpDoc
2123
* @psalm-import-type PsalmCustomInt from DummyWithPhpDoc as PsalmAliasedCustomInt
@@ -53,6 +55,13 @@ final class DummyWithTypeAliases
5355
public mixed $psalmOtherAliasedExternalAlias;
5456
}
5557

58+
/**
59+
* @phpstan-type Invalid = SomethingInvalid
60+
*/
61+
final class DummyWithInvalidTypeAlias
62+
{
63+
}
64+
5665
/**
5766
* @phpstan-import-type Invalid from DummyWithTypeAliases
5867
*/

src/Symfony/Component/TypeInfo/Tests/TypeContext/TypeContextFactoryTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
use Symfony\Component\TypeInfo\Exception\LogicException;
1616
use Symfony\Component\TypeInfo\Tests\Fixtures\AbstractDummy;
1717
use Symfony\Component\TypeInfo\Tests\Fixtures\Dummy;
18+
use Symfony\Component\TypeInfo\Tests\Fixtures\DummyWithInvalidTypeAlias;
1819
use Symfony\Component\TypeInfo\Tests\Fixtures\DummyWithInvalidTypeAliasImport;
1920
use Symfony\Component\TypeInfo\Tests\Fixtures\DummyWithTemplates;
2021
use Symfony\Component\TypeInfo\Tests\Fixtures\DummyWithTypeAliases;
@@ -128,27 +129,33 @@ public function testCollectTypeAliases()
128129
$this->assertEquals([
129130
'CustomString' => Type::string(),
130131
'CustomInt' => Type::int(),
132+
'CustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
131133
'AliasedCustomInt' => Type::int(),
132134
'PsalmCustomString' => Type::string(),
133135
'PsalmCustomInt' => Type::int(),
136+
'PsalmCustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
134137
'PsalmAliasedCustomInt' => Type::int(),
135138
], $this->typeContextFactory->createFromClassName(DummyWithTypeAliases::class)->typeAliases);
136139

137140
$this->assertEquals([
138141
'CustomString' => Type::string(),
139142
'CustomInt' => Type::int(),
143+
'CustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
140144
'AliasedCustomInt' => Type::int(),
141145
'PsalmCustomString' => Type::string(),
142146
'PsalmCustomInt' => Type::int(),
147+
'PsalmCustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
143148
'PsalmAliasedCustomInt' => Type::int(),
144149
], $this->typeContextFactory->createFromReflection(new \ReflectionClass(DummyWithTypeAliases::class))->typeAliases);
145150

146151
$this->assertEquals([
147152
'CustomString' => Type::string(),
148153
'CustomInt' => Type::int(),
154+
'CustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
149155
'AliasedCustomInt' => Type::int(),
150156
'PsalmCustomString' => Type::string(),
151157
'PsalmCustomInt' => Type::int(),
158+
'PsalmCustomArray' => Type::arrayShape([0 => Type::int(), 1 => Type::string(), 2 => Type::bool()]),
152159
'PsalmAliasedCustomInt' => Type::int(),
153160
], $this->typeContextFactory->createFromReflection(new \ReflectionProperty(DummyWithTypeAliases::class, 'localAlias'))->typeAliases);
154161
}
@@ -167,4 +174,12 @@ public function testThrowWhenImportingInvalidAlias()
167174

168175
$this->typeContextFactory->createFromClassName(DummyWithInvalidTypeAliasImport::class);
169176
}
177+
178+
public function testThrowWhenCannotResolveTypeAlias()
179+
{
180+
$this->expectException(LogicException::class);
181+
$this->expectExceptionMessage('Cannot resolve "Invalid" type alias.');
182+
183+
$this->typeContextFactory->createFromClassName(DummyWithInvalidTypeAlias::class);
184+
}
170185
}

src/Symfony/Component/TypeInfo/TypeContext/TypeContextFactory.php

Lines changed: 63 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -199,32 +199,82 @@ private function collectTypeAliases(\ReflectionClass $reflection, TypeContext $t
199199
}
200200

201201
$aliases = [];
202-
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-type') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-type') as $tag) {
203-
if (!$tag->value instanceof TypeAliasTagValueNode) {
202+
$resolvedAliases = [];
203+
204+
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-import-type') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-import-type') as $tag) {
205+
if (!$tag->value instanceof TypeAliasImportTagValueNode) {
204206
continue;
205207
}
206208

207-
$aliases[$tag->value->alias] = $this->stringTypeResolver->resolve((string) $tag->value->type, $typeContext);
209+
/** @var ObjectType $importedFromType */
210+
$importedFromType = $this->stringTypeResolver->resolve((string) $tag->value->importedFrom, $typeContext);
211+
$importedFromContext = $this->createFromClassName($importedFromType->getClassName());
212+
213+
$typeAlias = $importedFromContext->typeAliases[$tag->value->importedAlias] ?? null;
214+
if (!$typeAlias) {
215+
throw new LogicException(\sprintf('Cannot find any "%s" type alias in "%s".', $tag->value->importedAlias, $importedFromType->getClassName()));
216+
}
217+
218+
$resolvedAliases[$tag->value->importedAs ?? $tag->value->importedAlias] = $typeAlias;
208219
}
209220

210-
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-import-type') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-import-type') as $tag) {
211-
if (!$tag->value instanceof TypeAliasImportTagValueNode) {
221+
foreach ($this->getPhpDocNode($rawDocNode)->getTagsByName('@psalm-type') + $this->getPhpDocNode($rawDocNode)->getTagsByName('@phpstan-type') as $tag) {
222+
if (!$tag->value instanceof TypeAliasTagValueNode) {
212223
continue;
213224
}
214225

215-
/** @var ObjectType $importedType */
216-
$importedType = $this->stringTypeResolver->resolve((string) $tag->value->importedFrom, $typeContext);
217-
$importedTypeContext = $this->createFromClassName($importedType->getClassName());
226+
$aliases[$tag->value->alias] = (string) $tag->value->type;
227+
}
218228

219-
$typeAlias = $importedTypeContext->typeAliases[$tag->value->importedAlias] ?? null;
220-
if (!$typeAlias) {
221-
throw new LogicException(\sprintf('Cannot find any "%s" type alias in "%s".', $tag->value->importedAlias, $importedType->getClassName()));
229+
return $this->resolveTypeAliases($aliases, $resolvedAliases, $typeContext);
230+
}
231+
232+
/**
233+
* @param array<string, string> $toResolve
234+
* @param array<string, Type> $resolved
235+
*
236+
* @return array<string, Type>
237+
*/
238+
private function resolveTypeAliases(array $toResolve, array $resolved, TypeContext $typeContext): array
239+
{
240+
if ([] === $toResolve) {
241+
return [];
242+
}
243+
244+
$typeContext = new TypeContext(
245+
$typeContext->calledClassName,
246+
$typeContext->declaringClassName,
247+
$typeContext->namespace,
248+
$typeContext->uses,
249+
$typeContext->templates,
250+
$typeContext->typeAliases + $resolved,
251+
);
252+
253+
$succeeded = false;
254+
$lastFailure = null;
255+
$lastFailingAlias = null;
256+
257+
foreach ($toResolve as $alias => $type) {
258+
try {
259+
$resolved[$alias] = $this->stringTypeResolver->resolve($type, $typeContext);
260+
unset($toResolve[$alias]);
261+
$succeeded = true;
262+
} catch (UnsupportedException $lastFailure) {
263+
$lastFailingAlias = $alias;
222264
}
265+
}
266+
267+
// nothing has succeeded, the result won't be different from the
268+
// previous one, we can stop here.
269+
if (!$succeeded) {
270+
throw new LogicException(\sprintf('Cannot resolve "%s" type alias.', $lastFailingAlias), 0, $lastFailure);
271+
}
223272

224-
$aliases[$tag->value->importedAs ?? $tag->value->importedAlias] = $typeAlias;
273+
if ([] !== $toResolve) {
274+
return $this->resolveTypeAliases($toResolve, $resolved, $typeContext);
225275
}
226276

227-
return $aliases;
277+
return $resolved;
228278
}
229279

230280
private function getPhpDocNode(string $rawDocNode): PhpDocNode

0 commit comments

Comments
 (0)
0