gofront 1.8.0 → 1.9.0
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 +3 -3
- package/package.json +5 -4
- package/src/backend/js/expressions.js +849 -371
- package/src/backend/js/index.js +241 -147
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +512 -2547
- package/src/backend/js/statements.js +506 -103
- package/src/backend/js/stdlib/index.js +45 -93
- package/src/backend/js/templ.js +32 -45
- package/src/backend/js/type-info.js +177 -13
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +63 -171
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +490 -965
- package/src/backend/wasm/emit-builtins.js +643 -256
- package/src/backend/wasm/emit-exprs.js +124 -57
- package/src/backend/wasm/emit-maps.js +17 -367
- package/src/backend/wasm/emit-stmts.js +325 -75
- package/src/backend/wasm/emit.js +104 -13
- package/src/backend/wasm/globals.js +47 -8
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +36 -530
- package/src/backend/wasm/method-expr.js +12 -1
- package/src/backend/wasm/module-emitter.js +92 -717
- package/src/backend/wasm/monomorph.js +103 -6
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +5 -153
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -32
- package/src/backend/wasm/types.js +68 -20
- package/src/backend/wasm/variadic.js +3 -3
- package/src/cli/cli-core.js +5 -1
- package/src/cli/test-runner.js +115 -342
- package/src/compiler.js +108 -130
- package/src/lexer/lexer.js +105 -11
- package/src/lexer/templ-lexer.js +61 -18
- package/src/lexer/tokens.js +1 -0
- package/src/link/stdlib.js +354 -132
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +30 -6
- package/src/lower/bounds.js +153 -0
- package/src/lower/captures.js +92 -33
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +7 -1
- package/src/lower/json-codecs.js +228 -247
- package/src/lower/ownership.js +25 -7
- package/src/lower/reachability.js +69 -51
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +28 -5
- package/src/parser/expressions.js +86 -16
- package/src/parser/statements.js +1 -0
- package/src/parser/templ.js +44 -5
- package/src/parser/types.js +1 -0
- package/src/resolver.js +1 -2
- package/src/typechecker/assignability.js +124 -11
- package/src/typechecker/const-fold.js +70 -27
- package/src/typechecker/constants.js +32 -6
- package/src/typechecker/expressions.js +326 -98
- package/src/typechecker/index.js +208 -33
- package/src/typechecker/init-order.js +136 -40
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +192 -36
- package/src/typechecker/statements.js +122 -11
- package/src/typechecker/stdlib/excluded.js +9 -5
- package/src/typechecker/stdlib/index.js +25 -9
- package/src/typechecker/termination.js +10 -1
- package/src/typechecker/types.js +203 -573
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +171 -38
- package/stdlib/internal/jsonrt/enc.go +186 -36
- package/stdlib/internal/jsonrt/scanner.go +17 -8
- package/stdlib/io/io.go +157 -1
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +16 -14
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +391 -452
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +235 -50
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +141 -3
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/fmt-runtime.js +0 -573
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -119
- package/src/backend/js/stdlib/builder.js +0 -98
- package/src/backend/js/stdlib/bytes.js +0 -368
- package/src/backend/js/stdlib/errors.js +0 -45
- package/src/backend/js/stdlib/fmt.js +0 -517
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -1167
- package/src/backend/js/stdlib/maps.js +0 -37
- package/src/backend/js/stdlib/math.js +0 -85
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -34
- package/src/backend/js/stdlib/slices.js +0 -107
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -79
- package/src/backend/js/stdlib/strings.js +0 -301
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -41
- package/src/backend/js/stdlib/time.js +0 -152
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/stdlib/bytes.js +0 -6716
- package/src/backend/wasm/stdlib/core.js +0 -10278
- package/src/backend/wasm/stdlib/io.js +0 -3578
- package/src/backend/wasm/stdlib/packages.js +0 -2402
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -2229
- package/src/typechecker/stdlib/core.js +0 -1079
- package/src/typechecker/stdlib/extended.js +0 -1210
- package/stdlib/bytes/iter.go +0 -192
- package/stdlib/bytes/search.go +0 -240
- package/stdlib/bytes/split.go +0 -218
- package/stdlib/errors/overlay.go +0 -12
- package/stdlib/maps/iter.go +0 -65
- package/stdlib/slices/extra.go +0 -73
- package/stdlib/slices/iter.go +0 -113
- package/stdlib/strings/extra.go +0 -35
- package/stdlib/strings/iter.go +0 -122
- package/stdlib/sync/extra.go +0 -105
- package/stdlib/unicode/utf8/bytes.go +0 -239
package/src/typechecker/index.js
CHANGED
|
@@ -21,12 +21,14 @@ import { setupGlobals } from "./stdlib/index.js";
|
|
|
21
21
|
import { terminationMethods } from "./termination.js";
|
|
22
22
|
import {
|
|
23
23
|
ANY,
|
|
24
|
+
BOOL,
|
|
24
25
|
defaultType,
|
|
25
26
|
ERROR,
|
|
26
27
|
INTRINSICS,
|
|
27
28
|
isAny,
|
|
28
29
|
isBool,
|
|
29
30
|
isVoid,
|
|
31
|
+
NATIVES,
|
|
30
32
|
Scope,
|
|
31
33
|
TAINTED_ANY,
|
|
32
34
|
TypeCheckError,
|
|
@@ -39,6 +41,7 @@ import {
|
|
|
39
41
|
const TEMPL_NODE_CHECKS = {
|
|
40
42
|
TemplElement: "_checkTemplElement",
|
|
41
43
|
TemplExpr: "_checkTemplLeaf",
|
|
44
|
+
TemplChildren: "_checkTemplChildren",
|
|
42
45
|
TemplComponent: "_checkTemplLeaf",
|
|
43
46
|
TemplIf: "_checkTemplIf",
|
|
44
47
|
TemplFor: "_checkTemplFor",
|
|
@@ -105,6 +108,7 @@ export class TypeChecker {
|
|
|
105
108
|
this.universe = new Scope();
|
|
106
109
|
this.globals = new Scope(this.universe);
|
|
107
110
|
this.errors = [];
|
|
111
|
+
this._reportedAt = new WeakMap();
|
|
108
112
|
this.warnings = []; // non-fatal diagnostics (e.g. boundary-call-in-loop hints)
|
|
109
113
|
this.target = "js";
|
|
110
114
|
this.pkgName = null;
|
|
@@ -117,6 +121,12 @@ export class TypeChecker {
|
|
|
117
121
|
this._typeSwitchDepth = 0; // for rejecting fallthrough in type switch
|
|
118
122
|
this._deferCount = 0; // tracks defer usage in current function body
|
|
119
123
|
this._imports = []; // tracked imports for unused-import detection
|
|
124
|
+
// Lazy body checking (Go-source stdlib): with `lazyBodies` set, checkAll
|
|
125
|
+
// types declarations only and `checkBody` types a function or method
|
|
126
|
+
// body when it is first reached.
|
|
127
|
+
this.lazyBodies = false;
|
|
128
|
+
this.stats = { bodiesChecked: 0 };
|
|
129
|
+
this._lazyBodies = new Map(); // FuncDecl/MethodDecl -> its program
|
|
120
130
|
this._constExprs = new Map(); // name -> AST initializer expr
|
|
121
131
|
this._importedConsts = new Map(); // dot-imported const name -> value
|
|
122
132
|
this._externalNames = new Set(); // package-scope names from .d.ts / npm definitions
|
|
@@ -174,7 +184,10 @@ export class TypeChecker {
|
|
|
174
184
|
this._ambiguousImported.add(prior);
|
|
175
185
|
this._ambiguousImported.add(type);
|
|
176
186
|
}
|
|
177
|
-
|
|
187
|
+
// A renamed type (`Pt` declared as `Pt$$b`) never takes the bare name:
|
|
188
|
+
// another package owns it, and this package's own declaration would
|
|
189
|
+
// otherwise reuse the imported placeholder. It is reached as `pkg.Pt`.
|
|
190
|
+
if (type.name === name) this.types.set(name, type);
|
|
178
191
|
this.types.set(`${pkgName}.${name}`, type);
|
|
179
192
|
}
|
|
180
193
|
}
|
|
@@ -255,6 +268,14 @@ export class TypeChecker {
|
|
|
255
268
|
}
|
|
256
269
|
|
|
257
270
|
err(msg, node, hint = null) {
|
|
271
|
+
// One diagnostic per root cause: a node revisited by a later pass (a
|
|
272
|
+
// signature resolved at declaration and again in the body) reports once.
|
|
273
|
+
if (node && typeof node === "object") {
|
|
274
|
+
const seen = this._reportedAt.get(node) ?? new Set();
|
|
275
|
+
this._reportedAt.set(node, seen);
|
|
276
|
+
if (seen.has(msg)) return TAINTED_ANY;
|
|
277
|
+
seen.add(msg);
|
|
278
|
+
}
|
|
258
279
|
const e = new TypeCheckError(
|
|
259
280
|
msg,
|
|
260
281
|
node,
|
|
@@ -702,7 +723,11 @@ export class TypeChecker {
|
|
|
702
723
|
const { cycles } = orderPackageInits(programs);
|
|
703
724
|
for (const c of cycles) {
|
|
704
725
|
const owner = programs.find((p) =>
|
|
705
|
-
p.decls?.some(
|
|
726
|
+
p.decls?.some(
|
|
727
|
+
(d) =>
|
|
728
|
+
(d.kind === "VarDecl" || d.kind === "ConstDecl") &&
|
|
729
|
+
d.decls?.includes(c.spec),
|
|
730
|
+
),
|
|
706
731
|
);
|
|
707
732
|
if (owner) this._setCurrentFile(owner);
|
|
708
733
|
this.err(c.message, c.spec);
|
|
@@ -725,6 +750,11 @@ export class TypeChecker {
|
|
|
725
750
|
this._setCurrentFile(p);
|
|
726
751
|
for (const d of p.decls) {
|
|
727
752
|
if (
|
|
753
|
+
this.lazyBodies &&
|
|
754
|
+
(d.kind === "FuncDecl" || d.kind === "MethodDecl")
|
|
755
|
+
)
|
|
756
|
+
this._deferBody(d, p);
|
|
757
|
+
else if (
|
|
728
758
|
d.kind === "FuncDecl" ||
|
|
729
759
|
d.kind === "MethodDecl" ||
|
|
730
760
|
d.kind === "TemplDecl"
|
|
@@ -735,14 +765,48 @@ export class TypeChecker {
|
|
|
735
765
|
}
|
|
736
766
|
}
|
|
737
767
|
|
|
768
|
+
// Lazy mode: validate what does not need the body now, check it on demand.
|
|
769
|
+
_deferBody(decl, program) {
|
|
770
|
+
if (decl.intrinsic) this._checkIntrinsic(decl);
|
|
771
|
+
if (decl.native) this._checkNative(decl);
|
|
772
|
+
this._lazyBodies.set(decl, program);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Types the body of a function or method deferred by `lazyBodies`; a
|
|
776
|
+
// declaration checked already (or not deferred) is left alone.
|
|
777
|
+
checkBody(decl) {
|
|
778
|
+
const program = this._lazyBodies.get(decl);
|
|
779
|
+
if (!program) return;
|
|
780
|
+
this._lazyBodies.delete(decl);
|
|
781
|
+
this._setCurrentFile(program);
|
|
782
|
+
this.checkTopDecl(decl, this.globals);
|
|
783
|
+
}
|
|
784
|
+
|
|
738
785
|
// ── Shared pass helpers ──────────────────────────────────────
|
|
739
786
|
|
|
787
|
+
// Instances made while types were collected (`struct{ Inner[int] }`) have
|
|
788
|
+
// no methods yet, and a type embedding another embedder (`Deep` over `E`
|
|
789
|
+
// over `Base`) needs the inner promotion first, so repeat until nothing
|
|
790
|
+
// new is promoted.
|
|
740
791
|
_promoteEmbeddedMethods() {
|
|
792
|
+
const structs = [];
|
|
741
793
|
for (const type of this.types.values()) {
|
|
742
|
-
|
|
743
|
-
for (const inst of type._generic?._instances?.values() ?? [])
|
|
744
|
-
this.
|
|
794
|
+
structs.push(type.underlying);
|
|
795
|
+
for (const inst of type._generic?._instances?.values() ?? []) {
|
|
796
|
+
this._syncInstanceMethods(type._generic, inst);
|
|
797
|
+
structs.push(inst.underlying);
|
|
798
|
+
}
|
|
745
799
|
}
|
|
800
|
+
const count = () =>
|
|
801
|
+
structs.reduce(
|
|
802
|
+
(n, st) => n + (st?.methods?.size ?? 0) + (st?.fields?.size ?? 0),
|
|
803
|
+
0,
|
|
804
|
+
);
|
|
805
|
+
let before;
|
|
806
|
+
do {
|
|
807
|
+
before = count();
|
|
808
|
+
for (const st of structs) this._promoteEmbeddedInto(st);
|
|
809
|
+
} while (count() !== before);
|
|
746
810
|
}
|
|
747
811
|
|
|
748
812
|
_promoteEmbeddedInto(struct) {
|
|
@@ -750,8 +814,21 @@ export class TypeChecker {
|
|
|
750
814
|
for (const embed of struct._embeds) {
|
|
751
815
|
const inner = embed.kind === "pointer" ? embed.base : embed;
|
|
752
816
|
const base = inner.kind === "named" ? inner.underlying : inner;
|
|
753
|
-
|
|
754
|
-
|
|
817
|
+
// Fields of an embedded type declared after the embedder were not
|
|
818
|
+
// there when the embedder's fields were collected.
|
|
819
|
+
if (base?.kind === "struct" && struct.fields)
|
|
820
|
+
for (const [k, v] of base.fields)
|
|
821
|
+
if (!struct.fields.has(k)) struct.fields.set(k, v);
|
|
822
|
+
// A named non-struct type (`time.Duration`) keeps its methods on
|
|
823
|
+
// the named type itself.
|
|
824
|
+
const methods =
|
|
825
|
+
base?.kind === "struct"
|
|
826
|
+
? base.methods
|
|
827
|
+
: inner.kind === "named" && base?.kind !== "interface"
|
|
828
|
+
? inner.methods
|
|
829
|
+
: null;
|
|
830
|
+
if (!methods) continue;
|
|
831
|
+
for (const [mName, mType] of methods.entries()) {
|
|
755
832
|
if (!struct.methods.has(mName)) struct.methods.set(mName, mType);
|
|
756
833
|
}
|
|
757
834
|
}
|
|
@@ -801,6 +878,9 @@ export class TypeChecker {
|
|
|
801
878
|
this.globals.define(decl.name, named);
|
|
802
879
|
return;
|
|
803
880
|
}
|
|
881
|
+
// A type declared inside a function is registered before its fields
|
|
882
|
+
// resolve so `next *L` finds it; package-level types already are.
|
|
883
|
+
this._registerLocalTypePlaceholder(decl);
|
|
804
884
|
let underlying = this.resolveTypeNode(decl.type, this.globals);
|
|
805
885
|
// `type W V` with V a named array: W's underlying type is V's array.
|
|
806
886
|
if (!decl.isAlias && underlying.kind === "named") {
|
|
@@ -836,6 +916,13 @@ export class TypeChecker {
|
|
|
836
916
|
this.globals.define(decl.name, named);
|
|
837
917
|
}
|
|
838
918
|
|
|
919
|
+
_registerLocalTypePlaceholder(decl) {
|
|
920
|
+
if (decl.isAlias || this.types.has(decl.name)) return;
|
|
921
|
+
const placeholder = { kind: "named", name: decl.name, underlying: null };
|
|
922
|
+
this.types.set(decl.name, placeholder);
|
|
923
|
+
this.globals.define(decl.name, placeholder);
|
|
924
|
+
}
|
|
925
|
+
|
|
839
926
|
// type A[P any] = T[P] — Go 1.24 generic alias. The declaration is a
|
|
840
927
|
// template only: each instantiation A[X] substitutes X into the aliased
|
|
841
928
|
// type and resolves to exactly that type (no new named type).
|
|
@@ -888,6 +975,9 @@ export class TypeChecker {
|
|
|
888
975
|
_registerFuncDecl(decl, funcType, typeParamTypes) {
|
|
889
976
|
if (this.globals.symbols.has(decl.name) && decl.name !== "init")
|
|
890
977
|
this.err(`${decl.name} redeclared in this block`, decl);
|
|
978
|
+
if (decl.native) funcType.native = decl.native;
|
|
979
|
+
this._funcDeclNames ??= new Set();
|
|
980
|
+
this._funcDeclNames.add(decl.name);
|
|
891
981
|
if (typeParamTypes) {
|
|
892
982
|
this.globals.define(decl.name, {
|
|
893
983
|
kind: "generic",
|
|
@@ -932,6 +1022,9 @@ export class TypeChecker {
|
|
|
932
1022
|
}
|
|
933
1023
|
|
|
934
1024
|
_attachNonGenericMethod(decl, funcType) {
|
|
1025
|
+
// Codegen of an importing package reads the body of a Go-source
|
|
1026
|
+
// method (does it accept a nil receiver?).
|
|
1027
|
+
funcType._methodDecl = decl;
|
|
935
1028
|
const recvType = this.resolveTypeNodeName(decl.recvType, this.globals);
|
|
936
1029
|
const base = recvType?.underlying ?? recvType;
|
|
937
1030
|
if (base?.kind === "struct") {
|
|
@@ -1008,12 +1101,26 @@ export class TypeChecker {
|
|
|
1008
1101
|
|
|
1009
1102
|
_checkTemplElement(n, scope) {
|
|
1010
1103
|
for (const a of n.attrs ?? []) {
|
|
1011
|
-
if (a.kind === "expr"
|
|
1012
|
-
|
|
1104
|
+
if (a.kind === "expr") this._checkTemplExpr(a, a.tokens, scope);
|
|
1105
|
+
else if (a.kind === "cond-bool") this._checkTemplCondAttr(a, scope);
|
|
1013
1106
|
}
|
|
1014
1107
|
this._checkTemplNodes(n.children, scope);
|
|
1015
1108
|
}
|
|
1016
1109
|
|
|
1110
|
+
// `name?={ cond }` sets the attribute when `cond` is true.
|
|
1111
|
+
_checkTemplCondAttr(a, scope) {
|
|
1112
|
+
const t = this._checkTemplExpr(a, a.tokens, scope);
|
|
1113
|
+
if (t && !isBool(t) && !isAny(t))
|
|
1114
|
+
this.err(`Conditional attribute '${a.name}' must be bool`, a.expr);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// `{ name... }` names a variadic parameter. A templ without a `children`
|
|
1118
|
+
// parameter may still write `{ children... }` (it mounts nothing).
|
|
1119
|
+
_checkTemplChildren(n, scope) {
|
|
1120
|
+
if (n.name === "children" && !scope.lookup("children")) return;
|
|
1121
|
+
this._checkTemplExpr(n, [], scope);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1017
1124
|
_checkTemplLeaf(n, scope) {
|
|
1018
1125
|
this._checkTemplExpr(n, n.tokens, scope);
|
|
1019
1126
|
}
|
|
@@ -1028,8 +1135,13 @@ export class TypeChecker {
|
|
|
1028
1135
|
|
|
1029
1136
|
_checkTemplSwitch(n, scope) {
|
|
1030
1137
|
this._checkTemplExpr(n, n.exprTokens, scope);
|
|
1138
|
+
// A tag that failed to parse leaves `n.expr` unset: not a tagless switch.
|
|
1139
|
+
const tagless = !n.exprTokens?.length;
|
|
1031
1140
|
for (const c of n.cases ?? []) {
|
|
1032
1141
|
this._checkTemplExpr(c, c.caseTokens, scope);
|
|
1142
|
+
for (const e of c.exprs?.slice(1) ?? []) this.checkExpr(e, scope);
|
|
1143
|
+
if (n.expr || tagless)
|
|
1144
|
+
for (const e of c.exprs ?? []) this._checkCaseValue(n.expr, e);
|
|
1033
1145
|
this._checkTemplNodes(c.body, scope);
|
|
1034
1146
|
}
|
|
1035
1147
|
}
|
|
@@ -1147,8 +1259,27 @@ export class TypeChecker {
|
|
|
1147
1259
|
);
|
|
1148
1260
|
}
|
|
1149
1261
|
|
|
1262
|
+
_checkNative(decl) {
|
|
1263
|
+
if (!this.isStdlib && !this.nativesChecked)
|
|
1264
|
+
return this.err(
|
|
1265
|
+
"//gofront:native is reserved for the standard library",
|
|
1266
|
+
decl,
|
|
1267
|
+
);
|
|
1268
|
+
if (decl.kind !== "FuncDecl")
|
|
1269
|
+
return this.err(
|
|
1270
|
+
`native '${decl.native}' must mark a function, not a method`,
|
|
1271
|
+
decl,
|
|
1272
|
+
);
|
|
1273
|
+
if (!NATIVES.has(decl.native))
|
|
1274
|
+
return this.err(`unknown native '${decl.native}'`, decl);
|
|
1275
|
+
if (decl._bodyless)
|
|
1276
|
+
this.err(`native '${decl.native}' must have a Go body`, decl);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1150
1279
|
checkFuncDecl(decl, outer) {
|
|
1280
|
+
this.stats.bodiesChecked++;
|
|
1151
1281
|
if (decl.intrinsic) this._checkIntrinsic(decl);
|
|
1282
|
+
if (decl.native) this._checkNative(decl);
|
|
1152
1283
|
if (
|
|
1153
1284
|
(this.target === "wasm" || this.target === "both") &&
|
|
1154
1285
|
(decl.async || decl.isAsync)
|
|
@@ -1173,6 +1304,8 @@ export class TypeChecker {
|
|
|
1173
1304
|
}
|
|
1174
1305
|
|
|
1175
1306
|
checkMethodDecl(decl, outer) {
|
|
1307
|
+
this.stats.bodiesChecked++;
|
|
1308
|
+
if (decl.native) this._checkNative(decl);
|
|
1176
1309
|
if (
|
|
1177
1310
|
(this.target === "wasm" || this.target === "both") &&
|
|
1178
1311
|
(decl.async || decl.isAsync)
|
|
@@ -1199,7 +1332,13 @@ export class TypeChecker {
|
|
|
1199
1332
|
inner.define(p.name, this._paramType(p, inner));
|
|
1200
1333
|
}
|
|
1201
1334
|
const returnType = this._setupFuncReturnType(decl, inner);
|
|
1202
|
-
this.
|
|
1335
|
+
const savedRecvGeneric = this._recvGeneric;
|
|
1336
|
+
this._recvGeneric = recvNamedType?._generic;
|
|
1337
|
+
try {
|
|
1338
|
+
this._runFuncBody(decl, inner, returnType);
|
|
1339
|
+
} finally {
|
|
1340
|
+
this._recvGeneric = savedRecvGeneric;
|
|
1341
|
+
}
|
|
1203
1342
|
this._checkMissingReturn(decl, returnType, "method");
|
|
1204
1343
|
}
|
|
1205
1344
|
|
|
@@ -1210,14 +1349,19 @@ export class TypeChecker {
|
|
|
1210
1349
|
return p.variadic ? { kind: "slice", elem: t } : t;
|
|
1211
1350
|
}
|
|
1212
1351
|
|
|
1213
|
-
_injectTypeParams(decl, inner,
|
|
1352
|
+
_injectTypeParams(decl, inner, _outer) {
|
|
1214
1353
|
if (!decl.typeParams) return;
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
inner.define(tp.name,
|
|
1220
|
-
|
|
1354
|
+
// Declare every parameter first so a constraint may mention one that
|
|
1355
|
+
// comes later in the list (`[S ~[]E, E any]`).
|
|
1356
|
+
const params = decl.typeParams.map((tp) => {
|
|
1357
|
+
const t = { kind: "typeParam", name: tp.name, constraint: ANY };
|
|
1358
|
+
inner.define(tp.name, t);
|
|
1359
|
+
return t;
|
|
1360
|
+
});
|
|
1361
|
+
decl.typeParams.forEach((tp, i) => {
|
|
1362
|
+
if (tp.constraint)
|
|
1363
|
+
params[i].constraint = this.resolveTypeNode(tp.constraint, inner);
|
|
1364
|
+
});
|
|
1221
1365
|
}
|
|
1222
1366
|
|
|
1223
1367
|
// Inside a method of `G[T]` the receiver is the instance `G[T]` over the
|
|
@@ -1258,7 +1402,11 @@ export class TypeChecker {
|
|
|
1258
1402
|
}
|
|
1259
1403
|
|
|
1260
1404
|
_checkMissingReturn(decl, returnType, kind) {
|
|
1261
|
-
if (
|
|
1405
|
+
if (
|
|
1406
|
+
!decl._bodyless &&
|
|
1407
|
+
!isVoid(returnType) &&
|
|
1408
|
+
!returnType._hasNamedReturns
|
|
1409
|
+
) {
|
|
1262
1410
|
if (!this._isTerminating(decl.body)) {
|
|
1263
1411
|
this.err(`missing return at end of ${kind} '${decl.name}'`, decl);
|
|
1264
1412
|
}
|
|
@@ -1281,21 +1429,7 @@ export class TypeChecker {
|
|
|
1281
1429
|
for (const spec of decl.decls) {
|
|
1282
1430
|
let type = spec.type ? this.resolveTypeNode(spec.type, scope) : null;
|
|
1283
1431
|
if (spec.value) {
|
|
1284
|
-
const
|
|
1285
|
-
const isTuple = valTypes.length === 1 && valTypes[0]?.kind === "tuple";
|
|
1286
|
-
const valTypesFlat = isTuple ? valTypes[0].types : valTypes;
|
|
1287
|
-
if (spec.names.length !== valTypesFlat.length) {
|
|
1288
|
-
const vars =
|
|
1289
|
-
spec.names.length === 1
|
|
1290
|
-
? "1 variable"
|
|
1291
|
-
: `${spec.names.length} variables`;
|
|
1292
|
-
const vals = isTuple
|
|
1293
|
-
? `returns ${valTypesFlat.length} values`
|
|
1294
|
-
: valTypesFlat.length === 1
|
|
1295
|
-
? "1 value"
|
|
1296
|
-
: `${valTypesFlat.length} values`;
|
|
1297
|
-
this.err(`assignment mismatch: ${vars} but ${vals}`, spec);
|
|
1298
|
-
}
|
|
1432
|
+
const valTypesFlat = this._varSpecValueTypes(spec, scope);
|
|
1299
1433
|
for (let i = 0; i < spec.names.length; i++) {
|
|
1300
1434
|
const vt = valTypesFlat[i] ?? ANY;
|
|
1301
1435
|
const varType = type ?? defaultType(vt) ?? ANY;
|
|
@@ -1318,6 +1452,45 @@ export class TypeChecker {
|
|
|
1318
1452
|
this._sharedAllocOk = savedSharedAlloc;
|
|
1319
1453
|
}
|
|
1320
1454
|
|
|
1455
|
+
// The type of each value a `var` spec binds (a multi-value call or a
|
|
1456
|
+
// comma-ok form spreads over several names); reports a count mismatch.
|
|
1457
|
+
_varSpecValueTypes(spec, scope) {
|
|
1458
|
+
const commaOk = this._checkVarCommaOk(spec, scope);
|
|
1459
|
+
const valTypes = commaOk ?? spec.value.map((v) => this.checkExpr(v, scope));
|
|
1460
|
+
const isTuple =
|
|
1461
|
+
!commaOk && valTypes.length === 1 && valTypes[0]?.kind === "tuple";
|
|
1462
|
+
const flat = isTuple ? valTypes[0].types : valTypes;
|
|
1463
|
+
if (spec.names.length !== flat.length && !flat.some((t) => t?._tainted))
|
|
1464
|
+
this._reportAssignMismatch(spec.names.length, flat.length, isTuple, spec);
|
|
1465
|
+
return flat;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
_reportAssignMismatch(nameCount, valueCount, isTuple, node) {
|
|
1469
|
+
const vars = nameCount === 1 ? "1 variable" : `${nameCount} variables`;
|
|
1470
|
+
const vals = isTuple
|
|
1471
|
+
? `returns ${valueCount} values`
|
|
1472
|
+
: valueCount === 1
|
|
1473
|
+
? "1 value"
|
|
1474
|
+
: `${valueCount} values`;
|
|
1475
|
+
this.err(`assignment mismatch: ${vars} but ${vals}`, node);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// `var v, ok = m[k]` / `var v, ok = x.(T)`: the comma-ok forms yield the
|
|
1479
|
+
// value and a bool. Returns their types, or null for any other spec.
|
|
1480
|
+
_checkVarCommaOk(spec, scope) {
|
|
1481
|
+
if (spec.names.length !== 2 || spec.value.length !== 1) return null;
|
|
1482
|
+
const value = spec.value[0];
|
|
1483
|
+
if (value.kind === "TypeAssertExpr") {
|
|
1484
|
+
value._commaOk = true;
|
|
1485
|
+
spec._commaOk = true;
|
|
1486
|
+
return [this.checkExpr(value, scope), BOOL];
|
|
1487
|
+
}
|
|
1488
|
+
const stmt = { lhs: spec.names, rhs: spec.value };
|
|
1489
|
+
const types = this._checkAssignCommaOkMap(stmt, scope);
|
|
1490
|
+
if (types) spec._commaOk = true;
|
|
1491
|
+
return types;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1321
1494
|
checkConstDecl(decl, scope) {
|
|
1322
1495
|
for (const spec of decl.decls) {
|
|
1323
1496
|
const valTypes = spec.value.map((v) => this.checkExpr(v, scope));
|
|
@@ -1335,6 +1508,8 @@ export class TypeChecker {
|
|
|
1335
1508
|
expr,
|
|
1336
1509
|
"constant declaration",
|
|
1337
1510
|
);
|
|
1511
|
+
// `const D Duration = 1` is a BigInt in JS.
|
|
1512
|
+
if (spec.type && expr) this._tag64BitNode(this.resolveType(type), expr);
|
|
1338
1513
|
scope.defineConst(name, type, expr);
|
|
1339
1514
|
}
|
|
1340
1515
|
}
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
// (a constant may name one declared later).
|
|
9
9
|
//
|
|
10
10
|
// The checker reports cycles; both backends emit initialisers in this order.
|
|
11
|
-
// Shadowing
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// invents a cycle.
|
|
11
|
+
// Shadowing follows Go's block scoping: a name declared by a parameter, result,
|
|
12
|
+
// receiver or statement hides the package-level one from the next statement to
|
|
13
|
+
// the end of its block, and not outside it.
|
|
15
14
|
|
|
16
15
|
function forEachChildNode(node, fn) {
|
|
17
16
|
for (const key of Object.keys(node)) {
|
|
@@ -21,34 +20,35 @@ function forEachChildNode(node, fn) {
|
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return out;
|
|
23
|
+
// A lexical scope of locally declared names.
|
|
24
|
+
class Shadow {
|
|
25
|
+
constructor(parent = null) {
|
|
26
|
+
this.parent = parent;
|
|
27
|
+
this.names = new Set();
|
|
30
28
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} else if (node.kind === "RangeStmt" || node.kind === "ForRangeStmt") {
|
|
36
|
-
for (const k of [node.key, node.value, node.keyVar, node.valueVar])
|
|
37
|
-
if (k?.kind === "Ident") out.add(k.name);
|
|
38
|
-
if (typeof node.key === "string") out.add(node.key);
|
|
39
|
-
if (typeof node.value === "string") out.add(node.value);
|
|
40
|
-
} else if (node.kind === "TypeSwitchStmt" && node.assign) {
|
|
41
|
-
out.add(node.assign);
|
|
29
|
+
|
|
30
|
+
has(name) {
|
|
31
|
+
for (let s = this; s; s = s.parent) if (s.names.has(name)) return true;
|
|
32
|
+
return false;
|
|
42
33
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
34
|
+
|
|
35
|
+
add(...names) {
|
|
36
|
+
for (const n of names) if (n) this.names.add(n);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Names a function-like node declares for its body: receiver, parameters and
|
|
41
|
+
// named results.
|
|
42
|
+
function signatureNames(node) {
|
|
43
|
+
const out = [];
|
|
44
|
+
if (node.recvName) out.push(node.recvName);
|
|
45
|
+
for (const p of node.params ?? []) if (p?.name) out.push(p.name);
|
|
46
|
+
for (const r of node.returnType?._namedReturns ?? []) out.push(r.name);
|
|
49
47
|
return out;
|
|
50
48
|
}
|
|
51
49
|
|
|
50
|
+
const lhsName = (l) => (typeof l === "string" ? l : l?.name);
|
|
51
|
+
|
|
52
52
|
// The `Type.method` key of the declaration `name` selects on a value of type
|
|
53
53
|
// `t`, found the way the checker finds it: on the type itself, else on an
|
|
54
54
|
// embedded type (through pointers too). Interface methods have no body to
|
|
@@ -83,6 +83,60 @@ function litHasExprKeys(lit) {
|
|
|
83
83
|
return ["MapType", "SliceType", "ArrayType"].includes(lit.typeExpr?.kind);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
+
// Statements that open a scope and declare names as they run. Each handler
|
|
87
|
+
// walks the node's children in order, adding declarations to `shadow` after
|
|
88
|
+
// the expressions that cannot see them.
|
|
89
|
+
const SCOPED = {
|
|
90
|
+
FuncDecl: walkFunc,
|
|
91
|
+
MethodDecl: walkFunc,
|
|
92
|
+
FuncLit: walkFunc,
|
|
93
|
+
Block: (node, shadow, ctx, out) =>
|
|
94
|
+
walkRefs(node.stmts, new Shadow(shadow), ctx, out),
|
|
95
|
+
DefineStmt: (node, shadow, ctx, out) => {
|
|
96
|
+
walkRefs(node.rhs, shadow, ctx, out);
|
|
97
|
+
shadow.add(...[node.lhs].flat().map(lhsName));
|
|
98
|
+
},
|
|
99
|
+
VarDecl: walkDeclSpecs,
|
|
100
|
+
ConstDecl: walkDeclSpecs,
|
|
101
|
+
IfStmt: (node, shadow, ctx, out) => {
|
|
102
|
+
const inner = new Shadow(shadow);
|
|
103
|
+
for (const c of [node.init, node.cond, node.body, node.elseBody])
|
|
104
|
+
walkRefs(c, inner, ctx, out);
|
|
105
|
+
},
|
|
106
|
+
ForStmt: (node, shadow, ctx, out) => {
|
|
107
|
+
const inner = new Shadow(shadow);
|
|
108
|
+
for (const c of [node.init, node.cond, node.post, node.body])
|
|
109
|
+
walkRefs(c, inner, ctx, out);
|
|
110
|
+
},
|
|
111
|
+
SwitchStmt: walkSwitch,
|
|
112
|
+
TypeSwitchStmt: walkSwitch,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
function walkFunc(node, shadow, ctx, out) {
|
|
116
|
+
const inner = new Shadow(shadow);
|
|
117
|
+
inner.add(...signatureNames(node));
|
|
118
|
+
walkRefs(node.body, inner, ctx, out);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function walkDeclSpecs(node, shadow, ctx, out) {
|
|
122
|
+
for (const spec of node.decls ?? []) {
|
|
123
|
+
walkRefs(spec.value, shadow, ctx, out);
|
|
124
|
+
shadow.add(...(spec.names ?? []));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function walkSwitch(node, shadow, ctx, out) {
|
|
129
|
+
const inner = new Shadow(shadow);
|
|
130
|
+
walkRefs(node.init, inner, ctx, out);
|
|
131
|
+
walkRefs(node.tag ?? node.expr, inner, ctx, out);
|
|
132
|
+
for (const c of node.cases ?? []) {
|
|
133
|
+
const clause = new Shadow(inner);
|
|
134
|
+
if (node.assign) clause.add(node.assign);
|
|
135
|
+
walkRefs(c.list, clause, ctx, out);
|
|
136
|
+
walkRefs(c.stmts, clause, ctx, out);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
86
140
|
function walkRefs(node, shadow, ctx, out) {
|
|
87
141
|
if (!node || typeof node !== "object") return;
|
|
88
142
|
if (Array.isArray(node)) {
|
|
@@ -90,10 +144,17 @@ function walkRefs(node, shadow, ctx, out) {
|
|
|
90
144
|
return;
|
|
91
145
|
}
|
|
92
146
|
if (node.kind === "Ident") {
|
|
93
|
-
|
|
147
|
+
// The blank identifier names nothing, whatever `var _` the package has.
|
|
148
|
+
if (
|
|
149
|
+
node.name !== "_" &&
|
|
150
|
+
ctx.pkgNames.has(node.name) &&
|
|
151
|
+
!shadow.has(node.name)
|
|
152
|
+
)
|
|
94
153
|
out.names.add(node.name);
|
|
95
154
|
return;
|
|
96
155
|
}
|
|
156
|
+
const scoped = SCOPED[node.kind];
|
|
157
|
+
if (scoped) return scoped(node, shadow, ctx, out);
|
|
97
158
|
if (node.kind === "SelectorExpr") {
|
|
98
159
|
const key = methodKeyOf(node.expr?._type, node.field, ctx.funcs);
|
|
99
160
|
if (key) out.methods.add(key);
|
|
@@ -118,11 +179,10 @@ function walkRefs(node, shadow, ctx, out) {
|
|
|
118
179
|
/**
|
|
119
180
|
* Collects what `root` (an initialiser expression or a function declaration)
|
|
120
181
|
* refers to: package-level names (`names`) and declarations selected as methods
|
|
121
|
-
* (`methods`, keyed `Type.method`)
|
|
122
|
-
* a name declared anywhere inside `root` never counts as the package-level one.
|
|
182
|
+
* (`methods`, keyed `Type.method`), honouring block-scoped shadowing.
|
|
123
183
|
*/
|
|
124
184
|
function collectRefs(root, ctx, out) {
|
|
125
|
-
walkRefs(root,
|
|
185
|
+
walkRefs(root, new Shadow(), ctx, out);
|
|
126
186
|
}
|
|
127
187
|
|
|
128
188
|
const emptyRefs = () => ({ names: new Set(), methods: new Set() });
|
|
@@ -261,24 +321,52 @@ function orderVars(varEntries, depsOf) {
|
|
|
261
321
|
return { vars, cycles: [] };
|
|
262
322
|
}
|
|
263
323
|
|
|
264
|
-
// Constants depend on other constants only
|
|
324
|
+
// Constants depend on other constants only; a constant that reaches itself is
|
|
325
|
+
// a cycle (reported once, at the constant where the walk closed it).
|
|
265
326
|
function orderConsts(constEntries) {
|
|
266
327
|
const constNames = new Set(constEntries.flatMap((e) => e.names));
|
|
267
328
|
const ctx = { pkgNames: constNames, funcs: new Map() };
|
|
268
329
|
const owner = new Map();
|
|
269
330
|
for (const e of constEntries) for (const n of e.names) owner.set(n, e);
|
|
270
331
|
const consts = [];
|
|
271
|
-
const
|
|
332
|
+
const cycles = [];
|
|
333
|
+
const state = new Map(); // entry -> 1 while on the walk's path, 2 when done
|
|
334
|
+
const path = [];
|
|
272
335
|
const visit = (e) => {
|
|
273
|
-
|
|
274
|
-
|
|
336
|
+
state.set(e, 1);
|
|
337
|
+
path.push(e);
|
|
275
338
|
const refs = emptyRefs();
|
|
276
339
|
for (const v of e.spec.value ?? []) collectRefs(v, ctx, refs);
|
|
277
|
-
for (const n of refs.names)
|
|
340
|
+
for (const n of refs.names) {
|
|
341
|
+
const dep = owner.get(n);
|
|
342
|
+
if (!dep) continue;
|
|
343
|
+
if (state.get(dep) === 1) {
|
|
344
|
+
const names = path.slice(path.indexOf(dep)).map((p) => p.names[0]);
|
|
345
|
+
const chain = names.length === 1 ? names : [...names, names[0]];
|
|
346
|
+
cycles.push({ spec: dep.spec, message: cycleMessage(dep, chain) });
|
|
347
|
+
} else if (!state.has(dep)) visit(dep);
|
|
348
|
+
}
|
|
349
|
+
path.pop();
|
|
350
|
+
state.set(e, 2);
|
|
278
351
|
consts.push(e);
|
|
279
352
|
};
|
|
280
|
-
for (const e of constEntries) visit(e);
|
|
281
|
-
return consts;
|
|
353
|
+
for (const e of constEntries) if (!state.has(e)) visit(e);
|
|
354
|
+
return { consts, cycles };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// A literal initialiser (or none: the zero value) reads nothing and has no
|
|
358
|
+
// effect, and gc initialises it statically, so a function that runs during
|
|
359
|
+
// another variable's initialiser sees it set even when it is declared later
|
|
360
|
+
// (an interface method call adds no dependency in the spec).
|
|
361
|
+
const isStaticValue = (v) =>
|
|
362
|
+
v?.kind === "BasicLit" ||
|
|
363
|
+
(v?.kind === "UnaryExpr" &&
|
|
364
|
+
["-", "+"].includes(v.op) &&
|
|
365
|
+
isStaticValue(v.operand));
|
|
366
|
+
|
|
367
|
+
function staticInitsFirst(vars) {
|
|
368
|
+
const isStatic = (e) => (e.spec.value ?? []).every(isStaticValue);
|
|
369
|
+
return [...vars.filter(isStatic), ...vars.filter((e) => !isStatic(e))];
|
|
282
370
|
}
|
|
283
371
|
|
|
284
372
|
/**
|
|
@@ -290,8 +378,16 @@ function orderConsts(constEntries) {
|
|
|
290
378
|
export function orderPackageInits(programs) {
|
|
291
379
|
const decls = collectDecls(programs);
|
|
292
380
|
const depsOf = buildVarDeps(decls);
|
|
293
|
-
const
|
|
294
|
-
|
|
381
|
+
const ordered = orderVars(decls.varEntries, depsOf);
|
|
382
|
+
const { cycles } = ordered;
|
|
383
|
+
const vars =
|
|
384
|
+
cycles.length === 0 ? staticInitsFirst(ordered.vars) : ordered.vars;
|
|
385
|
+
const constOrder = orderConsts(decls.constEntries);
|
|
386
|
+
return {
|
|
387
|
+
consts: constOrder.consts,
|
|
388
|
+
vars,
|
|
389
|
+
cycles: [...constOrder.cycles, ...cycles],
|
|
390
|
+
};
|
|
295
391
|
}
|
|
296
392
|
|
|
297
393
|
/**
|