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