gofront 1.9.0 → 1.9.2
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/README.md +1 -1
- package/package.json +3 -3
- package/src/backend/js/expressions.js +329 -58
- package/src/backend/js/index.js +90 -10
- package/src/backend/js/runtime.js +51 -9
- package/src/backend/js/statements.js +193 -84
- package/src/backend/js/type-info.js +123 -8
- package/src/backend/shared/fmt-types.js +30 -1
- package/src/backend/wasm/emit-builtins.js +250 -9
- package/src/backend/wasm/emit-exprs.js +63 -22
- package/src/backend/wasm/emit-shared.js +4 -1
- package/src/backend/wasm/emit-stmts.js +239 -205
- package/src/backend/wasm/emit.js +73 -26
- package/src/backend/wasm/globals.js +1 -0
- package/src/backend/wasm/index.js +8 -0
- package/src/backend/wasm/module-emitter.js +211 -57
- package/src/backend/wasm/monomorph.js +36 -2
- package/src/backend/wasm/natives.js +4 -0
- package/src/backend/wasm/types.js +32 -3
- package/src/backend/wasm/variadic.js +6 -1
- package/src/cli/minifier.js +20 -6
- package/src/compiler.js +6 -1
- package/src/lexer/lexer.js +1 -0
- package/src/link/stdlib.js +1 -0
- package/src/link/type-names.js +72 -16
- package/src/lower/embedding.js +43 -15
- package/src/lower/fmt-formatters.js +38 -14
- package/src/lower/functions.js +223 -7
- package/src/lower/gen-types.js +21 -2
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +114 -6
- package/src/parser/declarations.js +4 -2
- package/src/parser/expressions.js +25 -41
- package/src/parser/statements.js +107 -0
- package/src/parser/types.js +11 -0
- package/src/typechecker/assignability.js +94 -12
- package/src/typechecker/constants.js +124 -2
- package/src/typechecker/expressions.js +171 -17
- package/src/typechecker/index.js +158 -29
- package/src/typechecker/operand-text.js +1 -0
- package/src/typechecker/resolve.js +308 -39
- package/src/typechecker/statements.js +171 -21
- package/src/typechecker/types.js +84 -2
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
FLOAT64,
|
|
13
13
|
is64BitInt,
|
|
14
14
|
isAny,
|
|
15
|
+
isBool,
|
|
15
16
|
isComplex,
|
|
16
17
|
isComplex64,
|
|
17
18
|
isComplexOrNumeric,
|
|
@@ -27,9 +28,11 @@ import {
|
|
|
27
28
|
SIZED_INT_NAMES,
|
|
28
29
|
STRING,
|
|
29
30
|
typeStr,
|
|
31
|
+
UNTYPED_BOOL,
|
|
30
32
|
UNTYPED_COMPLEX,
|
|
31
33
|
UNTYPED_FLOAT,
|
|
32
34
|
UNTYPED_INT,
|
|
35
|
+
UNTYPED_RUNE,
|
|
33
36
|
} from "./types.js";
|
|
34
37
|
|
|
35
38
|
// Unnamed type literal kinds for Go's "identical underlying types" assignability rule.
|
|
@@ -63,6 +66,23 @@ const UNTYPED_COMPAT = {
|
|
|
63
66
|
complex128: new Set(["complex128", "complex64"]),
|
|
64
67
|
};
|
|
65
68
|
|
|
69
|
+
// `&&` and `||` of two untyped bools stay untyped; a typed operand (including
|
|
70
|
+
// a defined bool type) gives its type, as in Go.
|
|
71
|
+
// Two typed operands must have the same type, and the result has it.
|
|
72
|
+
function logicalResultType(lt, rt) {
|
|
73
|
+
if (lt?.kind === "untyped" && rt?.kind === "untyped") return UNTYPED_BOOL;
|
|
74
|
+
if (rt?.kind === "untyped" && isBool(lt)) return lt;
|
|
75
|
+
if (lt?.kind === "untyped" && isBool(rt)) return rt;
|
|
76
|
+
if (isBool(lt) && isBool(rt) && typeStr(lt) === typeStr(rt)) return lt;
|
|
77
|
+
return BOOL;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Two untyped numeric operands combine to the later of int, rune, float.
|
|
81
|
+
function untypedIntegerKind(isFloat, lt, rt) {
|
|
82
|
+
if (isFloat) return UNTYPED_FLOAT;
|
|
83
|
+
return lt.rune || rt.rune ? UNTYPED_RUNE : UNTYPED_INT;
|
|
84
|
+
}
|
|
85
|
+
|
|
66
86
|
/** @type {ThisType<TypeChecker>} */
|
|
67
87
|
export const assignabilityMethods = {
|
|
68
88
|
// ── Binary result types ───────────────────────────────────────────
|
|
@@ -71,12 +91,26 @@ export const assignabilityMethods = {
|
|
|
71
91
|
if (lt?.kind === "typeParam" || rt?.kind === "typeParam")
|
|
72
92
|
return this._binaryResultTypeTypeParam(op, lt, rt);
|
|
73
93
|
if (CMP_OPS.has(op)) return this._binaryResultTypeCmp(op, lt, rt, node);
|
|
74
|
-
if (LOG_OPS.has(op))
|
|
94
|
+
if (LOG_OPS.has(op)) {
|
|
95
|
+
this._checkLogicalOperands(lt, rt, node);
|
|
96
|
+
return logicalResultType(lt, rt);
|
|
97
|
+
}
|
|
75
98
|
return this._binaryResultTypeNonComplex(op, lt, rt, node);
|
|
76
99
|
},
|
|
77
100
|
|
|
101
|
+
// `B && bool` mixes two distinct bool types, which Go rejects.
|
|
102
|
+
_checkLogicalOperands(lt, rt, node) {
|
|
103
|
+
if (!node || lt?.kind === "untyped" || rt?.kind === "untyped") return;
|
|
104
|
+
if (!isBool(lt) || !isBool(rt) || typeStr(lt) === typeStr(rt)) return;
|
|
105
|
+
this.err(
|
|
106
|
+
`invalid operation: ${exprText(node)} (mismatched types ${typeStr(lt)} and ${typeStr(rt)})`,
|
|
107
|
+
node,
|
|
108
|
+
);
|
|
109
|
+
},
|
|
110
|
+
|
|
78
111
|
_binaryResultTypeTypeParam(op, lt, rt) {
|
|
79
|
-
if (CMP_OPS.has(op)
|
|
112
|
+
if (CMP_OPS.has(op)) return UNTYPED_BOOL;
|
|
113
|
+
if (LOG_OPS.has(op)) return BOOL;
|
|
80
114
|
return lt?.kind === "typeParam" ? lt : rt;
|
|
81
115
|
},
|
|
82
116
|
|
|
@@ -137,7 +171,8 @@ export const assignabilityMethods = {
|
|
|
137
171
|
if (this._checkComplexCmpOp(op, lt, rt, node)) return ANY;
|
|
138
172
|
this._checkIntegerCmpKinds(op, lt, rt, node);
|
|
139
173
|
this._checkEqualityComparable(op, lt, rt, node);
|
|
140
|
-
|
|
174
|
+
// A comparison is an untyped bool: it also fits a defined bool type.
|
|
175
|
+
return UNTYPED_BOOL;
|
|
141
176
|
},
|
|
142
177
|
|
|
143
178
|
_binaryResultTypeComplex(op, lt, rt, node) {
|
|
@@ -163,7 +198,7 @@ export const assignabilityMethods = {
|
|
|
163
198
|
const rFloat = rName === "float32" || rName === "float64";
|
|
164
199
|
const isFloat = lFloat || rFloat;
|
|
165
200
|
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
166
|
-
return isFloat
|
|
201
|
+
return untypedIntegerKind(isFloat, lt, rt);
|
|
167
202
|
if (lt.kind === "untyped")
|
|
168
203
|
return isFloat && rt.name !== "float64" && rt.name !== "float32"
|
|
169
204
|
? FLOAT64
|
|
@@ -229,6 +264,19 @@ export const assignabilityMethods = {
|
|
|
229
264
|
}
|
|
230
265
|
},
|
|
231
266
|
|
|
267
|
+
// An untyped numeric constant that takes type float32 is rounded to
|
|
268
|
+
// float32 in JS output (`var c float32 = 0.01`, `v == 0.01`).
|
|
269
|
+
_tagFloat32Const(target, source, node) {
|
|
270
|
+
if (!node || typeof node !== "object" || !node._const) return;
|
|
271
|
+
if (!isUntyped(source) || this._float32Base(target) !== "float32") return;
|
|
272
|
+
node._f32Const = true;
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
_float32Base(t) {
|
|
276
|
+
const r = t?.kind === "named" ? t.underlying : t;
|
|
277
|
+
return r?.kind === "basic" || r?.kind === "untyped" ? r.name : null;
|
|
278
|
+
},
|
|
279
|
+
|
|
232
280
|
// `var m uint64 = (1 << k) - 1`: a non-constant shift whose left operand is
|
|
233
281
|
// an untyped constant takes the type its context gives it, so codegen
|
|
234
282
|
// computes it in that type (BigInt for 64-bit integers).
|
|
@@ -265,6 +313,17 @@ export const assignabilityMethods = {
|
|
|
265
313
|
!isComplex(source)
|
|
266
314
|
)
|
|
267
315
|
valueNode._asComplex = true;
|
|
316
|
+
this._tagComplex64Const(target, source, valueNode);
|
|
317
|
+
},
|
|
318
|
+
|
|
319
|
+
// An untyped constant that takes type complex64 is rounded to float32 in
|
|
320
|
+
// JS output: a real constant through `_f32Const`, a complex constant
|
|
321
|
+
// (`1 + 0.01i`) through `_c64Const`.
|
|
322
|
+
_tagComplex64Const(target, source, node) {
|
|
323
|
+
if (!node || typeof node !== "object") return;
|
|
324
|
+
if (!isComplex64(target) || !isUntyped(source)) return;
|
|
325
|
+
if (isComplex(source)) node._c64Const = true;
|
|
326
|
+
else if (node._const) node._f32Const = true;
|
|
268
327
|
},
|
|
269
328
|
|
|
270
329
|
// `operand` is the expression whose value flows in, when `node` (the
|
|
@@ -275,6 +334,7 @@ export const assignabilityMethods = {
|
|
|
275
334
|
target = this.resolveType(target);
|
|
276
335
|
source = this.resolveType(source);
|
|
277
336
|
this._tag64BitNode(target, node);
|
|
337
|
+
this._tagFloat32Const(target, source, operand ?? node);
|
|
278
338
|
this._retypeUntypedShift(target, source, operand ?? node);
|
|
279
339
|
this._tagComplexConst(target, source, operand ?? node);
|
|
280
340
|
this._markIfaceBox(target, source, node);
|
|
@@ -442,11 +502,13 @@ export const assignabilityMethods = {
|
|
|
442
502
|
},
|
|
443
503
|
|
|
444
504
|
// An interface-typed operand needs an explicit type assertion before it can
|
|
445
|
-
// flow into a concrete (non-interface) type
|
|
505
|
+
// flow into a concrete (non-interface) type, or into an interface with
|
|
506
|
+
// methods (`var e error = b` with `b any`).
|
|
446
507
|
_checkIfaceToConcrete(target, source, node, ctx, operand) {
|
|
447
508
|
if (!source?._declared || !operand || typeof operand !== "object")
|
|
448
509
|
return false;
|
|
449
|
-
if (!this._isConcreteTarget(target)
|
|
510
|
+
if (!this._isConcreteTarget(target) && !this._isMethodIfaceTarget(target))
|
|
511
|
+
return false;
|
|
450
512
|
this.err(
|
|
451
513
|
`cannot use ${exprText(operand)} (${operandKind(operand, typeStr(source))}) as ${typeStr(target)} value in ${ctx}: need type assertion`,
|
|
452
514
|
node,
|
|
@@ -454,6 +516,31 @@ export const assignabilityMethods = {
|
|
|
454
516
|
return true;
|
|
455
517
|
},
|
|
456
518
|
|
|
519
|
+
// `int(x)`, `(*T)(x)` and `I(x)` (I with methods) with a source-written
|
|
520
|
+
// `any` operand need an assertion.
|
|
521
|
+
_checkIfaceConversion(srcType, targetType, expr, operand) {
|
|
522
|
+
if (!srcType?._declared || !operand) return false;
|
|
523
|
+
if (
|
|
524
|
+
!this._isConcreteTarget(targetType) &&
|
|
525
|
+
!this._isMethodIfaceTarget(targetType)
|
|
526
|
+
)
|
|
527
|
+
return false;
|
|
528
|
+
this.err(
|
|
529
|
+
`cannot convert ${exprText(operand)} (${operandKind(operand, typeStr(srcType))}) to type ${typeStr(targetType)}: need type assertion`,
|
|
530
|
+
expr,
|
|
531
|
+
);
|
|
532
|
+
return true;
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
// An interface type (named or literal) with at least one method.
|
|
536
|
+
_isMethodIfaceTarget(target) {
|
|
537
|
+
if (!target || isAny(target)) return false;
|
|
538
|
+
const base = this.resolveType(
|
|
539
|
+
target.kind === "named" ? target.underlying : target,
|
|
540
|
+
);
|
|
541
|
+
return base?.kind === "interface" && base.methods?.size > 0;
|
|
542
|
+
},
|
|
543
|
+
|
|
457
544
|
_isConcreteTarget(target) {
|
|
458
545
|
if (!target || target.kind === "typeParam" || isAny(target)) return false;
|
|
459
546
|
if (isNil(target) || isUntyped(target)) return false;
|
|
@@ -584,12 +671,7 @@ export const assignabilityMethods = {
|
|
|
584
671
|
_isNumericCoercible(target, source) {
|
|
585
672
|
if (target.kind !== "basic" || source.kind !== "basic") return false;
|
|
586
673
|
const pair = `${target.name}:${source.name}`;
|
|
587
|
-
return
|
|
588
|
-
pair === "float64:int" ||
|
|
589
|
-
// rune was historically int in GoFront; keep rune/int interchangeable.
|
|
590
|
-
pair === "int:int32" ||
|
|
591
|
-
pair === "int32:int"
|
|
592
|
-
);
|
|
674
|
+
return pair === "float64:int";
|
|
593
675
|
},
|
|
594
676
|
|
|
595
677
|
// The lenient numeric pairs: accepted, except that a generic call result
|
|
@@ -15,7 +15,28 @@ import {
|
|
|
15
15
|
INT_RANGES,
|
|
16
16
|
intConst,
|
|
17
17
|
} from "./const-fold.js";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
compositeLitMaxIndex,
|
|
20
|
+
defaultType,
|
|
21
|
+
isAny,
|
|
22
|
+
isComplex,
|
|
23
|
+
isComplex64,
|
|
24
|
+
isUntyped,
|
|
25
|
+
typeStr,
|
|
26
|
+
VOID,
|
|
27
|
+
} from "./types.js";
|
|
28
|
+
|
|
29
|
+
// True when evaluating `node` runs a call or a channel receive (conversions
|
|
30
|
+
// excluded): such an operand makes `len` of an array a non-constant.
|
|
31
|
+
function hasCallOrReceive(node) {
|
|
32
|
+
if (!node || typeof node !== "object") return false;
|
|
33
|
+
if (Array.isArray(node)) return node.some(hasCallOrReceive);
|
|
34
|
+
if (node.kind === "CallExpr" && !node._isTypeConversion) return true;
|
|
35
|
+
if (node.kind === "UnaryExpr" && node.op === "<-") return true;
|
|
36
|
+
return Object.keys(node).some(
|
|
37
|
+
(key) => key.charCodeAt(0) !== 95 && hasCallOrReceive(node[key]),
|
|
38
|
+
);
|
|
39
|
+
}
|
|
19
40
|
|
|
20
41
|
// Basic type names usable as conversion functions (`int32(x)`).
|
|
21
42
|
const BASIC_CONVERSIONS = new Set([
|
|
@@ -38,6 +59,17 @@ const CONST_KINDS = new Set([
|
|
|
38
59
|
"SelectorExpr",
|
|
39
60
|
]);
|
|
40
61
|
|
|
62
|
+
// Describes a constant that is not integer-valued (`float64 constant`), or
|
|
63
|
+
// null for an integer, an untyped float with an integer value, or a rune.
|
|
64
|
+
function nonIntegerConstKind(v) {
|
|
65
|
+
if (v.k === "INT") return null;
|
|
66
|
+
if (v.k === "FLOAT") {
|
|
67
|
+
if (v.t) return `${v.t} constant`;
|
|
68
|
+
return constIntValue(v) === null ? "untyped float constant" : null;
|
|
69
|
+
}
|
|
70
|
+
return `${v.k.toLowerCase()} constant`;
|
|
71
|
+
}
|
|
72
|
+
|
|
41
73
|
const NEGATIVE_SHIFT_ERROR = "invalid shift count: negative shift count";
|
|
42
74
|
|
|
43
75
|
/** @type {ThisType<import('./index.js').TypeChecker>} */
|
|
@@ -49,6 +81,23 @@ export const constantCheckMethods = {
|
|
|
49
81
|
return raw && !raw.overflow && !raw.error ? raw : null;
|
|
50
82
|
},
|
|
51
83
|
|
|
84
|
+
// What makes `expr` a constant that cannot be an array length, index or
|
|
85
|
+
// size (`untyped bool constant`, `untyped nil`), or null when it is an
|
|
86
|
+
// integer constant or not a constant at all.
|
|
87
|
+
_nonIntegerConst(expr, scope = null) {
|
|
88
|
+
let e = expr;
|
|
89
|
+
while (e?.kind === "ParenExpr") e = e.expr;
|
|
90
|
+
if (e?.kind === "ImagLit") return "untyped complex constant";
|
|
91
|
+
if (e?.kind === "BasicLit" && e.litKind === "NIL") return "untyped nil";
|
|
92
|
+
let v = null;
|
|
93
|
+
try {
|
|
94
|
+
v = this._constValue(expr, scope);
|
|
95
|
+
} catch {
|
|
96
|
+
// not a constant the evaluator understands
|
|
97
|
+
}
|
|
98
|
+
return v ? nonIntegerConstKind(v) : null;
|
|
99
|
+
},
|
|
100
|
+
|
|
52
101
|
// Integer value of an integer (or untyped integral float) constant as a
|
|
53
102
|
// Number, or null.
|
|
54
103
|
_constIntValue(expr, scope = null) {
|
|
@@ -58,6 +107,23 @@ export const constantCheckMethods = {
|
|
|
58
107
|
return i === null ? null : Number(i);
|
|
59
108
|
},
|
|
60
109
|
|
|
110
|
+
// A named untyped complex constant has no folded value (`_const`): its
|
|
111
|
+
// initializer goes on the identifier as `_complexInit`, so the backends
|
|
112
|
+
// can fold it where it takes type complex64.
|
|
113
|
+
// A named `complex64` constant is tagged `_c64Const` as well, so a use
|
|
114
|
+
// takes float32 components as in Go.
|
|
115
|
+
_noteComplexConstInit(expr, scope) {
|
|
116
|
+
if (expr.kind !== "Ident") return;
|
|
117
|
+
const typed64 = !isUntyped(expr._type) && isComplex64(expr._type);
|
|
118
|
+
if (!typed64 && !(isUntyped(expr._type) && isComplex(expr._type))) return;
|
|
119
|
+
const owner = scope.lookupScope(expr.name);
|
|
120
|
+
if (!owner?._consts.has(expr.name)) return;
|
|
121
|
+
const init = owner.lookupConstExpr(expr.name);
|
|
122
|
+
if (!init) return;
|
|
123
|
+
expr._complexInit = init;
|
|
124
|
+
if (typed64) expr._c64Const = true;
|
|
125
|
+
},
|
|
126
|
+
|
|
61
127
|
// Raw evaluation result, memoized on checked nodes: a value, a value with
|
|
62
128
|
// `overflow`, `{ error }`, or null.
|
|
63
129
|
_constRaw(expr, scope) {
|
|
@@ -83,6 +149,7 @@ export const constantCheckMethods = {
|
|
|
83
149
|
// overflow / division by zero once per node.
|
|
84
150
|
_noteConst(expr, scope) {
|
|
85
151
|
if (!CONST_KINDS.has(expr.kind)) return;
|
|
152
|
+
this._noteComplexConstInit(expr, scope ?? this.globals);
|
|
86
153
|
const raw = this._constRaw(expr, scope);
|
|
87
154
|
expr._const = raw && !raw.overflow && !raw.error ? raw : null;
|
|
88
155
|
if (!raw || expr._constReported) return;
|
|
@@ -128,7 +195,13 @@ export const constantCheckMethods = {
|
|
|
128
195
|
|
|
129
196
|
_evalConstIdent(expr, scope) {
|
|
130
197
|
const owner = scope.lookupScope(expr.name);
|
|
131
|
-
|
|
198
|
+
// A constant declared after the type that uses it (`type B [k]int`) has
|
|
199
|
+
// no symbol yet, but its initializer is registered.
|
|
200
|
+
if (!owner) {
|
|
201
|
+
const early = scope.lookupConstExpr(expr.name);
|
|
202
|
+
return early ? this._constValue(early, scope) : null;
|
|
203
|
+
}
|
|
204
|
+
if (!owner._consts.has(expr.name)) return null;
|
|
132
205
|
const init = owner.lookupConstExpr(expr.name);
|
|
133
206
|
const v = init
|
|
134
207
|
? this._constValue(init, owner)
|
|
@@ -159,6 +232,8 @@ export const constantCheckMethods = {
|
|
|
159
232
|
const fn = expr.func;
|
|
160
233
|
if (fn?.kind !== "Ident" || scope.lookup(fn.name)?.kind !== "builtin")
|
|
161
234
|
return null;
|
|
235
|
+
const arrayLen = this._constArrayLen(fn.name, expr.args, scope);
|
|
236
|
+
if (arrayLen !== null) return intConst(BigInt(arrayLen));
|
|
162
237
|
const vals = (expr.args ?? []).map((a) => this._constValue(a, scope));
|
|
163
238
|
if (vals.length === 0 || vals.some((v) => !v)) return null;
|
|
164
239
|
if (fn.name === "min" || fn.name === "max")
|
|
@@ -168,6 +243,53 @@ export const constantCheckMethods = {
|
|
|
168
243
|
return isLen ? intConst(BigInt(vals[0].v.length)) : null;
|
|
169
244
|
},
|
|
170
245
|
|
|
246
|
+
// A package variable named in an array length may be declared after the
|
|
247
|
+
// type (or variable) that uses it, so it is not defined yet: its declared
|
|
248
|
+
// type, or the type of its composite literal, is resolved from the spec.
|
|
249
|
+
_earlyPackageVarType(arg, scope) {
|
|
250
|
+
let e = arg;
|
|
251
|
+
while (e?.kind === "ParenExpr") e = e.expr;
|
|
252
|
+
if (e?.kind !== "Ident") return null;
|
|
253
|
+
// An untyped package variable is `any` until its initializer is typed.
|
|
254
|
+
const known = scope.lookup(e.name);
|
|
255
|
+
if (known && !isAny(known)) return null;
|
|
256
|
+
const entry = this._pkgVarSpecs?.get(e.name);
|
|
257
|
+
if (!entry || entry.busy) return null;
|
|
258
|
+
const { spec, index } = entry;
|
|
259
|
+
const lit = spec.value?.[index];
|
|
260
|
+
const typeNode =
|
|
261
|
+
spec.type ?? (lit?.kind === "CompositeLit" ? lit.typeExpr : null);
|
|
262
|
+
if (!typeNode) return null;
|
|
263
|
+
entry.busy = true;
|
|
264
|
+
try {
|
|
265
|
+
const t = this.resolveTypeNode(typeNode, this.globals);
|
|
266
|
+
// `[...]T{...}`: the length is the literal's highest index + 1.
|
|
267
|
+
return t?.kind === "array" && t.size === null && lit?.elems
|
|
268
|
+
? { ...t, size: compositeLitMaxIndex(lit.elems) + 1 }
|
|
269
|
+
: t;
|
|
270
|
+
} finally {
|
|
271
|
+
entry.busy = false;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
// `len(a)` and `cap(a)` of an array (or pointer to an array) are constants
|
|
276
|
+
// unless `a` contains a function call or channel receive; `a` itself is not
|
|
277
|
+
// evaluated. Returns the length, or null.
|
|
278
|
+
_constArrayLen(name, args, scope) {
|
|
279
|
+
if ((name !== "len" && name !== "cap") || args?.length !== 1) return null;
|
|
280
|
+
const arg = args[0];
|
|
281
|
+
if (hasCallOrReceive(arg)) return null;
|
|
282
|
+
let t = this._earlyPackageVarType(arg, scope);
|
|
283
|
+
if (!t) {
|
|
284
|
+
if (arg._type === undefined) this.checkExpr(arg, scope);
|
|
285
|
+
t = arg._type;
|
|
286
|
+
}
|
|
287
|
+
t = this.resolveType(t);
|
|
288
|
+
if (t?.kind === "pointer") t = this.resolveType(t.base);
|
|
289
|
+
if (t?.kind === "named") t = this.resolveType(t.underlying);
|
|
290
|
+
return t?.kind === "array" && Number.isInteger(t.size) ? t.size : null;
|
|
291
|
+
},
|
|
292
|
+
|
|
171
293
|
// Target type of a conversion call (`MyInt(x)`, `int32(x)`), or null.
|
|
172
294
|
_constConversionTarget(expr, scope) {
|
|
173
295
|
if (expr.args?.length !== 1) return null;
|