Skip to content

Commit 6d7039c

Browse files
authored
Merge pull request #104 from xp-framework/feature/new_in_initializers
Support arbitrary expressions in property initializations and parameter defaults
2 parents 3a0d193 + 8370789 commit 6d7039c

File tree

3 files changed

+321
-17
lines changed

3 files changed

+321
-17
lines changed

src/main/php/lang/ast/emit/PHP.class.php

Lines changed: 104 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php namespace lang\ast\emit;
22

3-
use lang\ast\nodes\{InstanceExpression, ScopeExpression, Variable, Block};
3+
use lang\ast\Code;
4+
use lang\ast\nodes\{InstanceExpression, ScopeExpression, BinaryExpression, Variable, Literal, ArrayLiteral, Block};
45
use lang\ast\types\{IsUnion, IsFunction, IsArray, IsMap};
56
use lang\ast\{Emitter, Node, Type};
67

@@ -30,6 +31,34 @@ protected function declaration($name) {
3031
return substr($name, strrpos($name, '\\') + 1);
3132
}
3233

34+
/**
35+
* Returns whether a given node is a constant expression:
36+
*
37+
* - Any literal
38+
* - Arrays where all members are literals
39+
* - Scope expressions with literal members (self::class, T::const)
40+
* - Binary expression where left- and right hand side are literals
41+
*
42+
* @see https://wiki.php.net/rfc/const_scalar_exprs
43+
* @param lang.ast.Node $node
44+
* @return bool
45+
*/
46+
protected function isConstant($node) {
47+
if ($node instanceof Literal) {
48+
return true;
49+
} else if ($node instanceof ArrayLiteral) {
50+
foreach ($node->values as $node) {
51+
if (!$this->isConstant($node)) return false;
52+
}
53+
return true;
54+
} else if ($node instanceof ScopeExpression) {
55+
return $node->member instanceof Literal;
56+
} else if ($node instanceof BinaryExpression) {
57+
return $this->isConstant($node->left) && $this->isConstant($node->right);
58+
}
59+
return false;
60+
}
61+
3362
/**
3463
* As of PHP 7.4: Property type declarations support all type declarations
3564
* supported by PHP with the exception of void and callable.
@@ -88,6 +117,21 @@ protected function enclose($result, $node, $signature, $emit) {
88117
$result->locals= array_pop($result->stack);
89118
}
90119

120+
/**
121+
* Emits local initializations
122+
*
123+
* @param lang.ast.Result $result
124+
* @param [:lang.ast.Node] $init
125+
* @return void
126+
*/
127+
protected function emitInitializations($result, $init) {
128+
foreach ($init as $assign => $expression) {
129+
$result->out->write($assign.'=');
130+
$this->emitOne($result, $expression);
131+
$result->out->write(';');
132+
}
133+
}
134+
91135
/**
92136
* Convert blocks to IIFEs to allow a list of statements where PHP syntactically
93137
* doesn't, e.g. `fn`-style lambdas or match expressions.
@@ -144,8 +188,13 @@ protected function emitStatic($result, $static) {
144188
foreach ($static->initializations as $variable => $initial) {
145189
$result->out->write('static $'.$variable);
146190
if ($initial) {
147-
$result->out->write('=');
148-
$this->emitOne($result, $initial);
191+
if ($this->isConstant($initial)) {
192+
$result->out->write('=');
193+
$this->emitOne($result, $initial);
194+
} else {
195+
$result->out->write('= null; null === $'.$variable.' && $'.$variable.'= ');
196+
$this->emitOne($result, $initial);
197+
}
149198
}
150199
$result->out->write(';');
151200
}
@@ -235,8 +284,13 @@ protected function emitParameter($result, $parameter) {
235284
$result->out->write(($parameter->reference ? '&' : '').'$'.$parameter->name);
236285
}
237286
if ($parameter->default) {
238-
$result->out->write('=');
239-
$this->emitOne($result, $parameter->default);
287+
if ($this->isConstant($parameter->default)) {
288+
$result->out->write('=');
289+
$this->emitOne($result, $parameter->default);
290+
} else {
291+
$result->out->write('=null');
292+
$result->locals[1]['null === $'.$parameter->name.' && $'.$parameter->name]= $parameter->default;
293+
}
240294
}
241295
$result->locals[$parameter->name]= true;
242296
}
@@ -297,6 +351,7 @@ protected function emitLambda($result, $lambda) {
297351

298352
protected function emitClass($result, $class) {
299353
array_unshift($result->meta, []);
354+
$result->locals= [[], []];
300355

301356
$result->out->write(implode(' ', $class->modifiers).' class '.$this->declaration($class->name));
302357
$class->parent && $result->out->write(' extends '.$class->parent);
@@ -306,7 +361,16 @@ protected function emitClass($result, $class) {
306361
$this->emitOne($result, $member);
307362
}
308363

364+
// Create constructor for property initializations to non-static scalars
365+
// which have not already been emitted inside constructor
366+
if ($result->locals[1]) {
367+
$result->out->write('public function __construct() {');
368+
$this->emitInitializations($result, $result->locals[1]);
369+
$result->out->write('}');
370+
}
371+
309372
$result->out->write('static function __init() {');
373+
$this->emitInitializations($result, $result->locals[0]);
310374
$this->emitMeta($result, $class->name, $class->annotations, $class->comment);
311375
$result->out->write('}} '.$class->name.'::__init();');
312376
}
@@ -452,15 +516,31 @@ protected function emitProperty($result, $property) {
452516

453517
$result->out->write(implode(' ', $property->modifiers).' '.$this->propertyType($property->type).' $'.$property->name);
454518
if (isset($property->expression)) {
455-
$result->out->write('=');
456-
$this->emitOne($result, $property->expression);
519+
if ($this->isConstant($property->expression)) {
520+
$result->out->write('=');
521+
$this->emitOne($result, $property->expression);
522+
} else if (in_array('static', $property->modifiers)) {
523+
$result->locals[0]['self::$'.$property->name]= $property->expression;
524+
} else {
525+
$result->locals[1]['$this->'.$property->name]= $property->expression;
526+
}
457527
}
458528
$result->out->write(';');
459529
}
460530

461531
protected function emitMethod($result, $method) {
532+
533+
// Include non-static initializations for members in constructor, removing
534+
// them to signal emitClass() no constructor needs to be generated.
535+
if ('__construct' === $method->name && isset($result->locals[1])) {
536+
$locals= ['this' => true, 1 => $result->locals[1]];
537+
$result->locals[1]= [];
538+
} else {
539+
$locals= ['this' => true, 1 => []];
540+
}
462541
$result->stack[]= $result->locals;
463-
$result->locals= ['this' => true];
542+
$result->locals= $locals;
543+
464544
$meta= [
465545
DETAIL_RETURNS => $method->signature->returns ? $method->signature->returns->name() : 'var',
466546
DETAIL_ANNOTATIONS => $method->annotations,
@@ -469,11 +549,17 @@ protected function emitMethod($result, $method) {
469549
DETAIL_ARGUMENTS => []
470550
];
471551

472-
$declare= $promote= $params= '';
552+
$result->out->write(implode(' ', $method->modifiers).' function '.$method->name);
553+
$this->emitSignature($result, $method->signature);
554+
555+
$promoted= '';
473556
foreach ($method->signature->parameters as $param) {
557+
$meta[DETAIL_TARGET_ANNO][$param->name]= $param->annotations;
558+
$meta[DETAIL_ARGUMENTS][]= $param->type ? $param->type->name() : 'var';
559+
474560
if (isset($param->promote)) {
475-
$declare.= $param->promote.' $'.$param->name.';';
476-
$promote.= '$this->'.$param->name.'= '.($param->reference ? '&$' : '$').$param->name.';';
561+
$promoted.= $param->promote.' $'.$param->name.';';
562+
$result->locals[1]['$this->'.$param->name]= new Code(($param->reference ? '&$' : '$').$param->name);
477563
$result->meta[0][self::PROPERTY][$param->name]= [
478564
DETAIL_RETURNS => $param->type ? $param->type->name() : 'var',
479565
DETAIL_ANNOTATIONS => [],
@@ -482,21 +568,22 @@ protected function emitMethod($result, $method) {
482568
DETAIL_ARGUMENTS => []
483569
];
484570
}
485-
$meta[DETAIL_TARGET_ANNO][$param->name]= $param->annotations;
486-
$meta[DETAIL_ARGUMENTS][]= $param->type ? $param->type->name() : 'var';
571+
572+
if (isset($param->default) && !$this->isConstant($param->default)) {
573+
$meta[DETAIL_TARGET_ANNO][$param->name]['default']= [$param->default];
574+
}
487575
}
488-
$result->out->write($declare);
489-
$result->out->write(implode(' ', $method->modifiers).' function '.$method->name);
490-
$this->emitSignature($result, $method->signature);
491576

492577
if (null === $method->body) {
493578
$result->out->write(';');
494579
} else {
495-
$result->out->write(' {'.$promote);
580+
$result->out->write(' {');
581+
$this->emitInitializations($result, $result->locals[1]);
496582
$this->emitAll($result, $method->body);
497583
$result->out->write('}');
498584
}
499585

586+
$result->out->write($promoted);
500587
$result->meta[0][self::METHOD][$method->name]= $meta;
501588
$result->locals= array_pop($result->stack);
502589
}

src/test/php/lang/ast/unittest/emit/Handle.class.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,21 @@
44

55
/** Used by `UsingTest` */
66
class Handle implements \IDisposable {
7+
public static $DEFAULT;
78
public static $called= [];
89
private $id;
910

11+
static function __static() {
12+
self::$DEFAULT= new Handle(0);
13+
}
14+
1015
public function __construct($id) { $this->id= $id; }
1116

17+
public function redirect($id) {
18+
$this->id= $id;
19+
return $this;
20+
}
21+
1222
public function read($bytes= 8192) {
1323
self::$called[]= 'read@'.$this->id;
1424

0 commit comments

Comments
 (0)