@ball-lang/compiler 1.68.4 → 1.68.6
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/dist/compiler.d.ts +14 -2
- package/dist/compiler.d.ts.map +1 -1
- package/dist/compiler.js +75 -52
- package/dist/compiler.js.map +1 -1
- package/package.json +1 -1
- package/src/compiler.ts +89 -47
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ball-lang/compiler",
|
|
3
|
-
"version": "1.68.
|
|
3
|
+
"version": "1.68.6",
|
|
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
|
@@ -2702,9 +2702,18 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2702
2702
|
ctors.push(this.buildCtor(fn, mMeta, fieldNames, hasExtends));
|
|
2703
2703
|
} else {
|
|
2704
2704
|
// Named constructors are static factory methods that return a new
|
|
2705
|
-
// instance.
|
|
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).
|
|
2706
2709
|
methods.push(
|
|
2707
|
-
this.buildNamedCtor(
|
|
2710
|
+
this.buildNamedCtor(
|
|
2711
|
+
fn,
|
|
2712
|
+
mMeta,
|
|
2713
|
+
fieldNames,
|
|
2714
|
+
tsName,
|
|
2715
|
+
properties.filter((p) => !p.isStatic),
|
|
2716
|
+
),
|
|
2708
2717
|
);
|
|
2709
2718
|
}
|
|
2710
2719
|
} else if (mMeta["is_getter"] === true) {
|
|
@@ -2851,8 +2860,15 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2851
2860
|
}
|
|
2852
2861
|
}
|
|
2853
2862
|
} else {
|
|
2863
|
+
// ONLY a `this.`-formal writes a constructor parameter into the field of
|
|
2864
|
+
// the same name. A plain parameter that merely happens to share a
|
|
2865
|
+
// field's name is an ordinary local and must never clobber that field's
|
|
2866
|
+
// initializer — `class Foo { int x = 5; Foo(int x) { print(x); } }`
|
|
2867
|
+
// leaves `x` at 5 in Dart (#539/#564, conformance 453). The old
|
|
2868
|
+
// `|| classFields.has(p.name)` disjunct is the byte-identical defect the
|
|
2869
|
+
// Dart engine carried until #563.
|
|
2854
2870
|
for (const p of rawParams) {
|
|
2855
|
-
if (p.isThis
|
|
2871
|
+
if (p.isThis) {
|
|
2856
2872
|
prologueParts.push(`this.${p.name} = ${sanitize(p.name)};`);
|
|
2857
2873
|
}
|
|
2858
2874
|
}
|
|
@@ -2921,54 +2937,63 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2921
2937
|
|
|
2922
2938
|
/**
|
|
2923
2939
|
* Builds a named constructor as a static factory method.
|
|
2924
|
-
*
|
|
2925
|
-
*
|
|
2940
|
+
*
|
|
2941
|
+
* Every named constructor that is not a Dart `factory` CONSTRUCTS a real
|
|
2942
|
+
* instance — whether it carries an initializer list (`Point.origin() : x =
|
|
2943
|
+
* 0`), `this.`-formals (`Box.of(this.value)`), a plain body
|
|
2944
|
+
* (`Bar.named(int x) { print(x); }`), or any combination. The instance comes
|
|
2945
|
+
* 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).
|
|
2950
|
+
*
|
|
2951
|
+
* A `factory` named constructor is the one exception: by definition it
|
|
2952
|
+
* returns some other object, so its body runs as the static method's own
|
|
2953
|
+
* statements and its value is the method's result.
|
|
2926
2954
|
*/
|
|
2927
2955
|
private buildNamedCtor(
|
|
2928
2956
|
fn: FunctionDef,
|
|
2929
2957
|
meta: Struct,
|
|
2930
2958
|
classFields: Set<string>,
|
|
2931
2959
|
className: string,
|
|
2960
|
+
classProperties: Array<{
|
|
2961
|
+
name: string;
|
|
2962
|
+
type: string;
|
|
2963
|
+
rawDartType: string;
|
|
2964
|
+
dartInitializer?: string;
|
|
2965
|
+
}> = [],
|
|
2932
2966
|
) {
|
|
2933
2967
|
const params = extractParams(fn);
|
|
2934
2968
|
const ctorParams = extractCtorParams(meta);
|
|
2935
2969
|
const initializers = Array.isArray(meta["initializers"]) ? meta["initializers"] as any[] : [];
|
|
2936
2970
|
// Build the body: resolve field initializers and create a new instance.
|
|
2937
2971
|
const bodyParts: string[] = [];
|
|
2938
|
-
|
|
2939
|
-
//
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
2944
|
-
let resolvedValue: string;
|
|
2945
|
-
if (params.includes(valueStr)) {
|
|
2946
|
-
resolvedValue = sanitize(valueStr);
|
|
2947
|
-
} else if (/^-?\d+(\.\d+)?$/.test(valueStr)) {
|
|
2948
|
-
// Numeric literal — wrap in BallDouble if it has a decimal point
|
|
2949
|
-
resolvedValue = valueStr.includes(".") ? `new BallDouble(${valueStr})` : valueStr;
|
|
2950
|
-
} else if (valueStr.startsWith("'") || valueStr.startsWith('"')) {
|
|
2951
|
-
resolvedValue = valueStr;
|
|
2952
|
-
} else {
|
|
2953
|
-
// Try to parse indexed access like "coords[0]"
|
|
2954
|
-
const idxMatch = /^(\w+)\[(\d+)\]$/.exec(valueStr);
|
|
2955
|
-
if (idxMatch && params.includes(idxMatch[1])) {
|
|
2956
|
-
resolvedValue = `${sanitize(idxMatch[1])}[${idxMatch[2]}]`;
|
|
2957
|
-
} else {
|
|
2958
|
-
resolvedValue = valueStr;
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
2961
|
-
ctorArgs.push(resolvedValue);
|
|
2962
|
-
}
|
|
2963
|
-
}
|
|
2964
|
-
// Also check for is_this params — they pass directly to the constructor
|
|
2972
|
+
// Does the ctor have any field write of its own? Only WHETHER matters
|
|
2973
|
+
// 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()`.)
|
|
2965
2978
|
const thisParams = ctorParams.filter(p => p.isThis);
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
2971
|
-
|
|
2979
|
+
const hasFieldInitializers = initializers.some(
|
|
2980
|
+
(i: any) => i?.kind === "field" && typeof i.name === "string",
|
|
2981
|
+
);
|
|
2982
|
+
const hasCtorFieldWrites =
|
|
2983
|
+
hasFieldInitializers || (initializers.length === 0 && thisParams.length > 0);
|
|
2984
|
+
// A Dart `factory` returns some other object, so it is the ONE named
|
|
2985
|
+
// constructor that must not synthesize an instance of its own class.
|
|
2986
|
+
const isFactory = meta["is_factory"] === true;
|
|
2987
|
+
// Everything else constructs: an initializer list / `this.`-formal, or a
|
|
2988
|
+
// plain body, or both. A body-only named constructor
|
|
2989
|
+
// (`Bar.named(int x) { print(x); }`) used to fall through to the
|
|
2990
|
+
// run-the-body-as-a-static-method branch below, so it created no instance
|
|
2991
|
+
// at all — `this` inside it was the CLASS and the method returned the
|
|
2992
|
+
// body's value, i.e. `undefined` for a `print` (#564, conformance
|
|
2993
|
+
// 453/454).
|
|
2994
|
+
const constructsInstance =
|
|
2995
|
+
hasCtorFieldWrites || (fn.body !== undefined && !isFactory);
|
|
2996
|
+
if (constructsInstance) {
|
|
2972
2997
|
// Use Object.create to avoid calling the constructor (which might be a factory).
|
|
2973
2998
|
// This directly instantiates with fields set.
|
|
2974
2999
|
const assignments = initializers
|
|
@@ -2978,15 +3003,27 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
2978
3003
|
const thisAssignments = thisParams.map(p => `__inst.${p.name} = ${sanitize(p.name)};`);
|
|
2979
3004
|
const allAssignments = [...assignments, ...thisAssignments];
|
|
2980
3005
|
bodyParts.push(`const __inst = Object.create(${className}.prototype);`);
|
|
3006
|
+
// `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).
|
|
3011
|
+
// Without this a field the named constructor never mentions stayed
|
|
3012
|
+
// `undefined` forever (#564: `Init.viaList`'s `w`, `Baz.bare`'s `v`).
|
|
3013
|
+
for (const p of classProperties) {
|
|
3014
|
+
const def = dartInitializerToTs(p.dartInitializer, p.type, p.rawDartType);
|
|
3015
|
+
if (def !== undefined) bodyParts.push(`__inst.${p.name} = ${def};`);
|
|
3016
|
+
}
|
|
2981
3017
|
for (const a of allAssignments) bodyParts.push(a);
|
|
2982
|
-
// A named constructor may have
|
|
2983
|
-
//
|
|
2984
|
-
// `Countdown.from(this.value) { … }`
|
|
2985
|
-
// body; the body used to be dropped
|
|
2986
|
-
// field the body set stayed unset
|
|
2987
|
-
// — a real `function`, not an
|
|
2988
|
-
// instance as `this`, so its
|
|
2989
|
-
// while the constructor's
|
|
3018
|
+
// A named constructor may have an initializer list / `this.`-params, a
|
|
3019
|
+
// body, or both (`Countdown.pair(int s) : value = s { tail = …; }`,
|
|
3020
|
+
// `Countdown.from(this.value) { … }`, `Bar.named(int x) { print(x); }`).
|
|
3021
|
+
// Dart runs the list first, then the body; the body used to be dropped
|
|
3022
|
+
// entirely by this branch, so every field the body set stayed unset
|
|
3023
|
+
// (conformance 436/438). `.call(__inst)` — a real `function`, not an
|
|
3024
|
+
// arrow — gives the compiled body the instance as `this`, so its
|
|
3025
|
+
// `this.<field>` writes land on `__inst`, while the constructor's
|
|
3026
|
+
// parameters stay in scope by closure.
|
|
2990
3027
|
if (fn.body) {
|
|
2991
3028
|
const captured = this.withMethodContext(
|
|
2992
3029
|
new Set(params),
|
|
@@ -3002,7 +3039,10 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3002
3039
|
}
|
|
3003
3040
|
bodyParts.push(`return __inst;`);
|
|
3004
3041
|
} else if (fn.body) {
|
|
3005
|
-
//
|
|
3042
|
+
// A `factory` named constructor: its body IS the method — whatever it
|
|
3043
|
+
// evaluates to is what the caller gets, and no instance of this class is
|
|
3044
|
+
// synthesized. This is the only body-bearing shape that reaches here
|
|
3045
|
+
// (every non-factory body constructs, above).
|
|
3006
3046
|
const captured = this.withMethodContext(
|
|
3007
3047
|
new Set(params),
|
|
3008
3048
|
classFields,
|
|
@@ -3013,6 +3053,8 @@ function __isUnknownFnError(e: any): boolean {
|
|
|
3013
3053
|
);
|
|
3014
3054
|
bodyParts.push(captured);
|
|
3015
3055
|
} else {
|
|
3056
|
+
// Nothing to run and nothing to assign: let the real constructor build
|
|
3057
|
+
// it (which runs the class's inline field initializers itself).
|
|
3016
3058
|
bodyParts.push(`return new ${className}();`);
|
|
3017
3059
|
}
|
|
3018
3060
|
return {
|