|
16 | 16 | use PHPStan\PhpDocParser\Ast\PhpDoc\InvalidTagValueNode;
|
17 | 17 | use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
|
18 | 18 | use PHPStan\Reflection\ReflectionProvider\ReflectionProviderProvider;
|
| 19 | +use PHPStan\Type\Generic\GenericObjectType; |
19 | 20 | use PHPStan\Type\Generic\TemplateType;
|
20 | 21 | use PHPStan\Type\Generic\TemplateTypeFactory;
|
| 22 | +use PHPStan\Type\Generic\TemplateTypeHelper; |
21 | 23 | use PHPStan\Type\Generic\TemplateTypeMap;
|
22 | 24 | use function array_key_exists;
|
23 | 25 | use function file_exists;
|
@@ -214,7 +216,7 @@ private function shouldPhpDocNodeBeCachedToDisk(PhpDocNode $phpDocNode): bool
|
214 | 216 | private function getResolvedPhpDocMap(string $fileName): array
|
215 | 217 | {
|
216 | 218 | if (!isset($this->memoryCache[$fileName])) {
|
217 |
| - $cacheKey = sprintf('%s-phpdocstring-v6-generic-bound', $fileName); |
| 219 | + $cacheKey = sprintf('%s-phpdocstring-v7-generic-traits', $fileName); |
218 | 220 | $variableCacheKey = implode(',', array_map(static function (array $file): string {
|
219 | 221 | return sprintf('%s-%d', $file['filename'], $file['modifiedTime']);
|
220 | 222 | }, $this->getCachedDependentFilesWithTimestamps($fileName)));
|
@@ -313,56 +315,7 @@ function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAlia
|
313 | 315 | $resolvableTemplateTypes = true;
|
314 | 316 | }
|
315 | 317 | } elseif ($node instanceof Node\Stmt\TraitUse) {
|
316 |
| - $traitMethodAliases = []; |
317 |
| - foreach ($node->adaptations as $traitUseAdaptation) { |
318 |
| - if (!$traitUseAdaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { |
319 |
| - continue; |
320 |
| - } |
321 |
| - |
322 |
| - if ($traitUseAdaptation->trait === null) { |
323 |
| - continue; |
324 |
| - } |
325 |
| - |
326 |
| - if ($traitUseAdaptation->newName === null) { |
327 |
| - continue; |
328 |
| - } |
329 |
| - |
330 |
| - $traitMethodAliases[$traitUseAdaptation->trait->toString()][$traitUseAdaptation->method->toString()] = $traitUseAdaptation->newName->toString(); |
331 |
| - } |
332 |
| - |
333 |
| - foreach ($node->traits as $traitName) { |
334 |
| - /** @var class-string $traitName */ |
335 |
| - $traitName = (string) $traitName; |
336 |
| - $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); |
337 |
| - if (!$reflectionProvider->hasClass($traitName)) { |
338 |
| - continue; |
339 |
| - } |
340 |
| - |
341 |
| - $traitReflection = $reflectionProvider->getClass($traitName); |
342 |
| - if (!$traitReflection->isTrait()) { |
343 |
| - continue; |
344 |
| - } |
345 |
| - if ($traitReflection->getFileName() === false) { |
346 |
| - continue; |
347 |
| - } |
348 |
| - if (!file_exists($traitReflection->getFileName())) { |
349 |
| - continue; |
350 |
| - } |
351 |
| - |
352 |
| - $className = $classStack[count($classStack) - 1] ?? null; |
353 |
| - if ($className === null) { |
354 |
| - throw new \PHPStan\ShouldNotHappenException(); |
355 |
| - } |
356 |
| - |
357 |
| - $traitPhpDocMap = $this->createFilePhpDocMap( |
358 |
| - $traitReflection->getFileName(), |
359 |
| - $traitName, |
360 |
| - $className, |
361 |
| - $traitMethodAliases[$traitName] ?? [] |
362 |
| - ); |
363 |
| - $phpDocMap = array_merge($phpDocMap, $traitPhpDocMap); |
364 |
| - } |
365 |
| - return null; |
| 318 | + $resolvableTemplateTypes = true; |
366 | 319 | } elseif ($node instanceof Node\Stmt\ClassMethod) {
|
367 | 320 | $functionName = $node->name->name;
|
368 | 321 | if (array_key_exists($functionName, $traitMethodAliases)) {
|
@@ -431,10 +384,6 @@ function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAlia
|
431 | 384 | }
|
432 | 385 |
|
433 | 386 | $typeMapStack[] = function () use ($fileName, $className, $lookForTrait, $functionName, $phpDocString, $typeMapCb): TemplateTypeMap {
|
434 |
| - static $typeMap = null; |
435 |
| - if ($typeMap !== null) { |
436 |
| - return $typeMap; |
437 |
| - } |
438 | 387 | $resolvedPhpDoc = $this->getResolvedPhpDoc(
|
439 | 388 | $fileName,
|
440 | 389 | $className,
|
@@ -466,6 +415,113 @@ function (\PhpParser\Node $node) use ($fileName, $lookForTrait, $traitMethodAlia
|
466 | 415 |
|
467 | 416 | $uses[strtolower($use->getAlias()->name)] = sprintf('%s\\%s', $prefix, (string) $use->name);
|
468 | 417 | }
|
| 418 | + } elseif ($node instanceof Node\Stmt\TraitUse) { |
| 419 | + $traitMethodAliases = []; |
| 420 | + foreach ($node->adaptations as $traitUseAdaptation) { |
| 421 | + if (!$traitUseAdaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) { |
| 422 | + continue; |
| 423 | + } |
| 424 | + |
| 425 | + if ($traitUseAdaptation->trait === null) { |
| 426 | + continue; |
| 427 | + } |
| 428 | + |
| 429 | + if ($traitUseAdaptation->newName === null) { |
| 430 | + continue; |
| 431 | + } |
| 432 | + |
| 433 | + $traitMethodAliases[$traitUseAdaptation->trait->toString()][$traitUseAdaptation->method->toString()] = $traitUseAdaptation->newName->toString(); |
| 434 | + } |
| 435 | + |
| 436 | + $useDocComment = null; |
| 437 | + if ($node->getDocComment() !== null) { |
| 438 | + $useDocComment = $node->getDocComment()->getText(); |
| 439 | + } |
| 440 | + |
| 441 | + foreach ($node->traits as $traitName) { |
| 442 | + /** @var class-string $traitName */ |
| 443 | + $traitName = (string) $traitName; |
| 444 | + $reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider(); |
| 445 | + if (!$reflectionProvider->hasClass($traitName)) { |
| 446 | + continue; |
| 447 | + } |
| 448 | + |
| 449 | + $traitReflection = $reflectionProvider->getClass($traitName); |
| 450 | + if (!$traitReflection->isTrait()) { |
| 451 | + continue; |
| 452 | + } |
| 453 | + if ($traitReflection->getFileName() === false) { |
| 454 | + continue; |
| 455 | + } |
| 456 | + if (!file_exists($traitReflection->getFileName())) { |
| 457 | + continue; |
| 458 | + } |
| 459 | + |
| 460 | + $className = $classStack[count($classStack) - 1] ?? null; |
| 461 | + if ($className === null) { |
| 462 | + throw new \PHPStan\ShouldNotHappenException(); |
| 463 | + } |
| 464 | + |
| 465 | + $traitPhpDocMap = $this->createFilePhpDocMap( |
| 466 | + $traitReflection->getFileName(), |
| 467 | + $traitName, |
| 468 | + $className, |
| 469 | + $traitMethodAliases[$traitName] ?? [] |
| 470 | + ); |
| 471 | + $finalTraitPhpDocMap = []; |
| 472 | + foreach ($traitPhpDocMap as $phpDocKey => $callback) { |
| 473 | + $finalTraitPhpDocMap[$phpDocKey] = function () use ($callback, $traitReflection, $fileName, $className, $lookForTrait, $useDocComment): NameScopedPhpDocString { |
| 474 | + /** @var NameScopedPhpDocString $original */ |
| 475 | + $original = $callback(); |
| 476 | + if (!$traitReflection->isGeneric()) { |
| 477 | + return $original; |
| 478 | + } |
| 479 | + |
| 480 | + $traitTemplateTypeMap = $traitReflection->getTemplateTypeMap(); |
| 481 | + |
| 482 | + $useType = null; |
| 483 | + if ($useDocComment !== null) { |
| 484 | + $useTags = $this->getResolvedPhpDoc( |
| 485 | + $fileName, |
| 486 | + $className, |
| 487 | + $lookForTrait, |
| 488 | + null, |
| 489 | + $useDocComment |
| 490 | + )->getUsesTags(); |
| 491 | + foreach ($useTags as $useTag) { |
| 492 | + $useTagType = $useTag->getType(); |
| 493 | + if (!$useTagType instanceof GenericObjectType) { |
| 494 | + continue; |
| 495 | + } |
| 496 | + |
| 497 | + if ($useTagType->getClassName() !== $traitReflection->getName()) { |
| 498 | + continue; |
| 499 | + } |
| 500 | + |
| 501 | + $useType = $useTagType; |
| 502 | + break; |
| 503 | + } |
| 504 | + } |
| 505 | + |
| 506 | + if ($useType === null) { |
| 507 | + return new NameScopedPhpDocString( |
| 508 | + $original->getPhpDocString(), |
| 509 | + $original->getNameScope()->withTemplateTypeMap($traitTemplateTypeMap->resolveToBounds()) |
| 510 | + ); |
| 511 | + } |
| 512 | + |
| 513 | + $transformedTraitTypeMap = $traitReflection->typeMapFromList($useType->getTypes()); |
| 514 | + |
| 515 | + return new NameScopedPhpDocString( |
| 516 | + $original->getPhpDocString(), |
| 517 | + $original->getNameScope()->withTemplateTypeMap($traitTemplateTypeMap->map(static function (string $name, Type $type) use ($transformedTraitTypeMap): Type { |
| 518 | + return TemplateTypeHelper::resolveTemplateTypes($type, $transformedTraitTypeMap); |
| 519 | + })) |
| 520 | + ); |
| 521 | + }; |
| 522 | + } |
| 523 | + $phpDocMap = array_merge($phpDocMap, $finalTraitPhpDocMap); |
| 524 | + } |
469 | 525 | }
|
470 | 526 |
|
471 | 527 | return null;
|
|
0 commit comments