xq-lsp 1.8.2 → 1.8.3-next.430

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.
@@ -1,6 +1,6 @@
1
1
  import { formatQName, qnameKey } from "./types.js";
2
- import { findAll, isTerminal, directChildOf } from "./analyzer.js";
3
- import { asFunctionCall, asVarRef, asTypedBinding, literalKind, isPathExpr, argExpr } from "./ast-nodes.js";
2
+ import { findAll, isTerminal, directChildOf, directChildrenOf, firstTerminalValue, parseEQName, resolvePrefix, XMLNS_ARRAY, XMLNS_FN, XMLNS_MAP, } from "./analyzer.js";
3
+ import { asArithmeticExpr, asFunctionCall, asNamedFunctionRef, asVarRef, asVarName, literalKind, isPathExpr, argExpr } from "./ast-nodes.js";
4
4
  // ── Type constants ───────────────────────────────────────────────────────────
5
5
  const UNKNOWN = { kind: "unknown", occurrence: "" };
6
6
  // ── Type parsing ─────────────────────────────────────────────────────────────
@@ -174,15 +174,146 @@ export function isAssignable(from, to) {
174
174
  return isNodeSubtype(from.name, to.name);
175
175
  return true;
176
176
  }
177
+ // ── Type algebra ──────────────────────────────────────────────────────────────
178
+ const EMPTY = { kind: "empty", occurrence: "" };
179
+ const BOOLEAN = { kind: "atomic", name: "xs:boolean", occurrence: "" };
180
+ const STRING = { kind: "atomic", name: "xs:string", occurrence: "" };
181
+ const INTEGER = { kind: "atomic", name: "xs:integer", occurrence: "" };
182
+ const ONE = { min: 1, max: 1 };
183
+ function cardOf(t) {
184
+ if (t.kind === "empty")
185
+ return { min: 0, max: 0 };
186
+ if (t.kind === "unknown")
187
+ return { min: 0, max: 2 };
188
+ switch (t.occurrence) {
189
+ case "?":
190
+ return { min: 0, max: 1 };
191
+ case "+":
192
+ return { min: 1, max: 2 };
193
+ case "*":
194
+ return { min: 0, max: 2 };
195
+ default:
196
+ return ONE;
197
+ }
198
+ }
199
+ function withCard(t, c) {
200
+ if (t.kind === "unknown")
201
+ return t;
202
+ if (c.max === 0 || t.kind === "empty")
203
+ return EMPTY;
204
+ const occurrence = c.min === 1 ? (c.max === 1 ? "" : "+") : c.max === 1 ? "?" : "*";
205
+ return { ...t, occurrence };
206
+ }
207
+ /** Cardinality of evaluating `b` once per item of `a` (FLWOR `for`, `!`, path steps). */
208
+ function multiplyCard(a, b) {
209
+ const min = a.min === 1 && b.min === 1 ? 1 : 0;
210
+ const max = a.max === 0 || b.max === 0 ? 0 : a.max === 1 && b.max === 1 ? 1 : 2;
211
+ return { min, max };
212
+ }
213
+ /** The type of a single item of `t` — e.g. what a `for` variable over `t` holds. */
214
+ export function itemTypeOf(t) {
215
+ if (t.kind === "unknown" || t.kind === "empty")
216
+ return t;
217
+ return t.occurrence === "" ? t : { ...t, occurrence: "" };
218
+ }
219
+ function derivesFrom(from, to) {
220
+ for (let cur = from; cur !== undefined; cur = ATOMIC_TYPE_PARENT[cur]) {
221
+ if (cur === to)
222
+ return true;
223
+ }
224
+ return false;
225
+ }
226
+ // Nearest common supertype in the derivation hierarchy (no promotion), falling back to
227
+ // xs:numeric for mixed numerics (e.g. `if (…) then 1 else 1e0`) and xs:anyAtomicType otherwise.
228
+ function commonAtomicSupertype(a, b) {
229
+ for (let cur = a; cur !== undefined && cur !== "xs:anyAtomicType"; cur = ATOMIC_TYPE_PARENT[cur]) {
230
+ if (derivesFrom(b, cur))
231
+ return cur;
232
+ }
233
+ if (isAtomicSubtype(a, "xs:numeric") && isAtomicSubtype(b, "xs:numeric"))
234
+ return "xs:numeric";
235
+ return "xs:anyAtomicType";
236
+ }
237
+ // Least upper bound of two (non-empty, known) item types, ignoring occurrence.
238
+ function itemUnion(a, b) {
239
+ if (a.kind === b.kind) {
240
+ switch (a.kind) {
241
+ case "atomic":
242
+ return { kind: "atomic", name: commonAtomicSupertype(a.name ?? "", b.name ?? ""), occurrence: "" };
243
+ case "node":
244
+ return { kind: "node", name: a.name === b.name ? a.name : "node", occurrence: "" };
245
+ case "map":
246
+ case "array":
247
+ case "function":
248
+ return { kind: a.kind, name: a.name === b.name ? a.name : `${a.kind}(*)`, occurrence: "" };
249
+ default:
250
+ return { kind: a.kind, occurrence: "" };
251
+ }
252
+ }
253
+ return { kind: "item", occurrence: "" };
254
+ }
255
+ /** Type of an expression that yields either `a` or `b` (if/else, switch, typeswitch, try/catch). */
256
+ export function choiceType(a, b) {
257
+ if (a.kind === "unknown" || b.kind === "unknown")
258
+ return UNKNOWN;
259
+ if (a.kind === "empty" && b.kind === "empty")
260
+ return EMPTY;
261
+ const ca = cardOf(a);
262
+ const cb = cardOf(b);
263
+ const card = { min: ca.min === 1 && cb.min === 1 ? 1 : 0, max: ca.max > cb.max ? ca.max : cb.max };
264
+ if (a.kind === "empty")
265
+ return withCard(b, card);
266
+ if (b.kind === "empty")
267
+ return withCard(a, card);
268
+ return withCard(itemUnion(a, b), card);
269
+ }
270
+ /** Type of the sequence `(a, b)`. */
271
+ export function concatType(a, b) {
272
+ if (a.kind === "unknown" || b.kind === "unknown")
273
+ return UNKNOWN;
274
+ if (a.kind === "empty")
275
+ return b;
276
+ if (b.kind === "empty")
277
+ return a;
278
+ const ca = cardOf(a);
279
+ const cb = cardOf(b);
280
+ return withCard(itemUnion(a, b), { min: 1, max: ca.max + cb.max > 1 ? 2 : 1 });
281
+ }
282
+ // Splits the parameter list of a parameterized type name, e.g. "map(xs:string, item()*)"
283
+ // → ["xs:string", "item()*"], respecting nested parentheses.
284
+ function typeArgs(name) {
285
+ const open = name.indexOf("(");
286
+ const inner = name.slice(open + 1, name.lastIndexOf(")"));
287
+ const args = [];
288
+ let depth = 0;
289
+ let startIdx = 0;
290
+ for (let i = 0; i < inner.length; i++) {
291
+ if (inner[i] === "(")
292
+ depth++;
293
+ else if (inner[i] === ")")
294
+ depth--;
295
+ else if (inner[i] === "," && depth === 0) {
296
+ args.push(inner.slice(startIdx, i).trim());
297
+ startIdx = i + 1;
298
+ }
299
+ }
300
+ args.push(inner.slice(startIdx).trim());
301
+ return args.filter((a) => a !== "");
302
+ }
303
+ function typeText(t, fallback) {
304
+ return t.kind === "unknown" ? fallback : formatType(t);
305
+ }
177
306
  // ── Type inference ────────────────────────────────────────────────────────────
178
307
  const LITERAL_TYPE = {
179
- string: { kind: "atomic", name: "xs:string", occurrence: "" },
180
- integer: { kind: "atomic", name: "xs:integer", occurrence: "" },
308
+ string: STRING,
309
+ integer: INTEGER,
181
310
  decimal: { kind: "atomic", name: "xs:decimal", occurrence: "" },
182
311
  double: { kind: "atomic", name: "xs:double", occurrence: "" },
183
312
  };
184
313
  const NODE_STEP = { kind: "node", name: "node", occurrence: "*" };
185
314
  const RANGE_SEQUENCE = { kind: "atomic", name: "xs:integer", occurrence: "*" };
315
+ /** Scope key under which the context item's type (`.`) is tracked, e.g. inside `!` and predicates. */
316
+ const CONTEXT_ITEM_KEY = ".";
186
317
  // Reuses the existing xs:numeric union check and subtype/promotion chain (isAtomicSubtype)
187
318
  // rather than a separate hardcoded numeric ordering.
188
319
  function numericAtomicName(t) {
@@ -195,18 +326,79 @@ function widenNumeric(a, b) {
195
326
  return a;
196
327
  return "xs:double"; // neither promotes to the other — fall back to the widest common type
197
328
  }
198
- // Result type of a `+`/`-`/`*`/`div`/`idiv`/`mod` operator per XPath F&O arithmetic rules.
329
+ const UNTYPED_ATOMIC = "xs:untypedAtomic";
330
+ // Atomized item type of `t`, keeping its cardinality. Nodes are assumed untyped (not
331
+ // schema-validated), so element/attribute/text/document values atomize to xs:untypedAtomic;
332
+ // comments and processing instructions atomize to xs:string.
333
+ function atomize(t) {
334
+ if (t.kind === "atomic" || t.kind === "empty")
335
+ return t;
336
+ if (t.kind === "node") {
337
+ const name = t.name === "comment" || t.name === "processing-instruction" ? "xs:string" : UNTYPED_ATOMIC;
338
+ return { kind: "atomic", name, occurrence: t.occurrence };
339
+ }
340
+ return UNKNOWN;
341
+ }
342
+ /** Arithmetic and aggregate functions cast xs:untypedAtomic operands to xs:double. */
343
+ function arithmeticOperandName(t) {
344
+ return t.name === UNTYPED_ATOMIC ? "xs:double" : t.name;
345
+ }
346
+ const YEAR_MONTH = "xs:yearMonthDuration";
347
+ const DAY_TIME = "xs:dayTimeDuration";
348
+ const isDuration = (n) => n === YEAR_MONTH || n === DAY_TIME;
349
+ const isDateOrDateTime = (n) => n === "xs:date" || n === "xs:dateTime";
350
+ // Item type of `a op b` for single atomic operands, per the XPath 3.1 operator mapping table
351
+ // (numeric arithmetic, date/time subtraction, date/time ± duration, duration scaling).
352
+ function arithmeticItemName(op, a, b) {
353
+ const aNum = isAtomicSubtype(a, "xs:numeric");
354
+ const bNum = isAtomicSubtype(b, "xs:numeric");
355
+ if (aNum && bNum) {
356
+ if (op === "idiv")
357
+ return "xs:integer";
358
+ if (op === "div" && isAtomicSubtype(a, "xs:decimal") && isAtomicSubtype(b, "xs:decimal")) {
359
+ return widenNumeric(widenNumeric(a, b), "xs:decimal"); // integer div integer is never exact
360
+ }
361
+ return widenNumeric(a, b);
362
+ }
363
+ const additive = op === "+" || op === "-";
364
+ if (op === "-" && a === b && (isDateOrDateTime(a) || a === "xs:time"))
365
+ return DAY_TIME;
366
+ if (additive && isDateOrDateTime(a) && isDuration(b))
367
+ return a;
368
+ if (additive && a === "xs:time" && b === DAY_TIME)
369
+ return a;
370
+ if (op === "+" && isDuration(a) && (isDateOrDateTime(b) || (b === "xs:time" && a === DAY_TIME)))
371
+ return b;
372
+ if (additive && isDuration(a) && a === b)
373
+ return a;
374
+ if ((op === "*" || op === "div") && isDuration(a) && bNum)
375
+ return a;
376
+ if (op === "*" && aNum && isDuration(b))
377
+ return b;
378
+ if (op === "div" && isDuration(a) && a === b)
379
+ return "xs:decimal";
380
+ return null;
381
+ }
382
+ // Result type of a `+`/`-`/`*`/`div`/`idiv`/`mod` operator: operands are atomized, an empty
383
+ // operand gives (), an optional one makes the result optional, and more than one item is a
384
+ // type error (so we don't guess).
199
385
  function arithmeticResult(op, a, b) {
200
- const aName = numericAtomicName(a);
201
- const bName = numericAtomicName(b);
202
- if (!aName || !bName)
386
+ const ta = atomize(a);
387
+ const tb = atomize(b);
388
+ if (ta.kind === "unknown" || tb.kind === "unknown")
203
389
  return UNKNOWN;
204
- if (op === "idiv")
205
- return { kind: "atomic", name: "xs:integer", occurrence: "" };
206
- if (op === "div" && aName === "xs:integer" && bName === "xs:integer") {
207
- return { kind: "atomic", name: "xs:decimal", occurrence: "" }; // integer div integer is never exact
208
- }
209
- return { kind: "atomic", name: widenNumeric(aName, bName), occurrence: "" };
390
+ if (ta.kind === "empty" || tb.kind === "empty")
391
+ return EMPTY;
392
+ const ca = cardOf(ta);
393
+ const cb = cardOf(tb);
394
+ if (ca.max > 1 || cb.max > 1)
395
+ return UNKNOWN;
396
+ const aName = arithmeticOperandName(ta);
397
+ const bName = arithmeticOperandName(tb);
398
+ const name = aName && bName ? arithmeticItemName(op, aName, bName) : null;
399
+ if (!name)
400
+ return UNKNOWN;
401
+ return { kind: "atomic", name, occurrence: ca.min === 1 && cb.min === 1 ? "" : "?" };
210
402
  }
211
403
  function allFunctionsFlat(analysis, importedAnalyses) {
212
404
  const fns = [...analysis.functions];
@@ -214,15 +406,677 @@ function allFunctionsFlat(analysis, importedAnalyses) {
214
406
  fns.push(...a.functions);
215
407
  return fns;
216
408
  }
217
- function inferFunctionReturn(node, analysis, allFns) {
218
- const call = asFunctionCall(node, analysis);
219
- if (!call)
220
- return UNKNOWN;
221
- const fn = allFns.find((f) => f.qname.namespaceUri === call.qname.namespaceUri &&
222
- f.qname.localName === call.qname.localName &&
223
- f.arity === call.args.length);
409
+ /** Finds the declaration a call with `argCount` arguments resolves to, honouring variadic functions. */
410
+ export function resolveFunction(qname, argCount, allFns) {
411
+ return allFns.find((f) => f.qname.namespaceUri === qname.namespaceUri &&
412
+ f.qname.localName === qname.localName &&
413
+ (f.variadic ? argCount >= f.arity : f.arity === argCount));
414
+ }
415
+ /** Concatenated terminal text of a node — enough to reconstruct a SequenceType for `parseType`. */
416
+ function nodeText(node) {
417
+ if (isTerminal(node))
418
+ return node.value;
419
+ return node.children.map(nodeText).join("");
420
+ }
421
+ function sequenceTypeOf(node) {
422
+ if (!node)
423
+ return undefined;
424
+ const text = nodeText(node);
425
+ // Typeswitch case unions (`case xs:integer | xs:string`) are a choice of their members.
426
+ if (text.includes("|"))
427
+ return text.split("|").map(parseType).reduce(choiceType);
428
+ return parseType(text);
429
+ }
430
+ /** The declared `as SequenceType` of a binding, parameter or variable declaration, if any. */
431
+ function declaredTypeOf(node) {
432
+ const typeDecl = directChildOf(node, "TypeDeclaration");
433
+ return typeDecl ? sequenceTypeOf(directChildOf(typeDecl, "SequenceType")) : undefined;
434
+ }
435
+ // Generic built-ins whose declared signature (item()*) loses the argument's item type.
436
+ // Maps local name → the cardinality of the result relative to the first argument's item type.
437
+ const FN_SEQUENCE_PRESERVING = {
438
+ head: { min: 0, max: 1 },
439
+ "exactly-one": ONE,
440
+ "zero-or-one": { min: 0, max: 1 },
441
+ "one-or-more": { min: 1, max: 2 },
442
+ tail: { min: 0, max: 2 },
443
+ subsequence: { min: 0, max: 2 },
444
+ remove: { min: 0, max: 2 },
445
+ filter: { min: 0, max: 2 },
446
+ reverse: "same",
447
+ unordered: "same",
448
+ sort: "same",
449
+ foot: { min: 0, max: 1 },
450
+ trunk: { min: 0, max: 2 },
451
+ };
452
+ /** `R` of a single `function(…) as R` item, if known. */
453
+ function functionResultType(f) {
454
+ if (f.kind !== "function" || f.occurrence !== "" || !f.name?.includes(" as "))
455
+ return undefined;
456
+ const r = parseType(f.name.slice(f.name.lastIndexOf(" as ") + 4));
457
+ return r.kind === "unknown" ? undefined : r;
458
+ }
459
+ /** Member types of a single map(K, V) / array(T): `[K, V]` or `[T]`, or undefined for `map(*)` etc. */
460
+ function containerArgs(t, kind) {
461
+ if (t.kind !== kind || t.occurrence !== "" || !t.name)
462
+ return undefined;
463
+ const args = typeArgs(t.name).map(parseType);
464
+ if (args.length !== (kind === "map" ? 2 : 1) || args.some((a) => a.kind === "unknown"))
465
+ return undefined;
466
+ return args;
467
+ }
468
+ // Aggregates (sum/avg/min/max): the atomized item name with untypedAtomic cast to xs:double,
469
+ // or undefined when it isn't numeric or a duration (or, for min/max, another comparable type).
470
+ function aggregateItemName(t, allowAnyAtomic) {
471
+ const atomized = atomize(t);
472
+ if (atomized.kind !== "atomic")
473
+ return undefined;
474
+ const name = arithmeticOperandName(atomized);
475
+ if (!name)
476
+ return undefined;
477
+ if (isAtomicSubtype(name, "xs:numeric") || isDuration(name) || allowAnyAtomic)
478
+ return name;
479
+ return undefined;
480
+ }
481
+ // Built-ins whose declared signature (item()*, xs:anyAtomicType*, …) loses information the
482
+ // arguments carry: aggregates, atomization, higher-order functions, and map/array accessors.
483
+ function inferPolymorphicBuiltin(ns, name, args) {
484
+ const [first, second, third] = args;
485
+ if (!first || first.kind === "unknown")
486
+ return undefined;
487
+ const card = cardOf(first);
488
+ if (ns === XMLNS_FN) {
489
+ if (name in FN_SEQUENCE_PRESERVING) {
490
+ const rule = FN_SEQUENCE_PRESERVING[name];
491
+ return rule === "same" ? first : withCard(first, rule);
492
+ }
493
+ switch (name) {
494
+ case "data":
495
+ return args.length === 1 ? atomize(first) : undefined;
496
+ case "distinct-values": {
497
+ const atomized = atomize(first);
498
+ return atomized.kind === "unknown" ? undefined : withCard(atomized, { min: card.min, max: card.max });
499
+ }
500
+ case "sum": {
501
+ if (first.kind === "empty")
502
+ return args.length === 1 ? INTEGER : second;
503
+ const item = aggregateItemName(first, false);
504
+ if (!item)
505
+ return undefined;
506
+ const t = { kind: "atomic", name: item, occurrence: "" };
507
+ // sum(()) is 0, or the explicit zero value when one is given.
508
+ return card.min === 1 || args.length === 1 ? t : second && choiceType(t, second);
509
+ }
510
+ case "avg":
511
+ case "min":
512
+ case "max": {
513
+ if (first.kind === "empty")
514
+ return EMPTY;
515
+ const item = aggregateItemName(first, name !== "avg");
516
+ if (!item)
517
+ return undefined;
518
+ const avgName = isAtomicSubtype(item, "xs:decimal") ? "xs:decimal" : item;
519
+ return { kind: "atomic", name: name === "avg" ? avgName : item, occurrence: card.min === 1 ? "" : "?" };
520
+ }
521
+ case "for-each": {
522
+ const r = second && functionResultType(second);
523
+ return r && withCard(r, multiplyCard(card, cardOf(r)));
524
+ }
525
+ case "for-each-pair": {
526
+ const r = third && functionResultType(third);
527
+ if (!r || !second || second.kind === "unknown")
528
+ return undefined;
529
+ const other = cardOf(second);
530
+ const pairs = { min: card.min === 1 && other.min === 1 ? 1 : 0, max: card.max < other.max ? card.max : other.max };
531
+ return withCard(r, multiplyCard(pairs, cardOf(r)));
532
+ }
533
+ }
534
+ return undefined;
535
+ }
536
+ if (ns === XMLNS_MAP) {
537
+ switch (name) {
538
+ case "entry": {
539
+ const key = atomize(first);
540
+ if (key.kind !== "atomic" || !second || second.kind === "unknown")
541
+ return undefined;
542
+ return { kind: "map", name: `map(${formatType(itemTypeOf(key))}, ${formatType(second)})`, occurrence: "" };
543
+ }
544
+ }
545
+ const kv = containerArgs(first, "map");
546
+ if (!kv)
547
+ return undefined;
548
+ const [key, value] = kv;
549
+ switch (name) {
550
+ case "get":
551
+ return withCard(value, { min: 0, max: cardOf(value).max });
552
+ case "keys":
553
+ return withCard(key, { min: 0, max: 2 });
554
+ case "remove":
555
+ return first;
556
+ case "put": {
557
+ if (!second || !third || second.kind === "unknown" || third.kind === "unknown")
558
+ return undefined;
559
+ const newKey = atomize(itemTypeOf(second));
560
+ if (newKey.kind !== "atomic")
561
+ return undefined;
562
+ return { kind: "map", name: `map(${formatType(itemUnion(key, newKey))}, ${formatType(choiceType(value, third))})`, occurrence: "" };
563
+ }
564
+ case "for-each": {
565
+ const r = second && functionResultType(second);
566
+ return r && withCard(r, multiplyCard({ min: 0, max: 2 }, cardOf(r)));
567
+ }
568
+ }
569
+ return undefined;
570
+ }
571
+ if (ns === XMLNS_ARRAY) {
572
+ const members = containerArgs(first, "array");
573
+ if (!members)
574
+ return undefined;
575
+ const [member] = members;
576
+ switch (name) {
577
+ case "get":
578
+ case "head":
579
+ case "foot":
580
+ return member;
581
+ case "tail":
582
+ case "trunk":
583
+ case "reverse":
584
+ case "subarray":
585
+ case "remove":
586
+ case "filter":
587
+ case "sort":
588
+ return first;
589
+ case "append":
590
+ return second && second.kind !== "unknown" ? { kind: "array", name: `array(${formatType(choiceType(member, second))})`, occurrence: "" } : undefined;
591
+ case "for-each": {
592
+ const r = second && functionResultType(second);
593
+ return r && { kind: "array", name: `array(${formatType(r)})`, occurrence: "" };
594
+ }
595
+ }
596
+ }
597
+ return undefined;
598
+ }
599
+ function inferCallResult(qname, argTypes, argCount, allFns) {
600
+ if (argCount >= 1 && (qname.namespaceUri === XMLNS_FN || qname.namespaceUri === XMLNS_MAP || qname.namespaceUri === XMLNS_ARRAY)) {
601
+ const special = inferPolymorphicBuiltin(qname.namespaceUri, qname.localName, argTypes());
602
+ if (special)
603
+ return special;
604
+ }
605
+ const fn = resolveFunction(qname, argCount, allFns);
224
606
  return fn?.returnType ? parseType(fn.returnType) : UNKNOWN;
225
607
  }
608
+ function functionTypeOf(fn) {
609
+ const params = fn.params.map((p) => p.type ?? "item()*").join(", ");
610
+ return { kind: "function", name: `function(${params}) as ${fn.returnType ?? "item()*"}`, occurrence: "" };
611
+ }
612
+ // Kind of node selected by an axis step: `@x`/`attribute::x` → attribute, a plain name test on
613
+ // any other axis → element, a kind test → that kind, `..` → any node.
614
+ function axisStepType(step) {
615
+ const text = nodeText(step);
616
+ if (text.startsWith(".."))
617
+ return NODE_STEP;
618
+ const kindTests = findAll(step, "KindTest");
619
+ if (kindTests.length > 0) {
620
+ const kind = nodeText(kindTests[0]).replace(/\(.*$/s, "");
621
+ const name = kind === "schema-element" ? "element" : kind === "schema-attribute" ? "attribute" : kind;
622
+ return { kind: "node", name, occurrence: "*" };
623
+ }
624
+ const isAttributeAxis = text.startsWith("@") || text.startsWith("attribute::");
625
+ return { kind: "node", name: isAttributeAxis ? "attribute" : "element", occurrence: "*" };
626
+ }
627
+ function inferPathExpr(nt, varTypes, analysis, allFns) {
628
+ const steps = nt.children.filter((c) => !isTerminal(c));
629
+ if (steps.length === 0)
630
+ return { kind: "node", name: "document-node", occurrence: "" }; // lone "/"
631
+ // Every step before the last feeds the path's navigation — if one provably isn't a node
632
+ // (a literal, an atomic function result, …) the path is a type error; don't guess.
633
+ const definitelyNotNodes = steps.slice(0, -1).some((step) => {
634
+ const stepType = inferExprType(step, varTypes, analysis, allFns);
635
+ return stepType.kind !== "unknown" && stepType.kind !== "node" && stepType.kind !== "item";
636
+ });
637
+ if (definitelyNotNodes)
638
+ return UNKNOWN;
639
+ const last = inferExprType(steps[steps.length - 1], varTypes, analysis, allFns);
640
+ if (last.kind === "node" || last.kind === "atomic")
641
+ return withCard(last, { min: 0, max: 2 });
642
+ return NODE_STEP;
643
+ }
644
+ function inferPostfixExpr(nt, varTypes, analysis, allFns) {
645
+ const [base, ...postfixes] = nt.children.filter((c) => !isTerminal(c));
646
+ let t = inferExprType(base, varTypes, analysis, allFns);
647
+ for (const postfix of postfixes) {
648
+ if (t.kind === "unknown")
649
+ return UNKNOWN;
650
+ const card = cardOf(t);
651
+ if (postfix.type === "Predicate") {
652
+ const expr = directChildOf(postfix, "Expr");
653
+ const predScope = new Map(varTypes);
654
+ predScope.set(CONTEXT_ITEM_KEY, itemTypeOf(t));
655
+ const predType = expr ? inferExprType(expr, predScope, analysis, allFns) : UNKNOWN;
656
+ // A single numeric predicate (`$seq[1]`, `$seq[$i]`) selects at most one item.
657
+ const positional = predType.occurrence === "" && numericAtomicName(predType) !== null;
658
+ t = withCard(t, { min: 0, max: positional && card.max > 1 ? 1 : card.max });
659
+ }
660
+ else if (postfix.type === "Lookup") {
661
+ t = lookupType(t, postfix);
662
+ }
663
+ else if (postfix.type === "ArgumentList" && t.kind === "function" && t.occurrence === "" && t.name?.includes(" as ")) {
664
+ t = parseType(t.name.slice(t.name.lastIndexOf(" as ") + 4));
665
+ }
666
+ else {
667
+ return UNKNOWN;
668
+ }
669
+ }
670
+ return t;
671
+ }
672
+ // `$m?key` / `$a?1` / `$x?*` on a map(K, V) or array(T).
673
+ function lookupType(t, lookup) {
674
+ if ((t.kind !== "map" && t.kind !== "array") || !t.name)
675
+ return UNKNOWN;
676
+ const args = typeArgs(t.name);
677
+ const memberText = t.kind === "map" ? args[1] : args[0];
678
+ if (!memberText || memberText === "*")
679
+ return UNKNOWN;
680
+ const member = parseType(memberText);
681
+ if (member.kind === "unknown")
682
+ return UNKNOWN;
683
+ const wildcard = nodeText(lookup).endsWith("*");
684
+ // A map lookup of a missing key yields (); an array lookup out of bounds is an error.
685
+ const perLookup = wildcard ? { min: 0, max: 2 } : t.kind === "map" ? { min: 0, max: cardOf(member).max } : cardOf(member);
686
+ const each = wildcard || t.kind === "map" ? multiplyCard(perLookup, cardOf(member)) : perLookup;
687
+ return withCard(member, multiplyCard(cardOf(t), each));
688
+ }
689
+ function inferMapConstructor(nt, varTypes, analysis, allFns) {
690
+ const entries = directChildrenOf(nt, "MapConstructorEntry");
691
+ if (entries.length === 0)
692
+ return { kind: "map", name: "map(*)", occurrence: "" };
693
+ let key;
694
+ let value;
695
+ for (const entry of entries) {
696
+ const k = directChildOf(entry, "MapKeyExpr");
697
+ const v = directChildOf(entry, "MapValueExpr");
698
+ const kt = k ? itemTypeOf(inferExprType(k, varTypes, analysis, allFns)) : UNKNOWN;
699
+ const vt = v ? inferExprType(v, varTypes, analysis, allFns) : UNKNOWN;
700
+ key = key === undefined ? kt : kt.kind === "unknown" || key.kind === "unknown" ? UNKNOWN : itemUnion(key, kt);
701
+ value = value === undefined ? vt : choiceType(value, vt);
702
+ }
703
+ const keyText = key && key.kind === "atomic" ? formatType(key) : "xs:anyAtomicType";
704
+ return { kind: "map", name: `map(${keyText}, ${typeText(value ?? UNKNOWN, "item()*")})`, occurrence: "" };
705
+ }
706
+ function inferArrayConstructor(nt, varTypes, analysis, allFns) {
707
+ let member;
708
+ if (nt.type === "SquareArrayConstructor") {
709
+ for (const m of directChildrenOf(nt, "ExprSingle")) {
710
+ const mt = inferExprType(m, varTypes, analysis, allFns);
711
+ member = member === undefined ? mt : choiceType(member, mt);
712
+ }
713
+ }
714
+ else {
715
+ // array { E } has one member per item of E.
716
+ const enclosed = directChildOf(nt, "EnclosedExpr");
717
+ const et = enclosed ? inferExprType(enclosed, varTypes, analysis, allFns) : EMPTY;
718
+ if (et.kind !== "empty")
719
+ member = itemTypeOf(et);
720
+ }
721
+ if (member === undefined || member.kind === "empty")
722
+ return { kind: "array", name: "array(*)", occurrence: "" };
723
+ return { kind: "array", name: `array(${typeText(member, "item()*")})`, occurrence: "" };
724
+ }
725
+ function inferArrowExpr(nt, varTypes, analysis, allFns) {
726
+ let t;
727
+ let op;
728
+ for (let i = 0; i < nt.children.length; i++) {
729
+ const c = nt.children[i];
730
+ if (t === undefined) {
731
+ t = inferExprType(c, varTypes, analysis, allFns);
732
+ continue;
733
+ }
734
+ if (isTerminal(c)) {
735
+ op = c.value;
736
+ continue;
737
+ }
738
+ if (c.type !== "ArrowFunctionSpecifier")
739
+ continue;
740
+ const eqname = directChildOf(c, "EQName");
741
+ const argList = nt.children[i + 1];
742
+ if (op !== "=>" || !eqname || argList?.type !== "ArgumentList")
743
+ return UNKNOWN;
744
+ const raw = firstTerminalValue(eqname);
745
+ if (!raw)
746
+ return UNKNOWN;
747
+ const { prefix, localName, uri } = parseEQName(raw);
748
+ const qname = { localName, namespaceUri: uri ?? resolvePrefix(prefix, analysis) };
749
+ const args = directChildrenOf(argList, "Argument");
750
+ const piped = t;
751
+ t = inferCallResult(qname, () => [piped, ...args.map((a) => inferExprType(a, varTypes, analysis, allFns))], args.length + 1, allFns);
752
+ i++; // skip the ArgumentList we just consumed
753
+ }
754
+ return t ?? UNKNOWN;
755
+ }
756
+ function flworClauses(flwor) {
757
+ const out = [];
758
+ const unwrap = (n) => {
759
+ if (isTerminal(n))
760
+ return;
761
+ if (n.type === "InitialClause" || n.type === "IntermediateClause") {
762
+ for (const c of n.children)
763
+ unwrap(c);
764
+ }
765
+ else {
766
+ out.push(n);
767
+ }
768
+ };
769
+ for (const c of flwor.children)
770
+ unwrap(c);
771
+ return out;
772
+ }
773
+ function bindVarName(nameNode, analysis, t, scope) {
774
+ if (!nameNode)
775
+ return null;
776
+ const raw = firstTerminalValue(nameNode);
777
+ if (!raw)
778
+ return null;
779
+ const { prefix, localName, uri } = parseEQName(raw);
780
+ const key = qnameKey({ prefix, localName, namespaceUri: uri ?? (prefix ? resolvePrefix(prefix, analysis) : "") });
781
+ scope.set(key, t);
782
+ return key;
783
+ }
784
+ function processFLWOR(flwor, outer, analysis, allFns, visitor = {}) {
785
+ const scope = new Map(outer);
786
+ const boundHere = [];
787
+ let card = ONE;
788
+ let result = UNKNOWN;
789
+ const evaluate = (expr) => {
790
+ if (!expr)
791
+ return UNKNOWN;
792
+ visitor.expr?.(expr, scope);
793
+ return inferExprType(expr, scope, analysis, allFns);
794
+ };
795
+ const bind = (nameNode, declared, inferred) => {
796
+ const key = bindVarName(nameNode, analysis, declared ?? inferred, scope);
797
+ if (key)
798
+ boundHere.push(key);
799
+ if (!declared && nameNode)
800
+ visitor.binding?.(nameNode, inferred);
801
+ };
802
+ for (const clause of flworClauses(flwor)) {
803
+ switch (clause.type) {
804
+ case "ForClause":
805
+ for (const b of directChildrenOf(clause, "ForBinding")) {
806
+ const seq = evaluate(directChildOf(b, "ExprSingle"));
807
+ const allowingEmpty = b.children.some((c) => isTerminal(c) && c.value === "allowing");
808
+ let item = itemTypeOf(seq);
809
+ let seqCard = cardOf(seq);
810
+ if (allowingEmpty) {
811
+ item = seqCard.min === 1 ? item : withCard(item, { min: 0, max: 1 });
812
+ seqCard = { min: 1, max: seqCard.max === 0 ? 1 : seqCard.max };
813
+ }
814
+ card = multiplyCard(card, seqCard);
815
+ bind(directChildOf(b, "VarName"), declaredTypeOf(b), item);
816
+ const pos = directChildOf(b, "PositionalVar");
817
+ if (pos)
818
+ bindVarName(directChildOf(pos, "VarName"), analysis, INTEGER, scope);
819
+ }
820
+ break;
821
+ case "LetClause":
822
+ for (const b of directChildrenOf(clause, "LetBinding")) {
823
+ const t = evaluate(directChildOf(b, "ExprSingle"));
824
+ bind(directChildOf(b, "VarName"), declaredTypeOf(b), t);
825
+ }
826
+ break;
827
+ case "WindowClause":
828
+ for (const w of clause.children) {
829
+ if (isTerminal(w))
830
+ continue;
831
+ const seq = evaluate(directChildOf(w, "ExprSingle"));
832
+ const item = itemTypeOf(seq);
833
+ bind(directChildOf(w, "VarName"), declaredTypeOf(w), withCard(item, { min: 1, max: 2 }));
834
+ for (const cond of [directChildOf(w, "WindowStartCondition"), directChildOf(w, "WindowEndCondition")]) {
835
+ if (!cond)
836
+ continue;
837
+ const vars = directChildOf(cond, "WindowVars");
838
+ if (vars) {
839
+ bindVarName(directChildOf(vars, "CurrentItem"), analysis, item, scope);
840
+ const pos = directChildOf(vars, "PositionalVar");
841
+ if (pos)
842
+ bindVarName(directChildOf(pos, "VarName"), analysis, INTEGER, scope);
843
+ bindVarName(directChildOf(vars, "PreviousItem"), analysis, withCard(item, { min: 0, max: 1 }), scope);
844
+ bindVarName(directChildOf(vars, "NextItem"), analysis, withCard(item, { min: 0, max: 1 }), scope);
845
+ }
846
+ evaluate(directChildOf(cond, "ExprSingle"));
847
+ }
848
+ }
849
+ card = multiplyCard(card, { min: 0, max: 2 });
850
+ break;
851
+ case "WhereClause": {
852
+ const cond = directChildOf(clause, "ExprSingle");
853
+ evaluate(cond);
854
+ // Later clauses only see tuples for which the condition held.
855
+ if (cond)
856
+ for (const [k, t] of narrowScope(cond, scope, true, analysis))
857
+ scope.set(k, t);
858
+ card = { min: 0, max: card.max };
859
+ break;
860
+ }
861
+ case "CountClause":
862
+ bindVarName(directChildOf(clause, "VarName"), analysis, INTEGER, scope);
863
+ break;
864
+ case "GroupByClause": {
865
+ const keys = new Set();
866
+ for (const spec of findAll(clause, "GroupingSpec")) {
867
+ const groupVar = directChildOf(spec, "GroupingVariable");
868
+ const nameNode = groupVar ? directChildOf(groupVar, "VarName") : undefined;
869
+ const init = directChildOf(spec, "ExprSingle");
870
+ let key;
871
+ if (init) {
872
+ const t = itemTypeOf(evaluate(init));
873
+ const declared = declaredTypeOf(spec);
874
+ key = bindVarName(nameNode, analysis, declared ?? t, scope);
875
+ if (!declared && nameNode)
876
+ visitor.binding?.(nameNode, t);
877
+ }
878
+ else {
879
+ const existing = nameNode ? asVarName(nameNode, analysis) : null;
880
+ key = existing ? qnameKey(existing) : null;
881
+ if (key && scope.has(key))
882
+ scope.set(key, itemTypeOf(scope.get(key)));
883
+ }
884
+ if (key)
885
+ keys.add(key);
886
+ }
887
+ // Every other variable bound so far now holds the whole group's values.
888
+ for (const key of boundHere) {
889
+ if (keys.has(key))
890
+ continue;
891
+ const t = scope.get(key);
892
+ if (t)
893
+ scope.set(key, withCard(t, { min: cardOf(t).min, max: cardOf(t).max === 0 ? 0 : 2 }));
894
+ }
895
+ break;
896
+ }
897
+ case "ReturnClause":
898
+ result = evaluate(directChildOf(clause, "ExprSingle"));
899
+ break;
900
+ default:
901
+ // order by, and anything we don't model: just evaluate its sub-expressions in scope.
902
+ visitor.expr?.(clause, scope);
903
+ }
904
+ }
905
+ if (result.kind === "unknown")
906
+ return UNKNOWN;
907
+ return withCard(result, multiplyCard(card, cardOf(result)));
908
+ }
909
+ function processQuantified(node, outer, analysis, allFns, visitor = {}) {
910
+ const scope = new Map(outer);
911
+ let pendingName;
912
+ let pendingDeclared;
913
+ for (const c of node.children) {
914
+ if (c.type === "VarName") {
915
+ pendingName = c;
916
+ pendingDeclared = undefined;
917
+ }
918
+ else if (c.type === "TypeDeclaration") {
919
+ pendingDeclared = sequenceTypeOf(directChildOf(c, "SequenceType"));
920
+ }
921
+ else if (c.type === "ExprSingle") {
922
+ visitor.expr?.(c, scope);
923
+ if (pendingName) {
924
+ const t = inferExprType(c, scope, analysis, allFns);
925
+ bindVarName(pendingName, analysis, pendingDeclared ?? itemTypeOf(t), scope);
926
+ pendingName = undefined;
927
+ }
928
+ }
929
+ }
930
+ return BOOLEAN;
931
+ }
932
+ function processTypeswitch(node, outer, analysis, allFns, visitor = {}) {
933
+ const operand = directChildOf(node, "Expr");
934
+ if (operand)
935
+ visitor.expr?.(operand, outer);
936
+ const operandType = operand ? inferExprType(operand, outer, analysis, allFns) : UNKNOWN;
937
+ let result;
938
+ const branch = (varName, varType, expr) => {
939
+ const scope = new Map(outer);
940
+ bindVarName(varName, analysis, varType, scope);
941
+ if (!expr)
942
+ return;
943
+ visitor.expr?.(expr, scope);
944
+ const t = inferExprType(expr, scope, analysis, allFns);
945
+ result = result === undefined ? t : choiceType(result, t);
946
+ };
947
+ for (const clause of directChildrenOf(node, "CaseClause")) {
948
+ branch(directChildOf(clause, "VarName"), sequenceTypeOf(directChildOf(clause, "SequenceTypeUnion")) ?? UNKNOWN, directChildOf(clause, "ExprSingle"));
949
+ }
950
+ branch(directChildOf(node, "VarName"), operandType, directChildOf(node, "ExprSingle"));
951
+ return result ?? UNKNOWN;
952
+ }
953
+ function collectParamTypes(paramList, analysis, scope) {
954
+ const texts = [];
955
+ for (const param of paramList ? directChildrenOf(paramList, "Param") : []) {
956
+ const declared = declaredTypeOf(param);
957
+ bindVarName(directChildOf(param, "EQName"), analysis, declared ?? UNKNOWN, scope);
958
+ texts.push(declared ? formatType(declared) : "item()*");
959
+ }
960
+ return texts;
961
+ }
962
+ function processInlineFunction(node, outer, analysis, allFns, visitor = {}) {
963
+ // Inline functions close over the variables in scope, but have no context item.
964
+ const scope = new Map(outer);
965
+ scope.delete(CONTEXT_ITEM_KEY);
966
+ const params = collectParamTypes(directChildOf(node, "ParamList"), analysis, scope);
967
+ const body = directChildOf(node, "FunctionBody");
968
+ if (body)
969
+ visitor.expr?.(body, scope);
970
+ const declared = sequenceTypeOf(directChildOf(node, "SequenceType"));
971
+ const ret = declared ?? (body ? inferExprType(body, scope, analysis, allFns) : EMPTY);
972
+ return { kind: "function", name: `function(${params.join(", ")}) as ${typeText(ret, "item()*")}`, occurrence: "" };
973
+ }
974
+ // Unwraps the grammar's single-child precedence chain down to the node that does something.
975
+ function coreExpr(node) {
976
+ let n = node;
977
+ while (!isTerminal(n) && n.type !== "VarRef") {
978
+ const ops = nonTerminalChildren(n);
979
+ if (ops.length !== 1)
980
+ break;
981
+ n = ops[0];
982
+ }
983
+ return n;
984
+ }
985
+ function varKeyOf(node, analysis) {
986
+ const core = coreExpr(node);
987
+ const qname = core.type === "VarRef" ? asVarRef(core, analysis) : null;
988
+ return qname ? qnameKey(qname) : null;
989
+ }
990
+ /**
991
+ * Variable types that hold where `cond` is known to be true (`positive`) or false.
992
+ * Understands `$x instance of T`, `exists($x)`, `empty($x)`, a bare `$x` (its effective
993
+ * boolean value is false for ()), `not(…)`, and conjunctions / disjunctions of those.
994
+ */
995
+ function narrowScope(cond, scope, positive, analysis) {
996
+ const core = coreExpr(cond);
997
+ const ops = isTerminal(core) ? [] : nonTerminalChildren(core);
998
+ const nonEmpty = (key, out) => {
999
+ const t = key ? out.get(key) : undefined;
1000
+ if (!key || !t || t.kind === "unknown" || t.kind === "empty")
1001
+ return out;
1002
+ const c = cardOf(t);
1003
+ if (c.min === 0)
1004
+ out.set(key, withCard(t, { min: 1, max: c.max }));
1005
+ return out;
1006
+ };
1007
+ if ((core.type === "AndExpr" && positive) || (core.type === "OrExpr" && !positive)) {
1008
+ return ops.reduce((s, op) => narrowScope(op, s, positive, analysis), scope);
1009
+ }
1010
+ if (core.type === "VarRef" && positive)
1011
+ return nonEmpty(varKeyOf(core, analysis), new Map(scope));
1012
+ if (core.type === "InstanceofExpr" && positive && ops.length === 2) {
1013
+ const key = varKeyOf(ops[0], analysis);
1014
+ const narrowed = sequenceTypeOf(directChildOf(core, "SequenceType"));
1015
+ const current = key ? scope.get(key) : undefined;
1016
+ if (!key || !narrowed || narrowed.kind === "unknown")
1017
+ return scope;
1018
+ // Only ever make a type more specific: `$int instance of xs:decimal` tells us nothing new.
1019
+ if (current && current.kind !== "unknown" && !isAssignable(narrowed, current))
1020
+ return scope;
1021
+ return new Map(scope).set(key, narrowed);
1022
+ }
1023
+ if (core.type === "FunctionCall") {
1024
+ const call = asFunctionCall(core, analysis);
1025
+ if (!call || call.qname.namespaceUri !== XMLNS_FN || call.args.length !== 1)
1026
+ return scope;
1027
+ const arg = argExpr(call.args[0]);
1028
+ if (!arg)
1029
+ return scope;
1030
+ const name = call.qname.localName;
1031
+ if (name === "not")
1032
+ return narrowScope(arg, scope, !positive, analysis);
1033
+ if ((name === "exists" && positive) || (name === "empty" && !positive) || (name === "boolean" && positive)) {
1034
+ return nonEmpty(varKeyOf(arg, analysis), new Map(scope));
1035
+ }
1036
+ }
1037
+ return scope;
1038
+ }
1039
+ function processIf(node, scope, analysis, allFns, visitor = {}) {
1040
+ const cond = directChildOf(node, "Expr");
1041
+ if (cond)
1042
+ visitor.expr?.(cond, scope);
1043
+ // `then`/`else` are ExprSingle; XQuery 4's braced `if (c) { … }` has one EnclosedExpr.
1044
+ const branches = [...directChildrenOf(node, "ExprSingle"), ...directChildrenOf(node, "EnclosedExpr")];
1045
+ const types = branches.map((branch, i) => {
1046
+ const branchScope = cond ? narrowScope(cond, scope, i === 0, analysis) : scope;
1047
+ visitor.expr?.(branch, branchScope);
1048
+ return inferExprType(branch, branchScope, analysis, allFns);
1049
+ });
1050
+ if (types.length === 0)
1051
+ return UNKNOWN;
1052
+ return types.length === 1 ? choiceType(types[0], EMPTY) : types.reduce(choiceType);
1053
+ }
1054
+ // `copy $c := E modify U return R` — `$c` is a copy of E, so it has E's type.
1055
+ function processTransform(node, outer, analysis, allFns, visitor = {}) {
1056
+ const scope = new Map(outer);
1057
+ const bindingList = directChildOf(node, "XQUF_CopyBindingList");
1058
+ for (const b of bindingList ? directChildrenOf(bindingList, "XQUF_CopyBinding") : []) {
1059
+ const init = directChildOf(b, "ExprSingle");
1060
+ if (init)
1061
+ visitor.expr?.(init, scope);
1062
+ const t = init ? inferExprType(init, scope, analysis, allFns) : UNKNOWN;
1063
+ const nameNode = directChildOf(b, "VarName");
1064
+ bindVarName(nameNode, analysis, t, scope);
1065
+ if (nameNode)
1066
+ visitor.binding?.(nameNode, t);
1067
+ }
1068
+ const [modifyExpr, returnExpr] = directChildrenOf(node, "ExprSingle");
1069
+ if (modifyExpr)
1070
+ visitor.expr?.(modifyExpr, scope);
1071
+ if (!returnExpr)
1072
+ return UNKNOWN;
1073
+ visitor.expr?.(returnExpr, scope);
1074
+ return inferExprType(returnExpr, scope, analysis, allFns);
1075
+ }
1076
+ // ── Expression inference ──────────────────────────────────────────────────────
1077
+ function nonTerminalChildren(nt) {
1078
+ return nt.children.filter((c) => !isTerminal(c));
1079
+ }
226
1080
  export function inferExprType(node, varTypes, analysis, allFns) {
227
1081
  const lit = literalKind(node);
228
1082
  if (lit)
@@ -230,6 +1084,7 @@ export function inferExprType(node, varTypes, analysis, allFns) {
230
1084
  if (isTerminal(node))
231
1085
  return UNKNOWN;
232
1086
  const nt = node;
1087
+ const operands = nonTerminalChildren(nt);
233
1088
  switch (node.type) {
234
1089
  case "Literal":
235
1090
  case "NumericLiteral": {
@@ -244,51 +1099,171 @@ export function inferExprType(node, varTypes, analysis, allFns) {
244
1099
  const qname = asVarRef(node, analysis);
245
1100
  return qname ? (varTypes.get(qnameKey(qname)) ?? UNKNOWN) : UNKNOWN;
246
1101
  }
1102
+ case "ContextItemExpr":
1103
+ return varTypes.get(CONTEXT_ITEM_KEY) ?? UNKNOWN;
1104
+ case "ParenthesizedExpr":
1105
+ if (operands.length === 0)
1106
+ return EMPTY;
1107
+ break;
1108
+ case "Expr":
1109
+ // Comma operator: `(a, b, c)`.
1110
+ if (operands.length > 1) {
1111
+ return operands.map((o) => inferExprType(o, varTypes, analysis, allFns)).reduce(concatType);
1112
+ }
1113
+ break;
1114
+ case "IfExpr":
1115
+ return processIf(nt, varTypes, analysis, allFns);
1116
+ case "XQUF_TransformExpr":
1117
+ return processTransform(nt, varTypes, analysis, allFns);
1118
+ case "SwitchExpr": {
1119
+ const results = [
1120
+ ...directChildrenOf(node, "SwitchCaseClause").map((c) => directChildOf(c, "ExprSingle")),
1121
+ ...directChildrenOf(node, "ExprSingle"),
1122
+ ];
1123
+ if (results.some((r) => !r))
1124
+ return UNKNOWN;
1125
+ return results.map((r) => inferExprType(r, varTypes, analysis, allFns)).reduce(choiceType);
1126
+ }
1127
+ case "TypeswitchExpr":
1128
+ return processTypeswitch(nt, varTypes, analysis, allFns);
1129
+ case "TryCatchExpr": {
1130
+ const bodies = [...findAll(node, "EnclosedTryTargetExpr"), ...directChildrenOf(node, "CatchClause").map((c) => directChildOf(c, "EnclosedExpr"))];
1131
+ if (bodies.some((b) => !b))
1132
+ return UNKNOWN;
1133
+ return bodies.map((b) => inferExprType(b, varTypes, analysis, allFns)).reduce(choiceType);
1134
+ }
1135
+ case "FLWORExpr":
1136
+ return processFLWOR(node, varTypes, analysis, allFns);
1137
+ case "QuantifiedExpr":
1138
+ return processQuantified(nt, varTypes, analysis, allFns);
1139
+ case "InlineFunctionExpr":
1140
+ return processInlineFunction(nt, varTypes, analysis, allFns);
1141
+ case "NamedFunctionRef": {
1142
+ const ref = asNamedFunctionRef(node, analysis);
1143
+ const fn = ref ? resolveFunction(ref.qname, ref.arity, allFns) : undefined;
1144
+ return fn ? functionTypeOf(fn) : { kind: "function", name: "function(*)", occurrence: "" };
1145
+ }
1146
+ case "ComparisonExpr": {
1147
+ if (operands.length < 3)
1148
+ break;
1149
+ // General comparisons (=, <, …) are always a boolean. Value and node comparisons
1150
+ // (eq, is, <<, …) yield () for an empty operand.
1151
+ if (operands[1].type === "GeneralComp")
1152
+ return BOOLEAN;
1153
+ const [a, b] = [operands[0], operands[2]].map((o) => inferExprType(o, varTypes, analysis, allFns));
1154
+ if (a.kind === "unknown" || b.kind === "unknown")
1155
+ return withCard(BOOLEAN, { min: 0, max: 1 });
1156
+ if (a.kind === "empty" || b.kind === "empty")
1157
+ return EMPTY;
1158
+ return cardOf(a).min === 1 && cardOf(b).min === 1 ? BOOLEAN : withCard(BOOLEAN, { min: 0, max: 1 });
1159
+ }
1160
+ case "OrExpr":
1161
+ case "AndExpr":
1162
+ case "InstanceofExpr":
1163
+ case "CastableExpr":
1164
+ if (operands.length > 1)
1165
+ return BOOLEAN;
1166
+ break;
1167
+ case "StringConcatExpr":
1168
+ if (operands.length > 1)
1169
+ return STRING;
1170
+ break;
1171
+ case "StringConstructor":
1172
+ return STRING;
1173
+ case "CastExpr":
1174
+ if (operands.length > 1)
1175
+ return sequenceTypeOf(directChildOf(node, "SingleType")) ?? UNKNOWN;
1176
+ break;
1177
+ case "TreatExpr":
1178
+ if (operands.length > 1)
1179
+ return sequenceTypeOf(directChildOf(node, "SequenceType")) ?? UNKNOWN;
1180
+ break;
1181
+ case "UnaryExpr": {
1182
+ // Every expression passes through UnaryExpr; only a leading +/- makes it arithmetic.
1183
+ const operand = directChildOf(node, "ValueExpr");
1184
+ if (!operand || !nt.children.some(isTerminal))
1185
+ break;
1186
+ // Same operand rules as binary arithmetic: `-$x` behaves like `0 - $x` type-wise.
1187
+ return arithmeticResult("*", inferExprType(operand, varTypes, analysis, allFns), INTEGER);
1188
+ }
247
1189
  case "AdditiveExpr":
248
1190
  case "MultiplicativeExpr": {
249
- // Grammar produces a flat, left-associative chain: operand (op operand)*.
250
- let result;
251
- let pendingOp;
252
- for (const c of nt.children) {
253
- if (isTerminal(c)) {
254
- pendingOp = c.value;
255
- continue;
256
- }
257
- const t = inferExprType(c, varTypes, analysis, allFns);
258
- if (result === undefined) {
259
- result = t;
260
- }
261
- else if (pendingOp) {
262
- result = arithmeticResult(pendingOp, result, t);
263
- pendingOp = undefined;
264
- }
1191
+ const arith = asArithmeticExpr(node);
1192
+ if (!arith)
1193
+ break;
1194
+ let result = inferExprType(arith.operands[0], varTypes, analysis, allFns);
1195
+ for (let i = 0; i < arith.operators.length; i++) {
1196
+ const t = inferExprType(arith.operands[i + 1], varTypes, analysis, allFns);
1197
+ result = arithmeticResult(arith.operators[i], result, t);
265
1198
  }
266
- return result ?? UNKNOWN;
1199
+ return result;
267
1200
  }
1201
+ case "IntersectExceptExpr":
1202
+ // `a intersect b` / `a except b` select a subset of `a`.
1203
+ if (operands.length > 1) {
1204
+ const left = inferExprType(operands[0], varTypes, analysis, allFns);
1205
+ return left.kind === "node" ? withCard(left, { min: 0, max: cardOf(left).max }) : NODE_STEP;
1206
+ }
1207
+ break;
1208
+ case "UnionExpr":
1209
+ if (operands.length > 1) {
1210
+ const types = operands.map((o) => inferExprType(o, varTypes, analysis, allFns));
1211
+ const nodes = types.filter((t) => t.kind === "node");
1212
+ if (nodes.length !== types.length)
1213
+ return NODE_STEP;
1214
+ return withCard(nodes.reduce((a, b) => itemUnion(a, b)), { min: 0, max: 2 });
1215
+ }
1216
+ break;
1217
+ case "SimpleMapExpr": {
1218
+ if (operands.length < 2)
1219
+ break;
1220
+ let t = inferExprType(operands[0], varTypes, analysis, allFns);
1221
+ let card = cardOf(t);
1222
+ for (const next of operands.slice(1)) {
1223
+ if (t.kind === "unknown")
1224
+ return UNKNOWN;
1225
+ const scope = new Map(varTypes);
1226
+ scope.set(CONTEXT_ITEM_KEY, itemTypeOf(t));
1227
+ t = inferExprType(next, scope, analysis, allFns);
1228
+ card = multiplyCard(card, cardOf(t));
1229
+ }
1230
+ return withCard(t, card);
1231
+ }
1232
+ case "ArrowExpr":
1233
+ if (operands.length > 1)
1234
+ return inferArrowExpr(nt, varTypes, analysis, allFns);
1235
+ break;
1236
+ case "PostfixExpr":
1237
+ if (operands.length > 1)
1238
+ return inferPostfixExpr(nt, varTypes, analysis, allFns);
1239
+ break;
1240
+ case "MapConstructor":
1241
+ return inferMapConstructor(nt, varTypes, analysis, allFns);
1242
+ case "SquareArrayConstructor":
1243
+ case "CurlyArrayConstructor":
1244
+ return inferArrayConstructor(nt, varTypes, analysis, allFns);
268
1245
  case "FunctionCall": {
269
- // Partial application — one or more ArgumentPlaceholder nodes ('?') are present.
270
- // The result is a function type we don't fully infer yet; return UNKNOWN to avoid
271
- // misidentifying it as the function's plain return type.
272
- const argList = directChildOf(node, "ArgumentList");
273
- if (argList && findAll(argList, "ArgumentPlaceholder").length > 0)
1246
+ const call = asFunctionCall(node, analysis);
1247
+ if (!call)
274
1248
  return UNKNOWN;
275
- return inferFunctionReturn(node, analysis, allFns);
1249
+ // Partial application — one or more `?` placeholders: a function over the missing arguments.
1250
+ const placeholders = call.args.map((a) => directChildOf(a, "ArgumentPlaceholder") !== undefined);
1251
+ if (placeholders.some(Boolean)) {
1252
+ const fn = resolveFunction(call.qname, call.args.length, allFns);
1253
+ if (!fn || fn.variadic)
1254
+ return { kind: "function", name: "function(*)", occurrence: "" };
1255
+ const params = fn.params.filter((_, i) => placeholders[i]).map((p) => p.type ?? "item()*");
1256
+ return { kind: "function", name: `function(${params.join(", ")}) as ${fn.returnType ?? "item()*"}`, occurrence: "" };
1257
+ }
1258
+ return inferCallResult(call.qname, () => call.args.map((a) => inferExprType(a, varTypes, analysis, allFns)), call.args.length, allFns);
276
1259
  }
277
1260
  case "PathExpr":
278
- case "RelativePathExpr": {
1261
+ case "RelativePathExpr":
279
1262
  if (!isPathExpr(node))
280
1263
  break;
281
- // Every step's result feeds into (or is) the path's navigation — if any step
282
- // provably isn't a node (a literal, an atomic function result, etc.), the whole
283
- // path can't be node-producing either, regardless of which step it is.
284
- const steps = nt.children.filter((c) => !isTerminal(c));
285
- const definitelyNotNodes = steps.some((step) => {
286
- const stepType = inferExprType(step, varTypes, analysis, allFns);
287
- return stepType.kind !== "unknown" && stepType.kind !== "node" && stepType.kind !== "item";
288
- });
289
- return definitelyNotNodes ? UNKNOWN : NODE_STEP;
290
- }
1264
+ return inferPathExpr(nt, varTypes, analysis, allFns);
291
1265
  case "AxisStep":
1266
+ return axisStepType(node);
292
1267
  case "ForwardStep":
293
1268
  case "ReverseStep":
294
1269
  case "AbbrevForwardStep":
@@ -304,20 +1279,22 @@ export function inferExprType(node, varTypes, analysis, allFns) {
304
1279
  case "CompTextConstructor":
305
1280
  return { kind: "node", name: "text", occurrence: "" };
306
1281
  case "CompCommentConstructor":
1282
+ case "DirCommentConstructor":
307
1283
  return { kind: "node", name: "comment", occurrence: "" };
1284
+ case "CompPIConstructor":
1285
+ case "DirPIConstructor":
1286
+ return { kind: "node", name: "processing-instruction", occurrence: "" };
308
1287
  case "RangeExpr": {
309
1288
  // `e1 to e2` — only a genuine range (two operands) produces xs:integer*;
310
1289
  // with no "to" present the grammar still wraps a single child, which
311
1290
  // falls through to the generic single-child unwrap below.
312
- const kids = nt.children.filter((c) => !isTerminal(c));
313
- if (kids.length === 2)
1291
+ if (operands.length === 2)
314
1292
  return RANGE_SEQUENCE;
315
1293
  break;
316
1294
  }
317
1295
  }
318
- const ntChildren = nt.children.filter((c) => !isTerminal(c));
319
- if (ntChildren.length === 1)
320
- return inferExprType(ntChildren[0], varTypes, analysis, allFns);
1296
+ if (operands.length === 1)
1297
+ return inferExprType(operands[0], varTypes, analysis, allFns);
321
1298
  return UNKNOWN;
322
1299
  }
323
1300
  // ── Type name formatting ──────────────────────────────────────────────────────
@@ -330,29 +1307,160 @@ export function formatType(t) {
330
1307
  return `item()${t.occurrence}`;
331
1308
  if (t.kind === "node")
332
1309
  return `${t.name ?? "node"}()${t.occurrence}`;
1310
+ // `function(…) as T*` would read as a function returning T*; parenthesize the item type.
1311
+ if (t.kind === "function" && t.occurrence && t.name?.includes(" as "))
1312
+ return `(${t.name})${t.occurrence}`;
333
1313
  return `${t.name ?? t.kind}${t.occurrence}`;
334
1314
  }
335
- // ── Scope-aware type checking ─────────────────────────────────────────────────
336
- // Collect module-level VarDecl types — visible throughout the whole module.
337
- function buildModuleVarTypes(ast, text, analysis) {
338
- const types = new Map();
339
- for (const node of findAll(ast, "VarDecl")) {
340
- const binding = asTypedBinding(node, text, analysis);
341
- if (binding?.typeStr)
342
- types.set(qnameKey(binding.qname), parseType(binding.typeStr));
1315
+ function walkScoped(node, scope, analysis, allFns, hooks) {
1316
+ if (isTerminal(node))
1317
+ return;
1318
+ const nt = node;
1319
+ hooks.onNode?.(node, scope);
1320
+ const visitor = {
1321
+ expr: (n, s) => walkScoped(n, s, analysis, allFns, hooks),
1322
+ binding: hooks.onBinding,
1323
+ };
1324
+ switch (node.type) {
1325
+ case "FLWORExpr":
1326
+ processFLWOR(node, scope, analysis, allFns, visitor);
1327
+ return;
1328
+ case "QuantifiedExpr":
1329
+ processQuantified(nt, scope, analysis, allFns, visitor);
1330
+ return;
1331
+ case "TypeswitchExpr":
1332
+ processTypeswitch(nt, scope, analysis, allFns, visitor);
1333
+ return;
1334
+ case "InlineFunctionExpr":
1335
+ processInlineFunction(nt, scope, analysis, allFns, visitor);
1336
+ return;
1337
+ case "IfExpr":
1338
+ processIf(nt, scope, analysis, allFns, visitor);
1339
+ return;
1340
+ case "XQUF_TransformExpr":
1341
+ processTransform(nt, scope, analysis, allFns, visitor);
1342
+ return;
343
1343
  }
344
- return types;
1344
+ for (const child of nt.children)
1345
+ walkScoped(child, scope, analysis, allFns, hooks);
345
1346
  }
346
- // Collect typed param bindings from a ParamList into `scope`.
347
- function collectParams(paramList, text, analysis, scope) {
348
- if (!paramList)
349
- return;
350
- for (const param of findAll(paramList, "Param")) {
351
- const binding = asTypedBinding(param, text, analysis);
352
- if (binding?.typeStr)
353
- scope.set(qnameKey(binding.qname), parseType(binding.typeStr));
1347
+ /**
1348
+ * Fills in `returnType` for functions in this module declared without `as …`, inferred from
1349
+ * their bodies. Iterates to a fixpoint (bounded) so functions calling other untyped functions,
1350
+ * declared before or after them, resolve too; directly recursive functions stay unknown unless
1351
+ * a non-recursive branch alone determines the type. Callers use the returned list in place of
1352
+ * `allFns` so every call site — hints and type checks alike — sees the inferred types.
1353
+ */
1354
+ export function withInferredReturnTypes(ast, analysis, allFns, outerScope = new Map()) {
1355
+ const bodies = new Map();
1356
+ for (const annotated of findAll(ast, "AnnotatedDecl")) {
1357
+ const decl = directChildOf(annotated, "FunctionDecl");
1358
+ const body = decl ? directChildOf(decl, "FunctionBody") : undefined;
1359
+ if (decl && body && !directChildOf(decl, "SequenceType"))
1360
+ bodies.set(annotated.start, { decl, body });
354
1361
  }
1362
+ const untyped = analysis.functions.filter((f) => !f.returnType && f.sourceOffset !== undefined && bodies.has(f.sourceOffset));
1363
+ if (untyped.length === 0)
1364
+ return allFns;
1365
+ const inferred = new Map();
1366
+ let fns = allFns;
1367
+ for (let pass = 0; pass < 4; pass++) {
1368
+ let changed = false;
1369
+ const moduleTypes = new Map(outerScope);
1370
+ walkModuleVariables(ast, moduleTypes, analysis, fns, {});
1371
+ for (const fn of untyped) {
1372
+ const { decl, body } = bodies.get(fn.sourceOffset);
1373
+ const scope = new Map(moduleTypes);
1374
+ collectParamTypes(directChildOf(decl, "ParamList"), analysis, scope);
1375
+ const t = inferExprType(body, scope, analysis, fns);
1376
+ const text = formatType(t);
1377
+ // Only keep types that round-trip through the signature parser the call sites use.
1378
+ if (t.kind === "unknown" || parseType(text).kind === "unknown" || inferred.get(fn) === text)
1379
+ continue;
1380
+ inferred.set(fn, text);
1381
+ changed = true;
1382
+ }
1383
+ if (!changed)
1384
+ break;
1385
+ fns = allFns.map((f) => (inferred.has(f) ? { ...f, returnType: inferred.get(f) } : f));
1386
+ }
1387
+ return fns;
355
1388
  }
1389
+ /**
1390
+ * Types of variables visible in this module without being declared in its source: variables
1391
+ * of imported modules and runtime-predeclared ones. A declared `as` type is used as written;
1392
+ * an untyped imported variable is inferred from its initializer when that module's AST is at
1393
+ * hand (it isn't for lightweight glob summaries).
1394
+ */
1395
+ export function externalVariableTypes(analysis, imported, allFns) {
1396
+ const out = new Map();
1397
+ const add = (key, t) => {
1398
+ if (t && t.kind !== "unknown")
1399
+ out.set(key, t);
1400
+ };
1401
+ for (const v of analysis.moduleVariables) {
1402
+ if (v.offset < 0 && v.type)
1403
+ add(qnameKey(v.qname), parseType(v.type));
1404
+ }
1405
+ const seen = new Set([analysis]);
1406
+ for (const module of imported) {
1407
+ if (seen.has(module))
1408
+ continue; // the same module is registered under its URI and its `at` path
1409
+ seen.add(module);
1410
+ let inferred;
1411
+ for (const v of module.moduleVariables) {
1412
+ const key = qnameKey(v.qname);
1413
+ if (v.type) {
1414
+ add(key, parseType(v.type));
1415
+ }
1416
+ else if (module.ast) {
1417
+ if (!inferred) {
1418
+ inferred = new Map();
1419
+ walkModuleVariables(module.ast, inferred, module, allFns, {});
1420
+ }
1421
+ add(key, inferred.get(key));
1422
+ }
1423
+ }
1424
+ }
1425
+ return out;
1426
+ }
1427
+ // Module variables in declaration order: declared type, else inferred from the initializer.
1428
+ function walkModuleVariables(ast, moduleTypes, analysis, allFns, hooks) {
1429
+ for (const decl of findAll(ast, "VarDecl")) {
1430
+ const value = directChildOf(decl, "VarValue");
1431
+ if (value && (hooks.onNode || hooks.onBinding))
1432
+ walkScoped(value, moduleTypes, analysis, allFns, hooks);
1433
+ const declared = declaredTypeOf(decl);
1434
+ const inferred = value ? inferExprType(value, moduleTypes, analysis, allFns) : UNKNOWN;
1435
+ const nameNode = directChildOf(decl, "VarName");
1436
+ bindVarName(nameNode, analysis, declared ?? inferred, moduleTypes);
1437
+ if (!declared && value && nameNode)
1438
+ hooks.onBinding?.(nameNode, inferred);
1439
+ }
1440
+ }
1441
+ /**
1442
+ * Walks a whole module with lexically correct variable scopes: module variables (declared or
1443
+ * inferred, in declaration order), function parameters inside each function body, and
1444
+ * FLWOR / quantified / typeswitch / inline-function bindings within their own sub-trees.
1445
+ */
1446
+ export function walkModuleScopes(ast, analysis, allFns, hooks, outerScope = new Map()) {
1447
+ const moduleTypes = new Map(outerScope);
1448
+ walkModuleVariables(ast, moduleTypes, analysis, allFns, hooks);
1449
+ for (const annotated of findAll(ast, "AnnotatedDecl")) {
1450
+ const decl = directChildOf(annotated, "FunctionDecl");
1451
+ const body = decl ? directChildOf(decl, "FunctionBody") : undefined;
1452
+ if (!decl || !body)
1453
+ continue; // external function — nothing to walk
1454
+ const scope = new Map(moduleTypes);
1455
+ collectParamTypes(directChildOf(decl, "ParamList"), analysis, scope);
1456
+ walkScoped(body, scope, analysis, allFns, hooks);
1457
+ }
1458
+ // Top-level query body (present in main modules, absent in library modules).
1459
+ for (const queryBody of findAll(ast, "QueryBody")) {
1460
+ walkScoped(queryBody, new Map(moduleTypes), analysis, allFns, hooks);
1461
+ }
1462
+ }
1463
+ // ── Type checking ─────────────────────────────────────────────────────────────
356
1464
  // Check a single FunctionCall node against the known function signatures.
357
1465
  function typeCheckCall(callNode, varTypes, analysis, allFns, errors) {
358
1466
  const call = asFunctionCall(callNode, analysis);
@@ -399,55 +1507,40 @@ function typeCheckCall(callNode, varTypes, analysis, allFns, errors) {
399
1507
  }
400
1508
  }
401
1509
  }
402
- // Walk `node` checking function calls with `scopeTypes` as the variable context.
403
- // `moduleTypes` is passed separately so InlineFunctionExpr can start a fresh isolated scope.
404
- // LetBinding/ForBinding nodes extend `scopeTypes` in place as the walk proceeds.
405
- // InlineFunctionExpr nodes receive a new isolated scope (their own params + module vars).
406
- function walkScope(node, scopeTypes, moduleTypes, text, analysis, allFns, errors) {
407
- if (isTerminal(node))
1510
+ // Flags `a op b` chains where an operand is statically a string or boolean: neither is ever a
1511
+ // valid operand of an arithmetic operator (XPTY0004). Deliberately conservative: operands of
1512
+ // unknown or imprecise type, and empty operands, are not reported.
1513
+ function typeCheckArithmetic(node, varTypes, analysis, allFns, errors) {
1514
+ const arith = asArithmeticExpr(node);
1515
+ if (!arith)
408
1516
  return;
409
- const nt = node;
410
- if (node.type === "InlineFunctionExpr") {
411
- // New isolated scope: module vars + this inline function's own params.
412
- const innerScope = new Map(moduleTypes);
413
- collectParams(directChildOf(node, "ParamList"), text, analysis, innerScope);
414
- const body = directChildOf(node, "FunctionBody");
415
- if (body)
416
- walkScope(body, innerScope, moduleTypes, text, analysis, allFns, errors);
417
- return;
418
- }
419
- if (node.type === "LetBinding" || node.type === "ForBinding") {
420
- const binding = asTypedBinding(node, text, analysis);
421
- if (binding?.typeStr)
422
- scopeTypes.set(qnameKey(binding.qname), parseType(binding.typeStr));
423
- }
424
- if (node.type === "FunctionCall") {
425
- typeCheckCall(node, scopeTypes, analysis, allFns, errors);
426
- }
427
- for (const child of nt.children) {
428
- walkScope(child, scopeTypes, moduleTypes, text, analysis, allFns, errors);
1517
+ for (const operand of arith.operands) {
1518
+ const t = atomize(inferExprType(operand, varTypes, analysis, allFns));
1519
+ if (t.kind !== "atomic" || !t.name)
1520
+ continue;
1521
+ if (!isAtomicSubtype(t.name, "xs:string") && !isAtomicSubtype(t.name, "xs:boolean"))
1522
+ continue;
1523
+ errors.push({
1524
+ message: `Arithmetic operand of type ${formatType(t)} is not numeric, date/time or duration [XPTY0004]`,
1525
+ code: "XPTY0004",
1526
+ offset: operand.start,
1527
+ length: (operand.end ?? operand.start + 1) - operand.start,
1528
+ });
429
1529
  }
430
1530
  }
431
1531
  // ── Main entry point ─────────────────────────────────────────────────────────
432
- export function checkTypes(ast, text, analysis, importedAnalyses) {
1532
+ export function checkTypes(ast, _text, analysis, importedAnalyses) {
433
1533
  const errors = [];
434
- const allFns = allFunctionsFlat(analysis, importedAnalyses);
435
- const moduleTypes = buildModuleVarTypes(ast, text, analysis);
436
- // Each named function declaration gets its own isolated scope (params + module vars).
437
- for (const annotated of findAll(ast, "AnnotatedDecl")) {
438
- const decl = directChildOf(annotated, "FunctionDecl");
439
- if (!decl)
440
- continue;
441
- const body = directChildOf(decl, "FunctionBody");
442
- if (!body)
443
- continue; // external function — nothing to check
444
- const scopeTypes = new Map(moduleTypes);
445
- collectParams(directChildOf(decl, "ParamList"), text, analysis, scopeTypes);
446
- walkScope(body, scopeTypes, moduleTypes, text, analysis, allFns, errors);
447
- }
448
- // Top-level query body (present in main modules, absent in library modules).
449
- for (const queryBody of findAll(ast, "QueryBody")) {
450
- walkScope(queryBody, new Map(moduleTypes), moduleTypes, text, analysis, allFns, errors);
451
- }
1534
+ const declaredFns = allFunctionsFlat(analysis, importedAnalyses);
1535
+ const outerScope = externalVariableTypes(analysis, importedAnalyses.values(), declaredFns);
1536
+ const allFns = withInferredReturnTypes(ast, analysis, declaredFns, outerScope);
1537
+ walkModuleScopes(ast, analysis, allFns, {
1538
+ onNode: (node, scope) => {
1539
+ if (node.type === "FunctionCall")
1540
+ typeCheckCall(node, scope, analysis, allFns, errors);
1541
+ else
1542
+ typeCheckArithmetic(node, scope, analysis, allFns, errors);
1543
+ },
1544
+ }, outerScope);
452
1545
  return errors;
453
1546
  }