Chris@0: [aliasName => originalName]] */ Chris@0: protected $aliases; Chris@0: Chris@0: /** @var ErrorHandler Error handler */ Chris@0: protected $errorHandler; Chris@0: Chris@0: /** @var bool Whether to preserve original names */ Chris@0: protected $preserveOriginalNames; Chris@0: Chris@0: /** Chris@0: * Constructs a name resolution visitor. Chris@0: * Chris@0: * Options: If "preserveOriginalNames" is enabled, an "originalName" attribute will be added to Chris@0: * all name nodes that underwent resolution. Chris@0: * Chris@0: * @param ErrorHandler|null $errorHandler Error handler Chris@0: * @param array $options Options Chris@0: */ Chris@0: public function __construct(ErrorHandler $errorHandler = null, array $options = []) { Chris@0: $this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing; Chris@0: $this->preserveOriginalNames = !empty($options['preserveOriginalNames']); Chris@0: } Chris@0: Chris@0: public function beforeTraverse(array $nodes) { Chris@0: $this->resetState(); Chris@0: } Chris@0: Chris@0: public function enterNode(Node $node) { Chris@0: if ($node instanceof Stmt\Namespace_) { Chris@0: $this->resetState($node->name); Chris@0: } elseif ($node instanceof Stmt\Use_) { Chris@0: foreach ($node->uses as $use) { Chris@0: $this->addAlias($use, $node->type, null); Chris@0: } Chris@0: } elseif ($node instanceof Stmt\GroupUse) { Chris@0: foreach ($node->uses as $use) { Chris@0: $this->addAlias($use, $node->type, $node->prefix); Chris@0: } Chris@0: } elseif ($node instanceof Stmt\Class_) { Chris@0: if (null !== $node->extends) { Chris@0: $node->extends = $this->resolveClassName($node->extends); Chris@0: } Chris@0: Chris@0: foreach ($node->implements as &$interface) { Chris@0: $interface = $this->resolveClassName($interface); Chris@0: } Chris@0: Chris@0: if (null !== $node->name) { Chris@0: $this->addNamespacedName($node); Chris@0: } Chris@0: } elseif ($node instanceof Stmt\Interface_) { Chris@0: foreach ($node->extends as &$interface) { Chris@0: $interface = $this->resolveClassName($interface); Chris@0: } Chris@0: Chris@0: $this->addNamespacedName($node); Chris@0: } elseif ($node instanceof Stmt\Trait_) { Chris@0: $this->addNamespacedName($node); Chris@0: } elseif ($node instanceof Stmt\Function_) { Chris@0: $this->addNamespacedName($node); Chris@0: $this->resolveSignature($node); Chris@0: } elseif ($node instanceof Stmt\ClassMethod Chris@0: || $node instanceof Expr\Closure Chris@0: ) { Chris@0: $this->resolveSignature($node); Chris@0: } elseif ($node instanceof Stmt\Const_) { Chris@0: foreach ($node->consts as $const) { Chris@0: $this->addNamespacedName($const); Chris@0: } Chris@0: } elseif ($node instanceof Expr\StaticCall Chris@0: || $node instanceof Expr\StaticPropertyFetch Chris@0: || $node instanceof Expr\ClassConstFetch Chris@0: || $node instanceof Expr\New_ Chris@0: || $node instanceof Expr\Instanceof_ Chris@0: ) { Chris@0: if ($node->class instanceof Name) { Chris@0: $node->class = $this->resolveClassName($node->class); Chris@0: } Chris@0: } elseif ($node instanceof Stmt\Catch_) { Chris@0: foreach ($node->types as &$type) { Chris@0: $type = $this->resolveClassName($type); Chris@0: } Chris@0: } elseif ($node instanceof Expr\FuncCall) { Chris@0: if ($node->name instanceof Name) { Chris@0: $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_FUNCTION); Chris@0: } Chris@0: } elseif ($node instanceof Expr\ConstFetch) { Chris@0: $node->name = $this->resolveOtherName($node->name, Stmt\Use_::TYPE_CONSTANT); Chris@0: } elseif ($node instanceof Stmt\TraitUse) { Chris@0: foreach ($node->traits as &$trait) { Chris@0: $trait = $this->resolveClassName($trait); Chris@0: } Chris@0: Chris@0: foreach ($node->adaptations as $adaptation) { Chris@0: if (null !== $adaptation->trait) { Chris@0: $adaptation->trait = $this->resolveClassName($adaptation->trait); Chris@0: } Chris@0: Chris@0: if ($adaptation instanceof Stmt\TraitUseAdaptation\Precedence) { Chris@0: foreach ($adaptation->insteadof as &$insteadof) { Chris@0: $insteadof = $this->resolveClassName($insteadof); Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: } Chris@0: Chris@0: protected function resetState(Name $namespace = null) { Chris@0: $this->namespace = $namespace; Chris@0: $this->aliases = array( Chris@0: Stmt\Use_::TYPE_NORMAL => array(), Chris@0: Stmt\Use_::TYPE_FUNCTION => array(), Chris@0: Stmt\Use_::TYPE_CONSTANT => array(), Chris@0: ); Chris@0: } Chris@0: Chris@0: protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) { Chris@0: // Add prefix for group uses Chris@0: $name = $prefix ? Name::concat($prefix, $use->name) : $use->name; Chris@0: // Type is determined either by individual element or whole use declaration Chris@0: $type |= $use->type; Chris@0: Chris@0: // Constant names are case sensitive, everything else case insensitive Chris@0: if ($type === Stmt\Use_::TYPE_CONSTANT) { Chris@0: $aliasName = $use->alias; Chris@0: } else { Chris@0: $aliasName = strtolower($use->alias); Chris@0: } Chris@0: Chris@0: if (isset($this->aliases[$type][$aliasName])) { Chris@0: $typeStringMap = array( Chris@0: Stmt\Use_::TYPE_NORMAL => '', Chris@0: Stmt\Use_::TYPE_FUNCTION => 'function ', Chris@0: Stmt\Use_::TYPE_CONSTANT => 'const ', Chris@0: ); Chris@0: Chris@0: $this->errorHandler->handleError(new Error( Chris@0: sprintf( Chris@0: 'Cannot use %s%s as %s because the name is already in use', Chris@0: $typeStringMap[$type], $name, $use->alias Chris@0: ), Chris@0: $use->getAttributes() Chris@0: )); Chris@0: return; Chris@0: } Chris@0: Chris@0: $this->aliases[$type][$aliasName] = $name; Chris@0: } Chris@0: Chris@0: /** @param Stmt\Function_|Stmt\ClassMethod|Expr\Closure $node */ Chris@0: private function resolveSignature($node) { Chris@0: foreach ($node->params as $param) { Chris@0: $param->type = $this->resolveType($param->type); Chris@0: } Chris@0: $node->returnType = $this->resolveType($node->returnType); Chris@0: } Chris@0: Chris@0: private function resolveType($node) { Chris@0: if ($node instanceof Node\NullableType) { Chris@0: $node->type = $this->resolveType($node->type); Chris@0: return $node; Chris@0: } Chris@0: if ($node instanceof Name) { Chris@0: return $this->resolveClassName($node); Chris@0: } Chris@0: return $node; Chris@0: } Chris@0: Chris@0: protected function resolveClassName(Name $name) { Chris@0: if ($this->preserveOriginalNames) { Chris@0: // Save the original name Chris@0: $originalName = $name; Chris@0: $name = clone $originalName; Chris@0: $name->setAttribute('originalName', $originalName); Chris@0: } Chris@0: Chris@0: // don't resolve special class names Chris@0: if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) { Chris@0: if (!$name->isUnqualified()) { Chris@0: $this->errorHandler->handleError(new Error( Chris@0: sprintf("'\\%s' is an invalid class name", $name->toString()), Chris@0: $name->getAttributes() Chris@0: )); Chris@0: } Chris@0: return $name; Chris@0: } Chris@0: Chris@0: // fully qualified names are already resolved Chris@0: if ($name->isFullyQualified()) { Chris@0: return $name; Chris@0: } Chris@0: Chris@0: $aliasName = strtolower($name->getFirst()); Chris@0: if (!$name->isRelative() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) { Chris@0: // resolve aliases (for non-relative names) Chris@0: $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName]; Chris@0: return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); Chris@0: } Chris@0: Chris@0: // if no alias exists prepend current namespace Chris@0: return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); Chris@0: } Chris@0: Chris@0: protected function resolveOtherName(Name $name, $type) { Chris@0: if ($this->preserveOriginalNames) { Chris@0: // Save the original name Chris@0: $originalName = $name; Chris@0: $name = clone $originalName; Chris@0: $name->setAttribute('originalName', $originalName); Chris@0: } Chris@0: Chris@0: // fully qualified names are already resolved Chris@0: if ($name->isFullyQualified()) { Chris@0: return $name; Chris@0: } Chris@0: Chris@0: // resolve aliases for qualified names Chris@0: $aliasName = strtolower($name->getFirst()); Chris@0: if ($name->isQualified() && isset($this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName])) { Chris@0: $alias = $this->aliases[Stmt\Use_::TYPE_NORMAL][$aliasName]; Chris@0: return FullyQualified::concat($alias, $name->slice(1), $name->getAttributes()); Chris@0: } Chris@0: Chris@0: if ($name->isUnqualified()) { Chris@0: if ($type === Stmt\Use_::TYPE_CONSTANT) { Chris@0: // constant aliases are case-sensitive, function aliases case-insensitive Chris@0: $aliasName = $name->getFirst(); Chris@0: } Chris@0: Chris@0: if (isset($this->aliases[$type][$aliasName])) { Chris@0: // resolve unqualified aliases Chris@0: return new FullyQualified($this->aliases[$type][$aliasName], $name->getAttributes()); Chris@0: } Chris@0: Chris@0: if (null === $this->namespace) { Chris@0: // outside of a namespace unaliased unqualified is same as fully qualified Chris@0: return new FullyQualified($name, $name->getAttributes()); Chris@0: } Chris@0: Chris@0: // unqualified names inside a namespace cannot be resolved at compile-time Chris@0: // add the namespaced version of the name as an attribute Chris@0: $name->setAttribute('namespacedName', Chris@0: FullyQualified::concat($this->namespace, $name, $name->getAttributes())); Chris@0: return $name; Chris@0: } Chris@0: Chris@0: // if no alias exists prepend current namespace Chris@0: return FullyQualified::concat($this->namespace, $name, $name->getAttributes()); Chris@0: } Chris@0: Chris@0: protected function addNamespacedName(Node $node) { Chris@0: $node->namespacedName = Name::concat($this->namespace, $node->name); Chris@0: } Chris@0: }