@algosail/lang 0.2.12 → 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,77 @@
1
+ /**
2
+ * Parse / lex diagnostics E1001, E1003–E1007 — RFC-0.1 §13.
3
+ * E1001 body: conformance/cases/reject-e1001-unexpected-token/main.sail
4
+ * Полная матрица E1001–E1007: l1-parse-diagnostics-matrix.test.js
5
+ */
6
+ import test from 'brittle'
7
+ import { expectReject } from './helpers.js'
8
+
9
+ test('E1005: `->` inside executable quotation body (RFC-0.1 §13)', function (t) {
10
+ const src = [
11
+ '@bad ( -> )',
12
+ '',
13
+ ' ( 1 -> 2 )'
14
+ ].join('\n')
15
+ expectReject(
16
+ t,
17
+ src,
18
+ 'E1005',
19
+ "Стрелка сигнатуры '->'"
20
+ )
21
+ t.end()
22
+ })
23
+
24
+ test('E1006: effect prefix +Async outside signature parens (RFC-0.1 §13)', function (t) {
25
+ const src = [
26
+ '@bad ( -> )',
27
+ '',
28
+ ' +Async'
29
+ ].join('\n')
30
+ expectReject(
31
+ t,
32
+ src,
33
+ 'E1006',
34
+ "Префикс эффекта '+Async'"
35
+ )
36
+ t.end()
37
+ })
38
+
39
+ test('E1006: +Fail in word body → E1006', function (t) {
40
+ const src = '@bad ( -> )\n\n +Fail'
41
+ expectReject(t, src, 'E1006', '+Fail')
42
+ t.end()
43
+ })
44
+
45
+ test('E1007: +Async left of -> in signature (RFC-0.1 §5.7, §13)', function (t) {
46
+ expectReject(
47
+ t,
48
+ '@w ( +Async -> )\n\n',
49
+ 'E1007',
50
+ "справа от '->'"
51
+ )
52
+ t.end()
53
+ })
54
+
55
+ test('E1007: -Fail left of -> in signature', function (t) {
56
+ expectReject(
57
+ t,
58
+ '@w ( Num -Fail -> Num )\n\n',
59
+ 'E1007',
60
+ '-Fail'
61
+ )
62
+ t.end()
63
+ })
64
+
65
+ test('E1001: invalid step in word body (conformance reject-e1001-unexpected-token)', function (t) {
66
+ const src = [
67
+ '@bad ( -> )',
68
+ ' @not_a_valid_step'
69
+ ].join('\n')
70
+ expectReject(
71
+ t,
72
+ src,
73
+ 'E1001',
74
+ 'неожиданный токен'
75
+ )
76
+ t.end()
77
+ })
@@ -0,0 +1,28 @@
1
+ /**
2
+ * L1 import_decl — RFC-0.1 §12.2 (import_decl, import_tail, import_item), RFC-lex §7 path.
3
+ */
4
+ import test from 'brittle'
5
+ import { expectAccept, expectReject } from './helpers.js'
6
+
7
+ test('L1: simple import +ModuleName path', function (t) {
8
+ expectAccept(t, '+Math ./math.sail')
9
+ expectAccept(t, '+Pkg @scope/name')
10
+ t.end()
11
+ })
12
+
13
+ test('L1: bracket import with @WordName and &TypeName', function (t) {
14
+ const src = '+Lib ( @add &Point ) ./lib.sail'
15
+ expectAccept(t, src)
16
+ t.end()
17
+ })
18
+
19
+ test('L1: missing path after module name → E1003 (RFC-0.1 §13)', function (t) {
20
+ expectReject(t, '+OnlyName', 'E1003', 'путь импорта')
21
+ expectReject(t, '+OnlyName ', 'E1003', 'путь импорта')
22
+ t.end()
23
+ })
24
+
25
+ test('L1: path must not contain whitespace (RFC-lex §7)', function (t) {
26
+ expectReject(t, '+Lib ./bad path.sail', 'E1003', 'путь импорта')
27
+ t.end()
28
+ })
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Матрица диагностик L0/L1 (RFC-conformance-0.1 §3 L0/L1, RFC-0.1 §13 E1001–E1007).
3
+ * Неизвестное lower в теле слова не даёт E1001 — узел builtin, ошибка L2 E1206 (resolveSailNames).
4
+ *
5
+ * | Код | Источник | Тест(ы) |
6
+ * |-------|----------------------------------|---------|
7
+ * | E1001 | parse-source.js, lexer.js, index.js (не string) | l1-diagnostics (тело), l1-words (:Foo), contract (null input), **ниже** (строка, `/`, скобки импорта); чистый лексер — [l0-lex-diagnostics-matrix.test.js](l0-lex-diagnostics-matrix.test.js) |
8
+ * | E1002 | lexer — незакрытый `--` | l0-lex.test.js, l0-lex-diagnostics-matrix (nextToken) |
9
+ * | E1003 | parse-source — путь импорта | l1-import.test.js |
10
+ * | E1004 | parse-source — unknown top-level | l1-top-level.test.js |
11
+ * | E1005 | parse-source — `->` в quotation | l1-diagnostics.test.js |
12
+ * | E1006 | parse-source — эффект вне сигнатуры | l1-diagnostics.test.js |
13
+ * | E1007 | parse-source — эффект слева от `->` | l1-diagnostics.test.js |
14
+ */
15
+ import test from 'brittle'
16
+ import { expectReject } from './helpers.js'
17
+
18
+ test('L1 E1001: незакрытая строковая кавычка (лексер)', function (t) {
19
+ expectReject(t, '@w ( -> )\n\n "unclosed', 'E1001', '"')
20
+ expectReject(t, "@w ( -> )\n\n 'x", 'E1001', "'")
21
+ t.end()
22
+ })
23
+
24
+ test('L1 E1001: одинокий `/` в теле — не regexp и не /word', function (t) {
25
+ expectReject(t, '@w ( -> )\n\n /', 'E1001', '/')
26
+ t.end()
27
+ })
28
+
29
+ test('L1 E1001: скобочный импорт без закрывающей `)`', function (t) {
30
+ expectReject(t, '+Lib ( @x', 'E1001', 'EOF')
31
+ t.end()
32
+ })
@@ -0,0 +1,47 @@
1
+ /**
2
+ * L1 Parse — RFC-0.1 §12.2 (source_file, top_level), §12.1 (markers + & @).
3
+ */
4
+ import test from 'brittle'
5
+ import { expectAccept, expectReject } from './helpers.js'
6
+
7
+ test('L1: empty / whitespace-only module', function (t) {
8
+ expectAccept(t, '')
9
+ expectAccept(t, '\n')
10
+ t.end()
11
+ })
12
+
13
+ test('L1: top_level comment only', function (t) {
14
+ expectAccept(t, '-- only comment --')
15
+ t.end()
16
+ })
17
+
18
+ test('L1: sequence import, type, word', function (t) {
19
+ const src = [
20
+ '+Lib ./lib.sail',
21
+ '&T',
22
+ '-- type T --',
23
+ '| None',
24
+ '',
25
+ '@main ( -> )',
26
+ '',
27
+ ' /hello'
28
+ ].join('\n')
29
+ expectAccept(t, src)
30
+ t.end()
31
+ })
32
+
33
+ test('L1: unknown top-level starter → E1004 (RFC-0.1 §13)', function (t) {
34
+ expectReject(
35
+ t,
36
+ 'not_a_marker',
37
+ 'E1004',
38
+ "Неизвестный блок"
39
+ )
40
+ expectReject(
41
+ t,
42
+ 'not_a_marker\n+Ok ./x.sail',
43
+ 'E1004',
44
+ "Ожидался '+', '&' или '@'"
45
+ )
46
+ t.end()
47
+ })
@@ -0,0 +1,31 @@
1
+ /**
2
+ * L1 ADT declarations — RFC-0.1 §12.2 (sum_type_decl, product_type_decl, doc_block).
3
+ */
4
+ import test from 'brittle'
5
+ import { expectAccept } from './helpers.js'
6
+
7
+ test('L1: sum type with tags and doc blocks', function (t) {
8
+ const src = [
9
+ '&Maybe a',
10
+ '-- optional value --',
11
+ '| None',
12
+ '-- no payload --',
13
+ '| Some a',
14
+ '-- one slot --'
15
+ ].join('\n')
16
+ expectAccept(t, src)
17
+ t.end()
18
+ })
19
+
20
+ test('L1: product type with fields', function (t) {
21
+ const src = [
22
+ '&Point',
23
+ '-- 2d point --',
24
+ ':x Num',
25
+ '-- x --',
26
+ ':y Num',
27
+ '-- y --'
28
+ ].join('\n')
29
+ expectAccept(t, src)
30
+ t.end()
31
+ })
@@ -0,0 +1,49 @@
1
+ /**
2
+ * L1 word_decl, word_step — RFC-0.1 §12.2, §12.3 (named quotation sig sugar, SlotName).
3
+ */
4
+ import test from 'brittle'
5
+ import { expectAccept, expectReject } from './helpers.js'
6
+
7
+ test('L1: word with empty body', function (t) {
8
+ expectAccept(t, '@nop ( -> )\n\n')
9
+ t.end()
10
+ })
11
+
12
+ test('L1: word body — literals, calls, builtin, list, quotation, slots', function (t) {
13
+ const src = [
14
+ '@demo ( -> )',
15
+ '',
16
+ ' "hi"',
17
+ ' 42',
18
+ ' true',
19
+ ' nil',
20
+ ' /swap',
21
+ ' dup',
22
+ ' [ 1 2 ]',
23
+ ' ( /drop )',
24
+ ' :acc',
25
+ ' ;acc'
26
+ ].join('\n')
27
+ expectAccept(t, src)
28
+ t.end()
29
+ })
30
+
31
+ test('L1: named quotation parameter in signature (§12.3 note 2)', function (t) {
32
+ const src = [
33
+ '@withJust ( just: ( ~s -> ~s ) -> )',
34
+ '',
35
+ ' /id'
36
+ ].join('\n')
37
+ expectAccept(t, src)
38
+ t.end()
39
+ })
40
+
41
+ test('L1: slot name must start with lowercase — :Foo → E1001 (§12.3 note 22)', function (t) {
42
+ expectReject(
43
+ t,
44
+ '@bad ( -> )\n\n :Foo',
45
+ 'E1001',
46
+ 'неожиданный токен'
47
+ )
48
+ t.end()
49
+ })
@@ -0,0 +1,67 @@
1
+ /**
2
+ * L2: шаблоны диагностик E11xx / E12xx — RFC-0.1 §13 (контракт до resolve).
3
+ */
4
+ import test from 'brittle'
5
+ import * as d from '../../lib/parse/diagnostics.js'
6
+
7
+ test('E1101–E1113 message templates (RFC-0.1 §13)', function (t) {
8
+ t.is(d.e1101DuplicateWord('dup'), "Слово 'dup' уже объявлено в текущем модуле.")
9
+ t.is(d.e1102DuplicateType('T'), "Тип 'T' уже объявлен в текущем модуле.")
10
+ t.is(d.e1103DuplicateSumTag('Just'), "Тег 'Just' уже использован в sum-типе этого модуля.")
11
+ t.is(
12
+ d.e1104DuplicateProductField('x', 'Point'),
13
+ "Поле 'x' уже объявлено в типе 'Point'."
14
+ )
15
+ t.is(
16
+ d.e1105AutogenNameConflict('maybe'),
17
+ "Автогенерируемое имя 'maybe' конфликтует с существующим объявлением."
18
+ )
19
+ t.is(
20
+ d.e1106DuplicateJsSailBlock('foo'),
21
+ "Повторное объявление '@sail' для 'foo' в JS-модуле."
22
+ )
23
+ t.is(
24
+ d.e1108DuplicateBracketImportName('map'),
25
+ "Повторное имя 'map' в списке скобочного импорта."
26
+ )
27
+ t.is(
28
+ d.e1109BracketImportConflicts('x'),
29
+ "Имя 'x' уже объявлено в модуле или внесено другим импортом; конфликт со скобочным импортом."
30
+ )
31
+ t.is(
32
+ d.e1110SymbolMissingInExport('missing'),
33
+ "Символ 'missing' отсутствует в экспорте модуля (для JS — в извлечённом контракте '@sail')."
34
+ )
35
+ t.is(
36
+ d.e1111DuplicateModuleImport('Lib'),
37
+ "Модуль 'Lib' уже импортирован в этом файле."
38
+ )
39
+ t.is(
40
+ d.e1112CyclicSailImports('A, B, C'),
41
+ 'Циклическая зависимость между модулями Sail (в цикле участвуют, например: A, B, C).'
42
+ )
43
+ t.ok(
44
+ d.e1113InvalidJsSailJSDoc().includes("@sail"),
45
+ 'E1113 mentions @sail'
46
+ )
47
+ t.end()
48
+ })
49
+
50
+ test('E1201–E1206 message templates (RFC-0.1 §13)', function (t) {
51
+ t.is(d.e1201UnresolvedName('nope'), "Не удалось разрешить имя 'nope'.")
52
+ t.is(
53
+ d.e1202AmbiguousName('foo'),
54
+ "Неоднозначная ссылка 'foo' в текущем модуле."
55
+ )
56
+ t.is(d.e1203ModuleNotFound('Net'), "Модуль 'Net' не найден.")
57
+ t.is(
58
+ d.e1204MissingMember('Lib', 'z'),
59
+ "В модуле 'Lib' отсутствует 'z'."
60
+ )
61
+ t.is(
62
+ d.e1205ImportNeedsQualification('fetch', 'Net'),
63
+ "Обращение к 'fetch' из модуля 'Net' требует '~Net/fetch' (если символ не внесён скобочным импортом, §4.2)."
64
+ )
65
+ t.is(d.e1206UnknownBuiltin('xyz'), "Неизвестное встроенное слово 'xyz'.")
66
+ t.end()
67
+ })
@@ -0,0 +1,66 @@
1
+ /**
2
+ * L3: шаблоны диагностик E1301–E1314 — RFC-0.1 §13 (контракт сообщений typecheck).
3
+ */
4
+ import test from 'brittle'
5
+ import * as d from '../../lib/parse/diagnostics.js'
6
+
7
+ test('E1301–E1314 message templates (RFC-0.1 §13)', function (t) {
8
+ t.is(d.e1301WordSignature('bad'), "Некорректная сигнатура слова 'bad'.")
9
+ t.is(
10
+ d.e1302QuotationSig('oops'),
11
+ "Некорректная сигнатура quotation: 'oops'."
12
+ )
13
+ t.is(
14
+ d.e1303StackEffect('1 слот', '2 слота'),
15
+ 'Несогласованный стековый эффект: expected 1 слот, got 2 слота.'
16
+ )
17
+ t.is(
18
+ d.e1304TypeMismatch('Num', 'Str'),
19
+ 'Несовместимые типы: expected Num, got Str.'
20
+ )
21
+ t.is(
22
+ d.e1304UnknownTypeInSignature('Foo'),
23
+ "Неизвестное имя типа 'Foo' в сигнатуре."
24
+ )
25
+ t.is(
26
+ d.e1305OccursCheck('a'),
27
+ "Бесконечный тип недопустим: переменная 'a' входит сама в себя."
28
+ )
29
+ t.is(
30
+ d.e1306CallNeedsQuote('Num'),
31
+ 'Нельзя применить CALL: ожидается Quote (I -> O), получено Num.'
32
+ )
33
+ t.is(
34
+ d.e1307EliminatorBranches(),
35
+ 'Ветки eliminator-а несовместимы: выходные сигнатуры quotations (в т.ч. ~b) не унифицируются.'
36
+ )
37
+ t.is(
38
+ d.e1308EliminatorQuotes('maybe'),
39
+ "Некорректные аргументы eliminator-а 'maybe': quotations должны идти в порядке объявления тегов/полей."
40
+ )
41
+ t.is(
42
+ d.e1309ContainerContract('List', 'T', 'Str'),
43
+ "Нарушение ограничений контейнера 'List': expected T, got Str."
44
+ )
45
+ t.is(
46
+ d.e1311ParameterizedTypeNeedsParens('Maybe'),
47
+ "В сигнатуре параметризованный тип 'Maybe' должен быть записан в скобках как единый слот (§4.4)."
48
+ )
49
+ t.is(
50
+ d.e1310AsyncCall('run', 'main'),
51
+ "Вызов 'run' с эффектом '+Async' недопустим из слова 'main' без '+Async' или без снятия '-Async' обёрткой (§5.7)."
52
+ )
53
+ t.is(
54
+ d.e1312FailCall('risky', 'main'),
55
+ "Вызов 'risky' с эффектом '+Fail' недопустим из 'main' без '+Fail' или без снятия '-Fail' обёрткой (§5.7)."
56
+ )
57
+ t.is(
58
+ d.e1313DuplicateSlotWrite('x'),
59
+ "Повторная запись слота ':x' в теле слова (§6.1.6)."
60
+ )
61
+ t.is(
62
+ d.e1314SlotReadBeforeWrite('y'),
63
+ "Чтение слота ';y' до соответствующей записи ':y' (§6.1.6)."
64
+ )
65
+ t.end()
66
+ })
@@ -0,0 +1,83 @@
1
+ /**
2
+ * L3 этап 2: объявления ADT — module_type_ref в type_expr (пробел L2), параметры типа, adtByPath.
3
+ */
4
+ import test from 'brittle'
5
+ import path from 'node:path'
6
+ import { typecheckSail } from '../../index.js'
7
+
8
+ const root = path.resolve('/virtual/sail-l3-adt2')
9
+ function vfs (files) {
10
+ const norm = Object.fromEntries(
11
+ Object.entries(files).map(([k, v]) => [path.normalize(k), v])
12
+ )
13
+ return (p) => norm[path.normalize(p)] ?? null
14
+ }
15
+
16
+ test('L3 ADT: ~Lib/Ghost в payload sum — E1204 (тип не экспортирован)', function (t) {
17
+ const main = path.join(root, 'main.sail')
18
+ const lib = path.join(root, 'lib.sail')
19
+ const files = {
20
+ [main]: [
21
+ '+Lib ./lib.sail',
22
+ '&Foo',
23
+ ' |Bar ~Lib/Ghost',
24
+ '@main ( -> )',
25
+ ''
26
+ ].join('\n'),
27
+ [lib]: [
28
+ '&Point',
29
+ ' :x Num',
30
+ ' :y Num'
31
+ ].join('\n')
32
+ }
33
+ const r = typecheckSail({ entryPath: main, readFile: vfs(files) })
34
+ t.ok(r.ok === false)
35
+ const d = r.diagnostics.find((x) => x.code === 'E1204')
36
+ t.ok(d && d.message.includes('Ghost'), 'E1204 Ghost')
37
+ t.end()
38
+ })
39
+
40
+ test('L3 ADT: свободный type_var вне заголовка — E1304', function (t) {
41
+ const main = path.join(root, 'only.sail')
42
+ const src = [
43
+ '&Maybe a',
44
+ ' |Just b',
45
+ ' |Nothing',
46
+ '@main ( -> )',
47
+ ''
48
+ ].join('\n')
49
+ const r = typecheckSail({ entryPath: main, readFile: vfs({ [main]: src }) })
50
+ t.ok(r.ok === false)
51
+ const d = r.diagnostics.find((x) => x.code === 'E1304')
52
+ t.ok(d, 'E1304')
53
+ t.ok(d.message.includes('b') || d.message.includes(`'b'`), 'mentions b')
54
+ t.end()
55
+ })
56
+
57
+ test('L3 ADT: корректный ~Lib/Point в payload и adtByPath', function (t) {
58
+ const main = path.join(root, 'ok-main.sail')
59
+ const lib = path.join(root, 'ok-lib.sail')
60
+ const files = {
61
+ [main]: [
62
+ '+Lib ./ok-lib.sail',
63
+ '&Wrap',
64
+ ' |W ~Lib/Point',
65
+ ' |Empty',
66
+ '@main ( -> )',
67
+ ''
68
+ ].join('\n'),
69
+ [lib]: [
70
+ '&Point',
71
+ ' :x Num',
72
+ ' :y Num'
73
+ ].join('\n')
74
+ }
75
+ const r = typecheckSail({ entryPath: main, readFile: vfs(files) })
76
+ t.ok(r.ok, r.diagnostics.map((d) => d.code + ' ' + d.message).join('; '))
77
+ const adt = r.env?.adtByPath?.get(main)?.get('Wrap')
78
+ t.ok(adt, 'adt Wrap')
79
+ t.is(adt.kind, 'sum')
80
+ t.is(adt.name, 'Wrap')
81
+ t.ok(Array.isArray(adt.typeParams))
82
+ t.end()
83
+ })