@algosail/lang 0.2.11 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/bin/sail.mjs +4 -0
  2. package/cli/run-cli.js +176 -0
  3. package/index.js +11 -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 +33 -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,124 @@
1
+ /**
2
+ * L3 этап 7: объявленные эффекты сигнатуры (RFC-0.1 §5.7, RFC-IR-0.1 §6).
3
+ */
4
+
5
+ /**
6
+ * @typedef {{ plusAsync: boolean, minusAsync: boolean, plusFail: boolean, minusFail: boolean }} EffectFlags
7
+ */
8
+
9
+ /**
10
+ * @param {import('./normalize-sig.js').NormalizedSignature | null | undefined} sig
11
+ * @returns {EffectFlags}
12
+ */
13
+ export function declaredEffectFlags (sig) {
14
+ const f = {
15
+ plusAsync: false,
16
+ minusAsync: false,
17
+ plusFail: false,
18
+ minusFail: false
19
+ }
20
+ if (!sig) return f
21
+ for (const e of sig.effectsAdd ?? []) {
22
+ if (e.name === 'Async') f.plusAsync = true
23
+ if (e.name === 'Fail') f.plusFail = true
24
+ }
25
+ for (const e of sig.effectsRemove ?? []) {
26
+ if (e.name === 'Async') f.minusAsync = true
27
+ if (e.name === 'Fail') f.minusFail = true
28
+ }
29
+ return f
30
+ }
31
+
32
+ /**
33
+ * Контекст (слово или quotation inner) может содержать вызов callee, у которого всплывает +Async/+Fail к нему.
34
+ * Разрешено при +Name или при исходящем -Name (обёртка, §5.7).
35
+ *
36
+ * @param {EffectFlags} ctx
37
+ */
38
+ export function contextAllowsAsyncCallFromCallee (ctx) {
39
+ return ctx.plusAsync || ctx.minusAsync
40
+ }
41
+
42
+ /**
43
+ * @param {EffectFlags} ctx
44
+ */
45
+ export function contextAllowsFailCallFromCallee (ctx) {
46
+ return ctx.plusFail || ctx.minusFail
47
+ }
48
+
49
+ /**
50
+ * Вызов callee обязывает непосредственного вызывающего учитывать +Async (E1310), если у callee объявлен +Async
51
+ * и не снят исходящим -Async у callee (§5.7.1).
52
+ *
53
+ * @param {EffectFlags} callee
54
+ */
55
+ export function calleeAsyncPropagatesToCaller (callee) {
56
+ return callee.plusAsync && !callee.minusAsync
57
+ }
58
+
59
+ /**
60
+ * @param {EffectFlags} callee
61
+ */
62
+ export function calleeFailPropagatesToCaller (callee) {
63
+ return callee.plusFail && !callee.minusFail
64
+ }
65
+
66
+ /**
67
+ * @param {object | null} span
68
+ * @returns {{ effectsAdd: object[], effectsRemove: object[] }}
69
+ */
70
+ export function effectArraysFromFlags (flags, span) {
71
+ const effectsAdd = []
72
+ const effectsRemove = []
73
+ const side = 'right'
74
+ if (flags.plusAsync) {
75
+ effectsAdd.push({
76
+ kind: 'sig_effect_add',
77
+ span: span ?? null,
78
+ name: 'Async',
79
+ side
80
+ })
81
+ }
82
+ if (flags.plusFail) {
83
+ effectsAdd.push({
84
+ kind: 'sig_effect_add',
85
+ span: span ?? null,
86
+ name: 'Fail',
87
+ side
88
+ })
89
+ }
90
+ if (flags.minusAsync) {
91
+ effectsRemove.push({
92
+ kind: 'sig_effect_remove',
93
+ span: span ?? null,
94
+ name: 'Async',
95
+ side
96
+ })
97
+ }
98
+ if (flags.minusFail) {
99
+ effectsRemove.push({
100
+ kind: 'sig_effect_remove',
101
+ span: span ?? null,
102
+ name: 'Fail',
103
+ side
104
+ })
105
+ }
106
+ return { effectsAdd, effectsRemove }
107
+ }
108
+
109
+ /**
110
+ * Сравнение маркеров исходящей части вложенной quotation_sig при унификации Quote (этап 7).
111
+ *
112
+ * @param {import('./normalize-sig.js').NormalizedSignature} ia
113
+ * @param {import('./normalize-sig.js').NormalizedSignature} ib
114
+ */
115
+ export function quotationOutgoingEffectsEqual (ia, ib) {
116
+ const a = declaredEffectFlags(ia)
117
+ const b = declaredEffectFlags(ib)
118
+ return (
119
+ a.plusAsync === b.plusAsync &&
120
+ a.minusAsync === b.minusAsync &&
121
+ a.plusFail === b.plusFail &&
122
+ a.minusFail === b.minusFail
123
+ )
124
+ }
@@ -0,0 +1,55 @@
1
+ import { resolveSailNames } from '../names/resolve-sail.js'
2
+ import { buildTypecheckEnv } from './build-type-env.js'
3
+ import { validateAdtDeclarations } from './validate-adt.js'
4
+ import { normalizeAllSignatures } from './normalize-sig.js'
5
+ import { mergeAdtAutogenIntoSigIrByPath } from './adt-autogen-sigs.js'
6
+ import { checkAllWordBodies } from './check-word-body.js'
7
+
8
+ /**
9
+ * L3: типизация и проверка стека после успешного Parse и Name resolution (RFC-0.1 §11 п.1–2).
10
+ * Диагностики этой фазы — коды E13xx (RFC-0.1 §13). Алгоритм — RFC-typecheck-0.1.
11
+ *
12
+ * Вход совпадает с {@link resolveSailNames}: один обход графа модулей; при `ok === false`
13
+ * возвращаются диагностики L0/L1/L2 без запуска тела L3.
14
+ *
15
+ * **Порядок фаз `typecheckSail`** (имена шагов, не «этап N» в сообщениях об ошибках):
16
+ *
17
+ * | Шаг | Действие | Тесты (brittle) |
18
+ * |-----|----------|-----------------|
19
+ * | L2 + среда | `resolveSailNames` → `buildTypecheckEnv` | `typecheck-env.test.js`, `typecheck-pipeline.test.js` |
20
+ * | ADT | `validateAdtDeclarations` | `adt-decl-stage2.test.js` |
21
+ * | Сигнатуры | `normalizeAllSignatures` (+ автоген сигнатур ADT) | `sig-type-stage3.test.js` |
22
+ * | Тела слов | `checkAllWordBodies` (стек, унификация, эффекты) | `stack-check-stage4.test.js` … `stack-check-stage9.test.js` |
23
+ *
24
+ * При успехе L2 поле `env` — среда резолва и импортов. `env.adtByPath` — только **валидные** `&` (частично, если часть объявлений с ошибками; RFC-typecheck §5.1).
25
+ * `env.sigIrByPath` — **частичная** карта после нормализации + `mergeAdtAutogenIntoSigIrByPath` (автоген только для ADT из `adtByPath`).
26
+ * Итог: `ok === false`, если есть **хоть одна** диагностика ADT, сигнатур или тел; `diagnostics` — склейка **ADT → сигнатуры → тела** (полный отчёт, пайплайн не обрывается до проверки тел).
27
+ * `env.stackSnapshotsByPath` — успешные слова и **частичные** цепочки по RFC-typecheck §3 / RFC-IR §1.1 при ошибке в теле.
28
+ *
29
+ * **E1302** (некорректная сигнатура quotation, RFC-0.1 §13): код и шаблон `e1302QuotationSig` зарезервированы; эмиссия из
30
+ * нормализации сигнатур не подключена до уточнения RFC, какие сбои `quotation_sig` / `quotation_type` отличать от прочих E1304.
31
+ *
32
+ * @param {{ entryPath: string, readFile: (p: string) => string | null | undefined, resolvePackage?: (spec: string, fromPath: string) => string | null }} opts
33
+ * @returns {{ ok: boolean, diagnostics: object[], snapshots?: Map<string, object>, env?: object }}
34
+ */
35
+ export function typecheckSail (opts) {
36
+ const r = resolveSailNames({ ...opts, withSnapshots: true })
37
+ if (!r.ok) {
38
+ return { ok: false, diagnostics: r.diagnostics }
39
+ }
40
+ const env = buildTypecheckEnv({
41
+ snapshots: r.snapshots,
42
+ entryPath: opts.entryPath,
43
+ readFile: opts.readFile,
44
+ resolvePackage: opts.resolvePackage
45
+ })
46
+ const { diagnostics: adtDiags, adtByPath } = validateAdtDeclarations(env)
47
+ env.adtByPath = adtByPath
48
+ const { diagnostics: sigDiags, sigIrByPath } = normalizeAllSignatures(env)
49
+ mergeAdtAutogenIntoSigIrByPath(env, sigIrByPath)
50
+ env.sigIrByPath = sigIrByPath
51
+ const bodyDiags = checkAllWordBodies(env)
52
+ const diagnostics = [...adtDiags, ...sigDiags, ...bodyDiags]
53
+ const ok = diagnostics.length === 0
54
+ return { ok, diagnostics, snapshots: r.snapshots, env }
55
+ }
@@ -0,0 +1,369 @@
1
+ /**
2
+ * L3 этап 3: нормализация типовых выражений в сигнатурах (IR + source/decl).
3
+ */
4
+ import * as diag from '../parse/diagnostics.js'
5
+
6
+ /** @type {ReadonlySet<string>} */
7
+ export const PRIMITIVE_TYPE_NAMES = new Set([
8
+ 'Str',
9
+ 'String',
10
+ 'Num',
11
+ 'Int',
12
+ 'Bool',
13
+ 'Float',
14
+ 'Double',
15
+ 'Char',
16
+ 'Unit',
17
+ 'Nil'
18
+ ])
19
+
20
+ /** @type {ReadonlySet<string>} */
21
+ const BUILTIN_TYPE_CTORS = new Set(['List', 'Dict', 'Map'])
22
+
23
+ /**
24
+ * @param {object} span
25
+ * @param {string} code
26
+ * @param {string} message
27
+ */
28
+ function pushDiag (out, span, code, message) {
29
+ const d = { code, message }
30
+ if (span?.start) {
31
+ d.offset = span.start.offset
32
+ d.line = span.start.line
33
+ d.column = span.start.column
34
+ }
35
+ out.push(d)
36
+ }
37
+
38
+ /**
39
+ * Модуль, в `items` которого лежит данный узел верхнего уровня (sum/product/word/import).
40
+ *
41
+ * @param {Map<string, object>} snapshots
42
+ * @param {object} node
43
+ * @returns {string | null}
44
+ */
45
+ export function modulePathForAstNode (snapshots, node) {
46
+ for (const [p, snap] of snapshots) {
47
+ if (!snap?.ok) continue
48
+ for (const it of snap.items) {
49
+ if (it === node) return p
50
+ }
51
+ }
52
+ return null
53
+ }
54
+
55
+ /**
56
+ * @typedef {{ path: string, node: object }} TypeDeclRef
57
+ *
58
+ * @typedef {{ kind: 'prim', name: string, source: object, decl: null }} IrPrim
59
+ * @typedef {{ kind: 'tvar', name: string, source: object, decl?: TypeDeclRef | null }} IrTvar
60
+ * @typedef {{ kind: 'app', ctor: string, args: object[], source: object, decl: TypeDeclRef | null }} IrApp
61
+ * @typedef {{ kind: 'mod_adt', module: string, type: string, source: object, decl: TypeDeclRef }} IrModAdt
62
+ * @typedef {{ kind: 'adt', path: string, type: string, source: object, decl: TypeDeclRef }} IrAdt
63
+ * @typedef {{ kind: 'opaque', name: string, source: object, decl: null }} IrOpaque — номинальный opaque FFI (RFC-0.1 §5.4), без параметров.
64
+ * @typedef {{ kind: 'stack_label', name: string, source: object }} IrStackLabel
65
+ * @typedef {{ kind: 'quote', source: object, inner: NormalizedSignature }} IrQuote
66
+ * @typedef {{ kind: 'named_quote', prefix: string, source: object, inner: NormalizedSignature }} IrNamedQuote
67
+ *
68
+ * @typedef {{
69
+ * left: object[],
70
+ * right: object[],
71
+ * effectsAdd: object[],
72
+ * effectsRemove: object[]
73
+ * }} NormalizedSignature
74
+ */
75
+
76
+ /**
77
+ * @param {import('./build-type-env.js').TypecheckEnv} env
78
+ * @param {string} modulePath
79
+ * @param {{ importMap: Map<string, object>, typeIndex: Map<string, object> }} scopeInfo
80
+ */
81
+ function makeCtx (env, modulePath, scopeInfo) {
82
+ return {
83
+ env,
84
+ modulePath,
85
+ scopeInfo,
86
+ /** @type {object[]} */
87
+ diagnostics: []
88
+ }
89
+ }
90
+
91
+ /**
92
+ * @param {object} expr
93
+ * @param {ReturnType<typeof makeCtx>} ctx
94
+ * @returns {object | null}
95
+ */
96
+ export function normalizeTypeExpr (expr, ctx) {
97
+ if (!expr) return null
98
+ switch (expr.kind) {
99
+ case 'type_name': {
100
+ const nm = expr.name
101
+ if (PRIMITIVE_TYPE_NAMES.has(nm)) {
102
+ return { kind: 'prim', name: nm, source: expr, decl: null }
103
+ }
104
+ const def = ctx.scopeInfo.typeIndex.get(nm)
105
+ if (def && (def.kind === 'sum_type' || def.kind === 'product_type')) {
106
+ const npar = def.typeParams?.length ?? 0
107
+ if (npar > 0) {
108
+ pushDiag(
109
+ ctx.diagnostics,
110
+ expr.span,
111
+ 'E1311',
112
+ diag.e1311ParameterizedTypeNeedsParens(nm)
113
+ )
114
+ return null
115
+ }
116
+ const defPath =
117
+ modulePathForAstNode(ctx.env.snapshots, def) ?? ctx.modulePath
118
+ return {
119
+ kind: 'adt',
120
+ path: defPath,
121
+ type: nm,
122
+ source: expr,
123
+ decl: { path: defPath, node: def }
124
+ }
125
+ }
126
+ return { kind: 'opaque', name: nm, source: expr, decl: null }
127
+ }
128
+ case 'type_var':
129
+ return { kind: 'tvar', name: expr.name, source: expr, decl: null }
130
+ case 'module_type_ref': {
131
+ const { module: mod, type: ty } = expr
132
+ const dep = ctx.scopeInfo.importMap.get(mod)
133
+ if (!dep?.ok) {
134
+ pushDiag(ctx.diagnostics, expr.span, 'E1203', diag.e1203ModuleNotFound(mod))
135
+ return null
136
+ }
137
+ if (!dep.exportTypes.has(ty)) {
138
+ pushDiag(
139
+ ctx.diagnostics,
140
+ expr.span,
141
+ 'E1204',
142
+ diag.e1204MissingMember(mod, ty)
143
+ )
144
+ return null
145
+ }
146
+ const node = dep.typeIndex.get(ty)
147
+ if (!node) {
148
+ pushDiag(
149
+ ctx.diagnostics,
150
+ expr.span,
151
+ 'E1204',
152
+ diag.e1204MissingMember(mod, ty)
153
+ )
154
+ return null
155
+ }
156
+ const npar = node.typeParams?.length ?? 0
157
+ if (
158
+ npar > 0 &&
159
+ (node.kind === 'sum_type' || node.kind === 'product_type')
160
+ ) {
161
+ pushDiag(
162
+ ctx.diagnostics,
163
+ expr.span,
164
+ 'E1311',
165
+ diag.e1311ParameterizedTypeNeedsParens(ty)
166
+ )
167
+ return null
168
+ }
169
+ const declPath = dep.path
170
+ return {
171
+ kind: 'mod_adt',
172
+ module: mod,
173
+ type: ty,
174
+ source: expr,
175
+ decl: { path: declPath, node }
176
+ }
177
+ }
178
+ case 'paren_type':
179
+ return normalizeTypeExpr(expr.inner, ctx)
180
+ case 'quotation_type': {
181
+ const inner = normalizeSignature(expr.inner, ctx)
182
+ if (!inner) return null
183
+ return { kind: 'quote', source: expr, inner }
184
+ }
185
+ case 'type_app': {
186
+ const ctor = expr.ctor
187
+ const args = []
188
+ for (const a of expr.args) {
189
+ const na = normalizeTypeExpr(a, ctx)
190
+ if (!na) return null
191
+ args.push(na)
192
+ }
193
+ if (BUILTIN_TYPE_CTORS.has(ctor)) {
194
+ return { kind: 'app', ctor, args, source: expr, decl: null }
195
+ }
196
+ const def = ctx.scopeInfo.typeIndex.get(ctor)
197
+ if (def && (def.kind === 'sum_type' || def.kind === 'product_type')) {
198
+ const defPath =
199
+ modulePathForAstNode(ctx.env.snapshots, def) ?? ctx.modulePath
200
+ return {
201
+ kind: 'app',
202
+ ctor,
203
+ args,
204
+ source: expr,
205
+ decl: { path: defPath, node: def }
206
+ }
207
+ }
208
+ pushDiag(
209
+ ctx.diagnostics,
210
+ expr.span,
211
+ 'E1304',
212
+ diag.e1304UnknownTypeInSignature(ctor)
213
+ )
214
+ return null
215
+ }
216
+ default:
217
+ pushDiag(
218
+ ctx.diagnostics,
219
+ expr.span,
220
+ 'E1304',
221
+ diag.e1304TypeMismatch('известное типовое выражение', expr.kind)
222
+ )
223
+ return null
224
+ }
225
+ }
226
+
227
+ /**
228
+ * @param {object} sig
229
+ * @param {ReturnType<typeof makeCtx>} ctx
230
+ * @returns {NormalizedSignature | null}
231
+ */
232
+ export function normalizeSignature (sig, ctx) {
233
+ /** @type {object[]} */
234
+ const left = []
235
+ for (const item of sig.left) {
236
+ const s = normalizeSigItem(item, ctx)
237
+ if (!s) return null
238
+ left.push(s)
239
+ }
240
+ /** @type {object[]} */
241
+ const right = []
242
+ for (const item of sig.right) {
243
+ const s = normalizeSigItem(item, ctx)
244
+ if (!s) return null
245
+ right.push(s)
246
+ }
247
+ return {
248
+ left,
249
+ right,
250
+ effectsAdd: [...sig.effectsAdd],
251
+ effectsRemove: [...sig.effectsRemove]
252
+ }
253
+ }
254
+
255
+ /**
256
+ * @param {object} item
257
+ * @param {ReturnType<typeof makeCtx>} ctx
258
+ * @returns {object | null}
259
+ */
260
+ /**
261
+ * @param {object} item
262
+ * @param {import('./build-type-env.js').TypecheckEnv} env
263
+ * @param {string} modulePath
264
+ * @returns {{ ir: object | null, diagnostics: object[] }}
265
+ */
266
+ export function normalizeSigItemWithEnv (item, env, modulePath) {
267
+ const scopeInfo = env.scopeByPath.get(modulePath)
268
+ if (!scopeInfo) {
269
+ return { ir: null, diagnostics: [] }
270
+ }
271
+ const ctx = makeCtx(env, modulePath, scopeInfo)
272
+ const ir = normalizeSigItem(item, ctx)
273
+ return { ir, diagnostics: ctx.diagnostics }
274
+ }
275
+
276
+ export function normalizeSigItem (item, ctx) {
277
+ switch (item.kind) {
278
+ case 'sig_type_expr': {
279
+ return normalizeTypeExpr(item.type, ctx)
280
+ }
281
+ case 'stack_var':
282
+ return { kind: 'stack_label', name: item.name, source: item }
283
+ case 'quotation_sig': {
284
+ const inner = normalizeSignature(item.inner, ctx)
285
+ if (!inner) return null
286
+ return { kind: 'quote', source: item, inner }
287
+ }
288
+ case 'named_quotation_sig': {
289
+ const inner = normalizeSignature(item.quotation.inner, ctx)
290
+ if (!inner) return null
291
+ return {
292
+ kind: 'named_quote',
293
+ prefix: item.prefix,
294
+ source: item,
295
+ inner
296
+ }
297
+ }
298
+ default:
299
+ pushDiag(
300
+ ctx.diagnostics,
301
+ item.span,
302
+ 'E1304',
303
+ diag.e1304TypeMismatch('элемент сигнатуры', item.kind)
304
+ )
305
+ return null
306
+ }
307
+ }
308
+
309
+ /**
310
+ * @param {object} word
311
+ * @param {ReturnType<typeof makeCtx>} ctx
312
+ * @returns {NormalizedSignature | null}
313
+ */
314
+ export function normalizeWordSignature (word, ctx) {
315
+ if (!word.signature) {
316
+ pushDiag(
317
+ ctx.diagnostics,
318
+ word.span,
319
+ 'E1301',
320
+ diag.e1301WordSignature(word.name)
321
+ )
322
+ return null
323
+ }
324
+ const ir = normalizeSignature(word.signature, ctx)
325
+ if (!ir && ctx.diagnostics.length === 0) {
326
+ pushDiag(
327
+ ctx.diagnostics,
328
+ word.span,
329
+ 'E1301',
330
+ diag.e1301WordSignature(word.name)
331
+ )
332
+ }
333
+ return ir
334
+ }
335
+
336
+ /**
337
+ * @param {import('./build-type-env.js').TypecheckEnv} env
338
+ * @returns {{ diagnostics: object[], sigIrByPath: Map<string, Map<string, NormalizedSignature>> }}
339
+ */
340
+ export function normalizeAllSignatures (env) {
341
+ /** @type {object[]} */
342
+ const allDiags = []
343
+ /** @type {Map<string, Map<string, NormalizedSignature>>} */
344
+ const sigIrByPath = new Map()
345
+
346
+ for (const p of env.modulePathsOrdered) {
347
+ const snap = env.snapshots.get(p)
348
+ const scopeInfo = env.scopeByPath.get(p)
349
+ const words = env.wordDeclByPath.get(p)
350
+ if (!snap?.ok || !scopeInfo || !words) continue
351
+
352
+ /** @type {Map<string, NormalizedSignature>} */
353
+ const perPath = new Map()
354
+
355
+ for (const [name, word] of words) {
356
+ const ctx = makeCtx(env, p, scopeInfo)
357
+ const ir = normalizeWordSignature(word, ctx)
358
+ if (ctx.diagnostics.length > 0) {
359
+ allDiags.push(...ctx.diagnostics)
360
+ }
361
+ if (ir) {
362
+ perPath.set(name, ir)
363
+ }
364
+ }
365
+ sigIrByPath.set(p, perPath)
366
+ }
367
+
368
+ return { diagnostics: allDiags, sigIrByPath }
369
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * L3 этап 9: снимки стека по шагам для RFC-IR-0.1 §3, RFC-typecheck-0.1 §3 и §6.
3
+ * Полная цепочка на каждый шаг (сжатие до базовых блоков не делаем — см. RFC-IR §3).
4
+ */
5
+ import { applySubstDeep, unifyTypes } from './unify-type.js'
6
+
7
+ /**
8
+ * @typedef {{ pre: object[], post: object[] }} StackStepSnapshot
9
+ */
10
+
11
+ /**
12
+ * @typedef {object} WordStackSnapshotRecord
13
+ * @property {StackStepSnapshot[]} steps индекс = индекс шага в `word.body` (дно→вершина в pre/post)
14
+ * @property {Map<number, WordStackSnapshotRecord>} nestedByParentStep вложенные цитаты по индексу шага `( … )`
15
+ */
16
+
17
+ /**
18
+ * Копия стека для снимка: слоты после текущей подстановки (читаемо снаружи без повторного вывода).
19
+ *
20
+ * @param {object[]} stack
21
+ * @param {WeakMap<object, object>} subst
22
+ * @returns {object[]}
23
+ */
24
+ export function snapshotStackSlots (stack, subst) {
25
+ return stack.map((t) => applySubstDeep(t, subst))
26
+ }
27
+
28
+ /**
29
+ * RFC-IR-0.1 §3 п.2: для валидной цепочки post[i] унифицируется с pre[i+1].
30
+ *
31
+ * @param {WordStackSnapshotRecord} record
32
+ * @returns {boolean}
33
+ */
34
+ export function verifySnapshotChainStitches (record) {
35
+ const steps = record.steps
36
+ for (let i = 0; i < steps.length - 1; i++) {
37
+ const cur = steps[i]
38
+ const next = steps[i + 1]
39
+ if (cur == null || next == null) continue
40
+ const a = cur.post
41
+ const b = next.pre
42
+ if (a == null || b == null || !Array.isArray(a) || !Array.isArray(b)) {
43
+ continue
44
+ }
45
+ if (a.length !== b.length) return false
46
+ const subst = new WeakMap()
47
+ const diags = []
48
+ for (let j = 0; j < a.length; j++) {
49
+ if (!unifyTypes(a[j], b[j], subst, diags, null)) return false
50
+ }
51
+ }
52
+ for (const nested of record.nestedByParentStep.values()) {
53
+ if (!verifySnapshotChainStitches(nested)) return false
54
+ }
55
+ return true
56
+ }