Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/main/php/lang/ast/emit/PHP70.class.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?php namespace lang\ast\emit;

use lang\ast\Node;
use lang\ast\nodes\{InstanceExpression, ScopeExpression, Literal, Variable};
use lang\ast\nodes\{InstanceExpression, ScopeExpression, UnaryExpression, BinaryExpression, Literal};
use lang\ast\types\{IsUnion, IsIntersection, IsFunction, IsArray, IsMap, IsNullable, IsValue, IsLiteral, IsGeneric};

/**
Expand Down Expand Up @@ -121,8 +121,11 @@ protected function emitAssignment($result, $assignment) {

// Rewrite destructuring unless assignment consists only of variables
foreach ($assignment->variable->values as $pair) {
if (null === $pair[0] && (null === $pair[1] || $pair[1] instanceof Variable)) continue;
return $this->rewriteDestructuring($result, $assignment);
if (
$pair[0] ||
$pair[1] instanceof UnaryExpression ||
$pair[1] instanceof BinaryExpression
) return $this->rewriteDestructuring($result, $assignment);
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/main/php/lang/ast/emit/PHP73.class.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace lang\ast\emit;

use lang\ast\nodes\BinaryExpression;
use lang\ast\types\{IsUnion, IsIntersection, IsFunction, IsArray, IsMap, IsNullable, IsValue, IsLiteral, IsGeneric};

/**
Expand All @@ -22,6 +23,7 @@ class PHP73 extends PHP {
OmitPropertyTypes,
ReadonlyProperties,
ReadonlyClasses,
RewriteAssignments,
RewriteClassOnObjects,
RewriteEnums,
RewriteExplicitOctals,
Expand Down Expand Up @@ -62,8 +64,13 @@ protected function emitAssignment($result, $assignment) {
$this->emitOne($result, $assignment->variable);
$result->out->write('=');
$this->emitOne($result, $assignment->expression);
} else {
parent::emitAssignment($result, $assignment);
return;
} else if ('array' === $assignment->variable->kind) {
foreach ($assignment->variable->values as $pair) {
if ($pair[1] instanceof BinaryExpression) return $this->rewriteDestructuring($result, $assignment);
}
}

parent::emitAssignment($result, $assignment);
}
}
1 change: 1 addition & 0 deletions src/main/php/lang/ast/emit/PHP74.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class PHP74 extends PHP {
ReadonlyProperties,
RewriteBlockLambdaExpressions,
RewriteClassOnObjects,
RewriteDestructuring,
RewriteEnums,
RewriteExplicitOctals,
RewriteThrowableExpressions
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP80.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @see https://wiki.php.net/rfc#php_80
*/
class PHP80 extends PHP {
use RewriteBlockLambdaExpressions, RewriteExplicitOctals, RewriteEnums;
use RewriteBlockLambdaExpressions, RewriteDestructuring, RewriteExplicitOctals, RewriteEnums;
use ReadonlyClasses, ReadonlyProperties, CallablesAsClosures, ArrayUnpackUsingMerge;

/** Sets up type => literal mappings */
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP81.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* @see https://wiki.php.net/rfc#php_81
*/
class PHP81 extends PHP {
use RewriteBlockLambdaExpressions, ReadonlyClasses;
use RewriteBlockLambdaExpressions, RewriteDestructuring, ReadonlyClasses;

/** Sets up type => literal mappings */
public function __construct() {
Expand Down
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP82.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* @see https://wiki.php.net/rfc#php_82
*/
class PHP82 extends PHP {
use RewriteBlockLambdaExpressions, ReadonlyClasses;
use RewriteBlockLambdaExpressions, RewriteDestructuring, ReadonlyClasses;

/** Sets up type => literal mappings */
public function __construct() {
Expand Down
67 changes: 6 additions & 61 deletions src/main/php/lang/ast/emit/RewriteAssignments.class.php
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
<?php namespace lang\ast\emit;

use lang\ast\nodes\{
Assignment,
InstanceExpression,
Literal,
OffsetExpression,
ScopeExpression,
UnaryExpression,
Variable
};
use lang\ast\nodes\{UnaryExpression, BinaryExpression};

/**
* Rewrites list reference assignments and null-coalesce for PHP <= 7.3
Expand All @@ -17,54 +9,7 @@
* @see https://wiki.php.net/rfc/list_reference_assignment
*/
trait RewriteAssignments {

protected function rewriteDestructuring($result, $assignment) {
$t= $result->temp();
$result->out->write('is_array('.$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('&');
}

$temp= new Variable(substr($t, 1));
$this->emitOne($result, $assignment->expression);
$result->out->write(')?[');
foreach ($assignment->variable->values as $i => $pair) {
if (null === $pair[1]) {
$result->out->write('null,');
continue;
}

// Assign by reference
$value= new OffsetExpression($temp, $pair[0] ?? new Literal($i));
if ($pair[1] instanceof UnaryExpression) {
$this->emitAssignment($result, new Assignment($pair[1]->expression, '=&', $value));
} else {
$this->emitAssignment($result, new Assignment($pair[1], '=', $value));
}
$result->out->write(',');
}

$null= new Literal('null');
$result->out->write(']:([');
foreach ($assignment->variable->values as $pair) {
if (null === $pair[1]) {
continue;
} else if ($pair[1] instanceof UnaryExpression) {
$this->emitAssignment($result, new Assignment($pair[1]->expression, '=', $null));
} else if ($pair[1]) {
$this->emitAssignment($result, new Assignment($pair[1], '=', $null));
}
$result->out->write(',');
}
$result->out->write(']?'.$t.':null)');
}
use RewriteDestructuring { emitAssignment as private; }

protected function emitAssignment($result, $assignment) {
if ('??=' === $assignment->operator) {
Expand All @@ -77,11 +22,11 @@ protected function emitAssignment($result, $assignment) {
$this->emitOne($result, $assignment->expression);
return;
} else if ('array' === $assignment->variable->kind) {

// Rewrite destructuring unless assignment consists only of variables
foreach ($assignment->variable->values as $pair) {
if (null === $pair[1] || $pair[1] instanceof Variable) continue;
return $this->rewriteDestructuring($result, $assignment);
if (
$pair[1] instanceof UnaryExpression ||
$pair[1] instanceof BinaryExpression
) return $this->rewriteDestructuring($result, $assignment);
}
}

Expand Down
79 changes: 79 additions & 0 deletions src/main/php/lang/ast/emit/RewriteDestructuring.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php namespace lang\ast\emit;

use lang\ast\nodes\{
Assignment,
BinaryExpression,
InstanceExpression,
Literal,
OffsetExpression,
ScopeExpression,
UnaryExpression,
Variable
};

trait RewriteDestructuring {

protected function rewriteDestructuring($result, $assignment) {
$t= $result->temp();
$result->out->write('is_array('.$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('&');
}

$temp= new Variable(substr($t, 1));
$this->emitOne($result, $assignment->expression);
$result->out->write(')?[');
foreach ($assignment->variable->values as $i => $pair) {
if (null === $pair[1]) {
$result->out->write('null,');
continue;
}

// Assign by reference, null-coalesce handling
$value= new OffsetExpression($temp, $pair[0] ?? new Literal((string)$i));
if ($pair[1] instanceof UnaryExpression) {
$this->emitAssignment($result, new Assignment($pair[1]->expression, '=&', $value));
} else if ($pair[1] instanceof BinaryExpression) {
$target= $pair[1]->left;
$pair[1]->left= $value;
$this->emitAssignment($result, new Assignment($target, '=', $pair[1]));
} else {
$this->emitAssignment($result, new Assignment($pair[1], '=', $value));
}
$result->out->write(',');
}

$null= new Literal('null');
$result->out->write(']:([');
foreach ($assignment->variable->values as $pair) {
if (null === $pair[1]) {
continue;
} else if ($pair[1] instanceof UnaryExpression) {
$this->emitAssignment($result, new Assignment($pair[1]->expression, '=', $null));
} else if ($pair[1] instanceof BinaryExpression) {
$this->emitAssignment($result, new Assignment($pair[1]->left, '=', $null));
} else if ($pair[1]) {
$this->emitAssignment($result, new Assignment($pair[1], '=', $null));
}
$result->out->write(',');
}
$result->out->write(']?'.$t.':null)');
}

protected function emitAssignment($result, $assignment) {
if ('array' === $assignment->variable->kind) {
foreach ($assignment->variable->values as $pair) {
if ($pair[1] instanceof BinaryExpression) return $this->rewriteDestructuring($result, $assignment);
}
}

parent::emitAssignment($result, $assignment);
}
}
24 changes: 24 additions & 0 deletions src/test/php/lang/ast/unittest/emit/ArraysTest.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,30 @@ public function run($arg) {
Assert::equals([null, null, $value], $r);
}

#[Test, Values([['key=value', ['key', 'value']], ['key', ['key', null]]])]
public function destructuring_coalesce($input, $expected) {
$r= $this->run('class <T> {
public function run($input) {
[$a, $b ?? null]= explode("=", $input, 2);
return [$a, $b];
}
}', $input);

Assert::equals($expected, $r);
}

#[Test, Values([[['a' => 1, 'b' => 2], [1, 2]], [['a' => 1], [1, null]]])]
public function destructuring_coalesce_with_keys($input, $expected) {
$r= $this->run('class <T> {
public function run($input) {
["a" => $a, "b" => $b ?? null]= $input;
return [$a, $b];
}
}', $input);

Assert::equals($expected, $r);
}

#[Test]
public function init_with_variable() {
$r= $this->run('class <T> {
Expand Down