- 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