- 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