1 <?php declare(strict_types=1);
3 namespace PhpParser\NodeVisitor;
6 use PhpParser\ErrorHandler;
7 use PhpParser\NameContext;
9 use PhpParser\Node\Expr;
10 use PhpParser\Node\Name;
11 use PhpParser\Node\Name\FullyQualified;
12 use PhpParser\Node\Stmt;
13 use PhpParser\NodeVisitorAbstract;
15 class NameResolver extends NodeVisitorAbstract
17 /** @var NameContext Naming context */
18 protected $nameContext;
20 /** @var bool Whether to preserve original names */
21 protected $preserveOriginalNames;
23 /** @var bool Whether to replace resolved nodes in place, or to add resolvedNode attributes */
24 protected $replaceNodes;
27 * Constructs a name resolution visitor.
30 * * preserveOriginalNames (default false): An "originalName" attribute will be added to
31 * all name nodes that underwent resolution.
32 * * replaceNodes (default true): Resolved names are replaced in-place. Otherwise, a
33 * resolvedName attribute is added. (Names that cannot be statically resolved receive a
34 * namespacedName attribute, as usual.)
36 * @param ErrorHandler|null $errorHandler Error handler
37 * @param array $options Options
39 public function __construct(ErrorHandler $errorHandler = null, array $options = []) {
40 $this->nameContext = new NameContext($errorHandler ?? new ErrorHandler\Throwing);
41 $this->preserveOriginalNames = $options['preserveOriginalNames'] ?? false;
42 $this->replaceNodes = $options['replaceNodes'] ?? true;
46 * Get name resolution context.
50 public function getNameContext() : NameContext {
51 return $this->nameContext;
54 public function beforeTraverse(array $nodes) {
55 $this->nameContext->startNamespace();
59 public function enterNode(Node $node) {
60 if ($node instanceof Stmt\Namespace_) {
61 $this->nameContext->startNamespace($node->name);
62 } elseif ($node instanceof Stmt\Use_) {
63 foreach ($node->uses as $use) {
64 $this->addAlias($use, $node->type, null);
66 } elseif ($node instanceof Stmt\GroupUse) {
67 foreach ($node->uses as $use) {
68 $this->addAlias($use, $node->type, $node->prefix);
70 } elseif ($node instanceof Stmt\Class_) {
71 if (null !== $node->extends) {
72 $node->extends = $this->resolveClassName($node->extends);
75 foreach ($node->implements as &$interface) {
76 $interface = $this->resolveClassName($interface);
79 if (null !== $node->name) {
80 $this->addNamespacedName($node);
82 } elseif ($node instanceof Stmt\Interface_) {
83 foreach ($node->extends as &$interface) {
84 $interface = $this->resolveClassName($interface);
87 $this->addNamespacedName($node);
88 } elseif ($node instanceof Stmt\Trait_) {
89 $this->addNamespacedName($node);
90 } elseif ($node instanceof Stmt\Function_) {
91 $this->addNamespacedName($node);
92 $this->resolveSignature($node);
93 } elseif ($node instanceof Stmt\ClassMethod
94 || $node instanceof Expr\Closure
96 $this->resolveSignature($node);
97 } elseif ($node instanceof Stmt\Const_) {
98 foreach ($node->consts as $const) {
99 $this->addNamespacedName($const);
101 } elseif ($node instanceof Expr\StaticCall
102 || $node instanceof Expr\StaticPropertyFetch
103 || $node instanceof Expr\ClassConstFetch
104 || $node instanceof Expr\New_
105 || $node instanceof Expr\Instanceof_
107 if ($node->class instanceof Name) {
108 $node->class = $this->resolveClassName($node->class);
110 } elseif ($node instanceof Stmt\Catch_) {
111 foreach ($node->types as &$type) {
112 $type = $this->resolveClassName($type);
114 } elseif ($node instanceof Expr\FuncCall) {
115 if ($node->name instanceof Name) {
116 $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_FUNCTION);
118 } elseif ($node instanceof Expr\ConstFetch) {
119 $node->name = $this->resolveName($node->name, Stmt\Use_::TYPE_CONSTANT);
120 } elseif ($node instanceof Stmt\TraitUse) {
121 foreach ($node->traits as &$trait) {
122 $trait = $this->resolveClassName($trait);
125 foreach ($node->adaptations as $adaptation) {
126 if (null !== $adaptation->trait) {
127 $adaptation->trait = $this->resolveClassName($adaptation->trait);
130 if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) {
131 foreach ($adaptation->insteadof as &$insteadof) {
132 $insteadof = $this->resolveClassName($insteadof);
141 private function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
142 // Add prefix for group uses
143 $name = $prefix ? Name::concat($prefix, $use->name) : $use->name;
144 // Type is determined either by individual element or whole use declaration
147 $this->nameContext->addAlias(
148 $name, (string) $use->getAlias(), $type, $use->getAttributes()
152 /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */
153 private function resolveSignature($node) {
154 foreach ($node->params as $param) {
155 $param->type = $this->resolveType($param->type);
157 $node->returnType = $this->resolveType($node->returnType);
160 private function resolveType($node) {
161 if ($node instanceof Node\NullableType) {
162 $node->type = $this->resolveType($node->type);
165 if ($node instanceof Name) {
166 return $this->resolveClassName($node);
172 * Resolve name, according to name resolver options.
174 * @param Name $name Function or constant name to resolve
175 * @param int $type One of Stmt\Use_::TYPE_*
177 * @return Name Resolved name, or original name with attribute
179 protected function resolveName(Name $name, int $type) : Name {
180 if (!$this->replaceNodes) {
181 $resolvedName = $this->nameContext->getResolvedName($name, $type);
182 if (null !== $resolvedName) {
183 $name->setAttribute('resolvedName', $resolvedName);
185 $name->setAttribute('namespacedName', FullyQualified::concat(
186 $this->nameContext->getNamespace(), $name, $name->getAttributes()));
191 if ($this->preserveOriginalNames) {
192 // Save the original name
193 $originalName = $name;
194 $name = clone $originalName;
195 $name->setAttribute('originalName', $originalName);
198 $resolvedName = $this->nameContext->getResolvedName($name, $type);
199 if (null !== $resolvedName) {
200 return $resolvedName;
203 // unqualified names inside a namespace cannot be resolved at compile-time
204 // add the namespaced version of the name as an attribute
205 $name->setAttribute('namespacedName', FullyQualified::concat(
206 $this->nameContext->getNamespace(), $name, $name->getAttributes()));
210 protected function resolveClassName(Name $name) {
211 return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL);
214 protected function addNamespacedName(Node $node) {
215 $node->namespacedName = Name::concat(
216 $this->nameContext->getNamespace(), (string) $node->name);