1
1
<?php namespace lang \ast \emit ;
2
2
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 };
4
5
use lang \ast \types \{IsUnion , IsFunction , IsArray , IsMap };
5
6
use lang \ast \{Emitter , Node , Type };
6
7
@@ -30,6 +31,34 @@ protected function declaration($name) {
30
31
return substr ($ name , strrpos ($ name , '\\' ) + 1 );
31
32
}
32
33
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
+
33
62
/**
34
63
* As of PHP 7.4: Property type declarations support all type declarations
35
64
* supported by PHP with the exception of void and callable.
@@ -88,6 +117,21 @@ protected function enclose($result, $node, $signature, $emit) {
88
117
$ result ->locals = array_pop ($ result ->stack );
89
118
}
90
119
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
+
91
135
/**
92
136
* Convert blocks to IIFEs to allow a list of statements where PHP syntactically
93
137
* doesn't, e.g. `fn`-style lambdas or match expressions.
@@ -144,8 +188,13 @@ protected function emitStatic($result, $static) {
144
188
foreach ($ static ->initializations as $ variable => $ initial ) {
145
189
$ result ->out ->write ('static $ ' .$ variable );
146
190
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
+ }
149
198
}
150
199
$ result ->out ->write ('; ' );
151
200
}
@@ -235,8 +284,13 @@ protected function emitParameter($result, $parameter) {
235
284
$ result ->out ->write (($ parameter ->reference ? '& ' : '' ).'$ ' .$ parameter ->name );
236
285
}
237
286
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
+ }
240
294
}
241
295
$ result ->locals [$ parameter ->name ]= true ;
242
296
}
@@ -297,6 +351,7 @@ protected function emitLambda($result, $lambda) {
297
351
298
352
protected function emitClass ($ result , $ class ) {
299
353
array_unshift ($ result ->meta , []);
354
+ $ result ->locals = [[], []];
300
355
301
356
$ result ->out ->write (implode (' ' , $ class ->modifiers ).' class ' .$ this ->declaration ($ class ->name ));
302
357
$ class ->parent && $ result ->out ->write (' extends ' .$ class ->parent );
@@ -306,7 +361,16 @@ protected function emitClass($result, $class) {
306
361
$ this ->emitOne ($ result , $ member );
307
362
}
308
363
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
+
309
372
$ result ->out ->write ('static function __init() { ' );
373
+ $ this ->emitInitializations ($ result , $ result ->locals [0 ]);
310
374
$ this ->emitMeta ($ result , $ class ->name , $ class ->annotations , $ class ->comment );
311
375
$ result ->out ->write ('}} ' .$ class ->name .'::__init(); ' );
312
376
}
@@ -452,15 +516,31 @@ protected function emitProperty($result, $property) {
452
516
453
517
$ result ->out ->write (implode (' ' , $ property ->modifiers ).' ' .$ this ->propertyType ($ property ->type ).' $ ' .$ property ->name );
454
518
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
+ }
457
527
}
458
528
$ result ->out ->write ('; ' );
459
529
}
460
530
461
531
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
+ }
462
541
$ result ->stack []= $ result ->locals ;
463
- $ result ->locals = ['this ' => true ];
542
+ $ result ->locals = $ locals ;
543
+
464
544
$ meta = [
465
545
DETAIL_RETURNS => $ method ->signature ->returns ? $ method ->signature ->returns ->name () : 'var ' ,
466
546
DETAIL_ANNOTATIONS => $ method ->annotations ,
@@ -469,11 +549,17 @@ protected function emitMethod($result, $method) {
469
549
DETAIL_ARGUMENTS => []
470
550
];
471
551
472
- $ declare = $ promote = $ params = '' ;
552
+ $ result ->out ->write (implode (' ' , $ method ->modifiers ).' function ' .$ method ->name );
553
+ $ this ->emitSignature ($ result , $ method ->signature );
554
+
555
+ $ promoted = '' ;
473
556
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
+
474
560
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 ) ;
477
563
$ result ->meta [0 ][self ::PROPERTY ][$ param ->name ]= [
478
564
DETAIL_RETURNS => $ param ->type ? $ param ->type ->name () : 'var ' ,
479
565
DETAIL_ANNOTATIONS => [],
@@ -482,21 +568,22 @@ protected function emitMethod($result, $method) {
482
568
DETAIL_ARGUMENTS => []
483
569
];
484
570
}
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
+ }
487
575
}
488
- $ result ->out ->write ($ declare );
489
- $ result ->out ->write (implode (' ' , $ method ->modifiers ).' function ' .$ method ->name );
490
- $ this ->emitSignature ($ result , $ method ->signature );
491
576
492
577
if (null === $ method ->body ) {
493
578
$ result ->out ->write ('; ' );
494
579
} else {
495
- $ result ->out ->write (' { ' .$ promote );
580
+ $ result ->out ->write (' { ' );
581
+ $ this ->emitInitializations ($ result , $ result ->locals [1 ]);
496
582
$ this ->emitAll ($ result , $ method ->body );
497
583
$ result ->out ->write ('} ' );
498
584
}
499
585
586
+ $ result ->out ->write ($ promoted );
500
587
$ result ->meta [0 ][self ::METHOD ][$ method ->name ]= $ meta ;
501
588
$ result ->locals = array_pop ($ result ->stack );
502
589
}
0 commit comments