fix trait import name resolution

master
韩天峰 1 day ago
parent 7d698987d7
commit 7bdb6dcd44
  1. 9
      src/Resolver/NameResolutionTrait.php
  2. 40
      tests/compiler/namespace/use-import-root-semantics.phpt
  3. 88
      tests/compiler/trait/trait-import-same-as-namespace.phpt

@ -104,6 +104,15 @@ trait NameResolutionTrait
if (isset($this->zendTypeMap[strtolower($typeName)]) || in_array(strtolower($typeName), ['self', 'static', 'parent'], true)) {
return $type;
}
// NameResolver has already applied the declaring file's namespace
// imports. Keep that canonical identity when a trait signature is
// copied into its consuming class. In particular, after
// `use X\X; use X\Y;`, resolving Y produces X\Y; feeding that string
// through the import table again would incorrectly produce X\X\Y.
$resolvedName = $type->getAttribute('resolvedName');
if ($resolvedName instanceof Node\Name\FullyQualified) {
return new Node\Name\FullyQualified($resolvedName->toString(), $type->getAttributes());
}
$resolved = $typeName;
$firstSegment = explode('\\', $typeName, 2)[0];
$hasImportedPrefix = $this->getClassImportAlias($firstSegment) !== null;

@ -0,0 +1,40 @@
--TEST--
Namespace imports are rooted while qualified name uses still expand their first alias segment
--FILE--
<?php
namespace Carbon {
interface CarbonInterface {}
class Carbon implements CarbonInterface {}
}
namespace App {
use Carbon\Carbon;
use Carbon\CarbonInterface;
use Carbon\{Carbon as GroupedCarbon, CarbonInterface as GroupedInterface};
function importedNames(): array {
return [
Carbon::class,
CarbonInterface::class,
GroupedCarbon::class,
GroupedInterface::class,
Carbon\CarbonInterface::class,
];
}
}
namespace {
function main(): void {
foreach (App\importedNames() as $name) {
echo $name, "\n";
}
}
}
?>
--EXPECT--
Carbon\Carbon
Carbon\CarbonInterface
Carbon\Carbon
Carbon\CarbonInterface
Carbon\Carbon\CarbonInterface

@ -0,0 +1,88 @@
--TEST--
Trait types retain rooted imports when an imported class has the same short name as its namespace
--FILE--
<?php
namespace App {
use Carbon\Carbon;
use Carbon\CarbonInterface;
trait Checker {
public CarbonInterface $value;
public function accept(CarbonInterface $value): CarbonInterface {
$this->value = $value;
return $value;
}
public function acceptNullable(?CarbonInterface $value): ?CarbonInterface {
return $value;
}
public function check(mixed $value): bool {
return $value instanceof CarbonInterface;
}
public function make(): CarbonInterface {
return Carbon::parse('value');
}
public function importedNames(): array {
return [
Carbon::class,
CarbonInterface::class,
Carbon\CarbonInterface::class,
];
}
}
class Example {
use Checker;
}
}
namespace {
function main(): void {
eval(<<<'PHP'
namespace Carbon;
interface CarbonInterface {}
class Carbon implements CarbonInterface {
public static function parse(string $value): CarbonInterface {
return new self();
}
}
PHP);
$example = new App\Example();
$value = new Carbon\Carbon();
var_dump($example->accept($value) === $value);
var_dump($example->acceptNullable($value) === $value);
var_dump($example->acceptNullable(null));
var_dump($example->check($value));
var_dump($example->make() instanceof Carbon\CarbonInterface);
$method = new ReflectionMethod(App\Example::class, 'accept');
echo $method->getParameters()[0]->getType()->getName(), "\n";
echo $method->getReturnType()->getName(), "\n";
$property = new ReflectionProperty(App\Example::class, 'value');
echo $property->getType()->getName(), "\n";
foreach ($example->importedNames() as $name) {
echo $name, "\n";
}
}
}
?>
--EXPECT--
bool(true)
bool(true)
NULL
bool(true)
bool(true)
Carbon\CarbonInterface
Carbon\CarbonInterface
Carbon\CarbonInterface
Carbon\Carbon
Carbon\CarbonInterface
Carbon\Carbon\CarbonInterface
Loading…
Cancel
Save