gofront 0.0.2 → 0.0.4
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 +173 -52
- package/package.json +7 -3
- package/src/codegen/expressions.js +907 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +464 -0
- package/src/codegen.js +43 -996
- package/src/compiler.js +36 -22
- package/src/index.js +3 -3
- package/src/lexer.js +94 -6
- package/src/parser/expressions.js +389 -0
- package/src/parser/statements.js +355 -0
- package/src/parser/types.js +140 -0
- package/src/parser.js +72 -863
- package/src/typechecker/expressions.js +447 -0
- package/src/typechecker/statements.js +287 -0
- package/src/typechecker/types.js +208 -0
- package/src/typechecker.js +528 -709
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
// TypeChecker expression-checking methods — installed as a mixin on TypeChecker.prototype.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ANY,
|
|
5
|
+
BOOL,
|
|
6
|
+
ERROR,
|
|
7
|
+
INT,
|
|
8
|
+
isAny,
|
|
9
|
+
isBool,
|
|
10
|
+
isNumeric,
|
|
11
|
+
isString,
|
|
12
|
+
NIL,
|
|
13
|
+
Scope,
|
|
14
|
+
STRING,
|
|
15
|
+
typeStr,
|
|
16
|
+
UNTYPED_BOOL,
|
|
17
|
+
UNTYPED_FLOAT,
|
|
18
|
+
UNTYPED_INT,
|
|
19
|
+
UNTYPED_STRING,
|
|
20
|
+
VOID,
|
|
21
|
+
} from "./types.js";
|
|
22
|
+
|
|
23
|
+
export const expressionCheckMethods = {
|
|
24
|
+
checkExpr(expr, scope) {
|
|
25
|
+
const t = this._checkExpr(expr, scope);
|
|
26
|
+
expr._type = t;
|
|
27
|
+
return t;
|
|
28
|
+
},
|
|
29
|
+
|
|
30
|
+
_checkExpr(expr, scope) {
|
|
31
|
+
switch (expr.kind) {
|
|
32
|
+
case "BasicLit": {
|
|
33
|
+
switch (expr.litKind) {
|
|
34
|
+
case "INT":
|
|
35
|
+
return UNTYPED_INT;
|
|
36
|
+
case "FLOAT":
|
|
37
|
+
return UNTYPED_FLOAT;
|
|
38
|
+
case "STRING":
|
|
39
|
+
return UNTYPED_STRING;
|
|
40
|
+
case "BOOL":
|
|
41
|
+
return UNTYPED_BOOL;
|
|
42
|
+
case "NIL":
|
|
43
|
+
return NIL;
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
case "Ident": {
|
|
49
|
+
const t = scope.lookup(expr.name);
|
|
50
|
+
if (!t) return this.err(`Undefined: '${expr.name}'`, expr);
|
|
51
|
+
// Mark type-name identifiers so SelectorExpr can detect method expressions
|
|
52
|
+
if (this.types.has(expr.name)) expr._isTypeRef = true;
|
|
53
|
+
return t;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
case "UnaryExpr": {
|
|
57
|
+
const ot = this.checkExpr(expr.operand, scope);
|
|
58
|
+
if (expr.op === "!") {
|
|
59
|
+
if (!isBool(ot) && !isAny(ot)) this.err("! requires bool", expr);
|
|
60
|
+
return BOOL;
|
|
61
|
+
}
|
|
62
|
+
if (expr.op === "-" || expr.op === "+") {
|
|
63
|
+
if (!isNumeric(ot) && !isAny(ot))
|
|
64
|
+
this.err(`${expr.op} requires numeric`, expr);
|
|
65
|
+
return ot;
|
|
66
|
+
}
|
|
67
|
+
return ot;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
case "BinaryExpr": {
|
|
71
|
+
const lt = this.checkExpr(expr.left, scope);
|
|
72
|
+
const rt = this.checkExpr(expr.right, scope);
|
|
73
|
+
if (isAny(lt) || isAny(rt))
|
|
74
|
+
return this.binaryResultType(expr.op, ANY, ANY, expr);
|
|
75
|
+
return this.binaryResultType(expr.op, lt, rt, expr);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
case "CallExpr": {
|
|
79
|
+
return this.checkCall(expr, scope);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
case "SelectorExpr": {
|
|
83
|
+
const baseType = this.checkExpr(expr.expr, scope);
|
|
84
|
+
// Method expression: TypeName.MethodName → func(ReceiverType, ...params) ret
|
|
85
|
+
if (expr.expr._isTypeRef) {
|
|
86
|
+
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
87
|
+
base = this.resolveType(base);
|
|
88
|
+
if (base?.kind === "struct" && base.methods?.has(expr.field)) {
|
|
89
|
+
const methodType = base.methods.get(expr.field);
|
|
90
|
+
// Return a func type with the receiver prepended to params
|
|
91
|
+
expr._isMethodExpr = true;
|
|
92
|
+
expr._methodExprRecv = baseType;
|
|
93
|
+
return {
|
|
94
|
+
kind: "func",
|
|
95
|
+
params: [baseType, ...(methodType.params ?? [])],
|
|
96
|
+
returns: methodType.returns ?? [],
|
|
97
|
+
variadic: methodType.variadic,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const ft = this.fieldType(baseType, expr.field, expr);
|
|
102
|
+
// Mark method selectors so codegen can emit .bind() when used as a value
|
|
103
|
+
if (ft?.kind === "func") {
|
|
104
|
+
let base = baseType.kind === "named" ? baseType.underlying : baseType;
|
|
105
|
+
base = this.resolveType(base);
|
|
106
|
+
if (base?.kind === "struct" && base.methods?.has(expr.field)) {
|
|
107
|
+
expr._isMethodValue = true;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return ft;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
case "IndexExpr": {
|
|
114
|
+
const bt = this.checkExpr(expr.expr, scope);
|
|
115
|
+
this.checkExpr(expr.index, scope);
|
|
116
|
+
if (isAny(bt)) return ANY;
|
|
117
|
+
if (bt.kind === "slice" || bt.kind === "array") return bt.elem;
|
|
118
|
+
if (bt.kind === "map") {
|
|
119
|
+
expr._mapValueType = bt.value; // for codegen zero-value fallback
|
|
120
|
+
return bt.value;
|
|
121
|
+
}
|
|
122
|
+
if (isString(bt)) return INT; // byte
|
|
123
|
+
return this.err(`Cannot index type ${typeStr(bt)}`, expr);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
case "SliceExpr": {
|
|
127
|
+
const bt = this.checkExpr(expr.expr, scope);
|
|
128
|
+
if (expr.low) this.checkExpr(expr.low, scope);
|
|
129
|
+
if (expr.high) this.checkExpr(expr.high, scope);
|
|
130
|
+
if (expr.max) this.checkExpr(expr.max, scope);
|
|
131
|
+
if (isAny(bt)) return ANY;
|
|
132
|
+
if (bt.kind === "slice" || bt.kind === "array")
|
|
133
|
+
return { kind: "slice", elem: bt.elem };
|
|
134
|
+
if (isString(bt)) return STRING;
|
|
135
|
+
return this.err(`Cannot slice type ${typeStr(bt)}`, expr);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
case "CompositeLit": {
|
|
139
|
+
return this.checkCompositeLit(expr, scope);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
case "FuncLit": {
|
|
143
|
+
const inner = new Scope(scope);
|
|
144
|
+
for (const p of expr.params)
|
|
145
|
+
inner.define(
|
|
146
|
+
p.name,
|
|
147
|
+
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
148
|
+
);
|
|
149
|
+
const ret = expr.returnType
|
|
150
|
+
? this.resolveTypeNode(expr.returnType, scope)
|
|
151
|
+
: VOID;
|
|
152
|
+
const savedDefer = this._deferCount;
|
|
153
|
+
this._deferCount = 0;
|
|
154
|
+
this.checkBlock(expr.body, inner, ret);
|
|
155
|
+
this._reportUnused(inner, expr);
|
|
156
|
+
if (this._deferCount > 0) expr.body._hasDefer = true;
|
|
157
|
+
this._deferCount = savedDefer;
|
|
158
|
+
const paramTypes = expr.params.map((p) =>
|
|
159
|
+
p.type ? this.resolveTypeNode(p.type, scope) : ANY,
|
|
160
|
+
);
|
|
161
|
+
return {
|
|
162
|
+
kind: "func",
|
|
163
|
+
params: paramTypes,
|
|
164
|
+
returns: [ret],
|
|
165
|
+
async: expr.async,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
case "AwaitExpr": {
|
|
170
|
+
// await unwraps whatever the expression produces — no Promise type modelling
|
|
171
|
+
return this.checkExpr(expr.expr, scope);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
case "TypeConversion": {
|
|
175
|
+
this.checkExpr(expr.expr, scope);
|
|
176
|
+
return this.resolveTypeNode(expr.targetType, scope);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
case "TypeAssertExpr": {
|
|
180
|
+
const srcType = this.checkExpr(expr.expr, scope);
|
|
181
|
+
const targetType = this.resolveTypeNode(expr.type, scope);
|
|
182
|
+
|
|
183
|
+
// In Go, the source of a type assertion must be an interface type.
|
|
184
|
+
// We allow `any` (which is GoFront's basic any) and interface types.
|
|
185
|
+
if (srcType && !isAny(srcType)) {
|
|
186
|
+
let underlying =
|
|
187
|
+
srcType.kind === "named" ? srcType.underlying : srcType;
|
|
188
|
+
underlying = this.resolveType(underlying);
|
|
189
|
+
if (underlying?.kind !== "interface") {
|
|
190
|
+
this.err(
|
|
191
|
+
`invalid type assertion: ${typeStr(srcType)} is not an interface`,
|
|
192
|
+
expr,
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return targetType;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case "RangeExpr": {
|
|
201
|
+
// Type-check the iterated expression so its _type is annotated
|
|
202
|
+
const collType = this.checkExpr(expr.expr, scope);
|
|
203
|
+
const resolved =
|
|
204
|
+
collType?.kind === "named" ? collType.underlying : collType;
|
|
205
|
+
// Return a tuple of (index/key type, value type) so DefineStmt can
|
|
206
|
+
// assign the correct types to range variables.
|
|
207
|
+
const isString =
|
|
208
|
+
(resolved?.kind === "basic" && resolved.name === "string") ||
|
|
209
|
+
(resolved?.kind === "untyped" && resolved.base === "string");
|
|
210
|
+
if (isString) {
|
|
211
|
+
// string range: (int index, rune value) — rune is an alias for int
|
|
212
|
+
return { kind: "tuple", types: [INT, INT] };
|
|
213
|
+
}
|
|
214
|
+
if (resolved?.kind === "slice") {
|
|
215
|
+
return { kind: "tuple", types: [INT, resolved.elem ?? ANY] };
|
|
216
|
+
}
|
|
217
|
+
if (resolved?.kind === "map") {
|
|
218
|
+
return {
|
|
219
|
+
kind: "tuple",
|
|
220
|
+
types: [resolved.key ?? ANY, resolved.value ?? ANY],
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
// integer range or unknown — return single-element tuple
|
|
224
|
+
return { kind: "tuple", types: [collType] };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
default:
|
|
228
|
+
return ANY;
|
|
229
|
+
}
|
|
230
|
+
return ANY;
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
checkCall(expr, scope) {
|
|
234
|
+
const fnType = this.checkExpr(expr.func, scope);
|
|
235
|
+
|
|
236
|
+
// Handle built-ins before evaluating args; some (e.g. new) take type names as
|
|
237
|
+
// arguments, which would otherwise produce false "Undefined" errors.
|
|
238
|
+
if (fnType.kind === "builtin") {
|
|
239
|
+
const argTypes = expr.args.map((a, i) => {
|
|
240
|
+
// new(T) — first arg is a type name, not a value expression
|
|
241
|
+
if (fnType.name === "new" && i === 0) return ANY;
|
|
242
|
+
return this.checkExpr(a, scope);
|
|
243
|
+
});
|
|
244
|
+
return this.checkBuiltin(fnType.name, expr, argTypes, scope);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Multi-value forwarding: f(g()) where g() returns multiple values
|
|
248
|
+
// When a single argument is a multi-return call, flatten its tuple into args.
|
|
249
|
+
let argTypes;
|
|
250
|
+
if (expr.args.length === 1 && expr.args[0].kind === "CallExpr") {
|
|
251
|
+
const argType = this.checkExpr(expr.args[0], scope);
|
|
252
|
+
if (argType?.kind === "tuple") {
|
|
253
|
+
expr._multiForward = true; // flag for codegen
|
|
254
|
+
argTypes = argType.types;
|
|
255
|
+
} else {
|
|
256
|
+
argTypes = [argType];
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
argTypes = expr.args.map((a) => this.checkExpr(a, scope));
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (isAny(fnType)) return ANY;
|
|
263
|
+
|
|
264
|
+
if (fnType.kind !== "func") {
|
|
265
|
+
return this.err(`Cannot call non-function type ${typeStr(fnType)}`, expr);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Check arg count (allow variadic slack)
|
|
269
|
+
const minArgs = fnType.params.filter(
|
|
270
|
+
(_, i) => !fnType.variadic || i < fnType.params.length - 1,
|
|
271
|
+
).length;
|
|
272
|
+
if (argTypes.length < minArgs) {
|
|
273
|
+
this.err(
|
|
274
|
+
`Too few arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
275
|
+
expr,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
if (!fnType.variadic && argTypes.length > fnType.params.length) {
|
|
279
|
+
this.err(
|
|
280
|
+
`Too many arguments: expected ${fnType.params.length}, got ${argTypes.length}`,
|
|
281
|
+
expr,
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Check each argument is assignable to its parameter type
|
|
286
|
+
for (let i = 0; i < fnType.params.length && i < argTypes.length; i++) {
|
|
287
|
+
const paramIdx =
|
|
288
|
+
fnType.variadic && i >= fnType.params.length - 1
|
|
289
|
+
? fnType.params.length - 1
|
|
290
|
+
: i;
|
|
291
|
+
// Spread arg (f(slice...)) passes a whole slice into a variadic — skip per-element check
|
|
292
|
+
if (expr.args[i]?._spread) continue;
|
|
293
|
+
this.assertAssignable(fnType.params[paramIdx], argTypes[i], expr.args[i]);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const ret = fnType.returns;
|
|
297
|
+
if (!ret || ret.length === 0) return VOID;
|
|
298
|
+
if (ret.length === 1) return ret[0];
|
|
299
|
+
return { kind: "tuple", types: ret };
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
checkBuiltin(name, expr, argTypes, scope) {
|
|
303
|
+
switch (name) {
|
|
304
|
+
case "len":
|
|
305
|
+
case "cap":
|
|
306
|
+
return INT;
|
|
307
|
+
case "append":
|
|
308
|
+
return argTypes[0] ?? ANY;
|
|
309
|
+
case "copy":
|
|
310
|
+
return INT;
|
|
311
|
+
case "delete":
|
|
312
|
+
return VOID;
|
|
313
|
+
case "make": {
|
|
314
|
+
if (expr.args.length < 1) return ANY;
|
|
315
|
+
const typeArg = expr.args[0];
|
|
316
|
+
const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
|
|
317
|
+
return this.resolveTypeNode(typeNode, scope) ?? ANY;
|
|
318
|
+
}
|
|
319
|
+
case "new": {
|
|
320
|
+
if (expr.args.length < 1) return ANY;
|
|
321
|
+
const inner = this.resolveTypeNode(expr.args[0], scope);
|
|
322
|
+
return { kind: "pointer", base: inner };
|
|
323
|
+
}
|
|
324
|
+
case "print":
|
|
325
|
+
case "println":
|
|
326
|
+
case "panic":
|
|
327
|
+
return VOID;
|
|
328
|
+
case "recover":
|
|
329
|
+
return ANY;
|
|
330
|
+
case "error":
|
|
331
|
+
return ERROR;
|
|
332
|
+
case "min":
|
|
333
|
+
case "max":
|
|
334
|
+
return argTypes[0] ?? ANY;
|
|
335
|
+
case "clear":
|
|
336
|
+
return VOID;
|
|
337
|
+
default:
|
|
338
|
+
return ANY;
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
checkCompositeLit(expr, scope, hintType = null) {
|
|
343
|
+
// null typeExpr = implicit lit inside a slice/map: {X:1} in []Point{{X:1}}
|
|
344
|
+
const t =
|
|
345
|
+
expr.typeExpr === null
|
|
346
|
+
? (hintType ?? ANY)
|
|
347
|
+
: this.resolveTypeNode(expr.typeExpr, scope);
|
|
348
|
+
const base = t.kind === "named" ? t.underlying : t;
|
|
349
|
+
|
|
350
|
+
if (base?.kind === "struct") {
|
|
351
|
+
// Detect positional (unkeyed) literal: all elements must be non-KeyValueExpr
|
|
352
|
+
const hasPositional = expr.elems.some((e) => e.kind !== "KeyValueExpr");
|
|
353
|
+
const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
|
|
354
|
+
if (hasPositional && hasKeyed) {
|
|
355
|
+
this.err("mixture of field:value and value initializers", expr);
|
|
356
|
+
}
|
|
357
|
+
if (hasPositional) {
|
|
358
|
+
// Map positional elements to struct fields by declaration order
|
|
359
|
+
const fieldNames = [...(base.fields?.keys() ?? [])];
|
|
360
|
+
for (let i = 0; i < expr.elems.length; i++) {
|
|
361
|
+
const elem = expr.elems[i];
|
|
362
|
+
const fieldName = fieldNames[i];
|
|
363
|
+
if (!fieldName) {
|
|
364
|
+
this.err("too many values in struct literal", elem);
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
const fieldType = base.fields.get(fieldName);
|
|
368
|
+
const vt = this.checkExpr(elem, scope);
|
|
369
|
+
if (fieldType) this.assertAssignable(fieldType, vt, elem);
|
|
370
|
+
// Annotate for codegen
|
|
371
|
+
elem._positionalField = fieldName;
|
|
372
|
+
}
|
|
373
|
+
return t;
|
|
374
|
+
}
|
|
375
|
+
for (const elem of expr.elems) {
|
|
376
|
+
if (elem.kind === "KeyValueExpr") {
|
|
377
|
+
const keyName = elem.key.name;
|
|
378
|
+
const fieldType = base.fields?.get(keyName);
|
|
379
|
+
if (fieldType) {
|
|
380
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
381
|
+
this.assertAssignable(fieldType, vt, elem.value);
|
|
382
|
+
} else {
|
|
383
|
+
// Check if it's an embedded type name (e.g. Dog{Animal: Animal{...}})
|
|
384
|
+
const embed = base._embeds?.find(
|
|
385
|
+
(e) => (e.kind === "named" ? e.name : null) === keyName,
|
|
386
|
+
);
|
|
387
|
+
if (embed) {
|
|
388
|
+
const vt = this.checkExpr(elem.value, scope);
|
|
389
|
+
this.assertAssignable(embed, vt, elem.value);
|
|
390
|
+
elem._isEmbedInit = true;
|
|
391
|
+
} else {
|
|
392
|
+
this.err(`Unknown field '${keyName}'`, elem.key);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
this.checkExpr(elem, scope);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return t;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (base?.kind === "slice" || base?.kind === "array") {
|
|
403
|
+
for (const elem of expr.elems) {
|
|
404
|
+
if (elem.kind === "CompositeLit" && elem.typeExpr === null) {
|
|
405
|
+
const et = this.checkCompositeLit(elem, scope, base.elem);
|
|
406
|
+
elem._type = et;
|
|
407
|
+
} else if (elem.kind !== "KeyValueExpr") {
|
|
408
|
+
const et = this.checkExpr(elem, scope);
|
|
409
|
+
this.assertAssignable(base.elem, et, elem);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return t;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if (base?.kind === "map") {
|
|
416
|
+
for (const elem of expr.elems) {
|
|
417
|
+
if (elem.kind === "KeyValueExpr") {
|
|
418
|
+
const kt = this.checkExpr(elem.key, scope);
|
|
419
|
+
// value may be an implicit composite lit
|
|
420
|
+
let vt;
|
|
421
|
+
if (
|
|
422
|
+
elem.value.kind === "CompositeLit" &&
|
|
423
|
+
elem.value.typeExpr === null
|
|
424
|
+
) {
|
|
425
|
+
vt = this.checkCompositeLit(elem.value, scope, base.value);
|
|
426
|
+
elem.value._type = vt;
|
|
427
|
+
} else {
|
|
428
|
+
vt = this.checkExpr(elem.value, scope);
|
|
429
|
+
}
|
|
430
|
+
this.assertAssignable(base.key, kt, elem.key);
|
|
431
|
+
this.assertAssignable(base.value, vt, elem.value);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return t;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Unknown type context — still check elements
|
|
438
|
+
for (const elem of expr.elems) {
|
|
439
|
+
if (elem.kind === "KeyValueExpr") {
|
|
440
|
+
this.checkExpr(elem.value, scope);
|
|
441
|
+
} else {
|
|
442
|
+
this.checkExpr(elem, scope);
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
return t ?? ANY;
|
|
446
|
+
},
|
|
447
|
+
};
|