@algosail/lang 0.2.12 → 0.5.1

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 (145) hide show
  1. package/bin/sail.mjs +4 -0
  2. package/cli/run-cli.js +176 -0
  3. package/index.js +12 -2
  4. package/lib/codegen/README.md +230 -0
  5. package/lib/codegen/codegen-diagnostics.js +164 -0
  6. package/lib/codegen/compile-graph.js +107 -0
  7. package/lib/codegen/emit-adt.js +177 -0
  8. package/lib/codegen/emit-body.js +1265 -0
  9. package/lib/codegen/emit-builtin.js +371 -0
  10. package/lib/codegen/emit-jsdoc-sail.js +383 -0
  11. package/lib/codegen/emit-module.js +498 -0
  12. package/lib/codegen/esm-imports.js +26 -0
  13. package/lib/codegen/index.js +69 -0
  14. package/lib/codegen/out-layout.js +102 -0
  15. package/lib/ffi/extract-jsdoc-sail.js +34 -0
  16. package/lib/io-node/index.js +4 -0
  17. package/lib/io-node/package-root.js +18 -0
  18. package/lib/io-node/read-file.js +12 -0
  19. package/lib/io-node/resolve-package.js +24 -0
  20. package/lib/io-node/resolve-sail-names-from-disk.js +21 -0
  21. package/lib/ir/assert-json-serializable.js +30 -0
  22. package/lib/ir/attach-call-effects.js +108 -0
  23. package/lib/ir/bind-values.js +594 -0
  24. package/lib/ir/build-module-ir.js +290 -0
  25. package/lib/ir/index.js +31 -0
  26. package/lib/ir/lower-body-steps.js +170 -0
  27. package/lib/ir/module-metadata.js +65 -0
  28. package/lib/ir/schema-version.js +15 -0
  29. package/lib/ir/serialize.js +202 -0
  30. package/lib/ir/stitch-types.js +92 -0
  31. package/lib/names/adt-autogen.js +22 -0
  32. package/lib/names/import-path.js +28 -0
  33. package/lib/names/index.js +1 -0
  34. package/lib/names/local-declarations.js +127 -0
  35. package/lib/names/lower-first.js +6 -0
  36. package/lib/names/module-scope.js +120 -0
  37. package/lib/names/resolve-sail.js +365 -0
  38. package/lib/names/walk-ast-refs.js +91 -0
  39. package/lib/parse/ast-build.js +51 -0
  40. package/lib/parse/ast-spec.js +212 -0
  41. package/lib/parse/builtins-set.js +12 -0
  42. package/lib/parse/diagnostics.js +180 -0
  43. package/lib/parse/index.js +46 -0
  44. package/lib/parse/lexer.js +390 -0
  45. package/lib/parse/parse-source.js +912 -0
  46. package/lib/typecheck/adt-autogen-sigs.js +345 -0
  47. package/lib/typecheck/build-type-env.js +148 -0
  48. package/lib/typecheck/builtin-signatures.js +183 -0
  49. package/lib/typecheck/check-word-body.js +1021 -0
  50. package/lib/typecheck/effect-decl.js +124 -0
  51. package/lib/typecheck/index.js +55 -0
  52. package/lib/typecheck/normalize-sig.js +369 -0
  53. package/lib/typecheck/stack-step-snapshots.js +56 -0
  54. package/lib/typecheck/unify-type.js +665 -0
  55. package/lib/typecheck/validate-adt.js +201 -0
  56. package/package.json +4 -9
  57. package/scripts/regen-demo-full-syntax-ast.mjs +22 -0
  58. package/test/cli/sail-cli.test.js +64 -0
  59. package/test/codegen/compile-bracket-ffi-e2e.test.js +64 -0
  60. package/test/codegen/compile-stage0.test.js +128 -0
  61. package/test/codegen/compile-stage4-layout.test.js +124 -0
  62. package/test/codegen/e2e-prelude-ffi-adt/app/extra.sail +6 -0
  63. package/test/codegen/e2e-prelude-ffi-adt/app/lib.sail +34 -0
  64. package/test/codegen/e2e-prelude-ffi-adt/app/main.sail +28 -0
  65. package/test/codegen/e2e-prelude-ffi-adt/artifacts/.gitignore +2 -0
  66. package/test/codegen/e2e-prelude-ffi-adt/ffi/helpers.js +27 -0
  67. package/test/codegen/e2e-prelude-ffi-adt.test.js +100 -0
  68. package/test/codegen/emit-adt-stage6.test.js +168 -0
  69. package/test/codegen/emit-async-stage5.test.js +164 -0
  70. package/test/codegen/emit-body-stage2.test.js +139 -0
  71. package/test/codegen/emit-body.test.js +163 -0
  72. package/test/codegen/emit-builtins-stage7.test.js +258 -0
  73. package/test/codegen/emit-diagnostics-stage9.test.js +90 -0
  74. package/test/codegen/emit-jsdoc-stage8.test.js +113 -0
  75. package/test/codegen/emit-module-stage3.test.js +78 -0
  76. package/test/conformance/conformance-ir-l4.test.js +38 -0
  77. package/test/conformance/conformance-l5-codegen.test.js +111 -0
  78. package/test/conformance/conformance-runner.js +91 -0
  79. package/test/conformance/conformance-suite-l3.test.js +32 -0
  80. package/test/ffi/prelude-jsdoc.test.js +49 -0
  81. package/test/fixtures/demo-full-syntax.ast.json +1471 -0
  82. package/test/fixtures/demo-full-syntax.sail +35 -0
  83. package/test/fixtures/io-node-ffi-adt/ffi.js +7 -0
  84. package/test/fixtures/io-node-ffi-adt/use.sail +4 -0
  85. package/test/fixtures/io-node-mini/dep.sail +2 -0
  86. package/test/fixtures/io-node-mini/entry.sail +4 -0
  87. package/test/fixtures/io-node-prelude/entry.sail +4 -0
  88. package/test/fixtures/io-node-reexport-chain/a.sail +4 -0
  89. package/test/fixtures/io-node-reexport-chain/b.sail +2 -0
  90. package/test/fixtures/io-node-reexport-chain/c.sail +2 -0
  91. package/test/io-node/resolve-disk.test.js +59 -0
  92. package/test/ir/bind-values.test.js +84 -0
  93. package/test/ir/build-module-ir.test.js +100 -0
  94. package/test/ir/call-effects.test.js +97 -0
  95. package/test/ir/ffi-bracket-ir.test.js +59 -0
  96. package/test/ir/full-ir-document.test.js +51 -0
  97. package/test/ir/ir-document-assert.js +67 -0
  98. package/test/ir/lower-body-steps.test.js +90 -0
  99. package/test/ir/module-metadata.test.js +42 -0
  100. package/test/ir/serialization-model.test.js +172 -0
  101. package/test/ir/stitch-types.test.js +74 -0
  102. package/test/names/l2-resolve-adt-autogen.test.js +155 -0
  103. package/test/names/l2-resolve-bracket-ffi.test.js +108 -0
  104. package/test/names/l2-resolve-declaration-and-bracket-errors.test.js +276 -0
  105. package/test/names/l2-resolve-graph.test.js +105 -0
  106. package/test/names/l2-resolve-single-file.test.js +79 -0
  107. package/test/parse/ast-spec.test.js +56 -0
  108. package/test/parse/ast.test.js +476 -0
  109. package/test/parse/contract.test.js +37 -0
  110. package/test/parse/fixtures-full-syntax.test.js +24 -0
  111. package/test/parse/helpers.js +27 -0
  112. package/test/parse/l0-lex-diagnostics-matrix.test.js +59 -0
  113. package/test/parse/l0-lex.test.js +40 -0
  114. package/test/parse/l1-diagnostics.test.js +77 -0
  115. package/test/parse/l1-import.test.js +28 -0
  116. package/test/parse/l1-parse-diagnostics-matrix.test.js +32 -0
  117. package/test/parse/l1-top-level.test.js +47 -0
  118. package/test/parse/l1-types.test.js +31 -0
  119. package/test/parse/l1-words.test.js +49 -0
  120. package/test/parse/l2-diagnostics-contract.test.js +67 -0
  121. package/test/parse/l3-diagnostics-contract.test.js +66 -0
  122. package/test/typecheck/adt-decl-stage2.test.js +83 -0
  123. package/test/typecheck/container-contract-e1309.test.js +258 -0
  124. package/test/typecheck/ffi-bracket-l3.test.js +61 -0
  125. package/test/typecheck/l3-diagnostics-matrix.test.js +248 -0
  126. package/test/typecheck/l3-partial-pipeline.test.js +74 -0
  127. package/test/typecheck/opaque-ffi-type.test.js +78 -0
  128. package/test/typecheck/sig-type-stage3.test.js +190 -0
  129. package/test/typecheck/stack-check-stage4.test.js +149 -0
  130. package/test/typecheck/stack-check-stage5.test.js +74 -0
  131. package/test/typecheck/stack-check-stage6.test.js +56 -0
  132. package/test/typecheck/stack-check-stage7.test.js +160 -0
  133. package/test/typecheck/stack-check-stage8.test.js +146 -0
  134. package/test/typecheck/stack-check-stage9.test.js +105 -0
  135. package/test/typecheck/typecheck-env.test.js +53 -0
  136. package/test/typecheck/typecheck-pipeline.test.js +37 -0
  137. package/README.md +0 -37
  138. package/cli/sail.js +0 -151
  139. package/cli/typecheck.js +0 -39
  140. package/docs/ARCHITECTURE.md +0 -50
  141. package/docs/CHANGELOG.md +0 -18
  142. package/docs/FFI-GUIDE.md +0 -65
  143. package/docs/RELEASE.md +0 -36
  144. package/docs/TESTING.md +0 -86
  145. package/test/integration.test.js +0 -61
@@ -0,0 +1,665 @@
1
+ /**
2
+ * L3: унификация IR типов (RFC-typecheck-0.1 §5.5, RFC-0.1 §13 E1304/E1305/E1309).
3
+ */
4
+ import * as diag from '../parse/diagnostics.js'
5
+ import { quotationOutgoingEffectsEqual } from './effect-decl.js'
6
+
7
+ /** Встроенные контейнеры List / Dict / Map (совпадает с BUILTIN_TYPE_CTORS в normalize-sig.js). */
8
+ const BUILTIN_CONTAINER_CTORS = new Set(['List', 'Dict', 'Map'])
9
+
10
+ /**
11
+ * @param {object} span
12
+ * @param {string} code
13
+ * @param {string} message
14
+ */
15
+ function pushDiag (out, span, code, message) {
16
+ const d = { code, message }
17
+ if (span?.start) {
18
+ d.offset = span.start.offset
19
+ d.line = span.start.line
20
+ d.column = span.start.column
21
+ }
22
+ out.push(d)
23
+ }
24
+
25
+ /**
26
+ * @param {object} t
27
+ * @returns {string}
28
+ */
29
+ export function formatIrType (t) {
30
+ if (!t) return '?'
31
+ switch (t.kind) {
32
+ case 'prim':
33
+ return t.name
34
+ case 'tvar':
35
+ return t.name
36
+ case 'stack_label':
37
+ return `~${t.name}`
38
+ case 'app':
39
+ return `${t.ctor}(${t.args.map(formatIrType).join(' ')})`
40
+ case 'adt':
41
+ return t.type
42
+ case 'opaque':
43
+ return t.name
44
+ case 'mod_adt':
45
+ return `~${t.module}/${t.type}`
46
+ case 'quote':
47
+ return 'Quote(...)'
48
+ case 'named_quote':
49
+ return `${t.prefix}:Quote(...)`
50
+ default:
51
+ return t.kind || '?'
52
+ }
53
+ }
54
+
55
+ /**
56
+ * @param {object} t
57
+ * @returns {boolean}
58
+ */
59
+ export function irContainsStackLabel (t) {
60
+ if (!t || typeof t !== 'object') return false
61
+ switch (t.kind) {
62
+ case 'stack_label':
63
+ return true
64
+ case 'app':
65
+ return t.args.some(irContainsStackLabel)
66
+ case 'quote':
67
+ case 'named_quote':
68
+ return irContainsNormSigStackLabels(t.inner)
69
+ default:
70
+ return false
71
+ }
72
+ }
73
+
74
+ /**
75
+ * @param {import('./normalize-sig.js').NormalizedSignature} sig
76
+ * @returns {boolean}
77
+ */
78
+ export function irSignatureContainsStackLabel (sig) {
79
+ return irContainsNormSigStackLabels(sig)
80
+ }
81
+
82
+ function irContainsNormSigStackLabels (sig) {
83
+ return [...sig.left, ...sig.right].some(irContainsStackLabel)
84
+ }
85
+
86
+ /**
87
+ * Сигнатура Quote из шаблона встроенного слова (builtin-signatures): без привязки к узлу исходника.
88
+ *
89
+ * @param {object | null} t
90
+ */
91
+ function isBuiltinQuoteShell (t) {
92
+ return (
93
+ !!t &&
94
+ (t.kind === 'quote' || t.kind === 'named_quote') &&
95
+ t.source === null
96
+ )
97
+ }
98
+
99
+ /**
100
+ * Одна область имён на сигнатуру; вложенные quotation — новая область для tvar и stack_label.
101
+ *
102
+ * @param {import('./normalize-sig.js').NormalizedSignature} sig
103
+ * @returns {import('./normalize-sig.js').NormalizedSignature}
104
+ */
105
+ export function shareTvarsInSignature (sig) {
106
+ return shareTvarsInSignatureWithMap(sig, new Map(), new Map())
107
+ }
108
+
109
+ /**
110
+ * @param {import('./normalize-sig.js').NormalizedSignature} sig
111
+ * @param {Map<string, object>} tvarByName
112
+ * @param {Map<string, object>} stackLabelByName
113
+ */
114
+ function shareTvarsInSignatureWithMap (sig, tvarByName, stackLabelByName) {
115
+ function walkType (t) {
116
+ if (!t || typeof t !== 'object') return t
117
+ switch (t.kind) {
118
+ case 'tvar': {
119
+ let c = tvarByName.get(t.name)
120
+ if (!c) {
121
+ c = { kind: 'tvar', name: t.name, source: t.source, decl: t.decl ?? null }
122
+ tvarByName.set(t.name, c)
123
+ }
124
+ return c
125
+ }
126
+ case 'stack_label': {
127
+ let c = stackLabelByName.get(t.name)
128
+ if (!c) {
129
+ c = { kind: 'stack_label', name: t.name, source: t.source }
130
+ stackLabelByName.set(t.name, c)
131
+ }
132
+ return c
133
+ }
134
+ case 'app':
135
+ return { kind: 'app', ctor: t.ctor, args: t.args.map(walkType), source: t.source, decl: t.decl }
136
+ case 'quote': {
137
+ return {
138
+ kind: 'quote',
139
+ source: t.source,
140
+ inner: shareTvarsInSignatureWithMap(t.inner, new Map(), new Map())
141
+ }
142
+ }
143
+ case 'named_quote': {
144
+ return {
145
+ kind: 'named_quote',
146
+ prefix: t.prefix,
147
+ source: t.source,
148
+ inner: shareTvarsInSignatureWithMap(t.inner, new Map(), new Map())
149
+ }
150
+ }
151
+ default:
152
+ return t
153
+ }
154
+ }
155
+ const out = {
156
+ left: sig.left.map(walkType),
157
+ right: sig.right.map(walkType),
158
+ effectsAdd: sig.effectsAdd,
159
+ effectsRemove: sig.effectsRemove
160
+ }
161
+ if (sig.adtEliminator != null) out.adtEliminator = sig.adtEliminator
162
+ return out
163
+ }
164
+
165
+ /**
166
+ * Свежая копия сигнатуры для одного вызова: новые tvar и stack_label, согласованные внутри копии.
167
+ *
168
+ * @param {import('./normalize-sig.js').NormalizedSignature} sig
169
+ * @returns {import('./normalize-sig.js').NormalizedSignature}
170
+ */
171
+ export function freshInstanceSignature (sig) {
172
+ /** @type {WeakMap<object, object>} */
173
+ const tvarMap = new WeakMap()
174
+ /** @type {WeakMap<object, object>} */
175
+ const stackLabelMap = new WeakMap()
176
+
177
+ function cloneType (t) {
178
+ if (!t || typeof t !== 'object') return t
179
+ switch (t.kind) {
180
+ case 'tvar': {
181
+ let n = tvarMap.get(t)
182
+ if (!n) {
183
+ n = { kind: 'tvar', name: t.name, source: t.source, decl: t.decl ?? null }
184
+ tvarMap.set(t, n)
185
+ }
186
+ return n
187
+ }
188
+ case 'stack_label': {
189
+ let n = stackLabelMap.get(t)
190
+ if (!n) {
191
+ n = { kind: 'stack_label', name: t.name, source: t.source }
192
+ stackLabelMap.set(t, n)
193
+ }
194
+ return n
195
+ }
196
+ case 'app':
197
+ return {
198
+ kind: 'app',
199
+ ctor: t.ctor,
200
+ args: t.args.map(cloneType),
201
+ source: t.source,
202
+ decl: t.decl
203
+ }
204
+ case 'quote':
205
+ return { kind: 'quote', source: t.source, inner: cloneNormSig(t.inner) }
206
+ case 'named_quote':
207
+ return {
208
+ kind: 'named_quote',
209
+ prefix: t.prefix,
210
+ source: t.source,
211
+ inner: cloneNormSig(t.inner)
212
+ }
213
+ default:
214
+ return t
215
+ }
216
+ }
217
+
218
+ function cloneNormSig (s) {
219
+ const o = {
220
+ left: s.left.map(cloneType),
221
+ right: s.right.map(cloneType),
222
+ effectsAdd: s.effectsAdd,
223
+ effectsRemove: s.effectsRemove
224
+ }
225
+ if (s.adtEliminator != null) o.adtEliminator = s.adtEliminator
226
+ return o
227
+ }
228
+
229
+ return cloneNormSig(sig)
230
+ }
231
+
232
+ /**
233
+ * @param {object} t
234
+ * @param {WeakMap<object, object>} subst
235
+ * @returns {object}
236
+ */
237
+ export function derefType (t, subst) {
238
+ let u = t
239
+ while (
240
+ u &&
241
+ (u.kind === 'tvar' || u.kind === 'stack_label') &&
242
+ subst.has(u)
243
+ ) {
244
+ u = subst.get(u)
245
+ }
246
+ return u
247
+ }
248
+
249
+ /**
250
+ * @param {object} v — tvar
251
+ * @param {object} t
252
+ * @param {WeakMap<object, object>} subst
253
+ * @returns {boolean}
254
+ */
255
+ function occursIn (v, t, subst) {
256
+ const w = derefType(t, subst)
257
+ if (!w) return false
258
+ if (w.kind === 'tvar' || w.kind === 'stack_label') {
259
+ if (w === v) return true
260
+ if (subst.has(w)) return occursIn(v, subst.get(w), subst)
261
+ return false
262
+ }
263
+ if (w.kind === 'app') return w.args.some((a) => occursIn(v, a, subst))
264
+ if (w.kind === 'quote' || w.kind === 'named_quote') {
265
+ const s = w.inner
266
+ return [...s.left, ...s.right].some((x) => occursIn(v, x, subst))
267
+ }
268
+ return false
269
+ }
270
+
271
+ /**
272
+ * @param {object} a
273
+ * @param {object} b
274
+ * @param {WeakMap<object, object>} subst
275
+ * @param {object[]} diags
276
+ * @param {object | null} span
277
+ * @returns {boolean}
278
+ */
279
+ export function unifyTypes (a, b, subst, diags, span) {
280
+ a = derefType(a, subst)
281
+ b = derefType(b, subst)
282
+ if (a === b) return true
283
+ if (a.kind === 'tvar') {
284
+ if (occursIn(a, b, subst)) {
285
+ pushDiag(diags, span, 'E1305', diag.e1305OccursCheck(a.name))
286
+ return false
287
+ }
288
+ subst.set(a, b)
289
+ return true
290
+ }
291
+ if (b.kind === 'tvar') {
292
+ if (occursIn(b, a, subst)) {
293
+ pushDiag(diags, span, 'E1305', diag.e1305OccursCheck(b.name))
294
+ return false
295
+ }
296
+ subst.set(b, a)
297
+ return true
298
+ }
299
+ if (a.kind === 'stack_label' && b.kind === 'stack_label') {
300
+ if (a.name !== b.name) {
301
+ pushDiag(
302
+ diags,
303
+ span,
304
+ 'E1304',
305
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
306
+ )
307
+ return false
308
+ }
309
+ if (a !== b) subst.set(a, b)
310
+ return true
311
+ }
312
+ if (a.kind === 'stack_label') {
313
+ if (occursIn(a, b, subst)) {
314
+ pushDiag(
315
+ diags,
316
+ span,
317
+ 'E1305',
318
+ diag.e1305OccursCheck(`~${a.name}`)
319
+ )
320
+ return false
321
+ }
322
+ subst.set(a, b)
323
+ return true
324
+ }
325
+ if (b.kind === 'stack_label') {
326
+ if (occursIn(b, a, subst)) {
327
+ pushDiag(
328
+ diags,
329
+ span,
330
+ 'E1305',
331
+ diag.e1305OccursCheck(`~${b.name}`)
332
+ )
333
+ return false
334
+ }
335
+ subst.set(b, a)
336
+ return true
337
+ }
338
+ if (a.kind !== b.kind) {
339
+ if (
340
+ (a.kind === 'quote' && b.kind === 'named_quote') ||
341
+ (a.kind === 'named_quote' && b.kind === 'quote')
342
+ ) {
343
+ return unifyNormSigQuoteIO(a.inner, b.inner, subst, diags, span, {
344
+ ignoreOutgoingEffects:
345
+ isBuiltinQuoteShell(a) || isBuiltinQuoteShell(b)
346
+ })
347
+ }
348
+ pushDiag(
349
+ diags,
350
+ span,
351
+ 'E1304',
352
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
353
+ )
354
+ return false
355
+ }
356
+ switch (a.kind) {
357
+ case 'prim':
358
+ if (a.name !== b.name) {
359
+ pushDiag(
360
+ diags,
361
+ span,
362
+ 'E1304',
363
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
364
+ )
365
+ return false
366
+ }
367
+ return true
368
+ case 'opaque':
369
+ if (a.name !== b.name) {
370
+ pushDiag(
371
+ diags,
372
+ span,
373
+ 'E1304',
374
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
375
+ )
376
+ return false
377
+ }
378
+ return true
379
+ case 'app':
380
+ if (a.ctor !== b.ctor || a.args.length !== b.args.length) {
381
+ pushDiag(
382
+ diags,
383
+ span,
384
+ 'E1304',
385
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
386
+ )
387
+ return false
388
+ }
389
+ if (BUILTIN_CONTAINER_CTORS.has(a.ctor)) {
390
+ for (let i = 0; i < a.args.length; i++) {
391
+ const nested = []
392
+ if (!unifyTypes(a.args[i], b.args[i], subst, nested, span)) {
393
+ if (
394
+ nested.length === 1 &&
395
+ nested[0].code === 'E1304'
396
+ ) {
397
+ // Порядок (a, b) совпадает с вложенным e1304TypeMismatch для unifyTypes(a.args[i], b.args[i]).
398
+ pushDiag(
399
+ diags,
400
+ span,
401
+ 'E1309',
402
+ diag.e1309ContainerContract(
403
+ a.ctor,
404
+ formatIrType(derefType(a.args[i], subst)),
405
+ formatIrType(derefType(b.args[i], subst))
406
+ )
407
+ )
408
+ } else {
409
+ for (const nd of nested) {
410
+ diags.push(nd)
411
+ }
412
+ }
413
+ return false
414
+ }
415
+ }
416
+ return true
417
+ }
418
+ for (let i = 0; i < a.args.length; i++) {
419
+ if (!unifyTypes(a.args[i], b.args[i], subst, diags, span)) return false
420
+ }
421
+ return true
422
+ case 'adt':
423
+ if (a.type !== b.type || a.path !== b.path) {
424
+ pushDiag(
425
+ diags,
426
+ span,
427
+ 'E1304',
428
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
429
+ )
430
+ return false
431
+ }
432
+ return true
433
+ case 'mod_adt':
434
+ if (a.module !== b.module || a.type !== b.type) {
435
+ pushDiag(
436
+ diags,
437
+ span,
438
+ 'E1304',
439
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
440
+ )
441
+ return false
442
+ }
443
+ return true
444
+ case 'quote':
445
+ return unifyNormSigQuoteIO(a.inner, b.inner, subst, diags, span, {
446
+ ignoreOutgoingEffects: isBuiltinQuoteShell(a) || isBuiltinQuoteShell(b)
447
+ })
448
+ case 'named_quote':
449
+ if (a.prefix !== b.prefix) {
450
+ pushDiag(
451
+ diags,
452
+ span,
453
+ 'E1304',
454
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
455
+ )
456
+ return false
457
+ }
458
+ return unifyNormSigQuoteIO(a.inner, b.inner, subst, diags, span, {
459
+ ignoreOutgoingEffects: isBuiltinQuoteShell(a) || isBuiltinQuoteShell(b)
460
+ })
461
+ default:
462
+ pushDiag(
463
+ diags,
464
+ span,
465
+ 'E1304',
466
+ diag.e1304TypeMismatch(formatIrType(a), formatIrType(b))
467
+ )
468
+ return false
469
+ }
470
+ }
471
+
472
+ /**
473
+ * @param {object} t
474
+ * @param {WeakMap<object, object>} subst
475
+ * @returns {object}
476
+ */
477
+ export function applySubstDeep (t, subst) {
478
+ const u = derefType(t, subst)
479
+ if (!u) return u
480
+ switch (u.kind) {
481
+ case 'tvar':
482
+ case 'stack_label':
483
+ return derefType(u, subst)
484
+ case 'app':
485
+ return {
486
+ kind: 'app',
487
+ ctor: u.ctor,
488
+ args: u.args.map((x) => applySubstDeep(x, subst)),
489
+ source: u.source,
490
+ decl: u.decl
491
+ }
492
+ case 'quote':
493
+ return {
494
+ kind: 'quote',
495
+ source: u.source,
496
+ inner: applySubstToNormSig(u.inner, subst)
497
+ }
498
+ case 'named_quote':
499
+ return {
500
+ kind: 'named_quote',
501
+ prefix: u.prefix,
502
+ source: u.source,
503
+ inner: applySubstToNormSig(u.inner, subst)
504
+ }
505
+ default:
506
+ return u
507
+ }
508
+ }
509
+
510
+ /**
511
+ * @param {import('./normalize-sig.js').NormalizedSignature} sig
512
+ * @param {WeakMap<object, object>} subst
513
+ * @returns {import('./normalize-sig.js').NormalizedSignature}
514
+ */
515
+ export function applySubstToNormSig (sig, subst) {
516
+ const o = {
517
+ left: sig.left.map((x) => applySubstDeep(x, subst)),
518
+ right: sig.right.map((x) => applySubstDeep(x, subst)),
519
+ effectsAdd: sig.effectsAdd,
520
+ effectsRemove: sig.effectsRemove
521
+ }
522
+ if (sig.adtEliminator != null) o.adtEliminator = sig.adtEliminator
523
+ return o
524
+ }
525
+
526
+ /**
527
+ * @param {object[]} stack
528
+ * @param {WeakMap<object, object>} subst
529
+ * @returns {object[]}
530
+ */
531
+ export function applySubstToStack (stack, subst) {
532
+ return stack.map((x) => applySubstDeep(x, subst))
533
+ }
534
+
535
+ /**
536
+ * Унификация вложенных сигнатур Quote (I -> O) (RFC-typecheck-0.1 §5.6).
537
+ *
538
+ * @param {import('./normalize-sig.js').NormalizedSignature} ia
539
+ * @param {import('./normalize-sig.js').NormalizedSignature} ib
540
+ * @param {WeakMap<object, object>} subst
541
+ * @param {object[]} diags
542
+ * @param {object | null} span
543
+ * @param {{ ignoreOutgoingEffects?: boolean }} [opts]
544
+ * @returns {boolean}
545
+ */
546
+ /**
547
+ * Выходные слоты quotation (RFC-0.1 §6.1.5): имена ~s / ~b — метки региона стека;
548
+ * шаблон eliminator-а использует ~b, пользовательская цитата — ~s.
549
+ *
550
+ * @param {object} a
551
+ * @param {object} b
552
+ * @param {WeakMap<object, object>} subst
553
+ * @param {object[]} diags
554
+ * @param {object | null} span
555
+ * @returns {boolean}
556
+ */
557
+ function unifyQuotationOutputSlots (a, b, subst, diags, span) {
558
+ a = derefType(a, subst)
559
+ b = derefType(b, subst)
560
+ if (a.kind === 'stack_label' && b.kind === 'stack_label' && a.name !== b.name) {
561
+ if (occursIn(a, b, subst)) {
562
+ pushDiag(
563
+ diags,
564
+ span,
565
+ 'E1305',
566
+ diag.e1305OccursCheck(`~${a.name}`)
567
+ )
568
+ return false
569
+ }
570
+ subst.set(a, b)
571
+ return true
572
+ }
573
+ return unifyTypes(a, b, subst, diags, span)
574
+ }
575
+
576
+ export function unifyNormSigQuoteIO (ia, ib, subst, diags, span, opts) {
577
+ const ignoreOutgoingEffects = opts?.ignoreOutgoingEffects === true
578
+ if (ia.left.length !== ib.left.length) {
579
+ pushDiag(
580
+ diags,
581
+ span,
582
+ 'E1303',
583
+ diag.e1303StackEffect(
584
+ `${ib.left.length} слотов входа quotation`,
585
+ `${ia.left.length} слотов`
586
+ )
587
+ )
588
+ return false
589
+ }
590
+ for (let i = 0; i < ia.left.length; i++) {
591
+ if (!unifyTypes(ia.left[i], ib.left[i], subst, diags, span)) return false
592
+ }
593
+ if (ia.right.length !== ib.right.length) {
594
+ pushDiag(
595
+ diags,
596
+ span,
597
+ 'E1303',
598
+ diag.e1303StackEffect(
599
+ `${ib.right.length} слотов выхода quotation`,
600
+ `${ia.right.length} слотов`
601
+ )
602
+ )
603
+ return false
604
+ }
605
+ for (let i = 0; i < ia.right.length; i++) {
606
+ if (!unifyQuotationOutputSlots(ia.right[i], ib.right[i], subst, diags, span)) {
607
+ return false
608
+ }
609
+ }
610
+ if (
611
+ !ignoreOutgoingEffects &&
612
+ !quotationOutgoingEffectsEqual(ia, ib)
613
+ ) {
614
+ pushDiag(
615
+ diags,
616
+ span,
617
+ 'E1304',
618
+ diag.e1304TypeMismatch(
619
+ 'совпадение маркеров эффектов вложенной quotation_sig',
620
+ 'разные +Async/+Fail/-Async/-Fail'
621
+ )
622
+ )
623
+ return false
624
+ }
625
+ return true
626
+ }
627
+
628
+ /**
629
+ * Сравнение итогового стека с объявленным выходом (длина и покомпонентная унификация).
630
+ *
631
+ * @param {object[]} stackBottomToTop
632
+ * @param {object[]} expectedRight
633
+ * @param {WeakMap<object, object>} subst
634
+ * @param {object[]} diags
635
+ * @param {object | null} span
636
+ * @returns {boolean}
637
+ */
638
+ export function unifyStackWithOutput (stackBottomToTop, expectedRight, subst, diags, span) {
639
+ if (stackBottomToTop.length !== expectedRight.length) {
640
+ pushDiag(
641
+ diags,
642
+ span,
643
+ 'E1303',
644
+ diag.e1303StackEffect(
645
+ `${expectedRight.length} слотов`,
646
+ `${stackBottomToTop.length} слотов`
647
+ )
648
+ )
649
+ return false
650
+ }
651
+ for (let i = 0; i < expectedRight.length; i++) {
652
+ if (
653
+ !unifyTypes(
654
+ stackBottomToTop[i],
655
+ expectedRight[i],
656
+ subst,
657
+ diags,
658
+ span
659
+ )
660
+ ) {
661
+ return false
662
+ }
663
+ }
664
+ return true
665
+ }