From f127dadf5dc6e554ff5182fd35a6c499fea47242 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 17 Sep 2026 12:23:39 +0800 Subject: [PATCH] test(compiler): add comprehensive tests for late static binding and typed array conversions - Added test for toStdList and toStdDict methods validating PHP array conversion with type preservation - Included tests for late static calls from instance methods retaining receiver on inherited and overridden methods - Added tests verifying late static calls distinguish parent and child private methods in instance context - Covered various scenarios including scalar conversions, object arrays, and error handling cases - Verified proper behavior with numeric keys, class inheritance, and dynamic method calls - Tested type validation for list and dictionary conversions with multiple data types --- .../static/late-static-instance-call.phpt | 90 ++++++++++++ .../static/late-static-private-method.phpt | 68 +++++++++ .../typed-array/conversion-methods.phpt | 138 ++++++++++++++++++ 3 files changed, 296 insertions(+) create mode 100644 tests/compiler/static/late-static-instance-call.phpt create mode 100644 tests/compiler/static/late-static-private-method.phpt create mode 100644 tests/compiler/typed-array/conversion-methods.phpt diff --git a/tests/compiler/static/late-static-instance-call.phpt b/tests/compiler/static/late-static-instance-call.phpt new file mode 100644 index 00000000..8df87872 --- /dev/null +++ b/tests/compiler/static/late-static-instance-call.phpt @@ -0,0 +1,90 @@ +--TEST-- +Late static calls from instance methods retain the receiver on inherited and overridden methods +--FILE-- +value . $suffix; + } + + protected static function label(): string + { + return static::class; + } + + public function fixed(): string + { + return static::helper(); + } + + public function withArgs(): string + { + return static::helper('-arg'); + } + + public function dynamic(): string + { + $name = 'helper'; + return static::{$name}('-dynamic'); + } + + public function staticFixed(): string + { + return static::label(); + } + + public function staticDynamic(): string + { + $name = 'label'; + return static::{$name}(); + } +} + +class InstanceCallInherited extends InstanceCallBase +{ +} + +class InstanceCallOverride extends InstanceCallBase +{ + protected function helper(string $suffix = ''): string + { + return 'override-' . $this->value . $suffix; + } + + protected static function label(): string + { + return 'override:' . static::class; + } +} + +function main(): void +{ + foreach ([new InstanceCallBase(), new InstanceCallInherited(), new InstanceCallOverride()] as $item) { + echo $item->fixed(), "\n"; + echo $item->withArgs(), "\n"; + echo $item->dynamic(), "\n"; + echo $item->staticFixed(), "\n"; + echo $item->staticDynamic(), "\n"; + } +} +?> +--EXPECT-- +base-value +base-value-arg +base-value-dynamic +InstanceCallBase +InstanceCallBase +base-value +base-value-arg +base-value-dynamic +InstanceCallInherited +InstanceCallInherited +override-base-value +override-base-value-arg +override-base-value-dynamic +override:InstanceCallOverride +override:InstanceCallOverride diff --git a/tests/compiler/static/late-static-private-method.phpt b/tests/compiler/static/late-static-private-method.phpt new file mode 100644 index 00000000..4c0936a3 --- /dev/null +++ b/tests/compiler/static/late-static-private-method.phpt @@ -0,0 +1,68 @@ +--TEST-- +Late static calls distinguish parent and child private methods in an instance context +--FILE-- +value(); + } + + public function viaStatic(): string + { + return static::value(); + } + + public function viaDynamic(): string + { + $name = 'value'; + return static::{$name}(); + } +} + +class LatePrivateInherited extends LatePrivateBase +{ +} + +class LatePrivateRedeclared extends LatePrivateBase +{ + private function value(): string + { + return 'child'; + } +} + +function main(): void +{ + $inherited = new LatePrivateInherited(); + echo $inherited->viaThis(), "\n"; + echo $inherited->viaStatic(), "\n"; + echo $inherited->viaDynamic(), "\n"; + + $redeclared = new LatePrivateRedeclared(); + echo $redeclared->viaThis(), "\n"; + try { + echo $redeclared->viaStatic(), "\n"; + } catch (Error $error) { + echo get_class($error), "\n"; + } + try { + echo $redeclared->viaDynamic(), "\n"; + } catch (Error $error) { + echo get_class($error), "\n"; + } +} +?> +--EXPECT-- +base +base +base +base +Error +Error diff --git a/tests/compiler/typed-array/conversion-methods.phpt b/tests/compiler/typed-array/conversion-methods.phpt new file mode 100644 index 00000000..a16ce145 --- /dev/null +++ b/tests/compiler/typed-array/conversion-methods.phpt @@ -0,0 +1,138 @@ +--TEST-- +toStdList and toStdDict validate converted PHP arrays and preserve typed assignment +--FILE-- + 42]; } +} + +#[Native] +class ConvertedNativeSource { + public function toArray(): array { return [12]; } +} + +function make_source(): array { + echo "source called\n"; + return [13]; +} + +function receive_list(#[StdList(Type::Int)] array $values): int { + return $values[0]; +} + +function main(): void { + $rawList = [4, 5]; + $list = $rawList->toStdList(Type::Int); + $copy = $list->toStdList(Type::Int); + $copy[] = 6; + var_dump($list, $copy); + + $rawDict = ['answer' => 42]; + $dict = $rawDict->toStdDict(Type::Str, Type::Int); + var_dump($dict['answer']); + + $number = 9; + $fromScalar = $number->toStdList(Type::Int); + var_dump($fromScalar); + + $object = new ConvertedSource(); + $fromObject = $object->toStdDict(Type::Str, Type::Int); + var_dump($fromObject); + + $dynamic = std::any([7, 8]); + $fromDynamic = $dynamic->toStdList(Type::Int); + var_dump($fromDynamic[1], receive_list($rawList->toStdList(Type::Int))); + + $integerKeys = [10 => 'ten']; + $integerDict = $integerKeys->toStdDict(Type::Int, Type::Str); + var_dump($integerDict[10]); + + $vector = std::vector(Type::Int); + $vector[] = 11; + $fromVector = $vector->toStdList(Type::Int); + var_dump($fromVector[0]); + + $native = new ConvertedNativeSource(); + $fromNative = $native->toStdList(Type::Int); + var_dump($fromNative[0]); + $fromCall = make_source()->toStdList(Type::Int); + var_dump($fromCall[0]); + + $rawObjects = [new ConvertedValue(), new ConvertedChild()]; + $objects = $rawObjects->toStdList(ConvertedValue::class); + var_dump(count($objects), $objects[1] instanceof ConvertedChild); + + try { + $badListKeys = ['name' => 1]; + $badListKeys->toStdList(Type::Int); + } catch (TypeError $error) { echo "list key rejected\n"; } + try { + $badListValues = [1, 'two']; + $badListValues->toStdList(Type::Int); + } catch (TypeError $error) { echo "list value rejected\n"; } + try { + $badDictKeys = [1 => 1]; + $badDictKeys->toStdDict(Type::Str, Type::Int); + } catch (TypeError $error) { echo "dict key rejected\n"; } + try { + $normalizedNumericKey = ['123' => 1]; + $normalizedNumericKey->toStdDict(Type::Str, Type::Int); + } catch (TypeError $error) { echo "normalized key rejected\n"; } + try { + $badDictValues = ['one' => 'wrong']; + $badDictValues->toStdDict(Type::Str, Type::Int); + } catch (TypeError $error) { echo "dict value rejected\n"; } + try { + $badObjects = [new stdClass()]; + $badObjects->toStdList(ConvertedValue::class); + } catch (TypeError $error) { echo "class rejected\n"; } + try { + $otherType = std::list(Type::Str); + $otherType[] = 'not an integer'; + $otherType->toStdList(Type::Int); + } catch (TypeError $error) { echo "different contract rejected\n"; } +} +?> +--EXPECT-- +array(2) { + [0]=> + int(4) + [1]=> + int(5) +} +array(3) { + [0]=> + int(4) + [1]=> + int(5) + [2]=> + int(6) +} +int(42) +array(1) { + [0]=> + int(9) +} +array(1) { + ["answer"]=> + int(42) +} +int(8) +int(4) +string(3) "ten" +int(11) +int(12) +source called +int(13) +int(2) +bool(true) +list key rejected +list value rejected +dict key rejected +normalized key rejected +dict value rejected +class rejected +different contract rejected