Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP70.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ class PHP70 extends PHP {
OmitPropertyTypes,
ReadonlyClasses,
ReadonlyProperties,
RewriteAssignments,
RewriteClassOnObjects,
RewriteEnums,
RewriteExplicitOctals,
RewriteLambdaExpressions,
RewriteMultiCatch,
RewriteNullCoalesceAssignment,
RewriteThrowableExpressions
;

Expand Down
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP71.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ class PHP71 extends PHP {
OmitPropertyTypes,
ReadonlyProperties,
ReadonlyClasses,
RewriteAssignments,
RewriteClassOnObjects,
RewriteEnums,
RewriteExplicitOctals,
RewriteLambdaExpressions,
RewriteNullCoalesceAssignment,
RewriteThrowableExpressions
;

Expand Down
2 changes: 1 addition & 1 deletion src/main/php/lang/ast/emit/PHP72.class.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ class PHP72 extends PHP {
OmitPropertyTypes,
ReadonlyProperties,
ReadonlyClasses,
RewriteAssignments,
RewriteClassOnObjects,
RewriteEnums,
RewriteExplicitOctals,
RewriteLambdaExpressions,
RewriteNullCoalesceAssignment,
RewriteThrowableExpressions
;

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

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

/**
* 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 {
use
ArbitrayNewExpressions,
ArrayUnpackUsingMerge,
AttributesAsComments,
CallablesAsClosures,
MatchAsTernaries,
NonCapturingCatchVariables,
NullsafeAsTernaries,
OmitArgumentNames,
OmitPropertyTypes,
ReadonlyProperties,
ReadonlyClasses,
RewriteClassOnObjects,
RewriteEnums,
RewriteExplicitOctals,
RewriteLambdaExpressions,
RewriteThrowableExpressions
;

/** Sets up type => 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);
}
}
}
62 changes: 62 additions & 0 deletions src/main/php/lang/ast/emit/RewriteAssignments.class.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php namespace lang\ast\emit;

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

/**
* Rewrites list reference assignments and null-coalesce for PHP <= 7.3
*
* @see https://wiki.php.net/rfc/null_coalesce_equal_operator
* @see https://wiki.php.net/rfc/list_reference_assignment
*/
trait RewriteAssignments {

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 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);
}
}
}
22 changes: 0 additions & 22 deletions src/main/php/lang/ast/emit/RewriteNullCoalesceAssignment.class.php

This file was deleted.

27 changes: 20 additions & 7 deletions src/test/php/lang/ast/unittest/emit/ArraysTest.class.php
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?php namespace lang\ast\unittest\emit;

use unittest\actions\RuntimeVersion;
use unittest\{Action, Assert, Test, Values};
use unittest\{Assert, Test, Values};

class ArraysTest extends EmittingTest {

Expand Down Expand Up @@ -52,16 +51,18 @@ public function run() {
Assert::equals([1, 2], $r);
}

#[Test, Action(eval: 'new RuntimeVersion(">=7.3.0")')]
public function reference_destructuring() {
#[Test, Values(['$list', '$this->instance', 'self::$static'])]
public function reference_destructuring($reference) {
$r= $this->run('class <T> {
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.';
}
}');

Expand Down Expand Up @@ -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 <T> {
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 <T> {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php namespace lang\ast\unittest\emit;

use unittest\{Assert, Test, Values};

class NullCoalesceAssignmentTest extends EmittingTest {

#[Test, Values([[null, true], [false, false], ['Test', 'Test']])]
public function assigns_true_if_null($value, $expected) {
$r= $this->run('class <T> {
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 <T> {
public function run($arg) {
$arg[0]??= true;
return $arg;
}
}', $value);

Assert::equals($expected, $r[0]);
}
}

This file was deleted.