Skip to content

Commit

Permalink
Optimize FileTypeMapper for huge PHPDocs
Browse files Browse the repository at this point in the history
  • Loading branch information
ondrejmirtes committed Aug 24, 2023
1 parent 379008a commit a4fa95a
Showing 1 changed file with 180 additions and 15 deletions.
195 changes: 180 additions & 15 deletions src/Type/FileTypeMapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ public function getResolvedPhpDoc(

private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameScope, string $phpDocString, ?string $fileName): ResolvedPhpDocBlock
{
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
$phpDocNode = $this->phpDocStringResolver->resolve($phpDocString);
if ($this->resolvedPhpDocBlockCacheCount >= 2048) {
$this->resolvedPhpDocBlockCache = array_slice(
$this->resolvedPhpDocBlockCache,
Expand Down Expand Up @@ -164,11 +164,6 @@ private function createResolvedPhpDocBlock(string $phpDocKey, NameScope $nameSco
return $this->resolvedPhpDocBlockCache[$phpDocKey];
}

private function resolvePhpDocStringToDocNode(string $phpDocString): PhpDocNode
{
return $this->phpDocStringResolver->resolve($phpDocString);
}

/**
* @return NameScope[]
*/
Expand Down Expand Up @@ -198,7 +193,8 @@ private function getNameScopeMap(string $fileName): array
*/
private function createResolvedPhpDocMap(string $fileName): array
{
$nameScopeMap = $this->createNameScopeMap($fileName, null, null, [], $fileName);
$phpDocNodeMap = $this->createPhpDocNodeMap($fileName, null, $fileName, [], $fileName);
$nameScopeMap = $this->createNameScopeMap($fileName, null, null, [], $fileName, $phpDocNodeMap);
$resolvedNameScopeMap = [];

try {
Expand All @@ -219,6 +215,175 @@ private function createResolvedPhpDocMap(string $fileName): array

/**
* @param array<string, string> $traitMethodAliases
* @return array<string, PhpDocNode>
*/
private function createPhpDocNodeMap(string $fileName, ?string $lookForTrait, ?string $traitUseClass, array $traitMethodAliases, string $originalClassFileName): array
{
/** @var array<string, PhpDocNode> $phpDocNodeMap */
$phpDocNodeMap = [];

/** @var string[] $classStack */
$classStack = [];
if ($lookForTrait !== null && $traitUseClass !== null) {
$classStack[] = $traitUseClass;
}
$namespace = null;

$traitFound = false;

/** @var array<string|null> $functionStack */
$functionStack = [];
$this->processNodes(
$this->phpParser->parseFile($fileName),
function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodAliases, $originalClassFileName, &$phpDocNodeMap, &$classStack, &$namespace, &$functionStack): ?int {
if ($node instanceof Node\Stmt\ClassLike) {
if ($traitFound && $fileName === $originalClassFileName) {
return self::SKIP_NODE;
}

if ($lookForTrait !== null && !$traitFound) {
if (!$node instanceof Node\Stmt\Trait_) {
return self::SKIP_NODE;
}
if ((string) $node->namespacedName !== $lookForTrait) {
return self::SKIP_NODE;
}

$traitFound = true;
$functionStack[] = null;
} else {
if ($node->name === null) {
if (!$node instanceof Node\Stmt\Class_) {
throw new ShouldNotHappenException();
}

$className = $this->anonymousClassNameHelper->getAnonymousClassName($node, $fileName);
} elseif ((bool) $node->getAttribute('anonymousClass', false)) {
$className = $node->name->name;
} else {
if ($traitFound) {
return self::SKIP_NODE;
}
$className = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\');
}
$classStack[] = $className;
$functionStack[] = null;
}
} elseif ($node instanceof Node\Stmt\ClassMethod) {
if (array_key_exists($node->name->name, $traitMethodAliases)) {
$functionStack[] = $traitMethodAliases[$node->name->name];
} else {
$functionStack[] = $node->name->name;
}
} elseif ($node instanceof Node\Stmt\Function_) {
$functionStack[] = ltrim(sprintf('%s\\%s', $namespace, $node->name->name), '\\');
}

$className = $classStack[count($classStack) - 1] ?? null;
$functionName = $functionStack[count($functionStack) - 1] ?? null;

if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
$docComment = GetLastDocComment::forNode($node);
if ($docComment !== null) {
$nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName);
$phpDocNodeMap[$nameScopeKey] = $this->phpDocStringResolver->resolve($docComment);
}

return null;
}

if ($node instanceof Node\Stmt\Namespace_) {
$namespace = $node->name !== null ? (string) $node->name : null;
} elseif ($node instanceof Node\Stmt\TraitUse) {
$traitMethodAliases = [];
foreach ($node->adaptations as $traitUseAdaptation) {
if (!$traitUseAdaptation instanceof Node\Stmt\TraitUseAdaptation\Alias) {
continue;
}

if ($traitUseAdaptation->newName === null) {
continue;
}

$methodName = $traitUseAdaptation->method->toString();
$newTraitName = $traitUseAdaptation->newName->toString();

if ($traitUseAdaptation->trait === null) {
foreach ($node->traits as $traitName) {
$traitMethodAliases[$traitName->toString()][$methodName] = $newTraitName;
}
continue;
}

$traitMethodAliases[$traitUseAdaptation->trait->toString()][$methodName] = $newTraitName;
}

foreach ($node->traits as $traitName) {
/** @var class-string $traitName */
$traitName = (string) $traitName;
$reflectionProvider = $this->reflectionProviderProvider->getReflectionProvider();
if (!$reflectionProvider->hasClass($traitName)) {
continue;
}

$traitReflection = $reflectionProvider->getClass($traitName);
if (!$traitReflection->isTrait()) {
continue;
}
if ($traitReflection->getFileName() === null) {
continue;
}
if (!is_file($traitReflection->getFileName())) {
continue;
}

$className = $classStack[count($classStack) - 1] ?? null;
if ($className === null) {
throw new ShouldNotHappenException();
}

$phpDocNodeMap = array_merge($phpDocNodeMap, $this->createPhpDocNodeMap(
$traitReflection->getFileName(),
$traitName,
$className,
$traitMethodAliases[$traitName] ?? [],
$originalClassFileName,
));
}
}

return null;
},
static function (Node $node) use (&$namespace, &$functionStack, &$classStack): void {
if ($node instanceof Node\Stmt\ClassLike) {
if (count($classStack) === 0) {
throw new ShouldNotHappenException();
}
array_pop($classStack);

if (count($functionStack) === 0) {
throw new ShouldNotHappenException();
}

array_pop($functionStack);
} elseif ($node instanceof Node\Stmt\Namespace_) {
$namespace = null;
} elseif ($node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
if (count($functionStack) === 0) {
throw new ShouldNotHappenException();
}

array_pop($functionStack);
}
},
);

return $phpDocNodeMap;
}

/**
* @param array<string, string> $traitMethodAliases
* @param array<string, PhpDocNode> $phpDocNodeMap
* @return (callable(): NameScope)[]
*/
private function createNameScopeMap(
Expand All @@ -227,6 +392,7 @@ private function createNameScopeMap(
?string $traitUseClass,
array $traitMethodAliases,
string $originalClassFileName,
array $phpDocNodeMap,
): array
{
/** @var (callable(): NameScope)[] $nameScopeMap */
Expand Down Expand Up @@ -254,7 +420,7 @@ private function createNameScopeMap(
$constUses = [];
$this->processNodes(
$this->phpParser->parseFile($fileName),
function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodAliases, $originalClassFileName, &$nameScopeMap, &$classStack, &$typeAliasStack, &$namespace, &$functionStack, &$uses, &$typeMapStack, &$constUses): ?int {
function (Node $node) use ($fileName, $lookForTrait, $phpDocNodeMap, &$traitFound, $traitMethodAliases, $originalClassFileName, &$nameScopeMap, &$classStack, &$typeAliasStack, &$namespace, &$functionStack, &$uses, &$typeMapStack, &$constUses): ?int {
if ($node instanceof Node\Stmt\ClassLike) {
if ($traitFound && $fileName === $originalClassFileName) {
return self::SKIP_NODE;
Expand Down Expand Up @@ -302,12 +468,12 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA

$className = $classStack[count($classStack) - 1] ?? null;
$functionName = $functionStack[count($functionStack) - 1] ?? null;
$nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName);

if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
$phpDocString = GetLastDocComment::forNode($node);
if ($phpDocString !== null) {
$typeMapStack[] = function () use ($namespace, $uses, $className, $lookForTrait, $functionName, $phpDocString, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap {
$phpDocNode = $this->resolvePhpDocStringToDocNode($phpDocString);
if (array_key_exists($nameScopeKey, $phpDocNodeMap)) {
$phpDocNode = $phpDocNodeMap[$nameScopeKey];
$typeMapStack[] = function () use ($namespace, $uses, $className, $lookForTrait, $functionName, $phpDocNode, $typeMapStack, $typeAliasStack, $constUses): TemplateTypeMap {
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
$currentTypeMap = $typeMapCb !== null ? $typeMapCb() : null;
$typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? [];
Expand All @@ -333,7 +499,6 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
$typeMapCb = $typeMapStack[count($typeMapStack) - 1] ?? null;
$typeAliasesMap = $typeAliasStack[count($typeAliasStack) - 1] ?? [];

$nameScopeKey = $this->getNameScopeKey($originalClassFileName, $className, $lookForTrait, $functionName);
if (
$node instanceof Node\Stmt
&& !$node instanceof Node\Stmt\Namespace_
Expand Down Expand Up @@ -362,8 +527,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
}

if ($node instanceof Node\Stmt\ClassLike || $node instanceof Node\Stmt\ClassMethod || $node instanceof Node\Stmt\Function_) {
$phpDocString = GetLastDocComment::forNode($node);
if ($phpDocString !== null) {
if (array_key_exists($nameScopeKey, $phpDocNodeMap)) {
return self::POP_TYPE_MAP_STACK;
}

Expand Down Expand Up @@ -450,6 +614,7 @@ function (Node $node) use ($fileName, $lookForTrait, &$traitFound, $traitMethodA
$className,
$traitMethodAliases[$traitName] ?? [],
$originalClassFileName,
$phpDocNodeMap,
);
$finalTraitPhpDocMap = [];
foreach ($traitPhpDocMap as $nameScopeTraitKey => $callback) {
Expand Down

0 comments on commit a4fa95a

Please sign in to comment.