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 , 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,31 @@ 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 so-called "static scalar" expression:
36
+ *
37
+ * - Any literal
38
+ * - Arrays where all members are literals
39
+ * - Scope expressions with literal members (self::class, T::const)
40
+ *
41
+ * @see https://wiki.php.net/rfc/new_in_initializers
42
+ * @param lang.ast.Node $node
43
+ * @return bool
44
+ */
45
+ protected function staticScalar ($ node ) {
46
+ if ($ node instanceof Literal) {
47
+ return true ;
48
+ } else if ($ node instanceof ArrayLiteral) {
49
+ foreach ($ node ->values as $ node ) {
50
+ if (!$ this ->staticScalar ($ node )) return false ;
51
+ }
52
+ return true ;
53
+ } else if ($ node instanceof ScopeExpression) {
54
+ return $ node ->member instanceof Literal;
55
+ }
56
+ return false ;
57
+ }
58
+
33
59
/**
34
60
* As of PHP 7.4: Property type declarations support all type declarations
35
61
* supported by PHP with the exception of void and callable.
@@ -88,6 +114,21 @@ protected function enclose($result, $node, $signature, $emit) {
88
114
$ result ->locals = array_pop ($ result ->stack );
89
115
}
90
116
117
+ /**
118
+ * Emits local initializations
119
+ *
120
+ * @param lang.ast.Result $result
121
+ * @param [:lang.ast.Node] $init
122
+ * @return void
123
+ */
124
+ protected function emitInitializations ($ result , $ init ) {
125
+ foreach ($ init as $ assign => $ expression ) {
126
+ $ result ->out ->write ($ assign .'= ' );
127
+ $ this ->emitOne ($ result , $ expression );
128
+ $ result ->out ->write ('; ' );
129
+ }
130
+ }
131
+
91
132
/**
92
133
* Convert blocks to IIFEs to allow a list of statements where PHP syntactically
93
134
* doesn't, e.g. `fn`-style lambdas or match expressions.
@@ -235,8 +276,13 @@ protected function emitParameter($result, $parameter) {
235
276
$ result ->out ->write (($ parameter ->reference ? '& ' : '' ).'$ ' .$ parameter ->name );
236
277
}
237
278
if ($ parameter ->default ) {
238
- $ result ->out ->write ('= ' );
239
- $ this ->emitOne ($ result , $ parameter ->default );
279
+ if ($ this ->staticScalar ($ parameter ->default )) {
280
+ $ result ->out ->write ('= ' );
281
+ $ this ->emitOne ($ result , $ parameter ->default );
282
+ } else {
283
+ $ result ->out ->write ('=null ' );
284
+ $ result ->locals ['$ ' ]['null === $ ' .$ parameter ->name .' && $ ' .$ parameter ->name ]= $ parameter ->default ;
285
+ }
240
286
}
241
287
$ result ->locals [$ parameter ->name ]= true ;
242
288
}
@@ -297,6 +343,7 @@ protected function emitLambda($result, $lambda) {
297
343
298
344
protected function emitClass ($ result , $ class ) {
299
345
array_unshift ($ result ->meta , []);
346
+ $ result ->locals = ['$ ' => [], '@ ' => []];
300
347
301
348
$ result ->out ->write (implode (' ' , $ class ->modifiers ).' class ' .$ this ->declaration ($ class ->name ));
302
349
$ class ->parent && $ result ->out ->write (' extends ' .$ class ->parent );
@@ -306,7 +353,16 @@ protected function emitClass($result, $class) {
306
353
$ this ->emitOne ($ result , $ member );
307
354
}
308
355
356
+ // Create constructor for property initializations to non-static scalars
357
+ // which have not already been emitted inside constructor
358
+ if ($ result ->locals ['$ ' ]) {
359
+ $ result ->out ->write ('public function __construct() { ' );
360
+ $ this ->emitInitializations ($ result , $ result ->locals ['$ ' ]);
361
+ $ result ->out ->write ('} ' );
362
+ }
363
+
309
364
$ result ->out ->write ('static function __init() { ' );
365
+ $ this ->emitInitializations ($ result , $ result ->locals ['@ ' ]);
310
366
$ this ->emitMeta ($ result , $ class ->name , $ class ->annotations , $ class ->comment );
311
367
$ result ->out ->write ('}} ' .$ class ->name .'::__init(); ' );
312
368
}
@@ -452,15 +508,31 @@ protected function emitProperty($result, $property) {
452
508
453
509
$ result ->out ->write (implode (' ' , $ property ->modifiers ).' ' .$ this ->propertyType ($ property ->type ).' $ ' .$ property ->name );
454
510
if (isset ($ property ->expression )) {
455
- $ result ->out ->write ('= ' );
456
- $ this ->emitOne ($ result , $ property ->expression );
511
+ if ($ this ->staticScalar ($ property ->expression )) {
512
+ $ result ->out ->write ('= ' );
513
+ $ this ->emitOne ($ result , $ property ->expression );
514
+ } else if (in_array ('static ' , $ property ->modifiers )) {
515
+ $ result ->locals ['@ ' ]['self::$ ' .$ property ->name ]= $ property ->expression ;
516
+ } else {
517
+ $ result ->locals ['$ ' ]['$this-> ' .$ property ->name ]= $ property ->expression ;
518
+ }
457
519
}
458
520
$ result ->out ->write ('; ' );
459
521
}
460
522
461
523
protected function emitMethod ($ result , $ method ) {
524
+
525
+ // Include non-static initializations for members in constructor, removing
526
+ // them to signal emitClass() no constructor needs to be generated.
527
+ if ('__construct ' === $ method ->name && isset ($ result ->locals ['$ ' ])) {
528
+ $ locals = ['this ' => true , '$ ' => $ result ->locals ['$ ' ]];
529
+ $ result ->locals ['$ ' ]= [];
530
+ } else {
531
+ $ locals = ['this ' => true , '$ ' => []];
532
+ }
462
533
$ result ->stack []= $ result ->locals ;
463
- $ result ->locals = ['this ' => true ];
534
+ $ result ->locals = $ locals ;
535
+
464
536
$ meta = [
465
537
DETAIL_RETURNS => $ method ->signature ->returns ? $ method ->signature ->returns ->name () : 'var ' ,
466
538
DETAIL_ANNOTATIONS => $ method ->annotations ,
@@ -469,11 +541,14 @@ protected function emitMethod($result, $method) {
469
541
DETAIL_ARGUMENTS => []
470
542
];
471
543
472
- $ declare = $ promote = $ params = '' ;
544
+ $ result ->out ->write (implode (' ' , $ method ->modifiers ).' function ' .$ method ->name );
545
+ $ this ->emitSignature ($ result , $ method ->signature );
546
+
547
+ $ promoted = '' ;
473
548
foreach ($ method ->signature ->parameters as $ param ) {
474
549
if (isset ($ param ->promote )) {
475
- $ declare .= $ param ->promote .' $ ' .$ param ->name .'; ' ;
476
- $ promote .= ' $ this-> ' .$ param ->name . ' = ' .( $ param ->reference ? '&$ ' : '$ ' ).$ param ->name . ' ; ' ;
550
+ $ promoted .= $ param ->promote .' $ ' .$ param ->name .'; ' ;
551
+ $ result -> locals [ ' $ ' ][ ' $ this-> ' .$ param ->name ]= new Code (( $ param ->reference ? '&$ ' : '$ ' ).$ param ->name ) ;
477
552
$ result ->meta [0 ][self ::PROPERTY ][$ param ->name ]= [
478
553
DETAIL_RETURNS => $ param ->type ? $ param ->type ->name () : 'var ' ,
479
554
DETAIL_ANNOTATIONS => [],
@@ -485,18 +560,17 @@ protected function emitMethod($result, $method) {
485
560
$ meta [DETAIL_TARGET_ANNO ][$ param ->name ]= $ param ->annotations ;
486
561
$ meta [DETAIL_ARGUMENTS ][]= $ param ->type ? $ param ->type ->name () : 'var ' ;
487
562
}
488
- $ result ->out ->write ($ declare );
489
- $ result ->out ->write (implode (' ' , $ method ->modifiers ).' function ' .$ method ->name );
490
- $ this ->emitSignature ($ result , $ method ->signature );
491
563
492
564
if (null === $ method ->body ) {
493
565
$ result ->out ->write ('; ' );
494
566
} else {
495
- $ result ->out ->write (' { ' .$ promote );
567
+ $ result ->out ->write (' { ' );
568
+ $ this ->emitInitializations ($ result , $ result ->locals ['$ ' ]);
496
569
$ this ->emitAll ($ result , $ method ->body );
497
570
$ result ->out ->write ('} ' );
498
571
}
499
572
573
+ $ result ->out ->write ($ promoted );
500
574
$ result ->meta [0 ][self ::METHOD ][$ method ->name ]= $ meta ;
501
575
$ result ->locals = array_pop ($ result ->stack );
502
576
}
0 commit comments