• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1## Phases in `es2panda` as of 2024.10.20
2
3- `plugins-after-parse`: call compiler plugins that work immediately after parsing, if any.
4
5- `TopLevelStatements`: move top-level statements into a synthetic class. For example,
6```
7class C {
8  public constructor() {}
9}
10
11let x = 33
12
13function f(): int {
14  return x
15}
16```
17becomes
18```
19class C {
20  public constructor() {}
21}
22
23abstract class ETSGLOBAL {
24  public static main() {}
25
26  public static _$init$_() {
27    x = 33;
28  }
29
30  public static x = 33;
31
32  public static f(): int {
33    return x;
34  }
35
36
37  public static _$trigger_cctor$_() {}
38
39}
40```
41
42- `DefaultParameterLowering`: handle functions with default parameters
43```
44class C {
45  f(x: string = "oh") {}
46}
47```
48converts into
49```
50class C {
51  public f() {
52    this.f("oh");
53  }
54
55  public f(x: string) {}
56}
57```
58
59- `AmbientLowering` creates getter and setter for each indexer method in ambient classes (14.3.1 spec point), e.g.:
60```
61declare class A  {
62  [index: number]: string  // indexer declaration
63}
64```
65is converted to:
66```
67declare class A  {
68  $_get(index: number): string
69  $_set(index: number, value: string): void
70}
71```
72
73- `ScopesInitPhase`: create `Scope` structures for all scopes; create `Variable` structures for every class name, function name and variable name in the program
74
75- `OptionalLowering`: handle `a?.f` constructions
76```
77c?.f()
78```
79is replaced by
80```
81({let gensym$_1 = c;
82  ((gensym$_1 == null) ? undefined : gensym$_1.f())})
83```
84
85- `PromiseVoidInferencePhase`: return `undefined` instead of void from async functions
86```
87class C {
88  public async f() {
89    return;
90  }
91}
92```
93converts to
94```
95class C {
96  public async f() {
97    return undefined;
98  }
99}
100```
101
102- `StructLowering`: something to do with struct definitions, but those are not supported by the parser yet
103
104- `ExpressionLambdaConstruction`: handle lambdas with expression bodies
105```
106(): int => 33
107```
108is replaced with
109```
110(): int => { return 33 }
111```
112
113- `InterfacePropertyDeclarationPhase`: handle property declarations in interfaces.
114```
115interface I {
116  v: int
117}
118```
119is converted to
120```
121interface I {
122  get v(): int
123  set v(v: int)
124}
125```
126
127- `EnumLoweringPhase`: create auxiliary classes for enums.
128```
129enum E {
130  ONE = 0,
131  TWO = 1
132}
133
134```
135is converted to
136```
137enum E {
138  ONE = 0,
139  TWO = 1
140}
141
142class #E {
143  protected ordinal: int;
144
145  public static <cctor>() {}
146
147  public constructor(ordinal: int) {
148    this.ordinal = ordinal;
149  }
150
151  protected static NamesArray: String[] = ["ONE", "TWO"];
152
153  protected static ValuesArray: int[] = [0, 1];
154
155  protected static StringValuesArray: String[] = ["0", "1"];
156
157  protected static ItemsArray: E[] = [E.ONE, E.TWO];
158
159  protected static BoxedItemsArray: #E[] = [new #E(E.ONE as int), new #E(E.TWO as int)];
160
161  public static getName(ordinal: E): String {
162    return NamesArray[ordinal as int];
163  }
164
165  public static getValueOf(name: String): E throws {
166    for (let i = 0;(i < NamesArray.length);++i) {
167      if ((name == NamesArray[i])) {
168        return i as E;
169      }
170    }
171    throw new Exception(("No enum constant E." + name));
172  }
173
174  public static valueOf(e: E): int {
175    return ValuesArray[e as int];
176  }
177
178  public static toString(ordinal: E): String {
179    return StringValuesArray[ordinal as int];
180  }
181
182  public static values(): E[] {
183    return ItemsArray;
184  }
185
186  public static fromInt(ordinal: int): E throws {
187    if ((ordinal < ItemsArray.length)) {
188      return ItemsArray[ordinal];
189    }
190    throw new Exception(("No enum constant in E with ordinal value " + ordinal));
191  }
192
193  public unbox(): E {
194    return ItemsArray[this.ordinal];
195  }
196
197  public static boxedfromInt(ordinal: int): #E throws {
198    if ((ordinal < BoxedItemsArray.length)) {
199      return BoxedItemsArray[ordinal];
200    }
201    throw new Exception(("No enum constant in E with ordinal value " + ordinal));
202  }
203}
204```
205
206- `ResolveIdentifiers`: assign variables to all identifier usages.
207
208- `CheckerPhase`: assign types to all nodes that need to have a type. Subsequent phases are started under the assumption that all
209nodes have appropriate type
210
211- `EnumPostCheckLoweringPhase`: support access to enums
212
213```
214enum Commands {
215  Open = "fopen",
216  Close = "fclose"
217}
218
219function f(d: Commands): boolean {
220   return d == Commands.Close
221}
222```
223gets translated to
224```
225enum Commands {
226  Open = "fopen",
227  Close = "fclose"
228}
229
230function f(d: Commands): boolean {
231    return (#Commands.toString(d) == #Commands.toString(Commands.Close));
232}
233```
234
235- `SpreadConstructionPhase` deals with spread elements in array literals.
236```
237    let a = [1, 2, 3];
238    let b = [-1, -2, ...a, -3, -4];
239```
240becomes
241```
242    let a = [1, 2, 3];
243    let b = ({let length: int = ((4 + a.length) + 0);
244    type typeOfTempArray = double;
245
246    let tempArrayVar: typeOfTempArray[] = new typeOfTempArray[length];
247    let newArrayIndex = 0;
248    tempArrayVar[newArrayIndex] = -1
249    newArrayIndex++
250    tempArrayVar[newArrayIndex] = -2
251    newArrayIndex++
252    let elementOfSpread2: double;
253    for (elementOfSpread2 of a) {
254      tempArrayVar[newArrayIndex] = elementOfSpread2;
255      newArrayIndex++;
256    }
257    tempArrayVar[newArrayIndex] = -3
258    newArrayIndex++
259    tempArrayVar[newArrayIndex] = -4
260    newArrayIndex++
261    tempArrayVar});
262```
263
264- `plugins-after-check`:  call compiler plugins that work after checking types, if any.
265(This phase should come immediately after `CheckerPhase`, its current position is a bug)
266
267- `BigIntLowering`: convert bigint constants in field initializer to constructor calls; convert strict equality for bigints to normal equality.
268
269```
270class C {
271  public v = 1234567890n;
272}
273
274let b = (new C().v === 987654321987654321n);
275```
276is converted to
277```
278class C {
279  public v = new BigInt("1234567890");
280}
281
282let b = (new C().v == 987654321987654321n);
283```
284
285- `OpAssignmentLowering`: convert compound assignments to simple ones
286```
287f()[h()]++
288```
289gets converted to
290```
291 ({const gensym$_1 = f() as int[];
292   const gensym$_2 = h() as int;
293   const gensym$_3 = gensym$_1[gensym$_2] as int;
294   gensym$_1[gensym$_2] = (gensym$_3 + 1) as int
295   gensym$_3})
296```
297
298- `ConstStringToCharLowering`: convert single-char string constants to char constants where appropriate
299```
300public static f(c: char) {}
301f("c")
302```
303is converted to
304```
305public static f(c: char) {}
306f(99)
307```
308
309- `BoxingForLocals`: box local variables that are both
310  + effectively non-constant (get modified somewhere)
311  + captured in a lambda or local class
312```
313function f() {
314  let c = 33;
315  let lam = (): int => {
316    return c;
317  };
318  c = 44;
319}
320```
321converts to
322```
323function f() {
324  let c = new IntBox(33 as int);
325  let lam = (): int => {
326    return c.get() as int;
327  };
328  c.set(44 as int) as int;
329}
330```
331
332- `LambdaObjectConversion`: convert lambdas into classes that implement functional interfaces.
333```
334class C {
335  private p = 1;
336
337  public f(): int {
338    let ll = 2;
339    let lam = () {
340      return (this.p + ll);
341    };
342    return lam();
343  }
344}
345```
346converts to
347```
348class C {
349  private p = 1;
350
351  public f(): int {
352    let ll = 2;
353    let lam = new LambdaObject-C$lambda$invoke$0(this, ll);
354    return lam.invoke0();
355  }
356
357  public lambda$invoke$0(ll: int): int {
358    return (this.p + ll);
359  }
360
361}
362
363final class LambdaObject-C$lambda$invoke$0 implements Function0<Int> {
364  public $this: C;
365
366  public ll: int;
367
368  public constructor($this: C, ll: int) {
369    this.$this = $this;
370    this.ll = ll;
371  }
372
373  public invoke0(): Object|null|undefined {
374    return this.$this.lambda$invoke$0(this.ll) as Object|null|undefined;
375  }
376
377  public $_invoke(): int {
378    return this.$this.lambda$invoke$0(this.ll);
379  }
380}
381```
382
383- `RecordLowering`: support object literals for the utility class `Record`
384```
385    let x : Record<number, string> = {
386        1:"hello",
387        2:"hello2",
388    };
389```
390converts to
391```
392	let x: Record<number, string> = ({let gensym$_2 = new Record<Double, String>();
393      gensym$_2.set(1, "hello")
394      gensym$_2.set(2, "hello2")
395      gensym$_2});
396```
397
398- `ObjectIndexLowering`: convert object index access into getter and setter calls
399In
400```
401class C {
402  private x: int[] = [1, 3, 5];
403
404  public $_get(ind: int): int {
405    return this.x[ind];
406  }
407
408  public $_set(ind: int, val: int): void {
409    this.x[ind] = val;
410  }
411}
412
413function main() {
414  let c = new C();
415  c[1] = c[2];
416}
417```
418the last line converts to
419```
420  c.$_set(1, c.$_get(2));
421```
422
423- `ObjectIteratorLowering`: replace `for of` construction involving iterators into explicit iterator calls.
424```
425class A {
426    data: string[] = ['a', 'b', 'c'];
427    $_iterator() {
428      return new CIterator(this);
429    }
430}
431
432class CIterator implements Iterator<string> {
433    index = 0;
434    base: A;
435    constructor (base: A) {
436        this.base = base;
437    }
438    next(): IteratorResult<string> {
439        if (this.index >= this.base.data.length) {
440            return {
441                done: true,
442                value: undefined
443            }
444        }
445        return {
446          done: this.index >= this.base.data.length,
447          value: this.base.data[this.index++]
448        }
449    }
450}
451
452function main(): void {
453    let res = "";
454    let a = new A();
455    for (let x of a) res += x;
456}
457```
458the `for of` loop converts to
459```
460    let gensym$_4 = a.$_iterator();
461    let gensym$_5 = gensym$_4.next();
462    while (!gensym$_5.done) {
463      let x = gensym$_5.value!;
464      res = (res + x) as String;
465      gensym$_5 = gensym$_4.next();
466    }
467```
468
469- `UnionLowering` does two things:
470  1. processes field access to union
471  1. cast to primitive type
472
473If all union constituents has some common field then we need to generate special class for it for correct
474processing in runtime, e.g. for this code:
475
476```
477class A { n: int = 42 }
478class B { n: int = 43 }
479class C { n: int = 44 }
480function foo(x: A|B|C) {
481  x.n
482}
483```
484We will generate special class union_field with field named n which type is int. In the bytecode will look
485like this:
486
487```
488.union_field i32 n
489
490.function void ETSGLOBAL.foo(std.core.Object a0) <static, access.function=public> {
491  ets.ldobj.name a0, n
492  return.void
493}
494```
495Runtime will look at `union_field` inside handle of `ets.ldobj.name` instruction and will get the value of this filed
496from it.
497
498Note: ets.ldobj.name instruction generation happens at `CodeGen` stage when `ETSUnionType` is set to
499`ir::MemberExpression`.
500
501Union cast to primitive will be updated soon and will look like this:
502```
503function foo(x: string|int) {
504  console.log(x as int)
505}
506```
507will be transformed to:
508```
509function foo(x: string|int) {
510  console.log((x as Numeric).intValue())
511}
512```
513It will return the value of `x` by virtual call if `x` is an instance of `Numeric` type and will throw RTE in the other cases.
514
515- `ExpandBracketsPhase`: support for floats used as dimensions for array constructors
516```
517  let v = 4.0
518  let a = new int[v]
519```
520changes to
521```
522    let v = 4;
523    let a = ({let gensym$_2: double = v;
524    if (!isSafeInteger(gensym$_2)) {
525      throw new TypeError("Fractional part of index expression should be zero.");
526    }
527    new int[gensym$_2 as int]});
528```
529
530- `LocalClassConstructionPhase`: support for local classes and captured variables in them.
531```
532  function f() {
533    let v = 4;
534
535    class C {
536      m(): int {
537        return v;
538      }
539    }
540
541    let c = new C();
542  }
543
544```
545is converted to (roughly)
546```
547  function f() {
548    let v = 4;
549
550    class C {
551      m(): int {
552        return this.field#0;
553      }
554
555      constructor(v: int) {
556        this.field#0 = v;
557      }
558
559      field#0: int;
560    }
561
562    let c = new C(v);
563   }
564```
565
566- `InterfaceObjectLiteralLowering` creates synthetic classes for object literals of interface type:
567```
568interface I {
569  n: int
570  s: string
571}
572
573let o: I = {n: 33, s: "ouch"}
574```
575where `I` by this stage (after InterfacePropwertyDeclarationPhase) looks like
576```
577interface I {
578  public abstract n(n: int)
579
580  public abstract n(): int
581  public abstract n(n: int)
582  public abstract s(s: string)
583
584  public abstract s(): string
585  public abstract s(s: string)
586
587}
588```
589the initialization is converted to (approximately)
590```
591let o: I = {n: 33, s: "ouch"} as $anonymous_class$I
592
593class $anonymous_class$I implements I {
594  private _n: int;
595
596  set n(n: int) {
597    this._n = n;
598  }
599
600  get n(): int {
601    return this._n;
602  }
603
604  private _s: String;
605
606  set s(s: String) {
607    this._s = s;
608  }
609
610  get s(): String {
611    return this._s;
612  }
613
614  constructor() {}
615}
616```
617
618- `ObjectLiteralLowering` transforms object literals. In
619```
620class C {
621  n: int
622  s: string
623}
624
625let o: C = {n: 33, s: "ouch"}
626```
627initializer is converted to
628```
629let o: C = ({let gensym$_2: C = new C();
630  gensym$_2.n = 33
631  gensym$_2.s = "ouch"
632  gensym$_2});
633}
634```
635
636- `StringConstructorLowering` removes superfluous calls to string constructors
637```
638s = new string("const string");
639```
640becomes
641```
642s = "const string"
643```
644
645- `StringComparisonLowering` replaces comparison operators for strings with explicit calls to `String.compareTo()`
646```
647s > "test"
648```
649becomes
650```
651s.compareTo("test") > 0
652```
653
654- `PartialExportClassGen` generates declarations for `Partial<C>` where `C` is exported
655```
656export class C {
657  n: int = 0
658  s: string = ""
659}
660```
661leads to generation of
662```
663class C$partial {
664  public n: int | undefined = undefined;
665  public s: string | undefined = undefined;
666}
667```
668
669- `plugins-after-lowering` call compiler plugins that work at the end of the lowering process, if any.
670
671- `ConvertPrimitiveCastMethodCall` replaces all primitive cast method calls with corresponding 'as' expressions.
672NOTE: To be removed after delivering 'primitive types refactoring' patch
673
674Before phase:
675
676```
677let d: double = 3.14
678let i: int = d.toInt()
679```
680
681After phase:
682
683```
684let d: double = 3.14
685let i: int = d as int
686```
687