- Add support for (int), (bool), (float), (string), (array), (object) casts in constant expressions across all declaration contexts - Implement proper evaluation of cast expressions during stub generation - Add comprehensive test coverage for constant expression casts in various contexts including global constants, class constants, properties, and parameters - Update documentation to reflect PHP 8.5 cast support - Enhance RuntimeAttributeFactoryLowering to handle array cast nodes correctly - Add validation and error handling for unsupported cast types in constant expressionsmaster
parent
44095252d2
commit
3def25cc3c
21 changed files with 516 additions and 7 deletions
@ -0,0 +1,6 @@ |
||||
<?php |
||||
|
||||
class ClassConstantObjectCast |
||||
{ |
||||
public const VALUE = (object) ['value' => 1]; |
||||
} |
||||
@ -0,0 +1,8 @@ |
||||
<?php |
||||
|
||||
#[GlobalConstantMetadata] |
||||
const GLOBAL_CONSTANT_WITH_ATTRIBUTE = 1; |
||||
|
||||
function main(): void |
||||
{ |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
<?php |
||||
|
||||
#[Native] |
||||
class NativeWithoutToAny |
||||
{ |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
(new NativeWithoutToAny())->toAny(); |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
<?php |
||||
|
||||
#[Native] |
||||
class NativeUntypedToAny |
||||
{ |
||||
public function toAny() |
||||
{ |
||||
return 1; |
||||
} |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
<?php |
||||
|
||||
#[Native] |
||||
class NativeWrongToAnyReturn |
||||
{ |
||||
public function toAny(): int |
||||
{ |
||||
return 1; |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@ |
||||
<?php |
||||
|
||||
class PropertyDefaultObjectCast |
||||
{ |
||||
public object $value = (object) ['value' => 1]; |
||||
} |
||||
@ -0,0 +1,6 @@ |
||||
<?php |
||||
|
||||
interface ReservedKeywordInterfaceMethod |
||||
{ |
||||
public function toAny(): mixed; |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
<?php |
||||
|
||||
trait ReservedKeywordTraitMethod |
||||
{ |
||||
public function toRef(): mixed |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
<?php |
||||
|
||||
class ReservedToAnyMethod |
||||
{ |
||||
public function toAny(): mixed |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
<?php |
||||
|
||||
class ReservedToRefMethod |
||||
{ |
||||
public function TOREF(): mixed |
||||
{ |
||||
return $this; |
||||
} |
||||
} |
||||
@ -0,0 +1,82 @@ |
||||
--TEST-- |
||||
Class-like constants preserve attributes and Reflection metadata |
||||
--FILE-- |
||||
<?php |
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS_CONSTANT | Attribute::IS_REPEATABLE)] |
||||
class ConstantTag |
||||
{ |
||||
public function __construct( |
||||
public string $name, |
||||
public array $metadata = [], |
||||
) { |
||||
} |
||||
} |
||||
|
||||
class ClassConstantOwner |
||||
{ |
||||
#[ConstantTag('primary', ['kind' => 'class'])] |
||||
#[ConstantTag(name: 'secondary')] |
||||
public const VALUE = 42; |
||||
} |
||||
|
||||
interface InterfaceConstantOwner |
||||
{ |
||||
#[ConstantTag('interface')] |
||||
public const VALUE = 'interface'; |
||||
} |
||||
|
||||
trait TraitConstantOwner |
||||
{ |
||||
#[ConstantTag('trait')] |
||||
public const VALUE = 'trait'; |
||||
} |
||||
|
||||
class TraitConstantConsumer |
||||
{ |
||||
use TraitConstantOwner; |
||||
} |
||||
|
||||
enum EnumConstantOwner |
||||
{ |
||||
case Item; |
||||
|
||||
#[ConstantTag('enum')] |
||||
public const VALUE = 'enum'; |
||||
} |
||||
|
||||
function dumpConstantAttributes(string $class, string $constant): void |
||||
{ |
||||
$reflection = new ReflectionClassConstant($class, $constant); |
||||
echo $class, '::', $constant, '=', $reflection->getValue(), "\n"; |
||||
|
||||
$attributes = $reflection->getAttributes(ConstantTag::class); |
||||
var_dump(count($attributes)); |
||||
foreach ($attributes as $attribute) { |
||||
$instance = $attribute->newInstance(); |
||||
echo $instance->name, ':', $instance->metadata['kind'] ?? 'none', "\n"; |
||||
} |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
dumpConstantAttributes(ClassConstantOwner::class, 'VALUE'); |
||||
dumpConstantAttributes(InterfaceConstantOwner::class, 'VALUE'); |
||||
dumpConstantAttributes(TraitConstantConsumer::class, 'VALUE'); |
||||
dumpConstantAttributes(EnumConstantOwner::class, 'VALUE'); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
ClassConstantOwner::VALUE=42 |
||||
int(2) |
||||
primary:class |
||||
secondary:none |
||||
InterfaceConstantOwner::VALUE=interface |
||||
int(1) |
||||
interface:none |
||||
TraitConstantConsumer::VALUE=trait |
||||
int(1) |
||||
trait:none |
||||
EnumConstantOwner::VALUE=enum |
||||
int(1) |
||||
enum:none |
||||
@ -0,0 +1,158 @@ |
||||
--TEST-- |
||||
PHP 8.5 casts in constant expressions across declaration contexts |
||||
--FILE-- |
||||
<?php |
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS)] |
||||
class ConstantCastMetadata |
||||
{ |
||||
public function __construct( |
||||
public int $integer, |
||||
public bool $boolean, |
||||
public float $float, |
||||
public string $string, |
||||
public array $array, |
||||
public object $object, |
||||
) { |
||||
} |
||||
} |
||||
|
||||
const CAST_INT = (int) 12.75; |
||||
const CAST_BOOL = (bool) 0.5; |
||||
const CAST_FLOAT = (float) 7; |
||||
const CAST_STRING = (string) 123; |
||||
const CAST_ARRAY = (array) 'global'; |
||||
const CAST_OBJECT = (object) ['value' => 'global']; |
||||
const CAST_SOURCE = 15.75; |
||||
const CAST_FROM_CONSTANT = (int) CAST_SOURCE; |
||||
|
||||
class ConstantCastDefaults |
||||
{ |
||||
public const INTEGER = (int) 9.75; |
||||
public const BOOLEAN = (bool) 0; |
||||
public const FLOAT = (float) 8; |
||||
public const STRING = (string) 456; |
||||
public const ARRAY = (array) 'class'; |
||||
public const SOURCE = 14.75; |
||||
public const FROM_CONSTANT = (int) self::SOURCE; |
||||
|
||||
public int $integer = (int) 6.75; |
||||
public bool $boolean = (bool) 1; |
||||
public float $float = (float) 5; |
||||
public string $string = (string) 789; |
||||
public array $array = (array) 'property'; |
||||
} |
||||
|
||||
#[ConstantCastMetadata( |
||||
(int) 4.75, |
||||
(bool) 0.25, |
||||
(float) 3, |
||||
(string) 321, |
||||
(array) 'attribute', |
||||
(object) ['value' => 'attribute'], |
||||
)] |
||||
class ConstantCastTarget |
||||
{ |
||||
} |
||||
|
||||
function constantCastDefaults( |
||||
int $integer = (int) 2.75, |
||||
bool $boolean = (bool) 0, |
||||
float $float = (float) 1, |
||||
string $string = (string) 654, |
||||
array $array = (array) 'parameter', |
||||
object $object = (object) ['value' => 'parameter'], |
||||
): void { |
||||
var_dump($integer, $boolean, $float, $string, $array, $object->value); |
||||
} |
||||
|
||||
function main(): void |
||||
{ |
||||
var_dump( |
||||
CAST_INT, |
||||
CAST_BOOL, |
||||
CAST_FLOAT, |
||||
CAST_STRING, |
||||
CAST_ARRAY, |
||||
CAST_OBJECT->value, |
||||
CAST_FROM_CONSTANT, |
||||
); |
||||
var_dump( |
||||
ConstantCastDefaults::INTEGER, |
||||
ConstantCastDefaults::BOOLEAN, |
||||
ConstantCastDefaults::FLOAT, |
||||
ConstantCastDefaults::STRING, |
||||
ConstantCastDefaults::ARRAY, |
||||
ConstantCastDefaults::FROM_CONSTANT, |
||||
); |
||||
|
||||
$defaults = new ConstantCastDefaults(); |
||||
var_dump( |
||||
$defaults->integer, |
||||
$defaults->boolean, |
||||
$defaults->float, |
||||
$defaults->string, |
||||
$defaults->array, |
||||
); |
||||
|
||||
constantCastDefaults(); |
||||
|
||||
$attribute = (new ReflectionClass(ConstantCastTarget::class)) |
||||
->getAttributes(ConstantCastMetadata::class)[0] |
||||
->newInstance(); |
||||
var_dump( |
||||
$attribute->integer, |
||||
$attribute->boolean, |
||||
$attribute->float, |
||||
$attribute->string, |
||||
$attribute->array, |
||||
$attribute->object->value, |
||||
); |
||||
} |
||||
?> |
||||
--EXPECT-- |
||||
int(12) |
||||
bool(true) |
||||
float(7) |
||||
string(3) "123" |
||||
array(1) { |
||||
[0]=> |
||||
string(6) "global" |
||||
} |
||||
string(6) "global" |
||||
int(15) |
||||
int(9) |
||||
bool(false) |
||||
float(8) |
||||
string(3) "456" |
||||
array(1) { |
||||
[0]=> |
||||
string(5) "class" |
||||
} |
||||
int(14) |
||||
int(6) |
||||
bool(true) |
||||
float(5) |
||||
string(3) "789" |
||||
array(1) { |
||||
[0]=> |
||||
string(8) "property" |
||||
} |
||||
int(2) |
||||
bool(false) |
||||
float(1) |
||||
string(3) "654" |
||||
array(1) { |
||||
[0]=> |
||||
string(9) "parameter" |
||||
} |
||||
string(9) "parameter" |
||||
int(4) |
||||
bool(true) |
||||
float(3) |
||||
string(3) "321" |
||||
array(1) { |
||||
[0]=> |
||||
string(9) "attribute" |
||||
} |
||||
string(9) "attribute" |
||||
Loading…
Reference in new issue