diff --git a/src/main/php/lang/ast/emit/PHP70.class.php b/src/main/php/lang/ast/emit/PHP70.class.php index ad92cc15..417bf50f 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, + RewriteAssignments, 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..e8836a61 100755 --- a/src/main/php/lang/ast/emit/PHP71.class.php +++ b/src/main/php/lang/ast/emit/PHP71.class.php @@ -20,11 +20,11 @@ class PHP71 extends PHP { OmitPropertyTypes, ReadonlyProperties, ReadonlyClasses, + RewriteAssignments, RewriteClassOnObjects, 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..670bf3fb 100755 --- a/src/main/php/lang/ast/emit/PHP72.class.php +++ b/src/main/php/lang/ast/emit/PHP72.class.php @@ -20,11 +20,11 @@ class PHP72 extends PHP { OmitPropertyTypes, ReadonlyProperties, ReadonlyClasses, + RewriteAssignments, RewriteClassOnObjects, 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..d0e32976 --- /dev/null +++ b/src/main/php/lang/ast/emit/PHP73.class.php @@ -0,0 +1,69 @@ + 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; } + ]; + } + + 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/RewriteAssignments.class.php b/src/main/php/lang/ast/emit/RewriteAssignments.class.php new file mode 100755 index 00000000..9b3e8ef1 --- /dev/null +++ b/src/main/php/lang/ast/emit/RewriteAssignments.class.php @@ -0,0 +1,62 @@ +operator) { + $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/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 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..d5593f4e 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.'; } }'); @@ -105,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 { 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..c8ab6c4d --- /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], [[false], false], [['Test'], 'Test']])] + 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[0]); + } +} \ 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