diff --git a/src/Support/ReflectionClosure.php b/src/Support/ReflectionClosure.php index 97b73d21..05440043 100644 --- a/src/Support/ReflectionClosure.php +++ b/src/Support/ReflectionClosure.php @@ -508,8 +508,7 @@ public function getCode() break; case 'id_name': switch ($token[0]) { - // named arguments... - case ':': + case $token[0] === ':' && $context !== 'instanceof': if ($lastState === 'closure' && $context === 'root') { $state = 'closure'; $code .= $id_start.$token; diff --git a/tests/ReflectionClosure1Test.php b/tests/ReflectionClosure1Test.php index 3bfb8ac1..3004c21c 100644 --- a/tests/ReflectionClosure1Test.php +++ b/tests/ReflectionClosure1Test.php @@ -297,3 +297,90 @@ expect($f1)->toBeCode($e1); }); + +function reflection_closure_php_74_switch_statement_test_is_two($a) +{ + return $a === 2; +} + +class ReflectionClosurePhp74InstanceOfTest +{ +} + +class ReflectionClosurePhp74SwitchStatementTest +{ +} + +test('instanceof', function () { + $f1 = function ($a) { + $b = $a instanceof DateTime || $a instanceof ReflectionClosurePhp74InstanceOfTest || $a instanceof RegularClass; + + return [ + $b, + $a instanceof DateTime || $a instanceof ReflectionClosurePhp74InstanceOfTest || $a instanceof RegularClass, + (function ($a) { + return ($a instanceof DateTime || $a instanceof ReflectionClosurePhp74InstanceOfTest || $a instanceof RegularClass) === true; + })($a), + ]; + }; + + $e1 = 'function ($a) { + $b = $a instanceof \DateTime || $a instanceof \ReflectionClosurePhp74InstanceOfTest || $a instanceof \Tests\Fixtures\RegularClass; + + return [ + $b, + $a instanceof \DateTime || $a instanceof \ReflectionClosurePhp74InstanceOfTest || $a instanceof \Tests\Fixtures\RegularClass, + (function ($a) { + return ($a instanceof \DateTime || $a instanceof \ReflectionClosurePhp74InstanceOfTest || $a instanceof \Tests\Fixtures\RegularClass) === true; + })($a), + ]; + }'; + + expect($f1)->toBeCode($e1); +}); + +test('switch statement', function () { + $f1 = function ($a) { + switch (true) { + case $a === 1: + return 'one'; + case reflection_closure_php_74_switch_statement_test_is_two($a): + return 'two'; + case ReflectionClosurePhp74SwitchStatementTest::isThree($a): + return 'three'; + case (new ReflectionClosurePhp74SwitchStatementTest)->isFour($a): + return 'four'; + case $a instanceof ReflectionClosurePhp74SwitchStatementTest: + return 'five'; + case $a instanceof DateTime: + return 'six'; + case $a instanceof RegularClass: + return 'seven'; + default: + return 'other'; + } + }; + + $e1 = 'function ($a) { + switch (true) { + case $a === 1: + return \'one\'; + case \reflection_closure_php_74_switch_statement_test_is_two($a): + return \'two\'; + case \ReflectionClosurePhp74SwitchStatementTest::isThree($a): + return \'three\'; + case (new \ReflectionClosurePhp74SwitchStatementTest)->isFour($a): + return \'four\'; + case $a instanceof \ReflectionClosurePhp74SwitchStatementTest: + return \'five\'; + case $a instanceof \DateTime: + return \'six\'; + case $a instanceof \Tests\Fixtures\RegularClass: + return \'seven\'; + default: + return \'other\'; + } + }'; + + expect($f1)->toBeCode($e1); +}); diff --git a/tests/ReflectionClosurePhp80Test.php b/tests/ReflectionClosurePhp80Test.php index e28cf3ca..ded0ad9b 100644 --- a/tests/ReflectionClosurePhp80Test.php +++ b/tests/ReflectionClosurePhp80Test.php @@ -2,6 +2,7 @@ // Fake use Some\ClassName as ClassAlias; +use Tests\Fixtures\RegularClass; test('union types', function () { $f1 = fn (): string|int|false|Bar|null => 1; @@ -86,7 +87,7 @@ }"; expect($f1)->toBeCode($e1); -})->with('serializers'); +}); test('single named argument within closures', function () { $f1 = function () { @@ -98,7 +99,7 @@ }"; expect($f1)->toBeCode($e1); -})->with('serializers'); +}); test('multiple named arguments within closures', function () { $f1 = function () { @@ -110,7 +111,29 @@ }"; expect($f1)->toBeCode($e1); -})->with('serializers'); +}); + +test('named arguments with switch cases and instanceof', function () { + $f1 = function ($a) { + switch (true) { + case (new RegularClass(a2: $a))->a2 instanceof RegularClass: + return (new RegularClass(a2: $a))->a2; + default: + return new RegularClass(a2: RegularClass::C); + } + }; + + $e1 = 'function ($a) { + switch (true) { + case (new \Tests\Fixtures\RegularClass(a2: $a))->a2 instanceof \Tests\Fixtures\RegularClass: + return (new \Tests\Fixtures\RegularClass(a2: $a))->a2; + default: + return new \Tests\Fixtures\RegularClass(a2: \Tests\Fixtures\RegularClass::C); + } + }'; + + expect($f1)->toBeCode($e1); +}); test('multiple named arguments within nested closures', function () { $f1 = function () { @@ -159,3 +182,138 @@ public function getPrivate(): string return $this->private; } } + +function reflection_closure_php_80_switch_statement_test_is_two($a) +{ + return $a === 2; +} + +class ReflectionClosurePhp80InstanceOfTest +{ +}; + +class ReflectionClosurePhp80SwitchStatementTest +{ +} + +test('instanceof', function () { + $f1 = function (object $a): array { + $b = $a instanceof DateTime || $a instanceof ReflectionClosurePhp80InstanceOfTest || $a instanceof RegularClass; + + return [ + $b, + ($a instanceof DateTime || $a instanceof ReflectionClosurePhp80InstanceOfTest || $a instanceof RegularClass), + (function (object $a): bool { + return ($a instanceof DateTime || $a instanceof ReflectionClosurePhp80InstanceOfTest || $a instanceof RegularClass) === true; + })(a: $a), + ]; + }; + + $e1 = 'function (object $a): array { + $b = $a instanceof \DateTime || $a instanceof \ReflectionClosurePhp80InstanceOfTest || $a instanceof \Tests\Fixtures\RegularClass; + + return [ + $b, + ($a instanceof \DateTime || $a instanceof \ReflectionClosurePhp80InstanceOfTest || $a instanceof \Tests\Fixtures\RegularClass), + (function (object $a): bool { + return ($a instanceof \DateTime || $a instanceof \ReflectionClosurePhp80InstanceOfTest || $a instanceof \Tests\Fixtures\RegularClass) === true; + })(a: $a), + ]; + }'; + + expect($f1)->toBeCode($e1); +}); + +test('switch statement', function () { + $f1 = function ($a) { + switch (true) { + case $a === 1: + return 'one'; + case reflection_closure_php_80_switch_statement_test_is_two(a: $a): + return 'two'; + case ReflectionClosurePhp80SwitchStatementTest::isThree(a: $a): + return 'three'; + case (new ReflectionClosurePhp80SwitchStatementTest)->isFour(a: $a): + return 'four'; + case ($a instanceof ReflectionClosurePhp80SwitchStatementTest): + return 'five'; + case ($a instanceof DateTime): + return 'six'; + case ($a instanceof RegularClass): + return 'seven'; + default: + return 'other'; + } + }; + + $e1 = 'function ($a) { + switch (true) { + case $a === 1: + return \'one\'; + case \reflection_closure_php_80_switch_statement_test_is_two(a: $a): + return \'two\'; + case \ReflectionClosurePhp80SwitchStatementTest::isThree(a: $a): + return \'three\'; + case (new \ReflectionClosurePhp80SwitchStatementTest)->isFour(a: $a): + return \'four\'; + case ($a instanceof \ReflectionClosurePhp80SwitchStatementTest): + return \'five\'; + case ($a instanceof \DateTime): + return \'six\'; + case ($a instanceof \Tests\Fixtures\RegularClass): + return \'seven\'; + default: + return \'other\'; + } + }'; + + expect($f1)->toBeCode($e1); +}); + +function reflection_closure_php_80_match_statement_test_is_two($a) +{ + return $a === 2; +} + +class ReflectionClosurePhp80MatchStatementTest +{ + public static function isThree($a) + { + return $a === 3; + } + + public function isFour($a) + { + return $a === 4; + } +} + +test('match statement', function () { + $f1 = function ($a) { + return match (true) { + $a === 1 => 'one', + reflection_closure_php_80_match_statement_test_is_two($a) => 'two', + ReflectionClosurePhp80MatchStatementTest::isThree(a: $a) => 'three', + (new ReflectionClosurePhp80MatchStatementTest)->isFour($a) => 'four', + $a instanceof ReflectionClosurePhp80MatchStatementTest => 'five', + $a instanceof DateTime => 'six', + $a instanceof RegularClass => 'seven', + default => 'other', + }; + }; + + $e1 = 'function ($a) { + return match (true) { + $a === 1 => \'one\', + \reflection_closure_php_80_match_statement_test_is_two($a) => \'two\', + \ReflectionClosurePhp80MatchStatementTest::isThree(a: $a) => \'three\', + (new \ReflectionClosurePhp80MatchStatementTest)->isFour($a) => \'four\', + $a instanceof \ReflectionClosurePhp80MatchStatementTest => \'five\', + $a instanceof \DateTime => \'six\', + $a instanceof \Tests\Fixtures\RegularClass => \'seven\', + default => \'other\', + }; + }'; + + expect($f1)->toBeCode($e1); +}); diff --git a/tests/ReflectionClosurePhp81Test.php b/tests/ReflectionClosurePhp81Test.php index cbe50c82..5ee37f92 100644 --- a/tests/ReflectionClosurePhp81Test.php +++ b/tests/ReflectionClosurePhp81Test.php @@ -144,6 +144,11 @@ enum GlobalEnum { a18: reflection_closure_my_function(), a19: reflection_closure_my_function(ReflectionClosureGlobalEnum::Guest), a20: reflection_closure_my_function(enum: ReflectionClosureGlobalEnum::Guest), + a21: match (true) { + true => new RegularClass(), + false => (new RegularClass()) instanceof RegularClass, + default => reflection_closure_my_function(enum: ReflectionClosureGlobalEnum::Guest), + }, ); }; @@ -276,6 +281,11 @@ enum GlobalEnum { a18: \\reflection_closure_my_function(), a19: \\reflection_closure_my_function(\ReflectionClosureGlobalEnum::Guest), a20: \\reflection_closure_my_function(enum: \ReflectionClosureGlobalEnum::Guest), + a21: match (true) { + true => new \Tests\Fixtures\RegularClass(), + false => (new \Tests\Fixtures\RegularClass()) instanceof \Tests\Fixtures\RegularClass, + default => \\reflection_closure_my_function(enum: \ReflectionClosureGlobalEnum::Guest), + }, ); }"; diff --git a/tests/SerializerPhp80Test.php b/tests/SerializerPhp80Test.php index cc16d3ec..7e0ea8a2 100644 --- a/tests/SerializerPhp80Test.php +++ b/tests/SerializerPhp80Test.php @@ -57,6 +57,44 @@ ->and($instance->a2)->toBe('CONST'); })->with('serializers'); +test('named arguments with match statements', function () { + $f1 = function ($a) { + return new RegularClass(a2: match ($a) { + 1 => RegularClass::C, + 2 => null, + }); + }; + + $instance = s($f1)(1); + + expect($instance)->toBeInstanceOf(RegularClass::class) + ->and($instance->a1)->toBeNull() + ->and($instance->a2)->toBe('CONST'); + + $instance = s($f1)(2); + + expect($instance)->toBeInstanceOf(RegularClass::class) + ->and($instance->a1)->toBeNull() + ->and($instance->a2)->toBeNull(); +})->with('serializers'); + +test('named arguments with switch cases and instanceof', function () { + $f1 = function ($a) { + switch (true) { + case (new RegularClass(a2: $a))->a2 instanceof RegularClass: + return (new RegularClass(a2: $a))->a2; + default: + return new DateTime(); + } + }; + + $instance = s($f1)('anything'); + expect($instance)->toBeInstanceOf(DateTime::class); + + $instance = s($f1)(new RegularClass()); + expect($instance)->toBeInstanceOf(RegularClass::class); +})->with('serializers'); + test('named arguments with namespaced class instance parameter', function () { $f1 = function () { return new RegularClass(a2: new RegularClass()); @@ -76,3 +114,120 @@ public function publicMethod(string $namedArgument, $namedArgumentB = null) return $namedArgument.(string) $namedArgumentB; } } + +function serializer_php_80_switch_statement_test_is_two($a) +{ + return $a === 2; +} + +class SerializerPhp80SwitchStatementClass +{ + public static function isThree($a) + { + return $a === 3; + } + + public function isFour($a) + { + return $a === 4; + } +} + +class SerializerPhp80Class +{ +} + +test('instanceof', function () { + $closure = function (object $a): array { + $b = $a instanceof DateTime || $a instanceof SerializerPhp80Class; + + return [ + $b, + $a instanceof DateTime || $a instanceof SerializerPhp80Class, + (function (object $a): bool { + return ($a instanceof DateTime || $a instanceof SerializerPhp80Class) === true; + })(a: $a), + ]; + }; + + $u = s($closure); + + expect($u(new DateTime))->toEqual([true, true, true]) + ->and($u(new SerializerPhp80Class))->toEqual([true, true, true]) + ->and($u(new stdClass))->toEqual([false, false, false]); +})->with('serializers'); + +test('switch statement', function () { + $closure = function ($a) { + switch (true) { + case $a === 1: + return 'one'; + case serializer_php_80_switch_statement_test_is_two(a: $a): + return 'two'; + case SerializerPhp80SwitchStatementClass::isThree(a: $a): + return 'three'; + case (new SerializerPhp80SwitchStatementClass)->isFour(a: $a): + return 'four'; + case $a instanceof SerializerPhp80SwitchStatementClass: + return 'five'; + case $a instanceof DateTime: + return 'six'; + default: + return 'other'; + } + }; + + $u = s($closure); + + expect($u(1))->toEqual('one') + ->and($u(2))->toEqual('two') + ->and($u(3))->toEqual('three') + ->and($u(4))->toEqual('four') + ->and($u(new SerializerPhp80SwitchStatementClass))->toEqual('five') + ->and($u(new DateTime))->toEqual('six') + ->and($u(999))->toEqual('other'); +})->with('serializers'); + +function serializer_php_80_match_statement_test_is_two($a) +{ + return $a === 2; +} + +class SerializerPhp80MatchStatementTest +{ + public static function isThree($a) + { + return $a === 3; + } + + public function isFour($a) + { + return $a === 4; + } +} + +test('match statement', function () { + $closure = function ($a) { + return match (true) { + $a === 1 => 'one', + serializer_php_80_match_statement_test_is_two($a) => 'two', + SerializerPhp80MatchStatementTest::isThree($a) => 'three', + (new SerializerPhp80MatchStatementTest)->isFour(a: $a) => 'four', + $a instanceof SerializerPhp80MatchStatementTest => 'five', + $a instanceof DateTime => 'six', + $a instanceof RegularClass => 'seven', + default => 'other', + }; + }; + + $u = s($closure); + + expect($u(1))->toEqual('one') + ->and($u(2))->toEqual('two') + ->and($u(3))->toEqual('three') + ->and($u(4))->toEqual('four') + ->and($u(new SerializerPhp80MatchStatementTest))->toEqual('five') + ->and($u(new DateTime))->toEqual('six') + ->and($u(new RegularClass))->toEqual('seven') + ->and($u(999))->toEqual('other'); +})->with('serializers'); diff --git a/tests/SerializerTest.php b/tests/SerializerTest.php index a5169a97..9c2e70bd 100644 --- a/tests/SerializerTest.php +++ b/tests/SerializerTest.php @@ -485,6 +485,83 @@ function () { new CarbonImmutable, ]); +function serializer_php_74_switch_statement_test_is_two($a) +{ + return $a === 2; +} + +class SerializerPhp74SwitchStatementClass +{ + public static function isThree($a) + { + return $a === 3; + } + + public function isFour($a) + { + return $a === 4; + } +} + +class SerializerPhp74Class +{ +} + +test('instanceof', function () { + $closure = function ($a) { + $b = $a instanceof DateTime || $a instanceof SerializerPhp74Class || $a instanceof Model; + + return [ + $b, + $a instanceof DateTime || $a instanceof SerializerPhp74Class || $a instanceof Model, + (function ($a) { + return ($a instanceof DateTime || $a instanceof SerializerPhp74Class || $a instanceof Model) === true; + })($a), + ]; + }; + + $u = s($closure); + + expect($u(new DateTime))->toEqual([true, true, true]) + ->and($u(new SerializerPhp74Class))->toEqual([true, true, true]) + ->and($u(new Model))->toEqual([true, true, true]) + ->and($u(new stdClass))->toEqual([false, false, false]); +})->with('serializers'); + +test('switch statement', function () { + $closure = function ($a) { + switch (true) { + case $a === 1: + return 'one'; + case serializer_php_74_switch_statement_test_is_two($a): + return 'two'; + case SerializerPhp74SwitchStatementClass::isThree($a): + return 'three'; + case (new SerializerPhp74SwitchStatementClass)->isFour($a): + return 'four'; + case $a instanceof SerializerPhp74SwitchStatementClass: + return 'five'; + case $a instanceof DateTime: + return 'six'; + case $a instanceof Model: + return 'seven'; + default: + return 'other'; + } + }; + + $u = s($closure); + + expect($u(1))->toEqual('one') + ->and($u(2))->toEqual('two') + ->and($u(3))->toEqual('three') + ->and($u(4))->toEqual('four') + ->and($u(new SerializerPhp74SwitchStatementClass))->toEqual('five') + ->and($u(new DateTime))->toEqual('six') + ->and($u(new Model()))->toEqual('seven') + ->and($u(999))->toEqual('other'); +})->with('serializers'); + class A { protected static function aStaticProtected()