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