diff --git a/phpunit/code/interface_const_trait_import.php b/phpunit/code/interface_const_trait_import.php new file mode 100644 index 00000000..e7cafb32 --- /dev/null +++ b/phpunit/code/interface_const_trait_import.php @@ -0,0 +1,32 @@ +addFiles([$file]); + $compiler->prepareFile($file); + $compiler->convertFile($file); + + $arginfo = file_get_contents($compiler->getArgInfoHeaderFile($file)); + self::assertIsString($arginfo); + self::assertStringContainsString('ZVAL_LONG(&property_verbosity_default_value, 32);', $arginfo); + self::assertStringNotContainsString('ZVAL_LONG(&property_verbosity_default_value, 999);', $arginfo); + } } diff --git a/src/gen_stub.php b/src/gen_stub.php index 2fb24636..f40a3a77 100755 --- a/src/gen_stub.php +++ b/src/gen_stub.php @@ -69,6 +69,14 @@ function reportFilePutContents(string $filename, string $content): void { function getClassConstFetchClassName(Expr\ClassConstFetch $expr): string { $className = $expr->class->toString(); + if ($expr->class instanceof PhpParser\Node\Name && !$expr->class->isSpecialClassName()) { + // Trait members are copied into the using class after NameResolver has + // visited it. Their resolvedName still records the trait's own imports. + $resolvedName = $expr->class->getAttribute('resolvedName'); + if ($resolvedName instanceof PhpParser\Node\Name) { + return '\\' . ltrim($resolvedName->toString(), '\\'); + } + } if ($expr->class instanceof PhpParser\Node\Name\FullyQualified) { return '\\' . $className; } diff --git a/tests/compiler/const/interface-const-trait-import.phpt b/tests/compiler/const/interface-const-trait-import.phpt new file mode 100644 index 00000000..450cf704 --- /dev/null +++ b/tests/compiler/const/interface-const-trait-import.phpt @@ -0,0 +1,74 @@ +--TEST-- +Imported interface constants in trait defaults keep the trait's lexical namespace +--FILE-- + Output::VERBOSITY_VERBOSE]; + + public function verbosity(): int { return $this->verbosity; } + public function levels(): array { return $this->levels; } + } +} + +namespace Wrapper { + use Traits\HasOutput; + + trait WrappedOutput + { + use HasOutput; + } +} + +namespace Local { + use Source\OutputInterface; + + trait HasLocalOutput + { + protected int $verbosity = OutputInterface::VERBOSITY_NORMAL; + + public function verbosity(): int { return $this->verbosity; } + } + + class Command + { + use HasLocalOutput; + } +} + +namespace Consumer { + class Command + { + use \Wrapper\WrappedOutput; + } +} + +namespace { + function main(): void + { + $command = new Consumer\Command(); + var_dump($command->verbosity()); + var_dump($command->levels()); + var_dump((new Local\Command())->verbosity()); + } +} +?> +--EXPECT-- +int(32) +array(1) { + ["verbose"]=> + int(64) +} +int(32)