1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-09 22:49:31 +01:00
psalm/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php

256 lines
8.0 KiB
PHP
Raw Normal View History

<?php
// based on PhpParser's builtin one
2020-03-15 04:54:42 +01:00
namespace Psalm\Internal\PhpVisitor;
2018-07-23 00:00:08 +02:00
use PhpParser;
use PhpParser\ErrorHandler;
use PhpParser\NameContext;
use PhpParser\Node;
use PhpParser\Node\Expr;
use PhpParser\Node\Name;
2021-09-25 18:35:18 +02:00
use PhpParser\Node\NullableType;
use PhpParser\Node\Stmt;
use PhpParser\NodeVisitorAbstract;
/**
* @internal
*/
class SimpleNameResolver extends NodeVisitorAbstract
{
/** @var NameContext Naming context */
private $nameContext;
/** @var int|null */
private $start_change;
/** @var int|null */
private $end_change;
/**
* @param ErrorHandler $errorHandler Error handler
Add support for strict arrays, fix type alias intersection, fix array_is_list assertion on non-lists (#8395) * Immutable CodeLocation * Remove excess clones * Remove external clones * Remove leftover clones * Fix final clone issue * Immutable storages * Refactoring * Fixes * Fixes * Fix * Fix * Fixes * Simplify * Fixes * Fix * Fixes * Update * Fix * Cache global types * Fix * Update * Update * Fixes * Fixes * Refactor * Fixes * Fix * Fix * More caching * Fix * Fix * Update * Update * Fix * Fixes * Update * Refactor * Update * Fixes * Break one more test * Fix * FIx * Fix * Fix * Fix * Fix * Improve performance and readability * Equivalent logic * Fixes * Revert * Revert "Revert" This reverts commit f9175100c8452c80559234200663fd4c4f4dd889. * Fix * Fix reference bug * Make default TypeVisitor immutable * Bugfix * Remove clones * Partial refactoring * Refactoring * Fixes * Fix * Fixes * Fixes * cs-fix * Fix final bugs * Add test * Misc fixes * Update * Fixes * Experiment with removing different property * revert "Experiment with removing different property" This reverts commit ac1156e077fc4ea633530d51096d27b6e88bfdf9. * Uniform naming * Uniform naming * Hack hotfix * Clean up $_FILES ref #8621 * Undo hack, try fixing properly * Helper method * Remove redundant call * Partially fix bugs * Cleanup * Change defaults * Fix bug * Fix (?, hope this doesn't break anything else) * cs-fix * Review fixes * Bugfix * Bugfix * Improve logic * Add support for list{} and callable-list{} types, properly implement array_is_list assertions (fixes #8389) * Default to sealed arrays * Fix array_merge bug * Fixes * Fix * Sealed type checks * Properly infer properties-of and get_object_vars on final classes * Fix array_map zipping * Fix tests * Fixes * Fixes * Fix more stuff * Recursively resolve type aliases * Fix typo * Fixes * Fix array_is_list assertion on keyed array * Add BC docs * Fixes * fix * Update * Update * Update * Update * Seal arrays with count assertions * Fix #8528 * Fix * Update * Improve sealed array foreach logic * get_object_vars on template properties * Fix sealed array assertion reconciler logic * Improved reconciler * Add tests * Single source of truth for test types * Fix tests * Fixup tests * Fixup tests * Fixup tests * Update * Fix tests * Fix tests * Final fixes * Fixes * Use list syntax only when needed * Fix tests * Cs-fix * Update docs * Update docs * Update docs * Update docs * Update docs * Document missing types * Update docs * Improve class-string-map docs * Update * Update * I love working on psalm :) * Keep arrays unsealed by default * Fixup tests * Fix syntax mistake * cs-fix * Fix typo * Re-import missing types * Keep strict types only in return types * argc/argv fixes * argc/argv fixes * Fix test * Comment-out valinor code, pinging @romm pls merge https://github.com/CuyZ/Valinor/pull/246 so we can add valinor to the psalm docs :)
2022-11-05 22:34:42 +01:00
* @param null|array<int, strict-array{int, int, int, int, int, string}> $offset_map
*/
public function __construct(ErrorHandler $errorHandler, ?array $offset_map = null)
{
if ($offset_map) {
foreach ($offset_map as [, , $b_s, $b_e]) {
if ($this->start_change === null) {
$this->start_change = $b_s;
}
$this->end_change = $b_e;
}
}
$this->nameContext = new NameContext($errorHandler);
}
public function beforeTraverse(array $nodes): ?array
2019-07-05 22:24:00 +02:00
{
$this->nameContext->startNamespace();
2019-07-05 22:24:00 +02:00
return null;
}
public function enterNode(Node $node): ?int
2019-07-05 22:24:00 +02:00
{
if ($node instanceof Stmt\Namespace_) {
$this->nameContext->startNamespace($node->name);
} elseif ($node instanceof Stmt\Use_) {
foreach ($node->uses as $use) {
2021-09-26 21:09:20 +02:00
$this->addAlias($use, $node->type);
}
} elseif ($node instanceof Stmt\GroupUse) {
foreach ($node->uses as $use) {
$this->addAlias($use, $node->type, $node->prefix);
}
} elseif ($node instanceof Stmt\Class_) {
if (null !== $node->extends) {
$node->extends = $this->resolveClassName($node->extends);
}
foreach ($node->implements as &$interface) {
$interface = $this->resolveClassName($interface);
}
unset($interface);
$this->resolveAttrGroups($node);
if (null !== $node->name) {
$this->addNamespacedName($node);
}
}
2018-11-01 21:04:37 +01:00
if ($node instanceof Stmt\ClassMethod
&& $this->start_change
&& $this->end_change
) {
Add support for strict arrays, fix type alias intersection, fix array_is_list assertion on non-lists (#8395) * Immutable CodeLocation * Remove excess clones * Remove external clones * Remove leftover clones * Fix final clone issue * Immutable storages * Refactoring * Fixes * Fixes * Fix * Fix * Fixes * Simplify * Fixes * Fix * Fixes * Update * Fix * Cache global types * Fix * Update * Update * Fixes * Fixes * Refactor * Fixes * Fix * Fix * More caching * Fix * Fix * Update * Update * Fix * Fixes * Update * Refactor * Update * Fixes * Break one more test * Fix * FIx * Fix * Fix * Fix * Fix * Improve performance and readability * Equivalent logic * Fixes * Revert * Revert "Revert" This reverts commit f9175100c8452c80559234200663fd4c4f4dd889. * Fix * Fix reference bug * Make default TypeVisitor immutable * Bugfix * Remove clones * Partial refactoring * Refactoring * Fixes * Fix * Fixes * Fixes * cs-fix * Fix final bugs * Add test * Misc fixes * Update * Fixes * Experiment with removing different property * revert "Experiment with removing different property" This reverts commit ac1156e077fc4ea633530d51096d27b6e88bfdf9. * Uniform naming * Uniform naming * Hack hotfix * Clean up $_FILES ref #8621 * Undo hack, try fixing properly * Helper method * Remove redundant call * Partially fix bugs * Cleanup * Change defaults * Fix bug * Fix (?, hope this doesn't break anything else) * cs-fix * Review fixes * Bugfix * Bugfix * Improve logic * Add support for list{} and callable-list{} types, properly implement array_is_list assertions (fixes #8389) * Default to sealed arrays * Fix array_merge bug * Fixes * Fix * Sealed type checks * Properly infer properties-of and get_object_vars on final classes * Fix array_map zipping * Fix tests * Fixes * Fixes * Fix more stuff * Recursively resolve type aliases * Fix typo * Fixes * Fix array_is_list assertion on keyed array * Add BC docs * Fixes * fix * Update * Update * Update * Update * Seal arrays with count assertions * Fix #8528 * Fix * Update * Improve sealed array foreach logic * get_object_vars on template properties * Fix sealed array assertion reconciler logic * Improved reconciler * Add tests * Single source of truth for test types * Fix tests * Fixup tests * Fixup tests * Fixup tests * Update * Fix tests * Fix tests * Final fixes * Fixes * Use list syntax only when needed * Fix tests * Cs-fix * Update docs * Update docs * Update docs * Update docs * Update docs * Document missing types * Update docs * Improve class-string-map docs * Update * Update * I love working on psalm :) * Keep arrays unsealed by default * Fixup tests * Fix syntax mistake * cs-fix * Fix typo * Re-import missing types * Keep strict types only in return types * argc/argv fixes * argc/argv fixes * Fix test * Comment-out valinor code, pinging @romm pls merge https://github.com/CuyZ/Valinor/pull/246 so we can add valinor to the psalm docs :)
2022-11-05 22:34:42 +01:00
/** @var strict-array{startFilePos: int, endFilePos: int} */
$attrs = $node->getAttributes();
2018-11-01 21:04:37 +01:00
if ($cs = $node->getComments()) {
$attrs['startFilePos'] = $cs[0]->getStartFilePos();
2018-11-01 21:04:37 +01:00
}
if ($attrs['endFilePos'] < $this->start_change
|| $attrs['startFilePos'] > $this->end_change
) {
return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN;
}
}
if ($node instanceof Stmt\ClassMethod
|| $node instanceof Expr\Closure
) {
$this->resolveSignature($node);
} elseif ($node instanceof Expr\StaticCall
|| $node instanceof Expr\StaticPropertyFetch
|| $node instanceof Expr\ClassConstFetch
|| $node instanceof Expr\New_
|| $node instanceof Expr\Instanceof_
) {
if ($node->class instanceof Name) {
$node->class = $this->resolveClassName($node->class);
}
} elseif ($node instanceof Stmt\Catch_) {
foreach ($node->types as &$type) {
$type = $this->resolveClassName($type);
}
unset($type);
} elseif ($node instanceof Expr\FuncCall) {
if ($node->name instanceof Name) {
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
}
} elseif ($node instanceof Expr\ConstFetch) {
$node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
} elseif ($node instanceof Stmt\Trait_) {
$this->resolveTrait($node);
} elseif ($node instanceof Stmt\TraitUse) {
foreach ($node->traits as &$trait) {
$trait = $this->resolveClassName($trait);
}
unset($trait);
foreach ($node->adaptations as $adaptation) {
if (null !== $adaptation->trait) {
$adaptation->trait = $this->resolveClassName($adaptation->trait);
}
if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
foreach ($adaptation->insteadof as &$insteadof) {
$insteadof = $this->resolveClassName($insteadof);
}
unset($insteadof);
}
}
}
return null;
}
private function addAlias(Stmt\UseUse $use, int $type, ?Name $prefix = null): void
2019-07-05 22:24:00 +02:00
{
// Add prefix for group uses
2018-07-23 00:00:08 +02:00
/** @var Name $name */
$name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
// Type is determined either by individual element or whole use declaration
$type |= $use->type;
$this->nameContext->addAlias(
2019-07-05 22:24:00 +02:00
$name,
(string) $use->getAlias(),
$type,
$use->getAttributes()
);
}
2018-07-23 00:00:08 +02:00
/**
* @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node
*/
2020-10-12 21:46:47 +02:00
private function resolveSignature(PhpParser\NodeAbstract $node): void
2019-07-05 22:24:00 +02:00
{
foreach ($node->params as $param) {
$param->type = $this->resolveType($param->type);
}
$node->returnType = $this->resolveType($node->returnType);
}
2018-07-23 00:00:08 +02:00
/**
2021-09-25 17:57:18 +02:00
* @template T of Node|null
* @param T $node
* @return ($node is NullableType ? NullableType : ($node is Name ? Name : T))
2018-07-23 00:00:08 +02:00
*/
2021-09-25 17:57:18 +02:00
private function resolveType(?Node $node): ?Node
2019-07-05 22:24:00 +02:00
{
2021-09-25 18:38:01 +02:00
if ($node instanceof NullableType) {
$node->type = $this->resolveType($node->type);
2019-07-05 22:24:00 +02:00
return $node;
}
if ($node instanceof Name) {
return $this->resolveClassName($node);
}
2019-07-05 22:24:00 +02:00
return $node;
}
/**
* Resolve name, according to name resolver options.
*
* CAVE: Attribute values are of type `string`, this is
* different to PhpParser's `NameResolver` using objects.
*
* @param Name $name Function or constant name to resolve
* @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_*
*
* @return Name Resolved name, or original name with attribute
*/
2020-10-12 21:46:47 +02:00
protected function resolveName(Name $name, int $type): Name
2019-07-05 22:24:00 +02:00
{
$resolvedName = $this->nameContext->getResolvedName($name, $type);
if (null !== $resolvedName) {
$name->setAttribute('resolvedName', $resolvedName->toString());
} else {
$namespaceName = Name\FullyQualified::concat(
$this->nameContext->getNamespace(),
$name,
$name->getAttributes()
);
if ($namespaceName instanceof Name) {
$name->setAttribute('namespacedName', $namespaceName->toString());
}
}
return $name;
}
protected function resolveClassName(Name $name): Name
2019-07-05 22:24:00 +02:00
{
return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
}
protected function addNamespacedName(Stmt\Class_ $node): void
{
$node->setAttribute('namespacedName', Name::concat(
$this->nameContext->getNamespace(),
(string)$node->name
));
}
protected function resolveAttrGroups(Stmt\Class_ $node): void
{
foreach ($node->attrGroups as $attrGroup) {
foreach ($attrGroup->attrs as $attr) {
$attr->name = $this->resolveClassName($attr->name);
}
}
}
protected function resolveTrait(Stmt\Trait_ $node): void
{
$resolvedName = Name::concat($this->nameContext->getNamespace(), (string) $node->name);
if (null !== $resolvedName) {
$node->setAttribute('resolvedName', $resolvedName->toString());
}
}
}