- Add support for __NAMESPACE__ magic constant resolution in namespace, class, and global scopes - Add support for __PROPERTY__ magic constant that resolves to property name within property context - Implement namespace\name relative name resolution for functions, constants, classes, and members - Update AnonClassGenerator to properly parse identifiers instead of using toString() - Extend CompilerBase to handle Scalar_MagicConst_Namespace and Scalar_MagicConst_Property - Modify class inheritance and interface implementation to use parseIdentifier for proper name resolution - Add comprehensive tests for namespace magic constant behavior across different scopes - Add tests for property magic constant resolving correctly in property contexts only - Implement runtime attribute factory lowering with proper namespace context handling - Update visitor pattern to track property magic boundaries and namespace context correctly - Document incompatibility with PHP 8.5 #[NoDiscard]master
parent
f64a1d5cb7
commit
c5f478766e
10 changed files with 360 additions and 6 deletions
@ -0,0 +1,48 @@ |
||||
--TEST-- |
||||
__NAMESPACE__ resolves in namespace, class, and global scopes |
||||
--FILE-- |
||||
<?php |
||||
namespace Project\Feature { |
||||
#[\Attribute(\Attribute::TARGET_CLASS)] |
||||
class NamespaceName |
||||
{ |
||||
public function __construct(public string $name) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
const CURRENT_NAMESPACE = __NAMESPACE__; |
||||
|
||||
function namespaceName(): string |
||||
{ |
||||
return __NAMESPACE__; |
||||
} |
||||
|
||||
#[NamespaceName(__NAMESPACE__)] |
||||
class Scope |
||||
{ |
||||
public function namespaceName(): string |
||||
{ |
||||
return __NAMESPACE__; |
||||
} |
||||
} |
||||
} |
||||
|
||||
namespace { |
||||
function main(): void |
||||
{ |
||||
var_dump(__NAMESPACE__); |
||||
var_dump(Project\Feature\namespaceName()); |
||||
var_dump((new Project\Feature\Scope())->namespaceName()); |
||||
var_dump(Project\Feature\CURRENT_NAMESPACE); |
||||
$class = new ReflectionClass(Project\Feature\Scope::class); |
||||
var_dump($class->getAttributes(Project\Feature\NamespaceName::class)[0]->getArguments()[0]); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(0) "" |
||||
string(15) "Project\Feature" |
||||
string(15) "Project\Feature" |
||||
string(15) "Project\Feature" |
||||
string(15) "Project\Feature" |
||||
@ -0,0 +1,151 @@ |
||||
--TEST-- |
||||
namespace\name resolves relative functions, constants, classes, members, and declarations |
||||
--FILE-- |
||||
<?php |
||||
namespace { |
||||
const GLOBAL_VALUE = 'global constant'; |
||||
|
||||
function globalHelper(): string |
||||
{ |
||||
return 'global function'; |
||||
} |
||||
|
||||
class GlobalTarget |
||||
{ |
||||
public const VALUE = 'global class'; |
||||
} |
||||
} |
||||
|
||||
namespace RelativeNames { |
||||
const VALUE = 'namespaced constant'; |
||||
|
||||
#[\Attribute(\Attribute::TARGET_CLASS)] |
||||
class Marker |
||||
{ |
||||
public function __construct(public string $value) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
function helper(): string |
||||
{ |
||||
return 'namespaced function'; |
||||
} |
||||
|
||||
interface Contract |
||||
{ |
||||
public function target(): namespace\Target; |
||||
} |
||||
|
||||
trait Feature |
||||
{ |
||||
public function feature(): string |
||||
{ |
||||
return namespace\helper(); |
||||
} |
||||
} |
||||
|
||||
class Base |
||||
{ |
||||
public function base(): string |
||||
{ |
||||
return 'anonymous base'; |
||||
} |
||||
} |
||||
|
||||
class Target |
||||
{ |
||||
public const VALUE = 'class constant'; |
||||
public static string $value = 'static property'; |
||||
|
||||
public static function method(): string |
||||
{ |
||||
return 'static method'; |
||||
} |
||||
} |
||||
|
||||
class Failure extends \Exception |
||||
{ |
||||
} |
||||
|
||||
#[namespace\Marker(namespace\VALUE)] |
||||
class Child extends namespace\Base implements namespace\Contract |
||||
{ |
||||
use namespace\Feature; |
||||
|
||||
public function target(): namespace\Target |
||||
{ |
||||
return new namespace\Target(); |
||||
} |
||||
|
||||
public function accepts(namespace\Target $target): bool |
||||
{ |
||||
return $target instanceof namespace\Target; |
||||
} |
||||
} |
||||
|
||||
function run(): void |
||||
{ |
||||
var_dump(namespace\VALUE); |
||||
var_dump(namespace\helper()); |
||||
$callable = namespace\helper(...); |
||||
var_dump($callable()); |
||||
var_dump(namespace\Target::VALUE); |
||||
var_dump(namespace\Target::$value); |
||||
var_dump(namespace\Target::method()); |
||||
|
||||
$child = new namespace\Child(); |
||||
$target = $child->target(); |
||||
var_dump($child->accepts($target)); |
||||
var_dump($child->feature()); |
||||
$attribute = (new \ReflectionClass(namespace\Child::class)) |
||||
->getAttributes(namespace\Marker::class)[0]; |
||||
var_dump($attribute->getArguments()[0]); |
||||
|
||||
$anonymous = new class extends namespace\Base implements namespace\Contract { |
||||
use namespace\Feature; |
||||
|
||||
public function target(): namespace\Target |
||||
{ |
||||
return new namespace\Target(); |
||||
} |
||||
}; |
||||
var_dump($anonymous->base()); |
||||
var_dump($anonymous->feature()); |
||||
var_dump($anonymous->target() instanceof namespace\Target); |
||||
|
||||
try { |
||||
throw new namespace\Failure('caught'); |
||||
} catch (namespace\Failure $exception) { |
||||
var_dump($exception->getMessage()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
namespace { |
||||
function main(): void |
||||
{ |
||||
var_dump(namespace\GLOBAL_VALUE); |
||||
var_dump(namespace\globalHelper()); |
||||
var_dump(namespace\GlobalTarget::VALUE); |
||||
RelativeNames\run(); |
||||
} |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(15) "global constant" |
||||
string(15) "global function" |
||||
string(12) "global class" |
||||
string(19) "namespaced constant" |
||||
string(19) "namespaced function" |
||||
string(19) "namespaced function" |
||||
string(14) "class constant" |
||||
string(15) "static property" |
||||
string(13) "static method" |
||||
bool(true) |
||||
string(19) "namespaced function" |
||||
string(19) "namespaced constant" |
||||
string(14) "anonymous base" |
||||
string(19) "namespaced function" |
||||
bool(true) |
||||
string(6) "caught" |
||||
@ -0,0 +1,69 @@ |
||||
--TEST-- |
||||
PHP 8.4 __PROPERTY__ resolves in property contexts only |
||||
--FILE-- |
||||
<?php |
||||
|
||||
#[Attribute(Attribute::TARGET_PROPERTY)] |
||||
class PropertyName |
||||
{ |
||||
public function __construct(public string $name) |
||||
{ |
||||
} |
||||
} |
||||
|
||||
class PropertyMagicConstants |
||||
{ |
||||
public string $first = __PROPERTY__, $second = __PROPERTY__; |
||||
|
||||
#[PropertyName(__PROPERTY__)] |
||||
public string $annotated = __PROPERTY__; |
||||
|
||||
public string $hooked { |
||||
get => __PROPERTY__; |
||||
set { |
||||
var_dump(__PROPERTY__); |
||||
} |
||||
} |
||||
|
||||
private string $nested { |
||||
get => (function (): string { |
||||
return __PROPERTY__; |
||||
})(); |
||||
} |
||||
|
||||
public function outsideProperty(): string |
||||
{ |
||||
return __PROPERTY__; |
||||
} |
||||
|
||||
public function nestedProperty(): string |
||||
{ |
||||
return $this->nested; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
$object = new PropertyMagicConstants(); |
||||
var_dump($object->first); |
||||
var_dump($object->second); |
||||
var_dump($object->annotated); |
||||
$property = new ReflectionProperty(PropertyMagicConstants::class, 'annotated'); |
||||
var_dump($property->getAttributes(PropertyName::class)[0]->getArguments()[0]); |
||||
var_dump($object->hooked); |
||||
$object->hooked = 'ignored'; |
||||
var_dump($object->outsideProperty()); |
||||
var_dump($object->nestedProperty()); |
||||
var_dump(__PROPERTY__); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
string(5) "first" |
||||
string(6) "second" |
||||
string(9) "annotated" |
||||
string(9) "annotated" |
||||
string(6) "hooked" |
||||
string(6) "hooked" |
||||
string(0) "" |
||||
string(0) "" |
||||
string(0) "" |
||||
Loading…
Reference in new issue