From 0ce74e89b12a77176a630a4433e9acad3a0dceaf Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 12 Nov 2022 12:02:53 +0100 Subject: [PATCH 1/6] Rewrite `list(&$a)= $expr` for PHP 7.0, 7.1 and 7.2 Implements #31 (w/o the foreach part) --- src/main/php/lang/ast/emit/PHP70.class.php | 2 +- src/main/php/lang/ast/emit/PHP71.class.php | 2 +- src/main/php/lang/ast/emit/PHP72.class.php | 2 +- src/main/php/lang/ast/emit/PHP73.class.php | 56 ++++++++++++++++ .../ast/emit/RewriteDestructuring.class.php | 64 +++++++++++++++++++ .../ast/unittest/emit/ArraysTest.class.php | 15 +++-- 6 files changed, 131 insertions(+), 10 deletions(-) create mode 100755 src/main/php/lang/ast/emit/PHP73.class.php create mode 100755 src/main/php/lang/ast/emit/RewriteDestructuring.class.php diff --git a/src/main/php/lang/ast/emit/PHP70.class.php b/src/main/php/lang/ast/emit/PHP70.class.php index ad92cc15..bb306bec 100755 --- a/src/main/php/lang/ast/emit/PHP70.class.php +++ b/src/main/php/lang/ast/emit/PHP70.class.php @@ -21,12 +21,12 @@ class PHP70 extends PHP { OmitPropertyTypes, ReadonlyClasses, ReadonlyProperties, + RewriteDestructuring, RewriteClassOnObjects, RewriteEnums, RewriteExplicitOctals, RewriteLambdaExpressions, RewriteMultiCatch, - RewriteNullCoalesceAssignment, RewriteThrowableExpressions ; diff --git a/src/main/php/lang/ast/emit/PHP71.class.php b/src/main/php/lang/ast/emit/PHP71.class.php index c5c7d314..cd0300e3 100755 --- a/src/main/php/lang/ast/emit/PHP71.class.php +++ b/src/main/php/lang/ast/emit/PHP71.class.php @@ -21,10 +21,10 @@ class PHP71 extends PHP { ReadonlyProperties, ReadonlyClasses, RewriteClassOnObjects, + RewriteDestructuring, RewriteEnums, RewriteExplicitOctals, RewriteLambdaExpressions, - RewriteNullCoalesceAssignment, RewriteThrowableExpressions ; diff --git a/src/main/php/lang/ast/emit/PHP72.class.php b/src/main/php/lang/ast/emit/PHP72.class.php index 070109dc..18e462a6 100755 --- a/src/main/php/lang/ast/emit/PHP72.class.php +++ b/src/main/php/lang/ast/emit/PHP72.class.php @@ -21,10 +21,10 @@ class PHP72 extends PHP { ReadonlyProperties, ReadonlyClasses, RewriteClassOnObjects, + RewriteDestructuring, RewriteEnums, RewriteExplicitOctals, RewriteLambdaExpressions, - RewriteNullCoalesceAssignment, RewriteThrowableExpressions ; diff --git a/src/main/php/lang/ast/emit/PHP73.class.php b/src/main/php/lang/ast/emit/PHP73.class.php new file mode 100755 index 00000000..b0b70268 --- /dev/null +++ b/src/main/php/lang/ast/emit/PHP73.class.php @@ -0,0 +1,56 @@ + literal mappings */ + public function __construct() { + $this->literals= [ + IsArray::class => function($t) { return 'array'; }, + IsMap::class => function($t) { return 'array'; }, + IsFunction::class => function($t) { return 'callable'; }, + IsValue::class => function($t) { $l= $t->literal(); return 'static' === $l ? 'self' : $l; }, + IsNullable::class => function($t) { $l= $this->literal($t->element); return null === $l ? null : '?'.$l; }, + IsUnion::class => function($t) { return null; }, + IsIntersection::class => function($t) { return null; }, + IsLiteral::class => function($t) { + static $rewrite= [ + 'mixed' => 1, + 'null' => 1, + 'never' => 'void', + 'true' => 'bool', + 'false' => 'bool', + ]; + + $l= $t->literal(); + return (1 === ($r= $rewrite[$l] ?? $l)) ? null : $r; + }, + IsGeneric::class => function($t) { return null; } + ]; + } +} \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/RewriteDestructuring.class.php b/src/main/php/lang/ast/emit/RewriteDestructuring.class.php new file mode 100755 index 00000000..6d781cfb --- /dev/null +++ b/src/main/php/lang/ast/emit/RewriteDestructuring.class.php @@ -0,0 +1,64 @@ +operator) { + + // Inlined version of RewriteNullCoalesceAssignment + $this->emitAssign($result, $assignment->variable); + $result->out->write('??'); + $this->emitOne($result, $assignment->variable); + $result->out->write('='); + $this->emitOne($result, $assignment->expression); + } else if ('array' === $assignment->variable->kind) { + + // Check whether the list assignment consists only of variables + $supported= true; + foreach ($assignment->variable->values as $pair) { + if ($pair[1] instanceof Variable) continue; + $supported= false; + break; + } + if ($supported) return parent::emitAssignment($result, $assignment); + + $t= $result->temp(); + $result->out->write('null===('.$t.'='); + + // Create reference to right-hand if possible + $r= $assignment->expression; + if ( + ($r instanceof Variable) || + ($r instanceof InstanceExpression && $r->member instanceof Literal) || + ($r instanceof ScopeExpression && $r->member instanceof Variable) + ) { + $result->out->write('&'); + } + + $this->emitOne($result, $assignment->expression); + $result->out->write(')?null:['); + foreach ($assignment->variable->values as $i => $pair) { + + // Assign by reference + if ($pair[1] instanceof UnaryExpression) { + $this->emitAssign($result, $pair[1]->expression); + $result->out->write('='.$pair[1]->operator.$t.'['.$i.'],'); + } else { + $this->emitAssign($result, $pair[1]); + $result->out->write('='.$t.'['.$i.'],'); + } + } + $result->out->write(']'); + } else { + return parent::emitAssignment($result, $assignment); + } + } +} \ No newline at end of file diff --git a/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php b/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php index a7fe5ae3..806fd843 100755 --- a/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php @@ -1,7 +1,6 @@ =7.3.0")')] - public function reference_destructuring() { + #[Test, Values(['$list', '$this->instance', 'self::$static'])] + public function reference_destructuring($reference) { $r= $this->run('class { - private $list= [1, 2]; + private $instance= [1, 2]; + private static $static= [1, 2]; public function run() { - [&$a, &$b]= $this->list; + $list= [1, 2]; + [&$a, &$b]= '.$reference.'; $a++; $b--; - return $this->list; + return '.$reference.'; } }'); From 0752b352a3f04338c42e2ac9bd9a21bd94a7634e Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 12 Nov 2022 12:07:33 +0100 Subject: [PATCH 2/6] Inline RewriteNullCoalesceAssignment -> PHP 7.3 --- src/main/php/lang/ast/emit/PHP73.class.php | 17 ++++++++++++-- .../ast/emit/RewriteDestructuring.class.php | 4 +--- .../RewriteNullCoalesceAssignment.class.php | 22 ------------------- 3 files changed, 16 insertions(+), 27 deletions(-) delete mode 100755 src/main/php/lang/ast/emit/RewriteNullCoalesceAssignment.class.php diff --git a/src/main/php/lang/ast/emit/PHP73.class.php b/src/main/php/lang/ast/emit/PHP73.class.php index b0b70268..d0e32976 100755 --- a/src/main/php/lang/ast/emit/PHP73.class.php +++ b/src/main/php/lang/ast/emit/PHP73.class.php @@ -3,8 +3,10 @@ use lang\ast\types\{IsUnion, IsIntersection, IsFunction, IsArray, IsMap, IsNullable, IsValue, IsLiteral, IsGeneric}; /** - * PHP 7.3 syntax + * PHP 7.3 syntax. Same as PHP 7.2 but supports list reference assignments + * so only rewrites null-coalesce assignments. * + * @see https://wiki.php.net/rfc/list_reference_assignment * @see https://wiki.php.net/rfc#php_73 */ class PHP73 extends PHP { @@ -24,7 +26,6 @@ class PHP73 extends PHP { RewriteEnums, RewriteExplicitOctals, RewriteLambdaExpressions, - RewriteNullCoalesceAssignment, RewriteThrowableExpressions ; @@ -53,4 +54,16 @@ public function __construct() { IsGeneric::class => function($t) { return null; } ]; } + + protected function emitAssignment($result, $assignment) { + if ('??=' === $assignment->operator) { + $this->emitAssign($result, $assignment->variable); + $result->out->write('??'); + $this->emitOne($result, $assignment->variable); + $result->out->write('='); + $this->emitOne($result, $assignment->expression); + } else { + parent::emitAssignment($result, $assignment); + } + } } \ No newline at end of file diff --git a/src/main/php/lang/ast/emit/RewriteDestructuring.class.php b/src/main/php/lang/ast/emit/RewriteDestructuring.class.php index 6d781cfb..3f6109af 100755 --- a/src/main/php/lang/ast/emit/RewriteDestructuring.class.php +++ b/src/main/php/lang/ast/emit/RewriteDestructuring.class.php @@ -5,15 +5,13 @@ /** * Rewrites list reference assignments and null-coalesce for PHP <= 7.3 * - * @see lang.ast.emit.RewriteNullCoalesceAssignment + * @see https://wiki.php.net/rfc/null_coalesce_equal_operator * @see https://wiki.php.net/rfc/list_reference_assignment */ trait RewriteDestructuring { protected function emitAssignment($result, $assignment) { if ('??=' === $assignment->operator) { - - // Inlined version of RewriteNullCoalesceAssignment $this->emitAssign($result, $assignment->variable); $result->out->write('??'); $this->emitOne($result, $assignment->variable); diff --git a/src/main/php/lang/ast/emit/RewriteNullCoalesceAssignment.class.php b/src/main/php/lang/ast/emit/RewriteNullCoalesceAssignment.class.php deleted file mode 100755 index c86f30bd..00000000 --- a/src/main/php/lang/ast/emit/RewriteNullCoalesceAssignment.class.php +++ /dev/null @@ -1,22 +0,0 @@ -operator) { - $this->emitAssign($result, $assignment->variable); - $result->out->write('??'); - $this->emitOne($result, $assignment->variable); - $result->out->write('='); - $this->emitOne($result, $assignment->expression); - } else { - parent::emitAssignment($result, $assignment); - } - } -} \ No newline at end of file From 41db43bf0a6ff98f2b3152ba3ae666a16153690e Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 12 Nov 2022 12:19:09 +0100 Subject: [PATCH 3/6] Replace trait-based test for null-coalesce assignments with an emitter-based one --- .../emit/NullCoalesceAssignmentTest.class.php | 30 ++++++++++++++++++ ...ewriteNullCoalesceAssignmentTest.class.php | 31 ------------------- 2 files changed, 30 insertions(+), 31 deletions(-) create mode 100755 src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php delete mode 100755 src/test/php/lang/ast/unittest/emit/RewriteNullCoalesceAssignmentTest.class.php diff --git a/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php b/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php new file mode 100755 index 00000000..9f664bc0 --- /dev/null +++ b/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php @@ -0,0 +1,30 @@ +run('class { + public function run($arg) { + $arg??= true; + return $arg; + } + }', $value); + + Assert::equals($expected, $r); + } + + #[Test, Values([[[], [true]], [[null], [true]]])] + public function fills_array_if_non_existant_or_null($value, $expected) { + $r= $this->run('class { + public function run($arg) { + $arg[0]??= true; + return $arg; + } + }', $value); + + Assert::equals($expected, $r); + } +} \ No newline at end of file diff --git a/src/test/php/lang/ast/unittest/emit/RewriteNullCoalesceAssignmentTest.class.php b/src/test/php/lang/ast/unittest/emit/RewriteNullCoalesceAssignmentTest.class.php deleted file mode 100755 index 62f05a78..00000000 --- a/src/test/php/lang/ast/unittest/emit/RewriteNullCoalesceAssignmentTest.class.php +++ /dev/null @@ -1,31 +0,0 @@ -emit(new Assignment(new Variable('v'), '??=', new Literal('1'))) - ); - } - - #[Test] - public function does_not_rewrite_plus() { - Assert::equals( - '$v+=1', - $this->emit(new Assignment(new Variable('v'), '+=', new Literal('1'))) - ); - } -} \ No newline at end of file From 647da8112a4ab57b11189587d28ce38a51215c2a Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 12 Nov 2022 12:27:43 +0100 Subject: [PATCH 4/6] Rename RewriteDestructuring -> RewriteAssignments --- src/main/php/lang/ast/emit/PHP70.class.php | 2 +- src/main/php/lang/ast/emit/PHP71.class.php | 2 +- src/main/php/lang/ast/emit/PHP72.class.php | 2 +- ...riteDestructuring.class.php => RewriteAssignments.class.php} | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/main/php/lang/ast/emit/{RewriteDestructuring.class.php => RewriteAssignments.class.php} (98%) diff --git a/src/main/php/lang/ast/emit/PHP70.class.php b/src/main/php/lang/ast/emit/PHP70.class.php index bb306bec..417bf50f 100755 --- a/src/main/php/lang/ast/emit/PHP70.class.php +++ b/src/main/php/lang/ast/emit/PHP70.class.php @@ -21,7 +21,7 @@ class PHP70 extends PHP { OmitPropertyTypes, ReadonlyClasses, ReadonlyProperties, - RewriteDestructuring, + RewriteAssignments, RewriteClassOnObjects, RewriteEnums, RewriteExplicitOctals, diff --git a/src/main/php/lang/ast/emit/PHP71.class.php b/src/main/php/lang/ast/emit/PHP71.class.php index cd0300e3..e8836a61 100755 --- a/src/main/php/lang/ast/emit/PHP71.class.php +++ b/src/main/php/lang/ast/emit/PHP71.class.php @@ -20,8 +20,8 @@ class PHP71 extends PHP { OmitPropertyTypes, ReadonlyProperties, ReadonlyClasses, + RewriteAssignments, RewriteClassOnObjects, - RewriteDestructuring, RewriteEnums, RewriteExplicitOctals, RewriteLambdaExpressions, diff --git a/src/main/php/lang/ast/emit/PHP72.class.php b/src/main/php/lang/ast/emit/PHP72.class.php index 18e462a6..670bf3fb 100755 --- a/src/main/php/lang/ast/emit/PHP72.class.php +++ b/src/main/php/lang/ast/emit/PHP72.class.php @@ -20,8 +20,8 @@ class PHP72 extends PHP { OmitPropertyTypes, ReadonlyProperties, ReadonlyClasses, + RewriteAssignments, RewriteClassOnObjects, - RewriteDestructuring, RewriteEnums, RewriteExplicitOctals, RewriteLambdaExpressions, diff --git a/src/main/php/lang/ast/emit/RewriteDestructuring.class.php b/src/main/php/lang/ast/emit/RewriteAssignments.class.php similarity index 98% rename from src/main/php/lang/ast/emit/RewriteDestructuring.class.php rename to src/main/php/lang/ast/emit/RewriteAssignments.class.php index 3f6109af..9b3e8ef1 100755 --- a/src/main/php/lang/ast/emit/RewriteDestructuring.class.php +++ b/src/main/php/lang/ast/emit/RewriteAssignments.class.php @@ -8,7 +8,7 @@ * @see https://wiki.php.net/rfc/null_coalesce_equal_operator * @see https://wiki.php.net/rfc/list_reference_assignment */ -trait RewriteDestructuring { +trait RewriteAssignments { protected function emitAssignment($result, $assignment) { if ('??=' === $assignment->operator) { From 25e261a3368b362d8277a72470ce1f15117e4186 Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 12 Nov 2022 12:30:48 +0100 Subject: [PATCH 5/6] Extend NullCoalesceAssignmentTest --- .../ast/unittest/emit/NullCoalesceAssignmentTest.class.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php b/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php index 9f664bc0..c8ab6c4d 100755 --- a/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/NullCoalesceAssignmentTest.class.php @@ -16,7 +16,7 @@ public function run($arg) { Assert::equals($expected, $r); } - #[Test, Values([[[], [true]], [[null], [true]]])] + #[Test, Values([[[], true], [[null], true], [[false], false], [['Test'], 'Test']])] public function fills_array_if_non_existant_or_null($value, $expected) { $r= $this->run('class { public function run($arg) { @@ -25,6 +25,6 @@ public function run($arg) { } }', $value); - Assert::equals($expected, $r); + Assert::equals($expected, $r[0]); } } \ No newline at end of file From 0ff736a09c352f85ddd9bbf1612927cbd09d096d Mon Sep 17 00:00:00 2001 From: Timm Friebe Date: Sat, 12 Nov 2022 13:24:19 +0100 Subject: [PATCH 6/6] Assert destructuring returns non-array right-hand sides as-is --- .../php/lang/ast/unittest/emit/ArraysTest.class.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php b/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php index 806fd843..d5593f4e 100755 --- a/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php +++ b/src/test/php/lang/ast/unittest/emit/ArraysTest.class.php @@ -106,6 +106,18 @@ public function run() { Assert::equals([1, 2], $r); } + #[Test, Values([null, true, false, 0, 0.5, '', 'Test'])] + public function destructuring_with_non_array($value) { + $r= $this->run('class { + public function run($arg) { + $r= [$a, $b]= $arg; + return [$a, $b, $r]; + } + }', $value); + + Assert::equals([null, null, $value], $r); + } + #[Test] public function init_with_variable() { $r= $this->run('class {