@elaraai/east 1.0.83 → 1.0.85

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.
Files changed (252) hide show
  1. package/dist/src/analyze.d.ts.map +1 -1
  2. package/dist/src/analyze.js +48 -0
  3. package/dist/src/analyze.js.map +1 -1
  4. package/dist/src/ast_to_ir.coerce_to.spec.js +4 -4
  5. package/dist/src/ast_to_ir.coerce_to.spec.js.map +1 -1
  6. package/dist/src/ast_to_ir.d.ts +6 -1
  7. package/dist/src/ast_to_ir.d.ts.map +1 -1
  8. package/dist/src/ast_to_ir.js +20 -7
  9. package/dist/src/ast_to_ir.js.map +1 -1
  10. package/dist/src/builtins.d.ts +1 -1
  11. package/dist/src/builtins.d.ts.map +1 -1
  12. package/dist/src/builtins.js +52 -0
  13. package/dist/src/builtins.js.map +1 -1
  14. package/dist/src/codegen/canonical.d.ts +37 -0
  15. package/dist/src/codegen/canonical.d.ts.map +1 -0
  16. package/dist/src/codegen/canonical.js +208 -0
  17. package/dist/src/codegen/canonical.js.map +1 -0
  18. package/dist/src/codegen/codegen.spec.js +132 -201
  19. package/dist/src/codegen/codegen.spec.js.map +1 -1
  20. package/dist/src/codegen/printer.d.ts.map +1 -1
  21. package/dist/src/codegen/printer.js +142 -24
  22. package/dist/src/codegen/printer.js.map +1 -1
  23. package/dist/src/codegen/spellings.d.ts +4 -1
  24. package/dist/src/codegen/spellings.d.ts.map +1 -1
  25. package/dist/src/codegen/spellings.js +5 -1
  26. package/dist/src/codegen/spellings.js.map +1 -1
  27. package/dist/src/compile/builtins/index.d.ts.map +1 -1
  28. package/dist/src/compile/builtins/index.js +2 -0
  29. package/dist/src/compile/builtins/index.js.map +1 -1
  30. package/dist/src/compile/builtins/primitives.d.ts.map +1 -1
  31. package/dist/src/compile/builtins/primitives.js +2 -1
  32. package/dist/src/compile/builtins/primitives.js.map +1 -1
  33. package/dist/src/compile/builtins/query.d.ts +10 -0
  34. package/dist/src/compile/builtins/query.d.ts.map +1 -0
  35. package/dist/src/compile/builtins/query.js +5 -0
  36. package/dist/src/compile/builtins/query.js.map +1 -0
  37. package/dist/src/expr/block.d.ts.map +1 -1
  38. package/dist/src/expr/block.js +2 -1
  39. package/dist/src/expr/block.js.map +1 -1
  40. package/dist/src/expr/index.d.ts +75 -47
  41. package/dist/src/expr/index.d.ts.map +1 -1
  42. package/dist/src/expr/index.js +31 -0
  43. package/dist/src/expr/index.js.map +1 -1
  44. package/dist/src/expr/libs/datetime.d.ts +20 -20
  45. package/dist/src/expr/libs/datetime.d.ts.map +1 -1
  46. package/dist/src/expr/libs/datetime.js +208 -167
  47. package/dist/src/expr/libs/datetime.js.map +1 -1
  48. package/dist/src/expr/libs/float.d.ts +15 -15
  49. package/dist/src/expr/libs/float.d.ts.map +1 -1
  50. package/dist/src/expr/libs/float.js +298 -267
  51. package/dist/src/expr/libs/float.js.map +1 -1
  52. package/dist/src/expr/libs/integer.d.ts +12 -12
  53. package/dist/src/expr/libs/integer.d.ts.map +1 -1
  54. package/dist/src/expr/libs/integer.js +219 -194
  55. package/dist/src/expr/libs/integer.js.map +1 -1
  56. package/dist/src/expr/libs/lazy.d.ts +28 -0
  57. package/dist/src/expr/libs/lazy.d.ts.map +1 -0
  58. package/dist/src/expr/libs/lazy.js +55 -0
  59. package/dist/src/expr/libs/lazy.js.map +1 -0
  60. package/dist/src/expr/libs/lazy.spec.d.ts +6 -0
  61. package/dist/src/expr/libs/lazy.spec.d.ts.map +1 -0
  62. package/dist/src/expr/libs/lazy.spec.js +102 -0
  63. package/dist/src/expr/libs/lazy.spec.js.map +1 -0
  64. package/dist/src/expr/query.d.ts +60 -0
  65. package/dist/src/expr/query.d.ts.map +1 -0
  66. package/dist/src/expr/query.js +84 -0
  67. package/dist/src/expr/query.js.map +1 -0
  68. package/dist/src/index.d.ts +3 -0
  69. package/dist/src/index.d.ts.map +1 -1
  70. package/dist/src/index.js +3 -0
  71. package/dist/src/index.js.map +1 -1
  72. package/dist/src/internal.d.ts +4 -0
  73. package/dist/src/internal.d.ts.map +1 -1
  74. package/dist/src/internal.js +9 -0
  75. package/dist/src/internal.js.map +1 -1
  76. package/dist/src/location.d.ts +22 -0
  77. package/dist/src/location.d.ts.map +1 -1
  78. package/dist/src/location.js +31 -0
  79. package/dist/src/location.js.map +1 -1
  80. package/dist/src/naming.d.ts +12 -7
  81. package/dist/src/naming.d.ts.map +1 -1
  82. package/dist/src/naming.js +30 -13
  83. package/dist/src/naming.js.map +1 -1
  84. package/dist/src/naming.spec.js +49 -0
  85. package/dist/src/naming.spec.js.map +1 -1
  86. package/dist/src/portable.spec.d.ts +6 -0
  87. package/dist/src/portable.spec.d.ts.map +1 -0
  88. package/dist/src/portable.spec.js +181 -0
  89. package/dist/src/portable.spec.js.map +1 -0
  90. package/dist/src/query/evaluate.d.ts +98 -0
  91. package/dist/src/query/evaluate.d.ts.map +1 -0
  92. package/dist/src/query/evaluate.js +193 -0
  93. package/dist/src/query/evaluate.js.map +1 -0
  94. package/dist/src/query/index.d.ts +17 -0
  95. package/dist/src/query/index.d.ts.map +1 -0
  96. package/dist/src/query/index.js +17 -0
  97. package/dist/src/query/index.js.map +1 -0
  98. package/dist/src/query/jq/catalog.d.ts +174 -0
  99. package/dist/src/query/jq/catalog.d.ts.map +1 -0
  100. package/dist/src/query/jq/catalog.js +1740 -0
  101. package/dist/src/query/jq/catalog.js.map +1 -0
  102. package/dist/src/query/jq/check.d.ts +210 -0
  103. package/dist/src/query/jq/check.d.ts.map +1 -0
  104. package/dist/src/query/jq/check.js +2261 -0
  105. package/dist/src/query/jq/check.js.map +1 -0
  106. package/dist/src/query/jq/complete.d.ts +89 -0
  107. package/dist/src/query/jq/complete.d.ts.map +1 -0
  108. package/dist/src/query/jq/complete.js +320 -0
  109. package/dist/src/query/jq/complete.js.map +1 -0
  110. package/dist/src/query/jq/describe.d.ts +53 -0
  111. package/dist/src/query/jq/describe.d.ts.map +1 -0
  112. package/dist/src/query/jq/describe.js +149 -0
  113. package/dist/src/query/jq/describe.js.map +1 -0
  114. package/dist/src/query/jq/lex.d.ts +118 -0
  115. package/dist/src/query/jq/lex.d.ts.map +1 -0
  116. package/dist/src/query/jq/lex.js +371 -0
  117. package/dist/src/query/jq/lex.js.map +1 -0
  118. package/dist/src/query/jq/literals.d.ts +49 -0
  119. package/dist/src/query/jq/literals.d.ts.map +1 -0
  120. package/dist/src/query/jq/literals.js +78 -0
  121. package/dist/src/query/jq/literals.js.map +1 -0
  122. package/dist/src/query/jq/messages.d.ts +126 -0
  123. package/dist/src/query/jq/messages.d.ts.map +1 -0
  124. package/dist/src/query/jq/messages.js +172 -0
  125. package/dist/src/query/jq/messages.js.map +1 -0
  126. package/dist/src/query/jq/parse.d.ts +70 -0
  127. package/dist/src/query/jq/parse.d.ts.map +1 -0
  128. package/dist/src/query/jq/parse.js +1013 -0
  129. package/dist/src/query/jq/parse.js.map +1 -0
  130. package/dist/src/query/jq/print.d.ts +59 -0
  131. package/dist/src/query/jq/print.d.ts.map +1 -0
  132. package/dist/src/query/jq/print.js +587 -0
  133. package/dist/src/query/jq/print.js.map +1 -0
  134. package/dist/src/query/jq/shapes.d.ts +345 -0
  135. package/dist/src/query/jq/shapes.d.ts.map +1 -0
  136. package/dist/src/query/jq/shapes.js +584 -0
  137. package/dist/src/query/jq/shapes.js.map +1 -0
  138. package/dist/src/query/jq/spans.d.ts +133 -0
  139. package/dist/src/query/jq/spans.d.ts.map +1 -0
  140. package/dist/src/query/jq/spans.js +195 -0
  141. package/dist/src/query/jq/spans.js.map +1 -0
  142. package/dist/src/query/jq/split.d.ts +334 -0
  143. package/dist/src/query/jq/split.d.ts.map +1 -0
  144. package/dist/src/query/jq/split.js +2108 -0
  145. package/dist/src/query/jq/split.js.map +1 -0
  146. package/dist/src/query/jq/strftime.d.ts +20 -0
  147. package/dist/src/query/jq/strftime.d.ts.map +1 -0
  148. package/dist/src/query/jq/strftime.js +65 -0
  149. package/dist/src/query/jq/strftime.js.map +1 -0
  150. package/dist/src/query/jq/summary.d.ts +139 -0
  151. package/dist/src/query/jq/summary.d.ts.map +1 -0
  152. package/dist/src/query/jq/summary.js +222 -0
  153. package/dist/src/query/jq/summary.js.map +1 -0
  154. package/dist/src/query/jq/translate-builtins.d.ts +21 -0
  155. package/dist/src/query/jq/translate-builtins.d.ts.map +1 -0
  156. package/dist/src/query/jq/translate-builtins.js +1873 -0
  157. package/dist/src/query/jq/translate-builtins.js.map +1 -0
  158. package/dist/src/query/jq/translate.d.ts +627 -0
  159. package/dist/src/query/jq/translate.d.ts.map +1 -0
  160. package/dist/src/query/jq/translate.js +3364 -0
  161. package/dist/src/query/jq/translate.js.map +1 -0
  162. package/dist/src/query/types.d.ts +505 -0
  163. package/dist/src/query/types.d.ts.map +1 -0
  164. package/dist/src/query/types.js +231 -0
  165. package/dist/src/query/types.js.map +1 -0
  166. package/dist/src/runner_exec.d.ts +134 -0
  167. package/dist/src/runner_exec.d.ts.map +1 -0
  168. package/dist/src/runner_exec.js +371 -0
  169. package/dist/src/runner_exec.js.map +1 -0
  170. package/dist/src/runner_exec.spec.d.ts +6 -0
  171. package/dist/src/runner_exec.spec.d.ts.map +1 -0
  172. package/dist/src/runner_exec.spec.js +198 -0
  173. package/dist/src/runner_exec.spec.js.map +1 -0
  174. package/dist/src/runner_io.d.ts +370 -0
  175. package/dist/src/runner_io.d.ts.map +1 -0
  176. package/dist/src/runner_io.js +563 -0
  177. package/dist/src/runner_io.js.map +1 -0
  178. package/dist/src/runner_io.spec.d.ts +6 -0
  179. package/dist/src/runner_io.spec.d.ts.map +1 -0
  180. package/dist/src/runner_io.spec.js +224 -0
  181. package/dist/src/runner_io.spec.js.map +1 -0
  182. package/dist/src/runner_merge.d.ts +95 -0
  183. package/dist/src/runner_merge.d.ts.map +1 -0
  184. package/dist/src/runner_merge.js +238 -0
  185. package/dist/src/runner_merge.js.map +1 -0
  186. package/dist/src/runner_protocol.d.ts +60 -6
  187. package/dist/src/runner_protocol.d.ts.map +1 -1
  188. package/dist/src/runner_protocol.js +48 -6
  189. package/dist/src/runner_protocol.js.map +1 -1
  190. package/dist/src/serialization/beast2/index.d.ts +2 -2
  191. package/dist/src/serialization/beast2/index.d.ts.map +1 -1
  192. package/dist/src/serialization/beast2/index.js +1 -1
  193. package/dist/src/serialization/beast2/index.js.map +1 -1
  194. package/dist/src/serialization/beast2/v5/frames.d.ts +10 -6
  195. package/dist/src/serialization/beast2/v5/frames.d.ts.map +1 -1
  196. package/dist/src/serialization/beast2/v5/frames.js +10 -6
  197. package/dist/src/serialization/beast2/v5/frames.js.map +1 -1
  198. package/dist/src/serialization/beast2/v5/frames.spec.d.ts +6 -0
  199. package/dist/src/serialization/beast2/v5/frames.spec.d.ts.map +1 -0
  200. package/dist/src/serialization/beast2/v5/frames.spec.js +42 -0
  201. package/dist/src/serialization/beast2/v5/frames.spec.js.map +1 -0
  202. package/dist/src/serialization/beast2/v5/index.spec.js +108 -0
  203. package/dist/src/serialization/beast2/v5/index.spec.js.map +1 -1
  204. package/dist/src/serialization/beast2/v5/lazy.d.ts +74 -7
  205. package/dist/src/serialization/beast2/v5/lazy.d.ts.map +1 -1
  206. package/dist/src/serialization/beast2/v5/lazy.js +129 -27
  207. package/dist/src/serialization/beast2/v5/lazy.js.map +1 -1
  208. package/dist/src/serialization/beast2/v5/lazy.spec.js +251 -16
  209. package/dist/src/serialization/beast2/v5/lazy.spec.js.map +1 -1
  210. package/dist/src/serialization/beast2/v5/stream.d.ts +139 -23
  211. package/dist/src/serialization/beast2/v5/stream.d.ts.map +1 -1
  212. package/dist/src/serialization/beast2/v5/stream.js +196 -44
  213. package/dist/src/serialization/beast2/v5/stream.js.map +1 -1
  214. package/dist/src/serialization/beast2/v5/type-section.d.ts.map +1 -1
  215. package/dist/src/serialization/beast2/v5/type-section.js +25 -12
  216. package/dist/src/serialization/beast2/v5/type-section.js.map +1 -1
  217. package/dist/src/serialization/beast2/v5/type-section.spec.d.ts +6 -0
  218. package/dist/src/serialization/beast2/v5/type-section.spec.d.ts.map +1 -0
  219. package/dist/src/serialization/beast2/v5/type-section.spec.js +41 -0
  220. package/dist/src/serialization/beast2/v5/type-section.spec.js.map +1 -0
  221. package/dist/src/serialization/beast2/v5/weight.d.ts +36 -0
  222. package/dist/src/serialization/beast2/v5/weight.d.ts.map +1 -0
  223. package/dist/src/serialization/beast2/v5/weight.js +238 -0
  224. package/dist/src/serialization/beast2/v5/weight.js.map +1 -0
  225. package/dist/src/serialization/crc32.d.ts +38 -0
  226. package/dist/src/serialization/crc32.d.ts.map +1 -0
  227. package/dist/src/serialization/crc32.js +70 -0
  228. package/dist/src/serialization/crc32.js.map +1 -0
  229. package/dist/src/serialization/crc32.spec.d.ts +6 -0
  230. package/dist/src/serialization/crc32.spec.d.ts.map +1 -0
  231. package/dist/src/serialization/crc32.spec.js +92 -0
  232. package/dist/src/serialization/crc32.spec.js.map +1 -0
  233. package/dist/src/serialization/east-inferred.spec.js +4 -0
  234. package/dist/src/serialization/east-inferred.spec.js.map +1 -1
  235. package/dist/src/serialization/east.d.ts.map +1 -1
  236. package/dist/src/serialization/east.js +134 -110
  237. package/dist/src/serialization/east.js.map +1 -1
  238. package/dist/src/serialization/east.spec.js +185 -4
  239. package/dist/src/serialization/east.spec.js.map +1 -1
  240. package/dist/src/serialization/east_text.d.ts +25 -0
  241. package/dist/src/serialization/east_text.d.ts.map +1 -0
  242. package/dist/src/serialization/east_text.js +29 -0
  243. package/dist/src/serialization/east_text.js.map +1 -0
  244. package/dist/src/serialization/index.d.ts +1 -1
  245. package/dist/src/serialization/index.d.ts.map +1 -1
  246. package/dist/src/serialization/index.js +1 -1
  247. package/dist/src/serialization/index.js.map +1 -1
  248. package/dist/src/types.d.ts +1 -1
  249. package/dist/src/types.d.ts.map +1 -1
  250. package/dist/src/types.js +7 -6
  251. package/dist/src/types.js.map +1 -1
  252. package/package.json +7 -4
@@ -0,0 +1,2261 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+ /**
6
+ * The jq checker: types a program against the East type of its input, and
7
+ * gives the program and its types, which every surface translates, or the
8
+ * problems that stop it (`devdocs/QUERY.md` §2–§13). It only checks: it
9
+ * rewrites nothing.
10
+ *
11
+ * @packageDocumentation
12
+ */
13
+ import { variant } from "../../containers/variant.js";
14
+ import { ArrayType, BooleanType, DateTimeType, DictType, FloatType, IntegerType, NeverType, NullType, StringType, StructType, VariantType, isImmutableType, isTypeEqual, printType, } from "../../types.js";
15
+ import { BUILTINS } from "./catalog.js";
16
+ import { isoDateTime, jsonString, literalValue } from "./literals.js";
17
+ import { MESSAGES, closest, edit, report } from "./messages.js";
18
+ import { parseJq } from "./parse.js";
19
+ import { ERROR, MANY, MAYBE, ONE, SOME, ZERO, also, canBeNull, casesOf, describeType, descendTypes, either, membersOf, nullablePayload, JQ_TYPE_NAMES, narrowTypes, orNull, piped, refine, typed, unify, unifyShape, union, unwrap, wireMultiplicity, } from "./shapes.js";
20
+ import { childPath, jqChildren } from "./spans.js";
21
+ /** What a node that never runs gives. */
22
+ const DEAD = { shape: typed(NeverType), mult: { lo: 1, hi: 0 } };
23
+ /** The input of a node that never runs: no value reaches it. */
24
+ const NOTHING = { shape: typed(NeverType), mult: ONE };
25
+ /** The binary operators by kind. */
26
+ const ARITHMETIC = new Set(["+", "-", "*", "/", "%"]);
27
+ const COMPARISON = new Set(["==", "!=", "<", "<=", ">", ">="]);
28
+ /** Numbers: Integer and Float. */
29
+ function isNumber(type) {
30
+ return type.type === "Integer" || type.type === "Float";
31
+ }
32
+ /** `.`, as a path node. */
33
+ const IDENTITY = variant("identity", null);
34
+ /** What an update's path is made of, for the diagnostic that refuses another. */
35
+ const UPDATE_PATHS = "an update's path is made of field reads, indexes, slices, .[], select, the type selectors, empty, .., recurse and |";
36
+ /**
37
+ * The type selectors an update's path may hold, each a `select` of a type
38
+ * test: `(.. | numbers) |= . * 2`.
39
+ *
40
+ * @internal
41
+ */
42
+ export const UPDATE_SELECTORS = new Set([
43
+ "arrays", "objects", "iterables", "booleans", "numbers", "strings", "nulls", "values", "scalars", "normals", "finites",
44
+ ]);
45
+ /**
46
+ * The path `recurse(f)` walks in an update: field reads, then `.[]`, as
47
+ * `.children[]`.
48
+ *
49
+ * @param node - `f`
50
+ * @returns the names of the fields read before the `.[]`, or `undefined` for
51
+ * another path
52
+ *
53
+ * @internal
54
+ */
55
+ export function walkSteps(node) {
56
+ if (node.type !== "iterate")
57
+ return undefined;
58
+ const fields = [];
59
+ let target = node.value.target;
60
+ while (target.type === "field" && !target.value.optional) {
61
+ fields.unshift(target.value.name);
62
+ target = target.value.target;
63
+ }
64
+ return target.type === "identity" ? { fields } : undefined;
65
+ }
66
+ /** Whether values of type `b` are values of type `a`: equal types, or `a` recursive with `b` its node. */
67
+ function sameType(a, b) {
68
+ return isTypeEqual(a, b) || (a.type === "Recursive" && isTypeEqual(a.node, b));
69
+ }
70
+ /** A rebuilt value's type: the original, recursive wrapper and all, when what was rebuilt is its node. */
71
+ function rewrap(original, rebuilt) {
72
+ return original.type === "Recursive" && isTypeEqual(original.node, rebuilt) ? original : rebuilt;
73
+ }
74
+ /**
75
+ * A shape with each option that may be null opened: `null`, and its value,
76
+ * as members of their own. In a lenient place jq raises for a `null` a
77
+ * builtin cannot take and computes with a value, so each is checked apart.
78
+ *
79
+ * @returns the opened shape, or `undefined` when no member is such an option
80
+ */
81
+ function openOptions(shape) {
82
+ if (shape.kind === "error")
83
+ return undefined;
84
+ let opened = false;
85
+ const members = [];
86
+ for (const member of membersOf(shape)) {
87
+ const payload = nullablePayload(unwrap(member.shape.type));
88
+ if (payload === undefined || member.shape.facts?.kind === "present") {
89
+ members.push(member);
90
+ continue;
91
+ }
92
+ opened = true;
93
+ members.push({ shape: typed(NullType), case: member.case }, { shape: typed(payload), case: member.case });
94
+ }
95
+ return opened ? union(members) : undefined;
96
+ }
97
+ /** Checks one program. */
98
+ class Checker {
99
+ text;
100
+ spans;
101
+ options;
102
+ input;
103
+ diagnostics = [];
104
+ records = new Map();
105
+ /** The nodes checked, by path. */
106
+ nodes = new Map();
107
+ /** What is in scope at each node checked, by instance and path. */
108
+ scopes = new Map();
109
+ /** Each node's input when it was checked, by instance and path. */
110
+ inputs = new Map();
111
+ /** How many instances {@link Checker.retype} has made. */
112
+ retypes = 0;
113
+ /** The cases each update's `.value` step updates, by instance, path and variant type. */
114
+ updatedCases = new Map();
115
+ /** The builtin calls checked with their input's options opened, by instance and path: whether each raises for null. */
116
+ opened = new Map();
117
+ /** How many type errors a lenient place has let by, to run as errors. */
118
+ refused = 0;
119
+ reads = [];
120
+ /** Defs being instantiated, by instance signature: their output so far, for recursion. */
121
+ active = new Map();
122
+ /** The root's field names, with `root`. */
123
+ rootNames;
124
+ constructor(text, spans, options, input) {
125
+ this.text = text;
126
+ this.spans = spans;
127
+ this.options = options;
128
+ this.input = input;
129
+ const t = unwrap(input);
130
+ this.rootNames = options.root === true && t.type === "Struct" ? Object.keys(t.fields) : [];
131
+ }
132
+ // ─── Recording and reporting ───────────────────────────────────────────
133
+ range(path) {
134
+ return this.spans.get(path);
135
+ }
136
+ record(path, env, result) {
137
+ this.records.set(`${env.instance}|${path}`, result);
138
+ return result;
139
+ }
140
+ /** Reports a problem and gives the error shape. */
141
+ fail(range, code, message, options = {}) {
142
+ this.diagnostics.push(report(this.text, code, range, message, options));
143
+ return { shape: ERROR, mult: ONE };
144
+ }
145
+ /** A type error jq raises at run time: reported, or in a lenient place no output. */
146
+ mismatch(env, range, code, message, options = {}) {
147
+ if (env.lenient) {
148
+ this.refused += 1;
149
+ return { shape: typed(NeverType), mult: ZERO };
150
+ }
151
+ return this.fail(range, code, message, options);
152
+ }
153
+ warn(range, code, message, fixes = []) {
154
+ this.diagnostics.push(report(this.text, code, range, message, { fixes, warning: true }));
155
+ }
156
+ /** The text of a node, as written. */
157
+ source(path) {
158
+ const r = this.range(path);
159
+ return r === undefined ? "" : this.text.slice(r.from, r.to);
160
+ }
161
+ /** The range of a field read's `.name` (or `."name"`), without its target. */
162
+ accessorRange(node, path) {
163
+ const whole = this.range(path);
164
+ const target = this.range(childPath(path, "field.target"));
165
+ if (whole === undefined || target === undefined)
166
+ return whole;
167
+ const from = node.value.target.type === "identity" ? target.from : target.to;
168
+ return { from, to: node.value.optional ? whole.to - 1 : whole.to };
169
+ }
170
+ // ─── The walk ──────────────────────────────────────────────────────────
171
+ check(node, path, input, env) {
172
+ this.nodes.set(path, node);
173
+ this.scopes.set(`${env.instance}|${path}`, env);
174
+ this.inputs.set(`${env.instance}|${path}`, input.shape);
175
+ // A node whose input has no values never runs (a branch narrowing rules
176
+ // out, the elements of `[]`): it gives nothing, and `lo: 1, hi: 0` is the
177
+ // identity of `either`, so a dead branch leaves its `if`'s bounds alone.
178
+ if (input.shape.kind === "type" && input.shape.type.type === "Never")
179
+ return this.record(path, env, DEAD);
180
+ return this.record(path, env, this.checkNode(node, path, input, env));
181
+ }
182
+ checkNode(node, path, input, env) {
183
+ const at = (step) => childPath(path, step);
184
+ switch (node.type) {
185
+ case "identity":
186
+ return { shape: input.shape, mult: ONE, access: [], stream: input.stream };
187
+ case "descend":
188
+ return this.descend(input, path, env);
189
+ case "literal":
190
+ return { shape: typed(this.literal(node.value).type), mult: ONE };
191
+ case "variable":
192
+ return this.variable(node.value, path, env);
193
+ case "break":
194
+ if (!env.labels.has(node.value))
195
+ return this.fail(this.range(path), "unknown_function", MESSAGES.unknownLabel(node.value));
196
+ return { shape: typed(NeverType), mult: ZERO };
197
+ case "field":
198
+ return this.field(node, path, input, env);
199
+ case "index":
200
+ return this.index(node, path, input, env);
201
+ case "slice":
202
+ return this.slice(node, path, input, env);
203
+ case "iterate":
204
+ return this.iterate(node, path, input, env);
205
+ case "pipe": {
206
+ const left = this.check(node.value.left, at("pipe.left"), input, env);
207
+ const right = this.check(node.value.right, at("pipe.right"), left, env);
208
+ return this.compose(left, right);
209
+ }
210
+ case "comma": {
211
+ const left = this.check(node.value.left, at("comma.left"), input, env);
212
+ const right = this.check(node.value.right, at("comma.right"), input, env);
213
+ return { shape: union([...membersOf(left.shape), ...membersOf(right.shape)]), mult: also(left.mult, right.mult) };
214
+ }
215
+ case "alternative":
216
+ return this.alternative(node, path, input, env);
217
+ case "negate": {
218
+ const operand = this.check(node.value, at("negate"), input, env);
219
+ return this.mapMembers(operand, member => {
220
+ const t = unwrap(member.type);
221
+ if (isNumber(t))
222
+ return { shape: typed(t), mult: ONE };
223
+ // In a lenient place, an option is null, which raises, or a number, negated.
224
+ const payload = nullablePayload(t);
225
+ if (env.lenient && payload !== undefined && isNumber(unwrap(payload)))
226
+ return { shape: typed(unwrap(payload)), mult: MAYBE };
227
+ return this.mismatch(env, this.range(path), "type_mismatch", MESSAGES.negate(describeType(member.type)));
228
+ });
229
+ }
230
+ case "binary":
231
+ return this.binary(node, path, input, env);
232
+ case "array": {
233
+ if (node.value.type === "none")
234
+ return { shape: typed(ArrayType(NeverType)), mult: ONE };
235
+ const body = this.check(node.value.value, at("array.some"), input, env);
236
+ const element = this.collect(body, this.range(path), at("array.some"));
237
+ if (element === undefined)
238
+ return { shape: ERROR, mult: ONE };
239
+ const facts = body.shape.kind === "type" && body.shape.facts !== undefined ? { kind: "elements", element: body.shape.facts } : undefined;
240
+ return { shape: typed(ArrayType(element), facts), mult: ONE };
241
+ }
242
+ case "object":
243
+ return this.object(node, path, input, env);
244
+ case "string": {
245
+ let mult = ONE;
246
+ node.value.forEach((part, i) => {
247
+ if (part.type === "interpolate")
248
+ mult = piped(mult, this.check(part.value, at(`string[${i}].interpolate`), input, env).mult);
249
+ });
250
+ return { shape: typed(StringType), mult };
251
+ }
252
+ case "format":
253
+ return this.format(node, path, input, env);
254
+ case "if":
255
+ return this.conditional(node, path, input, env);
256
+ case "try":
257
+ return this.tryCatch(node, path, input, env);
258
+ case "reduce":
259
+ case "foreach":
260
+ return this.fold(node, path, input, env);
261
+ case "bind":
262
+ return this.bind(node, path, input, env);
263
+ case "label": {
264
+ const labels = new Set(env.labels);
265
+ labels.add(node.value.name);
266
+ const body = this.check(node.value.body, at("label.body"), input, { ...env, labels });
267
+ // A break stops the stream wherever it runs, so outputs the body would give after it may not come.
268
+ return { shape: body.shape, mult: breaksTo(node.value.body, node.value.name) ? { lo: 0, hi: body.mult.hi } : body.mult };
269
+ }
270
+ case "def": {
271
+ const defs = new Map(env.defs);
272
+ const params = node.value.params;
273
+ const key = `${node.value.name}/${params.length}`;
274
+ let closure;
275
+ const binding = { kind: "def", node, path, env: () => closure };
276
+ defs.set(key, binding);
277
+ closure = { ...env, defs };
278
+ return this.check(node.value.rest, at("def.rest"), input, closure);
279
+ }
280
+ case "call":
281
+ return this.call(node, path, input, env);
282
+ case "update":
283
+ return this.update(node, path, input, env);
284
+ }
285
+ }
286
+ /** `a | b`: `b` runs on each output of `a`. */
287
+ compose(left, right) {
288
+ const prefix = left.access;
289
+ return {
290
+ shape: right.shape,
291
+ mult: piped(left.mult, right.mult),
292
+ access: prefix !== undefined && right.access !== undefined ? [...prefix, ...right.access] : undefined,
293
+ caseOf: prefix !== undefined && right.caseOf !== undefined ? { ...right.caseOf, path: [...prefix, ...right.caseOf.path] } : undefined,
294
+ proves: prefix !== undefined && right.proves !== undefined ? right.proves.map(p => ({ ...p, path: [...prefix, ...p.path] })) : undefined,
295
+ typeOf: prefix !== undefined && right.typeOf !== undefined ? [...prefix, ...right.typeOf] : undefined,
296
+ partial: right.partial,
297
+ stream: right.stream,
298
+ };
299
+ }
300
+ /** Applies a typing to each member of a result's shape, and unions the outcomes. */
301
+ mapMembers(input, each) {
302
+ if (input.shape.kind === "error")
303
+ return { shape: ERROR, mult: input.mult };
304
+ const members = [];
305
+ let mult;
306
+ let error = false;
307
+ for (const member of membersOf(input.shape)) {
308
+ const out = each(member.shape, member.case);
309
+ if (out.shape.kind === "error")
310
+ error = true;
311
+ else
312
+ for (const m of membersOf(out.shape))
313
+ members.push({ shape: m.shape, case: m.case ?? member.case });
314
+ mult = mult === undefined ? out.mult : either(mult, out.mult);
315
+ }
316
+ if (error)
317
+ return { shape: ERROR, mult: mult ?? ONE };
318
+ return { shape: union(members), mult: mult ?? ZERO };
319
+ }
320
+ /**
321
+ * The one type a stream's outputs share, or a reported `ambiguous_output`.
322
+ *
323
+ * @param result - the stream
324
+ * @param range - where to report a problem
325
+ * @param at - the path of the node that gives the stream, when it is one:
326
+ * outputs that are fields read side by side (`.id, .customer_id`) get the
327
+ * suggestion to put them in an object
328
+ */
329
+ collect(result, range, at) {
330
+ if (result.shape.kind === "error")
331
+ return undefined;
332
+ const type = unifyShape(result.shape);
333
+ if (type !== undefined)
334
+ return type;
335
+ let acc = NeverType;
336
+ for (const member of membersOf(result.shape)) {
337
+ const next = unify(acc, member.shape.type);
338
+ if (next === undefined) {
339
+ const message = MESSAGES.noCommonType(describeType(acc), describeType(member.shape.type));
340
+ const outputs = at === undefined ? undefined : this.lastStage(at);
341
+ const names = outputs === undefined ? undefined : fieldsSideBySide(outputs.node);
342
+ const where = outputs === undefined ? undefined : this.range(outputs.path);
343
+ if (names === undefined || where === undefined) {
344
+ this.fail(range, "ambiguous_output", message);
345
+ }
346
+ else {
347
+ const suggestion = `{${names.join(", ")}}`;
348
+ this.fail(where, "ambiguous_output", message, { suggestions: [suggestion], fixes: [edit(`Use ${suggestion}`, where.from, where.to, suggestion)] });
349
+ }
350
+ return undefined;
351
+ }
352
+ acc = next;
353
+ }
354
+ return acc;
355
+ }
356
+ /** The node that gives a node's outputs: the last segment of its pipes, through `as` bodies. */
357
+ lastStage(path) {
358
+ let node = this.nodes.get(path);
359
+ let at = path;
360
+ while (node !== undefined) {
361
+ if (node.type === "pipe") {
362
+ at = childPath(at, "pipe.right");
363
+ node = node.value.right;
364
+ continue;
365
+ }
366
+ if (node.type === "bind") {
367
+ at = childPath(at, "bind.body");
368
+ node = node.value.body;
369
+ continue;
370
+ }
371
+ return { node, path: at };
372
+ }
373
+ return undefined;
374
+ }
375
+ // ─── Leaves ────────────────────────────────────────────────────────────
376
+ /** A literal's value and type, as the program writes it. */
377
+ literal(value) {
378
+ return literalValue(value);
379
+ }
380
+ variable(name, path, env) {
381
+ if (name === "__loc__")
382
+ return { shape: typed(StructType({ file: StringType, line: IntegerType })), mult: ONE };
383
+ if (name === "ENV" || name === "__prog_args")
384
+ return this.fail(this.range(path), "unsupported", MESSAGES.excluded(`$${name}`));
385
+ const bound = env.vars.get(name);
386
+ if (bound === undefined)
387
+ return this.fail(this.range(path), "unknown_function", MESSAGES.unknownVariable(name));
388
+ return { shape: bound.shape, mult: ONE, caseOf: undefined };
389
+ }
390
+ /** `..`: the value and every value nested in it, depth first. */
391
+ descend(input, path, env) {
392
+ if (this.refuseRoot(input, path, env))
393
+ return { shape: ERROR, mult: ONE };
394
+ if (input.shape.kind === "error")
395
+ return { shape: ERROR, mult: SOME };
396
+ const types = descendTypes(membersOf(input.shape).map(member => member.shape.type));
397
+ return { shape: union(types.map(type => ({ shape: typed(type), case: undefined }))), mult: SOME };
398
+ }
399
+ // ─── Paths ─────────────────────────────────────────────────────────────
400
+ field(node, path, input, env) {
401
+ const { name, optional, target } = node.value;
402
+ const base = this.check(target, childPath(path, "field.target"), input, env);
403
+ const range = this.accessorRange(node, path);
404
+ const lenient = optional ? { ...env, lenient: true } : env;
405
+ const read = this.readField(base, name, range, lenient, optional, path);
406
+ const composed = this.compose(base, { ...read, mult: read.mult });
407
+ return {
408
+ ...composed,
409
+ access: base.access !== undefined && read.access !== undefined ? [...base.access, ...read.access] : undefined,
410
+ caseOf: read.caseOf !== undefined && base.access !== undefined ? { ...read.caseOf, path: base.access } : undefined,
411
+ partial: read.partial,
412
+ stream: base.stream,
413
+ };
414
+ }
415
+ /** `.name` on each member of a shape. */
416
+ readField(base, name, range, env, optional, path) {
417
+ if (base.shape.kind === "error")
418
+ return { shape: ERROR, mult: ONE };
419
+ const isRoot = base.shape.kind === "type" && this.isRootShape(base.shape);
420
+ const all = membersOf(base.shape);
421
+ // The payloads of an un-narrowed variant: a case whose payload lacks the
422
+ // field gives null, as jq's `.value.f` does, unless no case has it.
423
+ const payloads = all.some(m => m.case !== undefined);
424
+ const withField = all.filter(m => m.case !== undefined && name in this.fieldsOf(m.shape.type));
425
+ if (payloads && withField.length === 0 && !optional && !env.lenient) {
426
+ const names = [...new Set(all.flatMap(m => Object.keys(this.fieldsOf(m.shape.type))))];
427
+ const suggestions = closest(name, names).map(n => `.${n}`);
428
+ const fixes = suggestions.length > 0 && range !== undefined ? [edit(`Use ${suggestions[0]}`, range.from, range.to, suggestions[0])] : [];
429
+ return this.fail(range, "unknown_field", MESSAGES.unknownPayloadField(`.${name}`, suggestions[0]), { suggestions, fixes });
430
+ }
431
+ const members = [];
432
+ let mult;
433
+ let caseOf;
434
+ let missing = false;
435
+ let error = false;
436
+ for (const member of all) {
437
+ const payload = unwrap(nullablePayload(unwrap(member.shape.type)) ?? member.shape.type).type;
438
+ const lacks = member.case !== undefined && (payload === "Struct" || payload === "Null") && !(name in this.fieldsOf(member.shape.type));
439
+ const out = this.fieldOfType(member.shape, name, range, env, optional || lacks, isRoot, path);
440
+ if (out.caseOf !== undefined)
441
+ caseOf = out.caseOf;
442
+ if (out.shape.kind === "error") {
443
+ error = true;
444
+ continue;
445
+ }
446
+ if (lacks)
447
+ missing = true;
448
+ for (const m of membersOf(out.shape))
449
+ members.push(m);
450
+ mult = mult === undefined ? out.mult : either(mult, out.mult);
451
+ }
452
+ if (error)
453
+ return { shape: ERROR, mult: ONE };
454
+ const partial = missing && withField.length === 1 ? { caseName: withField[0].case, leaf: name, at: path } : undefined;
455
+ // A field of the payloads is one field to jq: its values share a type when they can.
456
+ const merged = payloads ? unifyShape(union(members)) : undefined;
457
+ const shape = merged !== undefined ? typed(merged) : union(members);
458
+ return { shape, mult: mult ?? ONE, access: [name], caseOf, partial };
459
+ }
460
+ /** The fields jq sees on a type: a struct's, through an option. */
461
+ fieldsOf(type) {
462
+ const t = unwrap(nullablePayload(unwrap(type)) ?? type);
463
+ return t.type === "Struct" ? t.fields : {};
464
+ }
465
+ isRootShape(shape) {
466
+ return this.options.root === true && shape.type === this.input;
467
+ }
468
+ fieldOfType(member, name, range, env, optional, isRoot, path) {
469
+ const type = member.type;
470
+ const t = unwrap(type);
471
+ if (t.type === "Null")
472
+ return { shape: typed(NullType), mult: ONE };
473
+ const payload = nullablePayload(t);
474
+ if (payload !== undefined) {
475
+ const inner = this.fieldOfType(typed(payload, member.facts?.kind === "present" ? member.facts.payload : undefined), name, range, env, optional, false, path);
476
+ if (inner.shape.kind === "error" || member.facts?.kind === "present")
477
+ return inner;
478
+ // `.type` of an optional variant still tests its case: a true test proves the value is there.
479
+ const wrapped = this.mapMembers(inner, m => {
480
+ const t2 = orNull(m.type);
481
+ if (t2 === undefined)
482
+ return this.mismatch(env, range, "ambiguous_output", MESSAGES.noCommonType(describeType(m.type), "Null"));
483
+ return { shape: typed(t2, m.facts), mult: ONE };
484
+ });
485
+ return { ...wrapped, caseOf: inner.caseOf };
486
+ }
487
+ switch (t.type) {
488
+ case "Struct": {
489
+ const fields = t.fields;
490
+ if (name in fields) {
491
+ if (isRoot && !this.reads.includes(name))
492
+ this.reads.push(name);
493
+ const facts = member.facts?.kind === "fields" ? member.facts.fields.get(name) : undefined;
494
+ return { shape: typed(fields[name], facts), mult: ONE };
495
+ }
496
+ if (optional || env.lenient)
497
+ return { shape: typed(NullType), mult: ONE };
498
+ const suggestions = closest(name, Object.keys(fields)).map(s => `.${s}`);
499
+ const fixes = suggestions.length > 0 && range !== undefined ? [edit(`Use ${suggestions[0]}`, range.from, range.to, suggestions[0])] : [];
500
+ const message = isRoot ? MESSAGES.unknownDataset(`.${name}`, suggestions[0]) : MESSAGES.unknownField(`.${name}`, describeType(type), suggestions[0]);
501
+ return this.fail(range, "unknown_field", message, { suggestions, fixes });
502
+ }
503
+ case "Dict": {
504
+ if (unwrap(t.key).type !== "String") {
505
+ return this.mismatch(env, range, "type_mismatch", MESSAGES.keyType(`.${name}`, describeType(t.key), jsonString(name), "String"));
506
+ }
507
+ const value = orNull(t.value);
508
+ if (value === undefined)
509
+ return this.mismatch(env, range, "ambiguous_output", MESSAGES.noCommonType(describeType(t.value), "Null"));
510
+ return { shape: typed(value), mult: ONE };
511
+ }
512
+ case "Variant": {
513
+ const cases = casesOf(t, member.facts);
514
+ if (name === "type")
515
+ return { shape: typed(StringType), mult: ONE, caseOf: { path: [], variant: t, cases } };
516
+ if (name === "value") {
517
+ const payloadFacts = member.facts?.kind === "cases" ? member.facts.payload : undefined;
518
+ const all = t.cases;
519
+ return {
520
+ shape: union(cases.map(c => ({ shape: typed(all[c], cases.length === 1 ? payloadFacts : undefined), case: c }))),
521
+ mult: cases.length === 0 ? ZERO : ONE,
522
+ };
523
+ }
524
+ if (optional || env.lenient)
525
+ return { shape: typed(NullType), mult: ONE };
526
+ return this.fail(range, "unknown_field", MESSAGES.unknownVariantField(`.${name}`, this.source(childPath(path, "field.target")) || "."), {
527
+ suggestions: [".type", ".value"],
528
+ });
529
+ }
530
+ default:
531
+ if (optional)
532
+ return { shape: typed(NeverType), mult: ZERO };
533
+ return this.mismatch(env, range, "type_mismatch", MESSAGES.notAField(`.${name}`, describeType(type)));
534
+ }
535
+ }
536
+ index(node, path, input, env) {
537
+ const { index, optional, target } = node.value;
538
+ const base = this.check(target, childPath(path, "index.target"), input, env);
539
+ const keyPath = childPath(path, "index.index");
540
+ const key = this.check(index, keyPath, input, env);
541
+ const range = this.range(path);
542
+ const lenient = optional ? { ...env, lenient: true } : env;
543
+ // A literal string on a struct reads a field.
544
+ if (index.type === "literal") {
545
+ const literal = this.literal(index.value);
546
+ if (literal.type.type === "String" && membersOf(base.shape).every(m => unwrap(m.shape.type).type !== "Dict")) {
547
+ const read = this.readField(base, literal.value, range, lenient, optional, path);
548
+ return { ...this.compose(base, read), access: base.access !== undefined ? [...base.access, literal.value] : undefined, stream: base.stream };
549
+ }
550
+ }
551
+ if (key.shape.kind === "error")
552
+ return { shape: ERROR, mult: ONE };
553
+ const keyType = this.collect(key, this.range(keyPath));
554
+ if (keyType === undefined)
555
+ return { shape: ERROR, mult: ONE };
556
+ const form = `${this.source(childPath(path, "index.target")) || "."}[…]`;
557
+ const out = this.mapMembers(base, member => {
558
+ const t = unwrap(member.type);
559
+ if (t.type === "Null")
560
+ return { shape: typed(NullType), mult: ONE };
561
+ const payload = nullablePayload(t);
562
+ const container = payload !== undefined ? unwrap(payload) : t;
563
+ const element = (() => {
564
+ switch (container.type) {
565
+ case "Array":
566
+ case "Set": return { key: IntegerType, value: (container.type === "Array" ? container.value : container.key), facts: member.facts?.kind === "elements" ? member.facts.element : undefined };
567
+ case "Vector": return { key: IntegerType, value: container.element, facts: undefined };
568
+ case "Matrix": return { key: IntegerType, value: ArrayType(container.element), facts: undefined };
569
+ case "Dict": return { key: container.key, value: container.value, facts: member.facts?.kind === "values" ? member.facts.value : undefined };
570
+ default: return undefined;
571
+ }
572
+ })();
573
+ if (element === undefined) {
574
+ if (container.type === "Struct")
575
+ return this.mismatch(lenient, this.range(keyPath), "type_mismatch", MESSAGES.structKey(form));
576
+ if (optional)
577
+ return { shape: typed(NeverType), mult: ZERO };
578
+ return this.mismatch(lenient, range, "not_indexable", MESSAGES.notIndexable(this.source(childPath(path, "index.target")) || ".", describeType(member.type)));
579
+ }
580
+ if (!this.keyFits(keyType, element.key, index, keyPath, env)) {
581
+ return this.mismatch(lenient, this.range(keyPath), "type_mismatch", MESSAGES.keyType(form, describeType(element.key), this.source(keyPath), describeType(keyType)));
582
+ }
583
+ const value = orNull(element.value);
584
+ if (value === undefined)
585
+ return this.mismatch(lenient, range, "ambiguous_output", MESSAGES.noCommonType(describeType(element.value), "Null"));
586
+ return { shape: typed(value, element.facts), mult: ONE };
587
+ });
588
+ return { shape: out.shape, mult: piped(key.mult, piped(base.mult, out.mult)) };
589
+ }
590
+ /** Whether a key of `given` type indexes by `wanted`, reading a literal as its operand's type. */
591
+ keyFits(given, wanted, node, path, env) {
592
+ const w = unwrap(wanted);
593
+ if (isTypeEqual(given, w))
594
+ return true;
595
+ if (node.type === "literal")
596
+ return this.coerceLiteral(node, path, w, env) !== undefined;
597
+ return false;
598
+ }
599
+ /**
600
+ * A literal used where `wanted` is: an Integer where a Float is, an ISO
601
+ * string where a DateTime is, a string that is not an ISO-8601 date
602
+ * reported. The program keeps the literal as written (`devdocs/QUERY.md`
603
+ * §7). An Integer needs nothing more: its translation widens it wherever
604
+ * it meets a Float. A string read as a DateTime is recorded as one in this
605
+ * instance, and its translation gives the DateTime it writes.
606
+ *
607
+ * @returns the type the literal is read as, or `undefined` when it does
608
+ * not fit
609
+ */
610
+ coerceLiteral(node, path, wanted, env) {
611
+ const literal = this.literal(node.value);
612
+ if (isTypeEqual(literal.type, wanted))
613
+ return wanted;
614
+ if (wanted.type === "Float" && literal.type.type === "Integer")
615
+ return FloatType;
616
+ if (wanted.type === "DateTime" && literal.type.type === "String") {
617
+ if (isoDateTime(literal.value) === undefined) {
618
+ this.fail(this.range(path), "type_mismatch", MESSAGES.isoDate(this.source(path)));
619
+ return undefined;
620
+ }
621
+ this.records.set(`${env.instance}|${path}`, { shape: typed(DateTimeType), mult: ONE });
622
+ return DateTimeType;
623
+ }
624
+ return undefined;
625
+ }
626
+ /**
627
+ * An array literal of literals (`["2026-01-01", "2026-02-01"]`) used where
628
+ * an array of `wanted` is: each element read as {@link coerceLiteral} reads
629
+ * it, and what checking the array recorded retyped to match.
630
+ *
631
+ * @returns the array's new type, or `undefined` when it is not such an
632
+ * array, or an element does not fit
633
+ */
634
+ coerceArrayLiteral(node, path, wanted, env) {
635
+ if (node.type !== "array" || node.value.type !== "some")
636
+ return undefined;
637
+ const literals = [];
638
+ const commas = [];
639
+ const collect = (n, at) => {
640
+ if (n.type === "literal") {
641
+ literals.push({ node: n, path: at });
642
+ return true;
643
+ }
644
+ if (n.type !== "comma")
645
+ return false;
646
+ commas.push(at);
647
+ return collect(n.value.left, childPath(at, "comma.left")) && collect(n.value.right, childPath(at, "comma.right"));
648
+ };
649
+ if (!collect(node.value.value, childPath(path, "array.some")))
650
+ return undefined;
651
+ for (const literal of literals)
652
+ if (this.coerceLiteral(literal.node, literal.path, wanted, env) === undefined)
653
+ return undefined;
654
+ const retype = (at, type) => {
655
+ const key = `${env.instance}|${at}`;
656
+ const r = this.records.get(key);
657
+ if (r !== undefined)
658
+ this.records.set(key, { ...r, shape: typed(type) });
659
+ };
660
+ for (const at of [...commas, ...literals.map(l => l.path)])
661
+ retype(at, wanted);
662
+ retype(path, ArrayType(wanted));
663
+ return ArrayType(wanted);
664
+ }
665
+ slice(node, path, input, env) {
666
+ const { from, optional, target, to } = node.value;
667
+ const base = this.check(target, childPath(path, "slice.target"), input, env);
668
+ let mult = base.mult;
669
+ for (const [bound, step] of [[from, "slice.from.some"], [to, "slice.to.some"]]) {
670
+ if (bound.type !== "some")
671
+ continue;
672
+ const boundPath = childPath(path, step);
673
+ const r = this.check(bound.value, boundPath, input, env);
674
+ mult = piped(mult, r.mult);
675
+ const t = this.collect(r, this.range(boundPath));
676
+ if (t !== undefined && !this.keyFits(t, IntegerType, bound.value, boundPath, env) && !canBeNull(t)) {
677
+ this.mismatch(env, this.range(boundPath), "type_mismatch", MESSAGES.sliceBound(describeType(t)));
678
+ }
679
+ }
680
+ const lenient = optional ? { ...env, lenient: true } : env;
681
+ const out = this.mapMembers(base, member => {
682
+ const u = unwrap(member.type);
683
+ // `null` slices to null, as jq slices it: an option's value is sliced, and its null stays null.
684
+ const payload = nullablePayload(u);
685
+ const present = member.facts?.kind === "present";
686
+ const t = payload === undefined ? u : unwrap(payload);
687
+ const facts = payload === undefined ? member.facts : member.facts?.kind === "present" ? member.facts.payload : undefined;
688
+ const sliced = (type, f) => {
689
+ if (payload === undefined || present)
690
+ return { shape: typed(type, f), mult: ONE };
691
+ const option = orNull(type);
692
+ return option === undefined ? this.mismatch(lenient, this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(type), "Null")) : { shape: typed(option), mult: ONE };
693
+ };
694
+ switch (t.type) {
695
+ case "Null": return { shape: typed(NullType), mult: ONE };
696
+ case "Array":
697
+ case "String":
698
+ case "Vector": return sliced(t, facts);
699
+ case "Set": return sliced(ArrayType(t.key));
700
+ default:
701
+ if (optional)
702
+ return { shape: typed(NeverType), mult: ZERO };
703
+ return this.mismatch(lenient, this.range(path), "not_indexable", MESSAGES.notIndexable(this.source(childPath(path, "slice.target")) || ".", describeType(member.type)));
704
+ }
705
+ });
706
+ return { shape: out.shape, mult: piped(mult, out.mult) };
707
+ }
708
+ iterate(node, path, input, env) {
709
+ const { optional, target } = node.value;
710
+ const base = this.check(target, childPath(path, "iterate.target"), input, env);
711
+ if (this.refuseRoot(base, path, env))
712
+ return { shape: ERROR, mult: ONE };
713
+ const form = `${this.source(childPath(path, "iterate.target")) === "." ? "." : this.source(childPath(path, "iterate.target"))}[]`;
714
+ const out = this.mapMembers(base, member => this.elementsOf(member, optional, optional ? { ...env, lenient: true } : env, form, this.range(path)));
715
+ return { shape: out.shape, mult: piped(base.mult, out.mult), stream: path };
716
+ }
717
+ /** The elements `.[]` gives on one member. */
718
+ elementsOf(member, optional, env, form, range) {
719
+ const t = unwrap(member.type);
720
+ const payload = nullablePayload(t);
721
+ if (payload !== undefined) {
722
+ // Null raises; in a lenient place that gives no output, and a value's elements are given.
723
+ if (!optional && !env.lenient && member.facts?.kind !== "present") {
724
+ const fixes = range === undefined ? [] : [edit(`Use ${form}?`, range.to, range.to, "?")];
725
+ return this.mismatch(env, range, "not_iterable", MESSAGES.notIterableNull(form, describeType(member.type)), { fixes });
726
+ }
727
+ const inner = this.elementsOf(typed(payload, member.facts?.kind === "present" ? member.facts.payload : undefined), optional, env, form, range);
728
+ return { shape: inner.shape, mult: { lo: 0, hi: inner.mult.hi } };
729
+ }
730
+ switch (t.type) {
731
+ case "Array": return { shape: typed(t.value, member.facts?.kind === "elements" ? member.facts.element : undefined), mult: MANY };
732
+ case "Set": return { shape: typed(t.key), mult: MANY };
733
+ case "Vector": return { shape: typed(t.element), mult: MANY };
734
+ case "Matrix": return { shape: typed(ArrayType(t.element)), mult: MANY };
735
+ case "Dict": return { shape: typed(t.value, member.facts?.kind === "values" ? member.facts.value : undefined), mult: MANY };
736
+ case "Struct": {
737
+ const fields = t.fields;
738
+ const facts = member.facts?.kind === "fields" ? member.facts.fields : undefined;
739
+ return {
740
+ shape: union(Object.entries(fields).map(([name, f]) => ({ shape: typed(f, facts?.get(name)), case: undefined }))),
741
+ mult: Object.keys(fields).length === 0 ? ZERO : SOME,
742
+ };
743
+ }
744
+ default:
745
+ if (optional)
746
+ return { shape: typed(NeverType), mult: ZERO };
747
+ return this.mismatch(env, range, "not_iterable", MESSAGES.notIterable(form, describeType(member.type)));
748
+ }
749
+ }
750
+ /** With `root`, a filter that would read every dataset of the root is refused. */
751
+ refuseRoot(input, path, _env) {
752
+ if (this.options.root !== true || input.shape.kind !== "type" || !this.isRootShape(input.shape))
753
+ return false;
754
+ this.fail(this.range(path), "unsupported", MESSAGES.wholeRoot(this.rootNames));
755
+ return true;
756
+ }
757
+ /**
758
+ * "Narrow first": a payload field only one case has, read where its value
759
+ * is needed. The fix inserts `select(.F.type == "case") | ` before the read,
760
+ * so only rows of that case reach it.
761
+ */
762
+ narrowFirst(env, range, type, partial) {
763
+ // The read is `<base>.F.value.leaf`, where <base> is `.` or the node the field reads start from.
764
+ const leaf = this.nodes.get(partial.at);
765
+ const step = (node, path) => node?.type === "field" || node?.type === "index" ? { node: node.value.target, path: childPath(path, `${node.type}.target`) } : undefined;
766
+ const value = step(leaf, partial.at);
767
+ const variant = value === undefined ? undefined : step(value.node, value.path);
768
+ if (variant === undefined)
769
+ return this.mismatch(env, range, "type_mismatch", MESSAGES.input(this.source(partial.at), "a value", describeType(type)));
770
+ let base = variant;
771
+ while (base.node.type === "field" || base.node.type === "index")
772
+ base = step(base.node, base.path);
773
+ const chain = this.range(partial.at);
774
+ const start = base.node.type === "identity" ? chain.from : this.range(base.path).to;
775
+ const variantText = this.text.slice(start, this.range(variant.path).to);
776
+ const leafText = this.text.slice(start, chain.to);
777
+ const select = `select(${variantText}.type == ${jsonString(partial.caseName)})`;
778
+ const message = MESSAGES.narrowFirst(leafText, describeType(type), partial.caseName, variantText, partial.leaf);
779
+ const label = "Narrow first";
780
+ let fix;
781
+ if (base.node.type === "identity") {
782
+ fix = isPipePosition(partial.at)
783
+ ? edit(label, chain.from, chain.from, `${select} | `)
784
+ : { edits: [{ insert: `(${select} | `, length: 0n, offset: BigInt(chain.from) }, { insert: ")", length: 0n, offset: BigInt(chain.to) }], label };
785
+ }
786
+ else {
787
+ const inner = ` | ${select} | ${leafText}`;
788
+ fix = isPipePosition(partial.at)
789
+ ? edit(label, start, chain.to, inner)
790
+ : { edits: [{ insert: "(", length: 0n, offset: BigInt(chain.from) }, { insert: `${inner})`, length: BigInt(chain.to - start), offset: BigInt(start) }], label };
791
+ }
792
+ return this.mismatch(env, range, "type_mismatch", message, { fixes: [fix] });
793
+ }
794
+ /**
795
+ * An array builtin run on each element of a stream. The fix collects the
796
+ * stream, from the segment of the call's pipe chain that makes it to the
797
+ * segment before the call: `a | [b[] | c] | sort_by(…)`.
798
+ */
799
+ arrayOnElement(env, callPath, name, stream, type) {
800
+ const streamText = this.source(stream);
801
+ // The segments before the call in its pipe chain, first to last.
802
+ const segments = [];
803
+ let base = callPath;
804
+ while (base === "pipe.right" || base.endsWith(".pipe.right")) {
805
+ base = base === "pipe.right" ? "" : base.slice(0, -".pipe.right".length);
806
+ segments.unshift(childPath(base, "pipe.left"));
807
+ }
808
+ const first = segments.find(segment => stream === segment || stream.startsWith(`${segment}.`));
809
+ const from = first === undefined ? undefined : this.range(first);
810
+ const to = segments.length === 0 ? undefined : this.range(segments[segments.length - 1]);
811
+ if (from === undefined || to === undefined) {
812
+ return this.mismatch(env, this.range(callPath), "array_builtin_on_element", MESSAGES.arrayOnElement(name, streamText, describeType(type), `[${streamText}]`));
813
+ }
814
+ const collected = `[${this.text.slice(from.from, to.to)}]`;
815
+ const fix = {
816
+ edits: [{ insert: "[", length: 0n, offset: BigInt(from.from) }, { insert: "]", length: 0n, offset: BigInt(to.to) }],
817
+ label: `Collect ${streamText} first`,
818
+ };
819
+ return this.mismatch(env, this.range(callPath), "array_builtin_on_element", MESSAGES.arrayOnElement(name, streamText, describeType(type), collected), { fixes: [fix] });
820
+ }
821
+ // ─── Operators ─────────────────────────────────────────────────────────
822
+ alternative(node, path, input, env) {
823
+ const left = this.check(node.value.left, childPath(path, "alternative.left"), input, env);
824
+ const right = this.check(node.value.right, childPath(path, "alternative.right"), input, env);
825
+ if (left.shape.kind === "error" || right.shape.kind === "error")
826
+ return { shape: ERROR, mult: ONE };
827
+ const kept = [];
828
+ let alwaysTruthy = true;
829
+ for (const member of membersOf(left.shape)) {
830
+ const t = unwrap(member.shape.type);
831
+ if (t.type === "Null") {
832
+ alwaysTruthy = false;
833
+ continue;
834
+ }
835
+ const payload = nullablePayload(t);
836
+ if (payload !== undefined) {
837
+ alwaysTruthy = false;
838
+ kept.push({ shape: typed(payload), case: member.case });
839
+ continue;
840
+ }
841
+ if (t.type === "Boolean")
842
+ alwaysTruthy = false;
843
+ kept.push(member);
844
+ }
845
+ const shape = union([...kept, ...membersOf(right.shape)]);
846
+ const lo = right.mult.lo === 1 || (alwaysTruthy && left.mult.lo === 1) ? 1 : 0;
847
+ return { shape, mult: { lo, hi: Math.max(left.mult.hi, right.mult.hi) } };
848
+ }
849
+ binary(node, path, input, env) {
850
+ const { op } = node.value;
851
+ const leftPath = childPath(path, "binary.left");
852
+ const rightPath = childPath(path, "binary.right");
853
+ if (op === "and" || op === "or") {
854
+ const left = this.check(node.value.left, leftPath, input, env);
855
+ // The right side of `and` runs only when the left is true, so it sees the left's facts.
856
+ const rightInput = op === "and" && left.proves !== undefined ? this.narrow(input, left.proves) : input;
857
+ const right = this.check(node.value.right, rightPath, rightInput, env);
858
+ if (left.shape.kind === "error" || right.shape.kind === "error")
859
+ return { shape: ERROR, mult: ONE };
860
+ const proves = op === "and" ? [...(left.proves ?? []), ...(right.proves ?? [])] : undefined;
861
+ return { shape: typed(BooleanType), mult: piped(left.mult, right.mult), proves: proves?.length ? proves : undefined };
862
+ }
863
+ const left = this.check(node.value.left, leftPath, input, env);
864
+ const right = this.check(node.value.right, rightPath, input, env);
865
+ if (left.shape.kind === "error" || right.shape.kind === "error")
866
+ return { shape: ERROR, mult: piped(left.mult, right.mult) };
867
+ const mult = piped(left.mult, right.mult);
868
+ if (COMPARISON.has(op))
869
+ return { ...this.comparison(op, node, path, left, right, env), mult };
870
+ if (ARITHMETIC.has(op)) {
871
+ const out = this.arithmetic(op, left, right, { leftPath, rightPath, range: this.range(path) }, env);
872
+ return { shape: out.shape, mult: piped(mult, out.mult), partial: out.partial };
873
+ }
874
+ throw new Error(`checkJq: ${jsonString(op)} is not a jq binary operator`);
875
+ }
876
+ comparison(op, node, path, left, right, env) {
877
+ const leftPath = childPath(path, "binary.left");
878
+ const rightPath = childPath(path, "binary.right");
879
+ // `type == "number"`: a type test, which narrows.
880
+ for (const [typeSide, otherNode, otherPath] of [[left, node.value.right, rightPath], [right, node.value.left, leftPath]]) {
881
+ if (typeSide.typeOf === undefined || otherNode.type !== "literal" || (op !== "==" && op !== "!="))
882
+ continue;
883
+ const literal = this.literal(otherNode.value);
884
+ if (literal.type.type !== "String")
885
+ continue;
886
+ const name = literal.value;
887
+ if (!JQ_TYPE_NAMES.includes(name)) {
888
+ const suggestions = closest(name, JQ_TYPE_NAMES).map(c => jsonString(c));
889
+ const range = this.range(otherPath);
890
+ const fixes = suggestions.length > 0 && range !== undefined ? [edit(`Use ${suggestions[0]}`, range.from, range.to, suggestions[0])] : [];
891
+ return this.fail(range, "unknown_case", MESSAGES.unknownType(jsonString(name), JQ_TYPE_NAMES, suggestions[0]), { suggestions, fixes });
892
+ }
893
+ const types = op === "==" ? [name] : JQ_TYPE_NAMES.filter(n => n !== name);
894
+ return { shape: typed(BooleanType), mult: ONE, proves: [{ kind: "type", path: typeSide.typeOf, types }] };
895
+ }
896
+ // `.F.type == "case"`: a case test, which narrows.
897
+ for (const [caseSide, casePath, otherNode, otherPath] of [[left, leftPath, node.value.right, rightPath], [right, rightPath, node.value.left, leftPath]]) {
898
+ if (caseSide.caseOf === undefined || otherNode.type !== "literal")
899
+ continue;
900
+ const literal = this.literal(otherNode.value);
901
+ if (literal.type.type !== "String")
902
+ continue;
903
+ const name = literal.value;
904
+ const variantType = unwrap(caseSide.caseOf.variant);
905
+ const allCases = variantType.type === "Variant" ? Object.keys(variantType.cases) : [];
906
+ if (!allCases.includes(name)) {
907
+ const suggestions = closest(name, allCases).map(c => jsonString(c));
908
+ const range = this.range(otherPath);
909
+ const fixes = suggestions.length > 0 && range !== undefined ? [edit(`Use ${suggestions[0]}`, range.from, range.to, suggestions[0])] : [];
910
+ const variantPath = this.source(casePath).replace(/\s*\.\s*type$/, "");
911
+ return this.fail(range, "unknown_case", MESSAGES.unknownCase(variantPath, jsonString(name), allCases, suggestions[0]), { suggestions, fixes });
912
+ }
913
+ if (op === "==" || op === "!=") {
914
+ const cases = op === "==" ? [name] : caseSide.caseOf.cases.filter(c => c !== name);
915
+ return { shape: typed(BooleanType), mult: ONE, proves: [{ kind: "case", path: caseSide.caseOf.path, cases }] };
916
+ }
917
+ }
918
+ const lt = this.collect(left, this.range(leftPath));
919
+ const rt = this.collect(right, this.range(rightPath));
920
+ if (lt === undefined || rt === undefined)
921
+ return { shape: ERROR, mult: ONE };
922
+ if (lt.type === "Never" || rt.type === "Never")
923
+ return { shape: typed(BooleanType), mult: ONE };
924
+ // A whole variant compared with a string.
925
+ for (const [side, sidePath, other] of [[lt, leftPath, rt], [rt, rightPath, lt]]) {
926
+ const s = unwrap(nullablePayload(unwrap(side)) ?? side);
927
+ if (s.type === "Variant" && nullablePayload(s) === undefined && unwrap(other).type === "String") {
928
+ const text = this.source(sidePath);
929
+ const range = this.range(sidePath);
930
+ const fixes = range !== undefined ? [edit("Use .type", range.to, range.to, ".type")] : [];
931
+ return this.fail(range, "type_mismatch", MESSAGES.wholeVariant(text, describeType(side)), { fixes });
932
+ }
933
+ }
934
+ const reported = this.diagnostics.length;
935
+ const fits = this.comparable(lt, rt, node, path, env);
936
+ // A literal that failed to parse as the other side's type has said so.
937
+ if (this.diagnostics.length > reported)
938
+ return { shape: ERROR, mult: ONE };
939
+ if (fits) {
940
+ // An Integer never equals a Float with a fraction.
941
+ if ((op === "==" || op === "!=") && unwrap(nullablePayload(unwrap(lt)) ?? lt).type === "Integer" && node.value.right.type === "literal") {
942
+ const literal = this.literal(node.value.right.value);
943
+ if (literal.type.type === "Float" && !Number.isInteger(literal.value)) {
944
+ return this.fail(this.range(path), "type_mismatch", MESSAGES.neverEqual(op));
945
+ }
946
+ }
947
+ return { shape: typed(BooleanType), mult: ONE };
948
+ }
949
+ return this.mismatch(env, this.range(path), "type_mismatch", MESSAGES.compares(op, describeType(lt), describeType(rt)));
950
+ }
951
+ /** Whether two types compare: equal, numbers, a value and its option, or a literal read as the other's type. */
952
+ comparable(a, b, node, path, env) {
953
+ const ua = unwrap(a);
954
+ const ub = unwrap(b);
955
+ if (isTypeEqual(ua, ub))
956
+ return true;
957
+ // Numbers compare: the translation widens an Integer it compares with a Float.
958
+ if (isNumber(ua) && isNumber(ub))
959
+ return true;
960
+ if (ua.type === "Null" && canBeNull(ub))
961
+ return true;
962
+ if (ub.type === "Null" && canBeNull(ua))
963
+ return true;
964
+ const pa = nullablePayload(ua);
965
+ const pb = nullablePayload(ub);
966
+ if (pa !== undefined || pb !== undefined)
967
+ return this.comparable(pa ?? ua, pb ?? ub, node, path, env);
968
+ if (ua.type === "DateTime" && node.value.right.type === "literal")
969
+ return this.coerceLiteral(node.value.right, childPath(path, "binary.right"), DateTimeType, env) !== undefined;
970
+ if (ub.type === "DateTime" && node.value.left.type === "literal")
971
+ return this.coerceLiteral(node.value.left, childPath(path, "binary.left"), DateTimeType, env) !== undefined;
972
+ return unify(ua, ub) !== undefined && ua.type === ub.type;
973
+ }
974
+ arithmetic(op, left, right, operands, env) {
975
+ const { leftPath, rightPath } = operands;
976
+ const lt = this.collect(left, this.range(leftPath));
977
+ const rt = this.collect(right, this.range(rightPath));
978
+ if (lt === undefined || rt === undefined)
979
+ return { shape: ERROR, mult: ONE };
980
+ // An operand with no value (a recursion not yet known, `empty`) gives none.
981
+ if (lt.type === "Never" || rt.type === "Never")
982
+ return { shape: typed(NeverType), mult: ONE };
983
+ const fail = () => this.mismatch(env, operands.range, "type_mismatch", MESSAGES.arithmetic(op, describeType(lt), describeType(rt)));
984
+ let a = unwrap(lt);
985
+ let b = unwrap(rt);
986
+ // `null` is the identity for `+`.
987
+ if (op === "+") {
988
+ if (a.type === "Null")
989
+ return { shape: typed(b), mult: ONE };
990
+ if (b.type === "Null")
991
+ return { shape: typed(a), mult: ONE };
992
+ const pa = nullablePayload(a);
993
+ const pb = nullablePayload(b);
994
+ if (pa !== undefined && pb !== undefined) {
995
+ const inner = this.arithmetic(op, { shape: typed(pa), mult: ONE }, { shape: typed(pb), mult: ONE }, operands, env);
996
+ const t = inner.shape.kind === "type" ? orNull(inner.shape.type) : undefined;
997
+ return t === undefined ? inner : { shape: typed(t), mult: ONE };
998
+ }
999
+ if (pa !== undefined)
1000
+ a = unwrap(pa);
1001
+ if (pb !== undefined)
1002
+ b = unwrap(pb);
1003
+ }
1004
+ else if (nullablePayload(a) !== undefined || nullablePayload(b) !== undefined) {
1005
+ if (!env.lenient)
1006
+ return fail();
1007
+ // In a lenient place, an option is null, which raises, or its value, combined as a value is.
1008
+ const pa = nullablePayload(a);
1009
+ const pb = nullablePayload(b);
1010
+ const inner = this.arithmetic(op, { shape: typed(pa ?? a), mult: ONE }, { shape: typed(pb ?? b), mult: ONE }, operands, env);
1011
+ return inner.shape.kind === "error" ? inner : { ...inner, mult: { lo: 0, hi: inner.mult.hi } };
1012
+ }
1013
+ if (isNumber(a) && isNumber(b)) {
1014
+ if (op === "/")
1015
+ return { shape: typed(FloatType), mult: ONE };
1016
+ // jq's remainder of the numbers truncated to integers: a Float operand, which can be NaN, gives a Float.
1017
+ if (op === "%")
1018
+ return { shape: typed(a.type === "Integer" && b.type === "Integer" ? IntegerType : FloatType), mult: ONE };
1019
+ // An Integer with a Float is a Float: the translation widens the Integer.
1020
+ return { shape: typed(a.type === "Float" || b.type === "Float" ? FloatType : IntegerType), mult: ONE };
1021
+ }
1022
+ switch (op) {
1023
+ case "+":
1024
+ if (a.type === "String" && b.type === "String")
1025
+ return { shape: typed(StringType), mult: ONE };
1026
+ if (a.type === "Array" && b.type === "Array") {
1027
+ const element = unify(a.value, b.value);
1028
+ return element === undefined ? fail() : { shape: typed(ArrayType(element)), mult: ONE };
1029
+ }
1030
+ if (a.type === "Struct" && b.type === "Struct")
1031
+ return { shape: this.mergeStructs(left, right, false, operands.range), mult: ONE };
1032
+ if (a.type === "Dict" && b.type === "Dict") {
1033
+ const merged = unify(a, b);
1034
+ return merged === undefined ? fail() : { shape: typed(merged), mult: ONE };
1035
+ }
1036
+ if ((a.type === "Struct" && Object.keys(a.fields).length === 0 && b.type === "Dict") || (b.type === "Struct" && Object.keys(b.fields).length === 0 && a.type === "Dict")) {
1037
+ return { shape: typed(a.type === "Dict" ? a : b), mult: ONE };
1038
+ }
1039
+ return fail();
1040
+ case "-":
1041
+ if (a.type === "Array" && b.type === "Array" && unify(a.value, b.value) !== undefined)
1042
+ return { shape: typed(a), mult: ONE };
1043
+ return fail();
1044
+ case "*":
1045
+ if (a.type === "Struct" && b.type === "Struct")
1046
+ return { shape: this.mergeStructs(left, right, true, operands.range), mult: ONE };
1047
+ // Dicts merge deeply, as jq merges objects: a key both hold has their values merged when both are objects.
1048
+ if (a.type === "Dict" && b.type === "Dict") {
1049
+ const merged = unify(a, b);
1050
+ return merged === undefined ? fail() : { shape: typed(merged), mult: ONE };
1051
+ }
1052
+ if ((a.type === "Struct" && Object.keys(a.fields).length === 0 && b.type === "Dict") || (b.type === "Struct" && Object.keys(b.fields).length === 0 && a.type === "Dict")) {
1053
+ return { shape: typed(a.type === "Dict" ? a : b), mult: ONE };
1054
+ }
1055
+ // A string repeated, the count a number on either side: a negative count gives null.
1056
+ if ((a.type === "String" && isNumber(b)) || (isNumber(a) && b.type === "String"))
1057
+ return { shape: typed(orNull(StringType)), mult: ONE };
1058
+ return fail();
1059
+ case "/":
1060
+ if (a.type === "String" && b.type === "String")
1061
+ return { shape: typed(ArrayType(StringType)), mult: ONE };
1062
+ return fail();
1063
+ default:
1064
+ return fail();
1065
+ }
1066
+ }
1067
+ /**
1068
+ * `a + b` or `a * b` on structs: the fields of `a`, then the new fields of
1069
+ * `b`; `b`'s value wins, and `*` merges fields that are structs, or dicts,
1070
+ * in both deeply.
1071
+ */
1072
+ mergeStructs(left, right, deep, range) {
1073
+ let problem = false;
1074
+ const merge = (a, b, aFacts, bFacts) => {
1075
+ const ua = unwrap(a);
1076
+ const ub = unwrap(b);
1077
+ const af = ua.type === "Struct" ? ua.fields : {};
1078
+ const bf = ub.type === "Struct" ? ub.fields : {};
1079
+ const fields = {};
1080
+ const facts = new Map();
1081
+ const aKnown = aFacts?.kind === "fields" ? aFacts.fields : undefined;
1082
+ const bKnown = bFacts?.kind === "fields" ? bFacts.fields : undefined;
1083
+ for (const [name, t] of Object.entries(af)) {
1084
+ fields[name] = t;
1085
+ const f = aKnown?.get(name);
1086
+ if (f !== undefined)
1087
+ facts.set(name, f);
1088
+ }
1089
+ for (const [name, t] of Object.entries(bf)) {
1090
+ if (deep && name in af && unwrap(af[name]).type === "Struct" && unwrap(t).type === "Struct") {
1091
+ const inner = merge(af[name], t, aKnown?.get(name), bKnown?.get(name));
1092
+ fields[name] = inner.type;
1093
+ if (inner.facts !== undefined)
1094
+ facts.set(name, inner.facts);
1095
+ else
1096
+ facts.delete(name);
1097
+ }
1098
+ else if (deep && name in af && unwrap(af[name]).type === "Dict" && unwrap(t).type === "Dict") {
1099
+ // Two dicts merge into one holding both's keys: their types must unify.
1100
+ const inner = unify(unwrap(af[name]), unwrap(t));
1101
+ if (inner === undefined) {
1102
+ if (!problem)
1103
+ this.fail(range, "type_mismatch", MESSAGES.arithmetic("*", describeType(af[name]), describeType(t)));
1104
+ problem = true;
1105
+ }
1106
+ fields[name] = inner ?? t;
1107
+ facts.delete(name);
1108
+ }
1109
+ else {
1110
+ fields[name] = t;
1111
+ const f = bKnown?.get(name);
1112
+ if (f !== undefined)
1113
+ facts.set(name, f);
1114
+ else
1115
+ facts.delete(name);
1116
+ }
1117
+ }
1118
+ return { type: StructType(fields), facts: facts.size > 0 ? { kind: "fields", fields: facts } : undefined };
1119
+ };
1120
+ const l = left.shape;
1121
+ const r = right.shape;
1122
+ const merged = merge(l.type, r.type, l.facts, r.facts);
1123
+ return problem ? ERROR : typed(merged.type, merged.facts);
1124
+ }
1125
+ // ─── Construction ──────────────────────────────────────────────────────
1126
+ object(node, path, input, env) {
1127
+ // Names build a struct and computed keys a dict (§13.6): an object has one kind of key.
1128
+ const computedKeys = node.value.filter(entry => entry.key.type === "computed").length;
1129
+ if (computedKeys > 0 && computedKeys < node.value.length)
1130
+ return this.fail(this.range(path), "type_mismatch", MESSAGES.mixedKeys());
1131
+ let mult = ONE;
1132
+ const literal = [];
1133
+ const computed = [];
1134
+ let error = false;
1135
+ node.value.forEach((entry, i) => {
1136
+ const valuePath = childPath(path, `object[${i}].value.some`);
1137
+ const key = entry.key;
1138
+ let name;
1139
+ let value;
1140
+ if (key.type === "name")
1141
+ name = key.value;
1142
+ else if (key.type === "variable") {
1143
+ name = key.value;
1144
+ if (entry.value.type === "none")
1145
+ value = this.variable(key.value, path, env);
1146
+ }
1147
+ else {
1148
+ const keyPath = childPath(path, `object[${i}].key.computed`);
1149
+ const k = this.check(key.value, keyPath, input, env);
1150
+ // `{"\(f)"}` holds the input's value at the key, `.[key]`.
1151
+ const v = entry.value.type === "some"
1152
+ ? this.check(entry.value.value, valuePath, input, env)
1153
+ : this.entryAt(input, k, keyPath, env);
1154
+ mult = piped(mult, piped(k.mult, v.mult));
1155
+ if (k.shape.kind === "error" || v.shape.kind === "error")
1156
+ error = true;
1157
+ computed.push({ key: k, value: v, keyPath });
1158
+ return;
1159
+ }
1160
+ if (value === undefined) {
1161
+ value = entry.value.type === "some"
1162
+ ? this.check(entry.value.value, valuePath, input, env)
1163
+ : this.readField(input, name, this.range(path), env, false, path);
1164
+ }
1165
+ if (value.shape.kind === "error")
1166
+ error = true;
1167
+ mult = piped(mult, value.mult);
1168
+ const earlier = literal.findIndex(l => l.name === name);
1169
+ if (earlier !== -1) {
1170
+ this.warn(this.range(path), "duplicate_key", MESSAGES.duplicateKey(jsonString(name)));
1171
+ literal[earlier] = { name, result: value };
1172
+ }
1173
+ else {
1174
+ literal.push({ name, result: value });
1175
+ }
1176
+ });
1177
+ if (error)
1178
+ return { shape: ERROR, mult };
1179
+ if (computed.length > 0) {
1180
+ let keyType = NeverType;
1181
+ let valueType = NeverType;
1182
+ for (const c of computed) {
1183
+ const k = this.collect(c.key, this.range(c.keyPath));
1184
+ const v = this.collect(c.value, this.range(path));
1185
+ if (k === undefined || v === undefined)
1186
+ return { shape: ERROR, mult };
1187
+ const nk = unify(keyType, k);
1188
+ const nv = unify(valueType, v);
1189
+ if (nk === undefined)
1190
+ return this.fail(this.range(c.keyPath), "ambiguous_output", MESSAGES.noCommonType(describeType(keyType), describeType(k)));
1191
+ if (nv === undefined)
1192
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(valueType), describeType(v)));
1193
+ keyType = nk;
1194
+ valueType = nv;
1195
+ }
1196
+ if (!isImmutableType(keyType))
1197
+ return this.fail(this.range(computed[0].keyPath), "type_mismatch", MESSAGES.mutableKey(this.source(computed[0].keyPath), describeType(keyType)));
1198
+ return { shape: typed(DictType(keyType, valueType)), mult };
1199
+ }
1200
+ const fields = {};
1201
+ const facts = new Map();
1202
+ for (const l of literal) {
1203
+ const t = this.collect(l.result, this.range(path));
1204
+ if (t === undefined)
1205
+ return { shape: ERROR, mult };
1206
+ fields[l.name] = t;
1207
+ if (l.result.shape.kind === "type" && l.result.shape.facts !== undefined)
1208
+ facts.set(l.name, l.result.shape.facts);
1209
+ }
1210
+ return { shape: typed(StructType(fields), facts.size > 0 ? { kind: "fields", fields: facts } : undefined), mult };
1211
+ }
1212
+ /**
1213
+ * The value at a computed key of the input, as `.[key]` reads it: a dict's
1214
+ * value at the key, or null where it has none; `null`'s is null. A struct's
1215
+ * field needs its name written (§4).
1216
+ */
1217
+ entryAt(input, key, keyPath, env) {
1218
+ if (key.shape.kind === "error")
1219
+ return { shape: ERROR, mult: ONE };
1220
+ const keyType = this.collect(key, this.range(keyPath));
1221
+ if (keyType === undefined)
1222
+ return { shape: ERROR, mult: ONE };
1223
+ const range = this.range(keyPath);
1224
+ return this.mapMembers(input, member => {
1225
+ const u = unwrap(member.type);
1226
+ if (u.type === "Null")
1227
+ return { shape: typed(NullType), mult: ONE };
1228
+ const t = unwrap(nullablePayload(u) ?? u);
1229
+ if (t.type === "Dict") {
1230
+ if (!isTypeEqual(unwrap(keyType), unwrap(t.key))) {
1231
+ return this.mismatch(env, range, "type_mismatch", MESSAGES.keyType(`{${this.source(keyPath)}}`, describeType(t.key), this.source(keyPath), describeType(keyType)));
1232
+ }
1233
+ const value = orNull(t.value);
1234
+ if (value === undefined)
1235
+ return this.mismatch(env, range, "ambiguous_output", MESSAGES.noCommonType(describeType(t.value), "Null"));
1236
+ return { shape: typed(value), mult: ONE };
1237
+ }
1238
+ if (t.type === "Struct")
1239
+ return this.mismatch(env, range, "type_mismatch", MESSAGES.structKey(`{${this.source(keyPath)}}`));
1240
+ return this.mismatch(env, range, "not_indexable", MESSAGES.notIndexable(".", describeType(member.type)));
1241
+ });
1242
+ }
1243
+ format(node, path, input, env) {
1244
+ const { name, string } = node.value;
1245
+ const builtin = BUILTINS.get(`@${name}`);
1246
+ if (builtin === undefined) {
1247
+ const suggestions = closest(`@${name}`, [...BUILTINS.keys()].filter(k => k.startsWith("@")));
1248
+ return this.fail(this.range(path), "unknown_function", MESSAGES.unknownFunction(`@${name}`, 0, suggestions[0]), { suggestions });
1249
+ }
1250
+ const status = this.availability(builtin, `@${name}`, path);
1251
+ if (status !== undefined)
1252
+ return status;
1253
+ if (string.type === "some") {
1254
+ let mult = ONE;
1255
+ const inner = string.value;
1256
+ if (inner.type === "string") {
1257
+ inner.value.forEach((part, i) => {
1258
+ if (part.type === "interpolate") {
1259
+ const partPath = childPath(childPath(path, "format.string.some"), `string[${i}].interpolate`);
1260
+ const r = this.check(part.value, partPath, input, env);
1261
+ mult = piped(mult, r.mult);
1262
+ // Each interpolated value is formatted, so it must be one the format takes.
1263
+ if (r.shape.kind !== "error")
1264
+ builtin.typing(this.context(builtin, `@${name}`, partPath, r, env, [], []));
1265
+ }
1266
+ });
1267
+ }
1268
+ this.record(childPath(path, "format.string.some"), env, { shape: typed(StringType), mult });
1269
+ return { shape: typed(StringType), mult };
1270
+ }
1271
+ return builtin.typing(this.context(builtin, `@${name}`, path, input, env, [], []));
1272
+ }
1273
+ // ─── Control ───────────────────────────────────────────────────────────
1274
+ /** Adds facts to a result's shape. */
1275
+ narrow(input, proves) {
1276
+ let shape = input.shape;
1277
+ for (const proof of proves) {
1278
+ if (proof.kind === "case")
1279
+ shape = refine(shape, proof.path, proof.cases);
1280
+ else if (proof.path.length === 0)
1281
+ shape = narrowTypes(shape, proof.types);
1282
+ }
1283
+ return { ...input, shape };
1284
+ }
1285
+ conditional(node, path, input, env) {
1286
+ const { branches, otherwise } = node.value;
1287
+ const members = [];
1288
+ let mult;
1289
+ let error = false;
1290
+ let rest = input;
1291
+ let condMult = ONE;
1292
+ branches.forEach((branch, i) => {
1293
+ const condition = this.check(branch.condition, childPath(path, `if.branches[${i}].condition`), rest, env);
1294
+ // A condition after one that is always true never runs.
1295
+ if (rest !== NOTHING)
1296
+ condMult = piped(condMult, condition.mult);
1297
+ // A literal condition decides the branch, as the translation does: the branch it rules out never runs.
1298
+ const truth = this.truthOf(branch.condition);
1299
+ const thenInput = truth === false ? NOTHING : condition.proves !== undefined ? this.narrow(rest, condition.proves) : rest;
1300
+ const result = this.check(branch.then, childPath(path, `if.branches[${i}].then`), thenInput, env);
1301
+ if (condition.shape.kind === "error" || result.shape.kind === "error")
1302
+ error = true;
1303
+ members.push(...membersOf(result.shape));
1304
+ mult = mult === undefined ? result.mult : either(mult, result.mult);
1305
+ // The next branch runs when this condition is false.
1306
+ if (condition.proves !== undefined && condition.proves.length === 1) {
1307
+ const proof = condition.proves[0];
1308
+ if (proof.kind === "case") {
1309
+ const variantShape = this.shapeAt(rest.shape, proof.path);
1310
+ if (variantShape !== undefined) {
1311
+ const others = casesOf(variantShape.type, variantShape.facts).filter(c => !proof.cases.includes(c));
1312
+ rest = this.narrow(rest, [{ kind: "case", path: proof.path, cases: others }]);
1313
+ }
1314
+ }
1315
+ else {
1316
+ rest = this.narrow(rest, [{ kind: "type", path: proof.path, types: JQ_TYPE_NAMES.filter(name => !proof.types.includes(name)) }]);
1317
+ }
1318
+ }
1319
+ if (truth === true)
1320
+ rest = NOTHING;
1321
+ });
1322
+ const other = otherwise.type === "some"
1323
+ ? this.check(otherwise.value, childPath(path, "if.otherwise.some"), rest, env)
1324
+ : rest === NOTHING ? DEAD : { shape: rest.shape, mult: ONE };
1325
+ if (other.shape.kind === "error" || error)
1326
+ return { shape: ERROR, mult: ONE };
1327
+ members.push(...membersOf(other.shape));
1328
+ return { shape: union(members), mult: piped(condMult, either(mult ?? ONE, other.mult)) };
1329
+ }
1330
+ /** A literal's truth to jq: `false` and `null` are false, any other literal true; `undefined` for any other node. */
1331
+ truthOf(node) {
1332
+ if (node.type !== "literal")
1333
+ return undefined;
1334
+ const { type, value } = this.literal(node.value);
1335
+ if (type.type === "Boolean")
1336
+ return value;
1337
+ return type.type !== "Null";
1338
+ }
1339
+ /** The shape at a field path, for narrowing's else branch. */
1340
+ shapeAt(shape, path) {
1341
+ if (shape.kind !== "type")
1342
+ return undefined;
1343
+ let current = shape;
1344
+ for (const name of path) {
1345
+ const t = unwrap(current.type);
1346
+ if (t.type !== "Struct")
1347
+ return undefined;
1348
+ const fields = t.fields;
1349
+ if (!(name in fields))
1350
+ return undefined;
1351
+ current = typed(fields[name], current.facts?.kind === "fields" ? current.facts.fields.get(name) : undefined);
1352
+ }
1353
+ return current;
1354
+ }
1355
+ tryCatch(node, path, input, env) {
1356
+ const body = this.check(node.value.body, childPath(path, "try.body"), input, { ...env, lenient: true });
1357
+ if (node.value.catch.type === "none")
1358
+ return { shape: body.shape, mult: { lo: 0, hi: body.mult.hi } };
1359
+ // Error values are strings: `error(v)` raises `v`'s East text (§13.14).
1360
+ const handler = this.check(node.value.catch.value, childPath(path, "try.catch.some"), { shape: typed(StringType), mult: ONE }, env);
1361
+ if (body.shape.kind === "error" || handler.shape.kind === "error")
1362
+ return { shape: ERROR, mult: ONE };
1363
+ return { shape: union([...membersOf(body.shape), ...membersOf(handler.shape)]), mult: { lo: 0, hi: Math.max(body.mult.hi, handler.mult.hi) } };
1364
+ }
1365
+ fold(node, path, input, env) {
1366
+ const kind = node.type;
1367
+ const source = this.check(node.value.source, childPath(path, `${kind}.source`), input, env);
1368
+ const init = this.check(node.value.init, childPath(path, `${kind}.init`), input, env);
1369
+ if (source.shape.kind === "error" || init.shape.kind === "error")
1370
+ return { shape: ERROR, mult: ONE };
1371
+ const element = this.collect(source, this.range(childPath(path, `${kind}.source`)));
1372
+ let acc = this.collect(init, this.range(childPath(path, `${kind}.init`)));
1373
+ if (element === undefined || acc === undefined)
1374
+ return { shape: ERROR, mult: ONE };
1375
+ const bound = this.destructure(node.value.pattern, childPath(path, `${kind}.pattern`), { shape: typed(element), mult: ONE }, env);
1376
+ if (bound === undefined)
1377
+ return { shape: ERROR, mult: ONE };
1378
+ const inner = { ...env, vars: new Map([...env.vars, ...bound]) };
1379
+ let update;
1380
+ const first = acc;
1381
+ for (let round = 0; round < 8; round++) {
1382
+ const errors = this.diagnostics.length;
1383
+ update = this.check(node.value.update, childPath(path, `${kind}.update`), { shape: typed(acc), mult: ONE }, inner);
1384
+ if (update.shape.kind === "error")
1385
+ return { shape: ERROR, mult: ONE };
1386
+ const next = this.collect(update, this.range(childPath(path, `${kind}.update`)));
1387
+ if (next === undefined)
1388
+ return { shape: ERROR, mult: ONE };
1389
+ const merged = unify(acc, next);
1390
+ if (merged === undefined) {
1391
+ return this.fail(this.range(path), "cannot_infer", MESSAGES.accumulator(kind, describeType(first), describeType(next)));
1392
+ }
1393
+ if (isTypeEqual(merged, acc))
1394
+ break;
1395
+ // A round that changed the accumulator's type is checked again; drop its problems.
1396
+ this.diagnostics.length = errors;
1397
+ acc = merged;
1398
+ if (round === 7)
1399
+ return this.fail(this.range(path), "cannot_infer", MESSAGES.accumulator(kind, describeType(first), describeType(merged)));
1400
+ }
1401
+ // The accumulator's settled type, for the translator.
1402
+ this.record(`${path}#acc`, env, { shape: typed(acc), mult: ONE });
1403
+ if (kind === "reduce")
1404
+ return { shape: typed(acc), mult: ONE };
1405
+ const extract = node.value.extract;
1406
+ if (extract.type === "none")
1407
+ return { shape: typed(acc), mult: MANY };
1408
+ const out = this.check(extract.value, childPath(path, "foreach.extract.some"), { shape: typed(acc), mult: ONE }, inner);
1409
+ return { shape: out.shape, mult: MANY };
1410
+ }
1411
+ bind(node, path, input, env) {
1412
+ const source = this.check(node.value.source, childPath(path, "bind.source"), input, env);
1413
+ if (source.shape.kind === "error")
1414
+ return { shape: ERROR, mult: ONE };
1415
+ const alternatives = node.value.patterns;
1416
+ const bindings = [];
1417
+ alternatives.forEach((pattern, i) => {
1418
+ const errors = this.diagnostics.length;
1419
+ const bound = this.destructure(pattern, childPath(path, `bind.patterns[${i}]`), source, env);
1420
+ // With ?//, a pattern that cannot match is skipped at run time.
1421
+ if (bound === undefined && alternatives.length > 1 && i < alternatives.length - 1)
1422
+ this.diagnostics.length = errors;
1423
+ if (bound !== undefined)
1424
+ bindings.push(bound);
1425
+ });
1426
+ if (bindings.length === 0)
1427
+ return { shape: ERROR, mult: ONE };
1428
+ const names = new Set(bindings.flatMap(b => [...b.keys()]));
1429
+ const vars = new Map(env.vars);
1430
+ for (const name of names) {
1431
+ let type = NeverType;
1432
+ let facts;
1433
+ for (const b of bindings) {
1434
+ const r = b.get(name);
1435
+ const t = r === undefined ? NullType : (r.shape.kind === "type" ? r.shape.type : unifyShape(r.shape) ?? NeverType);
1436
+ type = unify(type, t) ?? type;
1437
+ if (bindings.length === 1 && r?.shape.kind === "type")
1438
+ facts = r.shape.facts;
1439
+ }
1440
+ vars.set(name, { shape: typed(type, facts), mult: ONE });
1441
+ }
1442
+ const body = this.check(node.value.body, childPath(path, "bind.body"), input, { ...env, vars });
1443
+ return { shape: body.shape, mult: piped(source.mult, body.mult), stream: body.stream };
1444
+ }
1445
+ /** The variables a pattern binds from a source, or `undefined` after a reported problem. */
1446
+ destructure(pattern, path, source, env) {
1447
+ const out = new Map();
1448
+ const visit = (p, at, value) => {
1449
+ if (value.shape.kind === "error")
1450
+ return false;
1451
+ switch (p.type) {
1452
+ case "variable":
1453
+ out.set(p.value, value);
1454
+ return true;
1455
+ case "array": {
1456
+ return p.value.every((item, i) => {
1457
+ const element = this.mapMembers(value, member => {
1458
+ const t = unwrap(member.type);
1459
+ const container = t.type === "Array" ? t.value : t.type === "Vector" ? t.element : undefined;
1460
+ if (container === undefined) {
1461
+ if (t.type === "Null")
1462
+ return { shape: typed(NullType), mult: ONE };
1463
+ return this.fail(this.range(at), "type_mismatch", MESSAGES.input("an array pattern", "an array", describeType(member.type)));
1464
+ }
1465
+ const e = orNull(container);
1466
+ return e === undefined ? this.fail(this.range(at), "ambiguous_output", MESSAGES.noCommonType(describeType(container), "Null")) : { shape: typed(e), mult: ONE };
1467
+ });
1468
+ return visit(item, childPath(at, `array[${i}]`), element);
1469
+ });
1470
+ }
1471
+ case "object": {
1472
+ return p.value.every((entry, i) => {
1473
+ const read = this.readField(value, entry.key, this.range(at), env, false, at);
1474
+ if (read.shape.kind === "error")
1475
+ return false;
1476
+ if (entry.value.type === "none") {
1477
+ out.set(entry.key, read);
1478
+ return true;
1479
+ }
1480
+ return visit(entry.value.value, childPath(at, `object[${i}].value.some`), read);
1481
+ });
1482
+ }
1483
+ }
1484
+ };
1485
+ return visit(pattern, path, { shape: source.shape, mult: ONE }) ? out : undefined;
1486
+ }
1487
+ // ─── Calls ─────────────────────────────────────────────────────────────
1488
+ call(node, path, input, env) {
1489
+ const { args, name } = node.value;
1490
+ const binding = env.defs.get(`${name}/${args.length}`);
1491
+ if (binding !== undefined) {
1492
+ if (binding.kind === "param") {
1493
+ const instance = `${env.instance}>${path}`;
1494
+ return this.check(binding.arg, binding.path, input, { ...binding.env, instance, lenient: env.lenient });
1495
+ }
1496
+ return this.callDef(binding, node, path, input, env);
1497
+ }
1498
+ const builtin = BUILTINS.get(name);
1499
+ // A builtin a query may not use says so, whatever it is given.
1500
+ if (builtin !== undefined && builtin.status !== "supported" && builtin.status !== "tooling")
1501
+ return this.availability(builtin, name, path);
1502
+ if (builtin === undefined || !builtin.arities.includes(args.length)) {
1503
+ const defined = [...env.defs.keys()].filter(k => k.startsWith(`${name}/`)).map(k => Number(k.slice(name.length + 1)));
1504
+ const arities = builtin?.arities ?? defined;
1505
+ if (arities.length > 0)
1506
+ return this.fail(this.range(path), "arity", MESSAGES.arity(name, [...arities].sort(), args.length));
1507
+ const candidates = [...new Set([...[...env.defs.keys()].map(k => k.split("/")[0]), ...[...BUILTINS.keys()].filter(k => !k.startsWith("@"))])];
1508
+ const suggestions = closest(name, candidates);
1509
+ return this.fail(this.range(path), "unknown_function", MESSAGES.unknownFunction(name, args.length, suggestions[0]), { suggestions });
1510
+ }
1511
+ const unavailable = this.availability(builtin, name, path);
1512
+ if (unavailable !== undefined)
1513
+ return unavailable;
1514
+ const argPaths = args.map((_, i) => childPath(path, `call.args[${i}]`));
1515
+ // In a lenient place, an option is null or its value, each checked apart: jq
1516
+ // raises for a null the builtin cannot take, and computes with a value.
1517
+ const opened = env.lenient ? openOptions(input.shape) : undefined;
1518
+ if (opened !== undefined) {
1519
+ // Whether the builtin takes null, from its typing on null alone; what that reports is reported again below.
1520
+ const reported = this.diagnostics.length;
1521
+ const refused = this.refused;
1522
+ builtin.typing(this.context(builtin, name, path, { ...input, shape: typed(NullType), partial: undefined }, env, args, argPaths));
1523
+ this.diagnostics.length = reported;
1524
+ this.opened.set(`${env.instance}|${path}`, { nullRaises: this.refused > refused });
1525
+ }
1526
+ const given = opened === undefined ? input : { ...input, shape: opened, partial: undefined };
1527
+ return builtin.typing(this.context(builtin, name, path, given, env, args, argPaths));
1528
+ }
1529
+ /** A builtin a query may not call: its diagnostic. */
1530
+ availability(builtin, name, path) {
1531
+ switch (builtin.status) {
1532
+ case "supported": return undefined;
1533
+ case "excluded": return this.fail(this.range(path), "unsupported", MESSAGES.excluded(name));
1534
+ case "unavailable": return this.fail(this.range(path), "unsupported", MESSAGES.unavailable(name, builtin.reason ?? ""));
1535
+ case "not_yet": return this.fail(this.range(path), "unsupported", MESSAGES.notYet(name));
1536
+ case "tooling": return this.options.tooling === true ? undefined : this.fail(this.range(path), "unsupported", MESSAGES.tooling(name));
1537
+ }
1538
+ }
1539
+ callDef(binding, node, path, input, env) {
1540
+ const def = binding.node.value;
1541
+ const closure = binding.env();
1542
+ const vars = new Map(closure.vars);
1543
+ const defs = new Map(closure.defs);
1544
+ let mult = ONE;
1545
+ const signature = [binding.path, input.shape.kind === "type" ? printType(input.shape.type) : "?"];
1546
+ let filters = false;
1547
+ def.params.forEach((param, i) => {
1548
+ const argPath = childPath(path, `call.args[${i}]`);
1549
+ if (param.startsWith("$")) {
1550
+ const value = this.check(node.value.args[i], argPath, input, env);
1551
+ mult = piped(mult, value.mult);
1552
+ const t = this.collect(value, this.range(argPath));
1553
+ const shape = t === undefined ? ERROR : typed(t);
1554
+ vars.set(param.slice(1), { shape, mult: ONE });
1555
+ defs.set(`${param.slice(1)}/0`, { kind: "param", arg: variant("variable", param.slice(1)), path: argPath, env: { ...closure, vars } });
1556
+ signature.push(t === undefined ? "?" : printType(t));
1557
+ }
1558
+ else {
1559
+ filters = true;
1560
+ defs.set(`${param}/0`, { kind: "param", arg: node.value.args[i], path: argPath, env });
1561
+ }
1562
+ });
1563
+ const key = signature.join("|");
1564
+ const active = this.active.get(key);
1565
+ if (active !== undefined) {
1566
+ // A recursive call: its output so far.
1567
+ if (active.filters)
1568
+ return this.fail(this.range(path), "unsupported", MESSAGES.recursiveFilter(def.name));
1569
+ active.recursed = true;
1570
+ return active.result;
1571
+ }
1572
+ const instance = `${env.instance}>${path}`;
1573
+ const bodyEnv = { vars, defs, labels: closure.labels, instance, lenient: env.lenient };
1574
+ // The recursion's outputs so far: none known yet, as `lo: 1, hi: 0` (the
1575
+ // identity of `either`), so the fixpoint can find a lower bound of one.
1576
+ const state = { result: { shape: typed(NeverType), mult: { lo: 1, hi: 0 } }, recursed: false, filters };
1577
+ this.active.set(key, state);
1578
+ try {
1579
+ for (let round = 0; round < 8; round++) {
1580
+ const errors = this.diagnostics.length;
1581
+ state.recursed = false;
1582
+ const result = this.check(def.body, childPath(binding.path, "def.body"), input, bodyEnv);
1583
+ if (result.shape.kind === "error" || !state.recursed)
1584
+ return { ...result, mult: piped(mult, result.mult) };
1585
+ const before = unifyShape(state.result.shape);
1586
+ const after = unifyShape(result.shape);
1587
+ const merged = before === undefined || after === undefined ? undefined : unify(before, after);
1588
+ if (merged === undefined)
1589
+ return this.fail(this.range(path), "cannot_infer", MESSAGES.recursion(def.name));
1590
+ const next = { lo: state.result.mult.lo === 1 && result.mult.lo === 1 ? 1 : 0, hi: Math.max(state.result.mult.hi, result.mult.hi) };
1591
+ if (isTypeEqual(merged, before) && next.lo === state.result.mult.lo && next.hi === state.result.mult.hi) {
1592
+ return { shape: result.shape, mult: piped(mult, result.mult) };
1593
+ }
1594
+ // A round that changed what the recursion gives is checked again; drop its problems.
1595
+ this.diagnostics.length = errors;
1596
+ state.result = { shape: typed(merged), mult: next };
1597
+ }
1598
+ return this.fail(this.range(path), "cannot_infer", MESSAGES.recursion(def.name));
1599
+ }
1600
+ finally {
1601
+ this.active.delete(key);
1602
+ }
1603
+ }
1604
+ /** The context a builtin types a call in. */
1605
+ context(builtin, name, path, input, env, args, argPaths) {
1606
+ return {
1607
+ name,
1608
+ path,
1609
+ input,
1610
+ args,
1611
+ argPaths,
1612
+ range: this.range(path),
1613
+ argRange: (i) => this.range(argPaths[i]),
1614
+ arg: (i, argInput) => this.check(args[i], argPaths[i], argInput ?? { ...input, access: undefined, stream: undefined }, env),
1615
+ literal: (i) => {
1616
+ const arg = args[i];
1617
+ return arg?.type === "literal" ? this.literal(arg.value) : undefined;
1618
+ },
1619
+ literalOf: (node) => this.literal(node.value),
1620
+ coerceArg: (i, wanted) => {
1621
+ const arg = args[i];
1622
+ return arg?.type === "literal" ? this.coerceLiteral(arg, argPaths[i], unwrap(wanted), env) : undefined;
1623
+ },
1624
+ coerceArgElements: (i, wanted) => {
1625
+ const arg = args[i];
1626
+ return arg === undefined ? undefined : this.coerceArrayLiteral(arg, argPaths[i], unwrap(wanted), env);
1627
+ },
1628
+ problems: () => this.diagnostics.length,
1629
+ fail: (code, message, options) => this.fail(options?.arg !== undefined ? this.range(argPaths[options.arg]) : this.range(path), code, message, options),
1630
+ mismatch: (code, message, arg, fixes) => this.mismatch(env, arg !== undefined ? this.range(argPaths[arg]) : this.range(path), code, message, { fixes: fixes ?? [] }),
1631
+ skipNulls: () => {
1632
+ const range = this.range(path);
1633
+ return range !== undefined && isPipePosition(path) ? edit("Skip nulls", range.from, range.from, "values | ") : undefined;
1634
+ },
1635
+ warn: (code, message, fixes) => this.warn(this.range(path), code, message, fixes),
1636
+ source: (i) => i === undefined ? this.source(path) : this.source(argPaths[i]),
1637
+ collect: (result, arg) => this.collect(result, arg !== undefined ? this.range(argPaths[arg]) : this.range(path)),
1638
+ refuseRoot: () => this.refuseRoot(input, path, env),
1639
+ narrow: (result, proves) => this.narrow(result, proves),
1640
+ narrowFirst: (type) => this.narrowFirst(env, this.range(path), type, input.partial),
1641
+ onElement: (type) => this.arrayOnElement(env, path, name, input.stream, type),
1642
+ text: this.text,
1643
+ rangeAt: (at) => this.range(at),
1644
+ resultAt: (at) => this.records.get(`${env.instance}|${at}`),
1645
+ builtin,
1646
+ };
1647
+ }
1648
+ // ─── Assignment ────────────────────────────────────────────────────────
1649
+ update(node, path, input, env) {
1650
+ const { op } = node.value;
1651
+ const pathPath = childPath(path, "update.path");
1652
+ const valuePath = childPath(path, "update.value");
1653
+ if (input.shape.kind === "error")
1654
+ return { shape: ERROR, mult: ONE };
1655
+ // An update gives the whole value it updates, which a root never is.
1656
+ if (this.refuseRoot(input, path, env))
1657
+ return { shape: ERROR, mult: ONE };
1658
+ // The value of `=` and of the arithmetic updates is evaluated on `.`.
1659
+ let value;
1660
+ if (op !== "|=") {
1661
+ value = this.check(node.value.value, valuePath, input, env);
1662
+ if (value.shape.kind === "error")
1663
+ return { shape: ERROR, mult: ONE };
1664
+ }
1665
+ // Each position's new value, and how many it has: `|=` keeps its update's
1666
+ // first output, and one with none deletes the position.
1667
+ const at = (current) => {
1668
+ if (op === "|=") {
1669
+ const r = this.check(node.value.value, valuePath, current, env);
1670
+ return { shape: r.shape, mult: { lo: r.mult.lo, hi: r.mult.hi === 0 ? 0 : 1 } };
1671
+ }
1672
+ if (op === "=")
1673
+ return { shape: value.shape, mult: ONE };
1674
+ const operator = op.slice(0, -1);
1675
+ if (operator === "//") {
1676
+ if (current.shape.kind === "type" && !canBeNull(unwrap(current.shape.type)) && unwrap(current.shape.type).type !== "Boolean") {
1677
+ this.warn(this.range(pathPath), "never_missing", MESSAGES.neverMissing(this.source(pathPath), describeType(current.shape.type)));
1678
+ }
1679
+ const kept = this.mapMembers(current, m => {
1680
+ const t = unwrap(m.type);
1681
+ const payload = nullablePayload(t);
1682
+ if (t.type === "Null")
1683
+ return { shape: typed(NeverType), mult: ZERO };
1684
+ return { shape: typed(payload ?? m.type), mult: ONE };
1685
+ });
1686
+ return { shape: union([...membersOf(kept.shape), ...membersOf(value.shape)]), mult: ONE };
1687
+ }
1688
+ return this.arithmetic(operator, current, value, { leftPath: pathPath, rightPath: valuePath, range: this.range(path) }, env);
1689
+ };
1690
+ const assigned = this.assign(input.shape, node.value.path, pathPath, at, env);
1691
+ if (assigned === undefined)
1692
+ return { shape: ERROR, mult: ONE };
1693
+ const shape = this.wholeShape(assigned, path);
1694
+ return { shape, mult: shape.kind === "error" || value === undefined ? ONE : value.mult };
1695
+ }
1696
+ /** `.` after an update: `null` where the update deleted it, as jq gives it. */
1697
+ wholeShape(assigned, path) {
1698
+ if (assigned.mult.hi === 0)
1699
+ return typed(NullType);
1700
+ if (assigned.mult.lo === 1)
1701
+ return assigned.shape;
1702
+ const type = unifyShape(assigned.shape);
1703
+ const option = type === undefined ? undefined : orNull(type);
1704
+ if (option === undefined)
1705
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(type ?? NeverType), "Null")).shape;
1706
+ return typed(option);
1707
+ }
1708
+ /**
1709
+ * The shape of `.` after assigning at the positions a path expression
1710
+ * names, each position's new value given by `at`.
1711
+ *
1712
+ * @returns the new shape, with its multiplicity: less than one where the
1713
+ * update deletes `.` itself; or `undefined` after a reported problem
1714
+ */
1715
+ assign(shape, pathNode, path, at, env) {
1716
+ if (shape.kind === "error")
1717
+ return undefined;
1718
+ const members = [];
1719
+ let mult;
1720
+ for (const member of membersOf(shape)) {
1721
+ const next = this.assignMember(member.shape, pathNode, path, at, env);
1722
+ if (next === undefined)
1723
+ return undefined;
1724
+ members.push({ shape: next.shape, case: member.case });
1725
+ mult = mult === undefined ? next.mult : either(mult, next.mult);
1726
+ }
1727
+ return { shape: union(members), mult: mult ?? ONE };
1728
+ }
1729
+ assignMember(shape, pathNode, path, at, env) {
1730
+ const range = this.range(path);
1731
+ switch (pathNode.type) {
1732
+ case "identity": {
1733
+ const r = at({ shape, mult: ONE });
1734
+ const t = this.collect(r, range);
1735
+ return t === undefined ? undefined : { shape: typed(t), mult: r.mult };
1736
+ }
1737
+ case "pipe": {
1738
+ const { left, right } = pathNode.value;
1739
+ return this.assignMember(shape, left, childPath(path, "pipe.left"), current => this.assign(current.shape, right, childPath(path, "pipe.right"), at, env) ?? { shape: ERROR, mult: ONE }, env);
1740
+ }
1741
+ case "field":
1742
+ case "index":
1743
+ case "iterate":
1744
+ case "slice": {
1745
+ // An index's key and a slice's bounds are taken on the step's own input, as jq takes them.
1746
+ const keys = this.stepKeys(pathNode, path, shape, env);
1747
+ if (keys === undefined)
1748
+ return undefined;
1749
+ return this.assignMember(shape, pathNode.value.target, childPath(path, `${pathNode.type}.target`), current => {
1750
+ const inner = this.assignStep(current, pathNode, path, at, env, keys);
1751
+ return inner === undefined ? { shape: ERROR, mult: ONE } : { shape: inner, mult: ONE };
1752
+ }, env);
1753
+ }
1754
+ case "descend":
1755
+ return this.assignWalk(shape, undefined, path, at, env);
1756
+ case "call": {
1757
+ const { args, name } = pathNode.value;
1758
+ if (name === "select" && args.length === 1) {
1759
+ const condition = this.check(args[0], childPath(path, "call.args[0]"), { shape, mult: ONE }, env);
1760
+ if (condition.shape.kind === "error")
1761
+ return undefined;
1762
+ const kept = condition.proves !== undefined ? this.narrow({ shape, mult: ONE }, condition.proves) : { shape, mult: ONE };
1763
+ return this.selected(shape, { shape: kept.shape, mult: { lo: 0, hi: condition.mult.hi === 0 ? 0 : 1 } }, at, path);
1764
+ }
1765
+ if (UPDATE_SELECTORS.has(name) && args.length === 0) {
1766
+ const builtin = BUILTINS.get(name);
1767
+ return this.selected(shape, builtin.typing(this.context(builtin, name, path, { shape, mult: ONE }, env, [], [])), at, path);
1768
+ }
1769
+ if (name === "empty" && args.length === 0)
1770
+ return { shape, mult: ONE };
1771
+ if (name === "recurse" && args.length <= 1) {
1772
+ return this.assignWalk(shape, args.length === 1 ? { node: args[0], path: childPath(path, "call.args[0]") } : undefined, path, at, env);
1773
+ }
1774
+ this.fail(range, "unsupported", MESSAGES.unavailable(`assigning to ${name}(…)`, UPDATE_PATHS));
1775
+ return undefined;
1776
+ }
1777
+ default:
1778
+ this.fail(range, "unsupported", MESSAGES.unavailable(`assigning to ${this.source(path)}`, UPDATE_PATHS));
1779
+ return undefined;
1780
+ }
1781
+ }
1782
+ /**
1783
+ * An update of a value where a filter keeps it (`select(f)`, `numbers`):
1784
+ * the value as the filter keeps it is updated; where it is not kept, it
1785
+ * stays.
1786
+ *
1787
+ * @param shape - the value
1788
+ * @param kept - what the filter gives on it, narrowed
1789
+ */
1790
+ selected(shape, kept, at, path) {
1791
+ if (kept.shape.kind === "error")
1792
+ return undefined;
1793
+ if (kept.mult.hi === 0 || (kept.shape.kind === "type" && kept.shape.type.type === "Never"))
1794
+ return { shape, mult: ONE };
1795
+ const replaced = at({ shape: kept.shape, mult: ONE });
1796
+ const t = this.collect(replaced, this.range(path));
1797
+ if (t === undefined)
1798
+ return undefined;
1799
+ const merged = sameType(shape.type, t) ? shape.type : unify(shape.type, t);
1800
+ if (merged === undefined) {
1801
+ this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(shape.type), describeType(t)));
1802
+ return undefined;
1803
+ }
1804
+ // Kept every time, it has the update's values; else it keeps its own where it is not kept.
1805
+ const mult = kept.mult.lo === 1 ? replaced.mult : { lo: replaced.mult.lo, hi: 1 };
1806
+ return { shape: typed(merged), mult };
1807
+ }
1808
+ /**
1809
+ * An index's key type, checked on the step's own input, or a slice's bounds:
1810
+ * each gives one value.
1811
+ *
1812
+ * @returns the key's type (none for a field, `.[]` or a slice), or
1813
+ * `undefined` after a reported problem
1814
+ */
1815
+ stepKeys(step, path, input, env) {
1816
+ const one = (node, at) => {
1817
+ const r = this.check(node, at, { shape: input, mult: ONE }, env);
1818
+ if (r.shape.kind === "error")
1819
+ return undefined;
1820
+ if (r.mult.lo !== 1 || r.mult.hi !== 1) {
1821
+ this.fail(this.range(at), "unsupported", MESSAGES.updateKey(this.source(at)));
1822
+ return undefined;
1823
+ }
1824
+ return this.collect(r, this.range(at));
1825
+ };
1826
+ if (step.type === "index") {
1827
+ if (step.value.index.type === "literal" && this.literal(step.value.index.value).type.type === "String")
1828
+ return { key: StringType };
1829
+ const key = one(step.value.index, childPath(path, "index.index"));
1830
+ return key === undefined ? undefined : { key };
1831
+ }
1832
+ if (step.type === "slice") {
1833
+ for (const [bound, s] of [[step.value.from, "slice.from.some"], [step.value.to, "slice.to.some"]]) {
1834
+ if (bound.type !== "some")
1835
+ continue;
1836
+ const at = childPath(path, s);
1837
+ const t = one(bound.value, at);
1838
+ if (t === undefined)
1839
+ return undefined;
1840
+ if (!this.keyFits(t, IntegerType, bound.value, at, env) && !canBeNull(unwrap(t))) {
1841
+ this.fail(this.range(at), "type_mismatch", MESSAGES.sliceBound(describeType(t)));
1842
+ return undefined;
1843
+ }
1844
+ }
1845
+ }
1846
+ return { key: undefined };
1847
+ }
1848
+ /**
1849
+ * One step of an update's path on a value: `.name`, `.[k]`, `.[a:b]` or
1850
+ * `.[]`. A step with `?` names no position on a value it cannot take.
1851
+ */
1852
+ assignStep(current, step, path, at, env, keys) {
1853
+ const failed = { shape: ERROR, mult: ONE };
1854
+ const assigned = this.assign(current.shape, IDENTITY, path, whole => {
1855
+ const member = whole.shape;
1856
+ const t = unwrap(member.type);
1857
+ const unchanged = { shape: member, mult: ONE };
1858
+ const done = (type) => ({ shape: typed(rewrap(member.type, type)), mult: ONE });
1859
+ // A position's new value, and how many it has.
1860
+ const replace = (old, facts) => {
1861
+ const r = at({ shape: typed(old, facts), mult: ONE });
1862
+ const type = this.collect(r, this.range(path));
1863
+ return type === undefined ? undefined : { type, mult: r.mult };
1864
+ };
1865
+ const optional = step.value.optional;
1866
+ const literalName = step.type === "index" && step.value.index.type === "literal" && t.type !== "Dict" ? this.literal(step.value.index.value) : undefined;
1867
+ const name = step.type === "field" ? step.value.name : literalName?.type.type === "String" ? literalName.value : undefined;
1868
+ if (name !== undefined) {
1869
+ const payload = nullablePayload(t);
1870
+ if (payload !== undefined) {
1871
+ // An option: `null` is updated as null is, and a value as it is; the two must share a type.
1872
+ const present = member.facts?.kind === "present" ? member.facts.payload : undefined;
1873
+ const whenNull = this.assignStep({ shape: typed(NullType), mult: ONE }, step, path, at, env, keys);
1874
+ const whenSome = this.assignStep({ shape: typed(payload, present), mult: ONE }, step, path, at, env, keys);
1875
+ const a = whenNull === undefined ? undefined : unifyShape(whenNull);
1876
+ const b = whenSome === undefined ? undefined : unifyShape(whenSome);
1877
+ if (a === undefined || b === undefined)
1878
+ return failed;
1879
+ const merged = unify(a, b);
1880
+ if (merged === undefined)
1881
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(a), describeType(b)));
1882
+ return done(merged);
1883
+ }
1884
+ if (t.type === "Struct") {
1885
+ const fields = { ...t.fields };
1886
+ const facts = member.facts?.kind === "fields" ? member.facts.fields.get(name) : undefined;
1887
+ const next = replace(name in fields ? fields[name] : NullType, facts);
1888
+ if (next === undefined)
1889
+ return failed;
1890
+ // An update that never gives a value deletes the field, as jq deletes the key.
1891
+ if (next.mult.hi === 0)
1892
+ delete fields[name];
1893
+ else
1894
+ fields[name] = next.type;
1895
+ return done(StructType(fields));
1896
+ }
1897
+ if (t.type === "Null") {
1898
+ const next = replace(NullType, undefined);
1899
+ if (next === undefined)
1900
+ return failed;
1901
+ return next.mult.hi === 0 ? unchanged : done(StructType({ [name]: next.type }));
1902
+ }
1903
+ if (t.type === "Dict" && unwrap(t.key).type === "String") {
1904
+ const old = orNull(t.value) ?? t.value;
1905
+ const next = replace(old, undefined);
1906
+ if (next === undefined)
1907
+ return failed;
1908
+ const merged = unify(t.value, next.type);
1909
+ if (merged === undefined)
1910
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(t.value), describeType(next.type)));
1911
+ return done(DictType(t.key, merged));
1912
+ }
1913
+ // A variant's payload, in each case it can hold here; its case's name is its type, not a value to update.
1914
+ if (t.type === "Variant" && nullablePayload(t) === undefined && (name === "value" || name === "type")) {
1915
+ if (name === "type")
1916
+ return this.fail(this.range(path), "unsupported", MESSAGES.unavailable(`assigning to ${this.source(path)}`, "a variant's case is its type; build the new value instead"));
1917
+ const cases = casesOf(t, member.facts);
1918
+ const payloads = { ...t.cases };
1919
+ const facts = member.facts?.kind === "cases" && cases.length === 1 ? member.facts.payload : undefined;
1920
+ for (const c of cases) {
1921
+ const next = replace(payloads[c], facts);
1922
+ if (next === undefined)
1923
+ return failed;
1924
+ // A payload cannot be deleted: with no value, it keeps its type, and the update raises there.
1925
+ if (next.mult.hi !== 0)
1926
+ payloads[c] = next.type;
1927
+ }
1928
+ this.updatedCases.set(`${env.instance}|${path}|${printType(t)}`, cases);
1929
+ return done(VariantType(payloads));
1930
+ }
1931
+ if (optional)
1932
+ return unchanged;
1933
+ return this.mismatch(env, this.range(path), "type_mismatch", MESSAGES.notAField(`.${name}`, describeType(member.type)));
1934
+ }
1935
+ if (step.type === "index") {
1936
+ const keyPath = childPath(path, "index.index");
1937
+ const keyType = keys.key;
1938
+ if (t.type === "Array") {
1939
+ if (!this.keyFits(keyType, IntegerType, step.value.index, keyPath, env)) {
1940
+ return this.mismatch(env, this.range(keyPath), "type_mismatch", MESSAGES.keyType(".[…]", "Integer", this.source(keyPath), describeType(keyType)));
1941
+ }
1942
+ // The element itself: an index past the end is an error, where jq pads with nulls (§13.23).
1943
+ const next = replace(t.value, member.facts?.kind === "elements" ? member.facts.element : undefined);
1944
+ if (next === undefined)
1945
+ return failed;
1946
+ const merged = unify(t.value, next.type);
1947
+ if (merged === undefined)
1948
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(t.value), describeType(next.type)));
1949
+ return done(ArrayType(merged));
1950
+ }
1951
+ if (t.type === "Dict" || (t.type === "Struct" && Object.keys(t.fields).length === 0) || t.type === "Null") {
1952
+ const dictKey = t.type === "Dict" ? t.key : keyType;
1953
+ const dictValue = t.type === "Dict" ? t.value : NeverType;
1954
+ if (!this.keyFits(keyType, dictKey, step.value.index, keyPath, env)) {
1955
+ return this.mismatch(env, this.range(keyPath), "type_mismatch", MESSAGES.keyType(".[…]", describeType(dictKey), this.source(keyPath), describeType(keyType)));
1956
+ }
1957
+ if (!isImmutableType(dictKey))
1958
+ return this.fail(this.range(keyPath), "type_mismatch", MESSAGES.mutableKey(this.source(keyPath), describeType(dictKey)));
1959
+ const next = replace(dictValue.type === "Never" ? NullType : (orNull(dictValue) ?? dictValue), undefined);
1960
+ if (next === undefined)
1961
+ return failed;
1962
+ const merged = unify(dictValue, next.type);
1963
+ if (merged === undefined)
1964
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(dictValue), describeType(next.type)));
1965
+ return done(DictType(dictKey, merged));
1966
+ }
1967
+ if (optional)
1968
+ return unchanged;
1969
+ return this.mismatch(env, this.range(path), "not_indexable", MESSAGES.notIndexable(this.source(childPath(path, "index.target")) || ".", describeType(member.type)));
1970
+ }
1971
+ if (step.type === "slice") {
1972
+ // The slice's new value is an array, spliced in its place; none deletes the slice.
1973
+ if (t.type === "Array" || t.type === "Null") {
1974
+ const next = t.type === "Array" ? replace(t, member.facts) : replace(NullType, undefined);
1975
+ if (next === undefined)
1976
+ return failed;
1977
+ if (next.mult.hi === 0)
1978
+ return unchanged;
1979
+ const given = unwrap(next.type);
1980
+ if (given.type !== "Array")
1981
+ return this.mismatch(env, this.range(path), "type_mismatch", MESSAGES.sliceUpdate(this.source(path), describeType(next.type)));
1982
+ // `null`'s slice is null, so the array the update gives is the value, or null where it gives none.
1983
+ if (t.type === "Null")
1984
+ return done(next.mult.lo === 1 ? next.type : orNull(next.type));
1985
+ const merged = unify(t.value, given.value);
1986
+ if (merged === undefined)
1987
+ return this.fail(this.range(path), "ambiguous_output", MESSAGES.noCommonType(describeType(t), describeType(next.type)));
1988
+ return done(ArrayType(merged));
1989
+ }
1990
+ if (optional)
1991
+ return unchanged;
1992
+ if (t.type === "String")
1993
+ return this.mismatch(env, this.range(path), "type_mismatch", MESSAGES.stringSlice(this.source(path)));
1994
+ return this.mismatch(env, this.range(path), "not_indexable", MESSAGES.notIndexable(this.source(childPath(path, "slice.target")) || ".", describeType(member.type)));
1995
+ }
1996
+ // `.[]`: every element; an element with no value is deleted.
1997
+ switch (t.type) {
1998
+ case "Array": {
1999
+ const next = replace(t.value, member.facts?.kind === "elements" ? member.facts.element : undefined);
2000
+ return next === undefined ? failed : done(ArrayType(next.type));
2001
+ }
2002
+ case "Dict": {
2003
+ const next = replace(t.value, member.facts?.kind === "values" ? member.facts.value : undefined);
2004
+ return next === undefined ? failed : done(DictType(t.key, next.type));
2005
+ }
2006
+ case "Struct": {
2007
+ const facts = member.facts?.kind === "fields" ? member.facts.fields : undefined;
2008
+ const fields = {};
2009
+ for (const [name, f] of Object.entries(t.fields)) {
2010
+ const next = replace(f, facts?.get(name));
2011
+ if (next === undefined)
2012
+ return failed;
2013
+ if (next.mult.hi !== 0)
2014
+ fields[name] = next.type;
2015
+ }
2016
+ return done(StructType(fields));
2017
+ }
2018
+ default:
2019
+ if (optional)
2020
+ return unchanged;
2021
+ return this.mismatch(env, this.range(path), "not_iterable", MESSAGES.notIterable(".[]", describeType(member.type)));
2022
+ }
2023
+ }, env);
2024
+ return assigned?.shape;
2025
+ }
2026
+ /**
2027
+ * `..`, `recurse` or `recurse(.a[])` in an update's path: every value the
2028
+ * walk visits is a position, and the update must give each back with its
2029
+ * own type.
2030
+ */
2031
+ assignWalk(shape, child, path, at, env) {
2032
+ const range = this.range(path);
2033
+ const steps = child === undefined ? undefined : walkSteps(child.node);
2034
+ if (child !== undefined && steps === undefined) {
2035
+ this.fail(range, "unsupported", MESSAGES.unavailable(`assigning through recurse(${this.source(child.path)})`, "an update's recurse walks .[], or field reads then .[], as recurse(.children[])"));
2036
+ return undefined;
2037
+ }
2038
+ // The kinds of position the walk visits.
2039
+ const kinds = [];
2040
+ let problem = false;
2041
+ const visit = (t) => {
2042
+ if (problem || kinds.some(k => isTypeEqual(k, t)))
2043
+ return;
2044
+ kinds.push(t);
2045
+ const inner = child === undefined ? this.walkKinds(t, path) : this.stepKinds(t, child, env);
2046
+ if (inner === undefined) {
2047
+ problem = true;
2048
+ return;
2049
+ }
2050
+ inner.forEach(visit);
2051
+ };
2052
+ visit(shape.type);
2053
+ if (problem)
2054
+ return undefined;
2055
+ let root = ONE;
2056
+ for (const kind of kinds) {
2057
+ const r = at({ shape: typed(kind), mult: ONE });
2058
+ if (r.shape.kind === "error")
2059
+ return undefined;
2060
+ const t = this.collect(r, range);
2061
+ if (t === undefined)
2062
+ return undefined;
2063
+ if (t.type !== "Never" && !sameType(kind, t)) {
2064
+ this.fail(range, "type_mismatch", MESSAGES.walkUpdate(this.source(path), describeType(kind), describeType(t)));
2065
+ return undefined;
2066
+ }
2067
+ if (kind === kinds[0])
2068
+ root = r.mult;
2069
+ }
2070
+ return { shape: typed(shape.type), mult: root };
2071
+ }
2072
+ /**
2073
+ * The kinds of position `..` finds directly inside a value in an update:
2074
+ * the elements of an array or set, a dict's values, a struct's fields, an
2075
+ * option's value's positions, and a variant's payloads (its case's name is
2076
+ * not a position). A Vector or Matrix is refused.
2077
+ */
2078
+ walkKinds(type, path) {
2079
+ const u = unwrap(type);
2080
+ const payload = nullablePayload(u);
2081
+ const t = payload === undefined ? u : unwrap(payload);
2082
+ switch (t.type) {
2083
+ case "Array": return [t.value];
2084
+ case "Set": return [t.key];
2085
+ case "Dict": return [t.value];
2086
+ case "Struct": return Object.values(t.fields);
2087
+ case "Variant": return Object.values(t.cases);
2088
+ case "Vector":
2089
+ case "Matrix":
2090
+ this.fail(this.range(path), "unsupported", MESSAGES.unavailable(`assigning through ${this.source(path)}`, `it would walk ${describeType(t)}, whose elements an update does not rebuild`));
2091
+ return undefined;
2092
+ default: return [];
2093
+ }
2094
+ }
2095
+ /** The kinds of position `recurse(f)` finds from a value: `f`'s outputs on it, checked as a read. */
2096
+ stepKinds(type, child, env) {
2097
+ const r = this.check(child.node, child.path, { shape: typed(type), mult: ONE }, env);
2098
+ if (r.shape.kind === "error")
2099
+ return undefined;
2100
+ return membersOf(r.shape).map(m => m.shape.type);
2101
+ }
2102
+ /** Checks a node again on another input, in a new instance; see {@link CheckJqResult.retype}. */
2103
+ retype(path, instance, input) {
2104
+ const env = this.scopes.get(`${instance}|${path}`);
2105
+ const node = this.nodes.get(path);
2106
+ if (env === undefined || node === undefined)
2107
+ return null;
2108
+ this.retypes += 1;
2109
+ const next = `${instance}~${this.retypes}`;
2110
+ const reported = this.diagnostics.length;
2111
+ const result = this.check(node, path, { shape: typed(input), mult: ONE }, { ...env, instance: next });
2112
+ const failed = result.shape.kind === "error" || this.diagnostics.slice(reported).some(d => d.severity.type === "error");
2113
+ // The program was checked already; what this finds is the translator's to act on, not the author's.
2114
+ this.diagnostics.length = reported;
2115
+ return failed ? null : next;
2116
+ }
2117
+ }
2118
+ /** The field names of outputs that are field reads side by side, `.a, .b`: what `{a, b}` would hold. */
2119
+ function fieldsSideBySide(node) {
2120
+ if (node.type === "comma") {
2121
+ const left = fieldsSideBySide(node.value.left);
2122
+ const right = fieldsSideBySide(node.value.right);
2123
+ return left === undefined || right === undefined ? undefined : [...left, ...right];
2124
+ }
2125
+ if (node.type === "field" && node.value.target.type === "identity" && !node.value.optional && /^[A-Za-z_][A-Za-z0-9_]*$/.test(node.value.name)) {
2126
+ return [node.value.name];
2127
+ }
2128
+ return undefined;
2129
+ }
2130
+ /** The steps that lead to a node where a pipe can start without parentheses. */
2131
+ const PIPE_POSITION = /(^|\.)(pipe\.(left|right)|call\.args\[\d+\]|array\.some|bind\.body|label\.body|def\.(body|rest)|if\.branches\[\d+\]\.(condition|then)|if\.otherwise\.some|object\[\d+\]\.value\.some|reduce\.update|foreach\.update|foreach\.extract\.some|try\.catch\.some)$/;
2132
+ /** Whether a node at a path can have `f | ` put before it without parentheses. */
2133
+ function isPipePosition(path) {
2134
+ return path === "" || PIPE_POSITION.test(path);
2135
+ }
2136
+ /** A node's record in an instance; with none given, outside every def, or else the first instance checked. */
2137
+ function anyInstance(records, path, instance) {
2138
+ if (instance !== undefined)
2139
+ return records.get(`${instance}|${path}`);
2140
+ const outside = records.get(`|${path}`);
2141
+ if (outside !== undefined)
2142
+ return outside;
2143
+ for (const [key, value] of records)
2144
+ if (key.endsWith(`|${path}`) && key.slice(0, -path.length - 1).startsWith(">"))
2145
+ return value;
2146
+ return undefined;
2147
+ }
2148
+ /** Whether a `break $name` for a label is in a node, outside any label of the same name inside it. */
2149
+ function breaksTo(node, name) {
2150
+ if (node.type === "break")
2151
+ return node.value === name;
2152
+ if (node.type === "label" && node.value.name === name)
2153
+ return false;
2154
+ return jqChildren(node).some(child => child.node !== undefined && breaksTo(child.node, name));
2155
+ }
2156
+ /** The top-level pipeline's stages: its segments, through the bodies of the `as` that lead it. */
2157
+ function spine(node, path) {
2158
+ if (node.type === "pipe")
2159
+ return [{ node: node.value.left, path: childPath(path, "pipe.left") }, ...spine(node.value.right, childPath(path, "pipe.right"))];
2160
+ if (node.type === "bind")
2161
+ return spine(node.value.body, childPath(path, "bind.body"));
2162
+ return [{ node, path }];
2163
+ }
2164
+ /**
2165
+ * Checks a jq program against the East type of its input.
2166
+ *
2167
+ * @param program - the program's text, or what {@link parseJq} made of it
2168
+ * @param input - the type the program runs on
2169
+ * @param options - `root` for an e3 root, `tooling` for the tooling-only builtins
2170
+ * @returns the program as written when there is no error, the type it was
2171
+ * checked against, the element type and multiplicity, the root fields
2172
+ * read, the stages, every node's type, and the diagnostics
2173
+ *
2174
+ * @remarks
2175
+ * The checker types every node over East types as `devdocs/QUERY.md` says: it
2176
+ * narrows variants through `select` and `if`, infers `reduce` and `foreach`
2177
+ * accumulators and recursive `def`s by fixpoint, and reads a literal as the
2178
+ * type it is used as (an Integer where a Float is, an ISO-8601 string where a
2179
+ * DateTime is), reporting a string that is not a date or a `strftime` format
2180
+ * East has no token for. It rewrites nothing: the translator gives each value
2181
+ * from the types it reads (#1138). A problem is a diagnostic with its span,
2182
+ * one sentence, suggestions and fixes; lints are warnings. `program` is
2183
+ * present exactly when no diagnostic is an error, and with `inputType`,
2184
+ * `elementType` and `multiplicity` it is what every reader of the query —
2185
+ * the translator, the split planner, a host — works from.
2186
+ *
2187
+ * @example
2188
+ * ```ts
2189
+ * const Order = StructType({ id: IntegerType, total: FloatType });
2190
+ * const checked = checkJq(".orders | map(.total) | add", StructType({ orders: ArrayType(Order) }));
2191
+ * checked.elementType; // FloatType
2192
+ * checked.multiplicity; // "one"
2193
+ *
2194
+ * checkJq(".orders[0].totl", StructType({ orders: ArrayType(Order) })).diagnostics[0]!.message;
2195
+ * // "unknown_field: .totl is not a field of Struct{id: Integer, total: Float}. Did you mean .total?"
2196
+ * ```
2197
+ */
2198
+ export function checkJq(program, input, options = {}) {
2199
+ const parsed = typeof program === "string" ? parseJq(program) : program;
2200
+ const empty = (diagnostics) => ({
2201
+ program: null, inputType: input, elementType: null, multiplicity: null, reads: [], stages: [], typeAt: () => null, diagnostics,
2202
+ resultAt: () => null, scopeAt: () => null, source: { text: parsed.text, spans: parsed.spans, root: options.root === true },
2203
+ inputAt: () => null, retype: () => null, updatedCases: () => null, opened: () => null,
2204
+ });
2205
+ if (parsed.program.type === "none")
2206
+ return empty(parsed.diagnostics);
2207
+ const root = parsed.program.value;
2208
+ const checker = new Checker(parsed.text, parsed.spans, options, input);
2209
+ const env = { vars: new Map(), defs: new Map(), labels: new Set(), instance: "", lenient: false };
2210
+ const start = { shape: typed(input), mult: ONE };
2211
+ const result = checker.check(root, "", start, env);
2212
+ if (options.root === true && result.shape.kind === "type" && checker.isRootShape(result.shape)) {
2213
+ checker.refuseRoot(result, "", env);
2214
+ }
2215
+ const elementType = result.shape.kind === "error" ? undefined : checker.collect(result, parsed.spans.get(""), "");
2216
+ const multiplicity = wireMultiplicity(result.mult);
2217
+ // The stages of the top-level pipeline.
2218
+ const stages = [];
2219
+ let cumulative = ONE;
2220
+ for (const stage of spine(root, "")) {
2221
+ const record = checker.records.get(`|${stage.path}`);
2222
+ const range = parsed.spans.get(stage.path);
2223
+ if (record === undefined || range === undefined)
2224
+ break;
2225
+ cumulative = piped(cumulative, record.mult);
2226
+ const type = unifyShape(record.shape);
2227
+ if (type === undefined)
2228
+ break;
2229
+ stages.push({ path: stage.path, from: range.from, to: range.to, type, multiplicity: wireMultiplicity(cumulative) });
2230
+ }
2231
+ const errors = checker.diagnostics.some(d => d.severity.type === "error");
2232
+ const typeAt = (path, instance = "") => {
2233
+ const record = checker.records.get(`${instance}|${path}`);
2234
+ if (record === undefined)
2235
+ return null;
2236
+ const type = unifyShape(record.shape);
2237
+ return type === undefined ? null : { type, multiplicity: wireMultiplicity(record.mult) };
2238
+ };
2239
+ const checks = !errors && elementType !== undefined;
2240
+ return {
2241
+ program: checks ? root : null,
2242
+ inputType: input,
2243
+ elementType: checks ? elementType : null,
2244
+ multiplicity: checks ? multiplicity : null,
2245
+ reads: checker.reads,
2246
+ stages,
2247
+ typeAt,
2248
+ diagnostics: checker.diagnostics,
2249
+ resultAt: (path, instance) => anyInstance(checker.records, path, instance) ?? null,
2250
+ scopeAt: (path, instance) => {
2251
+ const env = anyInstance(checker.scopes, path, instance);
2252
+ return env === undefined ? null : { vars: env.vars, defs: [...env.defs.keys()] };
2253
+ },
2254
+ source: { text: parsed.text, spans: parsed.spans, root: options.root === true },
2255
+ inputAt: (path, instance) => checker.inputs.get(`${instance}|${path}`) ?? null,
2256
+ retype: (path, instance, input) => checker.retype(path, instance, input),
2257
+ updatedCases: (path, instance, variant) => checker.updatedCases.get(`${instance}|${path}|${printType(variant)}`) ?? null,
2258
+ opened: (path, instance) => checker.opened.get(`${instance}|${path}`) ?? null,
2259
+ };
2260
+ }
2261
+ //# sourceMappingURL=check.js.map