• 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): void
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- `TupleLowering` inserts explicit conversions needed to support tuples.
470(The reason is that tuples are represented as arrays of the LUB type of their elements,
471and sometimes implicit conversions do not work)
472```
473    let t: [string, short] = ["oh", 11];
474    t[1] = 12;
475```
476The last assignment changes to
477```
478    t[1] = 12 as Short as String|Short;
479```
480
481- `UnionLowering` does two things:
482  1. processes field access to union
483  1. cast to primitive type
484
485If all union constituents has some common field then we need to generate special class for it for correct
486processing in runtime, e.g. for this code:
487
488```
489class A { n: int = 42 }
490class B { n: int = 43 }
491class C { n: int = 44 }
492function foo(x: A|B|C) {
493  x.n
494}
495```
496We will generate special class union_field with field named n which type is int. In the bytecode will look
497like this:
498
499```
500.union_field i32 n
501
502.function void ETSGLOBAL.foo(std.core.Object a0) <static, access.function=public> {
503  ets.ldobj.name a0, n
504  return.void
505}
506```
507Runtime will look at `union_field` inside handle of `ets.ldobj.name` instruction and will get the value of this filed
508from it.
509
510Note: ets.ldobj.name instruction generation happens at `CodeGen` stage when `ETSUnionType` is set to
511`ir::MemberExpression`.
512
513Union cast to primitive will be updated soon and will look like this:
514```
515function foo(x: string|int) {
516  console.log(x as int)
517}
518```
519will be transformed to:
520```
521function foo(x: string|int) {
522  console.log((x as Numeric).intValue())
523}
524```
525It 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.
526
527- `ExpandBracketsPhase`: support for floats used as dimensions for array constructors
528```
529  let v = 4.0
530  let a = new int[v]
531```
532changes to
533```
534    let v = 4;
535    let a = ({let gensym$_2: double = v;
536    if (!isSafeInteger(gensym$_2)) {
537      throw new TypeError("Fractional part of index expression should be zero.");
538    }
539    new int[gensym$_2 as int]});
540```
541
542- `LocalClassConstructionPhase`: support for local classes and captured variables in them.
543```
544  function f() {
545    let v = 4;
546
547    class C {
548      m(): int {
549        return v;
550      }
551    }
552
553    let c = new C();
554  }
555
556```
557is converted to (roughly)
558```
559  function f() {
560    let v = 4;
561
562    class C {
563      m(): int {
564        return this.field#0;
565      }
566
567      constructor(v: int) {
568        this.field#0 = v;
569      }
570
571      field#0: int;
572    }
573
574    let c = new C(v);
575   }
576```
577
578- `InterfaceObjectLiteralLowering` creates synthetic classes for object literals of interface type:
579```
580interface I {
581  n: int
582  s: string
583}
584
585let o: I = {n: 33, s: "ouch"}
586```
587where `I` by this stage (after InterfacePropwertyDeclarationPhase) looks like
588```
589interface I {
590  public abstract n(n: int)
591
592  public abstract n(): int
593  public abstract n(n: int)
594  public abstract s(s: string)
595
596  public abstract s(): string
597  public abstract s(s: string)
598
599}
600```
601the initialization is converted to (approximately)
602```
603let o: I = {n: 33, s: "ouch"} as $anonymous_class$I
604
605class $anonymous_class$I implements I {
606  private _n: int;
607
608  set n(n: int): void {
609    this._n = n;
610  }
611
612  get n(): int {
613    return this._n;
614  }
615
616  private _s: String;
617
618  set s(s: String): void {
619    this._s = s;
620  }
621
622  get s(): String {
623    return this._s;
624  }
625
626  constructor() {}
627}
628```
629
630- `ObjectLiteralLowering` transforms object literals. In
631```
632class C {
633  n: int
634  s: string
635}
636
637let o: C = {n: 33, s: "ouch"}
638```
639initializer is converted to
640```
641let o: C = ({let gensym$_2: C = new C();
642  gensym$_2.n = 33
643  gensym$_2.s = "ouch"
644  gensym$_2});
645}
646```
647
648- `StringConstructorLowering` removes superfluous calls to string constructors
649```
650s = new string("const string");
651```
652becomes
653```
654s = "const string"
655```
656
657- `StringComparisonLowering` replaces comparison operators for strings with explicit calls to `String.compareTo()`
658```
659s > "test"
660```
661becomes
662```
663s.compareTo("test") > 0
664```
665
666- `PartialExportClassGen` generates declarations for `Partial<C>` where `C` is exported
667```
668export class C {
669  n: int = 0
670  s: string = ""
671}
672```
673leads to generation of
674```
675class C$partial {
676  public n: int | undefined = undefined;
677  public s: string | undefined = undefined;
678}
679```
680
681- `plugins-after-lowering` call compiler plugins that work at the end of the lowering process, if any.
682