@ball-lang/compiler 1.70.1 → 1.71.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ball-lang/compiler",
3
- "version": "1.70.1",
3
+ "version": "1.71.1",
4
4
  "description": "Ball → TypeScript compiler. Consumes a Ball protobuf Program and emits idiomatic TypeScript via ts-morph. The canonical TS compiler for Ball — lives in TS land so TS syntax knowledge doesn't leak into other languages.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/compiler.ts CHANGED
@@ -2609,7 +2609,22 @@ function __isUnknownFnError(e: any): boolean {
2609
2609
  // Include inherited fields from the superclass chain so that
2610
2610
  // references to inherited fields inside methods emit `this.name`
2611
2611
  // instead of bare `name` (which would be undefined in JS).
2612
+ //
2613
+ // The walk also collects each ancestor level's own field SPECS (type +
2614
+ // inline initializer), not just the names: a named constructor builds its
2615
+ // instance with `Object.create`, which runs no constructor at all — so it
2616
+ // never runs the real `super()` that would otherwise execute the
2617
+ // superclass's inline field initializers as its own prologue. The seeding
2618
+ // loop in `buildNamedCtor` therefore has to reproduce the whole chain in
2619
+ // one flat pass, the way the Dart reference engine's constructor frame
2620
+ // recurses through `_invokeSuperConstructor` (#581).
2612
2621
  const superclass = typeof meta["superclass"] === "string" ? meta["superclass"] : undefined;
2622
+ const inheritedLevels: Array<Array<{
2623
+ name: string;
2624
+ type: string;
2625
+ rawDartType: string;
2626
+ dartInitializer?: string;
2627
+ }>> = [];
2613
2628
  if (superclass) {
2614
2629
  const entryMod = this.program.modules.find(m => m.name === this.program.entryModule);
2615
2630
  let sup: string | undefined = superclass;
@@ -2618,15 +2633,43 @@ function __isUnknownFnError(e: any): boolean {
2618
2633
  if (!supTd) break;
2619
2634
  const supMeta: Struct = supTd.metadata ?? {};
2620
2635
  const supFields = Array.isArray(supMeta["fields"]) ? supMeta["fields"] as any[] : [];
2636
+ const level: Array<{
2637
+ name: string;
2638
+ type: string;
2639
+ rawDartType: string;
2640
+ dartInitializer?: string;
2641
+ }> = [];
2621
2642
  for (const sf of supFields) {
2622
- if (sf?.name) fieldNames.add(sf.name);
2643
+ if (!sf?.name) continue;
2644
+ fieldNames.add(sf.name);
2645
+ // Statics live as module-level consts, never on the instance.
2646
+ if (sf.is_static === true) continue;
2647
+ level.push({
2648
+ name: sf.name,
2649
+ type: typeof sf.type === "string" ? this.dartTypeToTs(sf.type) : "any",
2650
+ rawDartType: typeof sf.type === "string" ? sf.type : "",
2651
+ dartInitializer: typeof sf.initializer === "string" ? sf.initializer : undefined,
2652
+ });
2623
2653
  }
2624
2654
  if (supTd.descriptor?.field) {
2625
2655
  for (const f of supTd.descriptor.field) fieldNames.add(f.name);
2626
2656
  }
2657
+ // Descriptor fallback mirrors the own-class path above: only when the
2658
+ // ancestor carries no richer `metadata.fields` at all. A bare
2659
+ // descriptor field has no initializer, so it seeds nothing — it is
2660
+ // collected purely so the level list matches the name set.
2661
+ if (level.length === 0 && supTd.descriptor?.field) {
2662
+ for (const f of supTd.descriptor.field) {
2663
+ level.push({ name: f.name, type: "any", rawDartType: "" });
2664
+ }
2665
+ }
2666
+ inheritedLevels.push(level);
2627
2667
  sup = typeof supMeta["superclass"] === "string" ? supMeta["superclass"] : undefined;
2628
2668
  }
2629
2669
  }
2670
+ // Root ancestor first, matching Dart's superclass-initializes-before-
2671
+ // subclass ordering, so a subclass field that shadows an ancestor's wins.
2672
+ const inheritedProperties = inheritedLevels.slice().reverse().flat();
2630
2673
 
2631
2674
  // Method-name set for `this.foo()` routing inside bodies.
2632
2675
  // Exclude static fields — they're emitted as module-level consts
@@ -2702,17 +2745,18 @@ function __isUnknownFnError(e: any): boolean {
2702
2745
  ctors.push(this.buildCtor(fn, mMeta, fieldNames, hasExtends));
2703
2746
  } else {
2704
2747
  // Named constructors are static factory methods that return a new
2705
- // instance. The class's own (non-static) field declarations go with
2706
- // them: the instance is built with `Object.create`, which runs no
2707
- // field initializer, so the builder has to seed each default itself
2708
- // (#564).
2748
+ // instance. Every (non-static) field declaration reachable from this
2749
+ // class goes with them inherited ones first, then the class's own:
2750
+ // the instance is built with `Object.create`, which runs no field
2751
+ // initializer AND no real `super()`, so the builder has to seed each
2752
+ // default along the whole superclass chain itself (#564, #581).
2709
2753
  methods.push(
2710
2754
  this.buildNamedCtor(
2711
2755
  fn,
2712
2756
  mMeta,
2713
2757
  fieldNames,
2714
2758
  tsName,
2715
- properties.filter((p) => !p.isStatic),
2759
+ [...inheritedProperties, ...properties.filter((p) => !p.isStatic)],
2716
2760
  ),
2717
2761
  );
2718
2762
  }
@@ -2943,10 +2987,13 @@ function __isUnknownFnError(e: any): boolean {
2943
2987
  * 0`), `this.`-formals (`Box.of(this.value)`), a plain body
2944
2988
  * (`Bar.named(int x) { print(x); }`), or any combination. The instance comes
2945
2989
  * from `Object.create(C.prototype)` so no user constructor runs, which means
2946
- * the class's inline field initializers do not run either: every declared
2947
- * field is seeded with its own default FIRST (Dart's ordering inline field
2948
- * initializers, then the initializer list, then the body), and only then do
2949
- * the constructor-specific writes land on top (#564).
2990
+ * neither the class's inline field initializers nor the real `super()` that
2991
+ * would run the SUPERCLASS's inline initializers execute either: every
2992
+ * declared field reachable from this class inherited ones first, then the
2993
+ * class's own is seeded with its default FIRST (Dart's ordering — inline
2994
+ * field initializers, then the initializer list, then the body), and only
2995
+ * then do the constructor-specific writes land on top (#564, #581). The
2996
+ * caller (`emitClass`) supplies that whole-chain `classProperties` list.
2950
2997
  *
2951
2998
  * A `factory` named constructor is the one exception: by definition it
2952
2999
  * returns some other object, so its body runs as the static method's own
@@ -2971,16 +3018,16 @@ function __isUnknownFnError(e: any): boolean {
2971
3018
  const bodyParts: string[] = [];
2972
3019
  // Does the ctor have any field write of its own? Only WHETHER matters
2973
3020
  // here; each value is resolved where the assignment is emitted, by
2974
- // `resolveInitializerValue`. (A `this.`-formal counts only when there is
2975
- // no initializer list at all, preserving the historical branch selection
2976
- // for the `Foo.named(this.x) : super(...)` shape, which still falls
2977
- // through to `return new Foo()`.)
3021
+ // `resolveInitializerValue`. A `this.`-formal ALWAYS counts, whatever else
3022
+ // the initializer list holds: this used to also require
3023
+ // `initializers.length === 0`, so `Foo.named(this.x) : super(1)` a
3024
+ // non-empty list with no `field` entry — took neither construct branch and
3025
+ // fell through to `return new Foo()`, silently dropping the formal (#582).
2978
3026
  const thisParams = ctorParams.filter(p => p.isThis);
2979
3027
  const hasFieldInitializers = initializers.some(
2980
3028
  (i: any) => i?.kind === "field" && typeof i.name === "string",
2981
3029
  );
2982
- const hasCtorFieldWrites =
2983
- hasFieldInitializers || (initializers.length === 0 && thisParams.length > 0);
3030
+ const hasCtorFieldWrites = hasFieldInitializers || thisParams.length > 0;
2984
3031
  // A Dart `factory` returns some other object, so it is the ONE named
2985
3032
  // constructor that must not synthesize an instance of its own class.
2986
3033
  const isFactory = meta["is_factory"] === true;
@@ -3004,12 +3051,15 @@ function __isUnknownFnError(e: any): boolean {
3004
3051
  const allAssignments = [...assignments, ...thisAssignments];
3005
3052
  bodyParts.push(`const __inst = Object.create(${className}.prototype);`);
3006
3053
  // `Object.create` deliberately runs no constructor — which also means it
3007
- // runs none of the class's inline field initializers. Seed every
3008
- // declared field with the SAME default the class declaration emits,
3009
- // before any constructor-specific write, exactly as Dart orders them
3010
- // (inline initializers, then the initializer list, then the body).
3054
+ // runs none of the class's inline field initializers, and none of the
3055
+ // SUPERCLASS's either (a real `super()` would have). `classProperties`
3056
+ // is therefore the whole superclass chain root-first, then this class's
3057
+ // own fields. Seed each with the SAME default its class declaration
3058
+ // emits, before any constructor-specific write, exactly as Dart orders
3059
+ // them (inline initializers, then the initializer list, then the body).
3011
3060
  // Without this a field the named constructor never mentions stayed
3012
- // `undefined` forever (#564: `Init.viaList`'s `w`, `Baz.bare`'s `v`).
3061
+ // `undefined` forever (#564: `Init.viaList`'s `w`, `Baz.bare`'s `v`;
3062
+ // #581: every field declared by an ancestor).
3013
3063
  for (const p of classProperties) {
3014
3064
  const def = dartInitializerToTs(p.dartInitializer, p.type, p.rawDartType);
3015
3065
  if (def !== undefined) bodyParts.push(`__inst.${p.name} = ${def};`);