freelang-v4 4.3.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.
- package/README.md +548 -0
- package/dist/ast.d.ts +367 -0
- package/dist/ast.js +4 -0
- package/dist/ast.js.map +1 -0
- package/dist/async-basic.test.d.ts +1 -0
- package/dist/async-basic.test.js +88 -0
- package/dist/async-basic.test.js.map +1 -0
- package/dist/async-jest.test.d.ts +1 -0
- package/dist/async-jest.test.js +99 -0
- package/dist/async-jest.test.js.map +1 -0
- package/dist/channel-jest.test.d.ts +1 -0
- package/dist/channel-jest.test.js +148 -0
- package/dist/channel-jest.test.js.map +1 -0
- package/dist/checker-jest.test.d.ts +1 -0
- package/dist/checker-jest.test.js +160 -0
- package/dist/checker-jest.test.js.map +1 -0
- package/dist/checker.d.ts +149 -0
- package/dist/checker.js +1565 -0
- package/dist/checker.js.map +1 -0
- package/dist/checker.test.d.ts +1 -0
- package/dist/checker.test.js +217 -0
- package/dist/checker.test.js.map +1 -0
- package/dist/compiler-jest.test.d.ts +1 -0
- package/dist/compiler-jest.test.js +233 -0
- package/dist/compiler-jest.test.js.map +1 -0
- package/dist/compiler.d.ts +127 -0
- package/dist/compiler.js +1588 -0
- package/dist/compiler.js.map +1 -0
- package/dist/compiler.test.d.ts +1 -0
- package/dist/compiler.test.js +313 -0
- package/dist/compiler.test.js.map +1 -0
- package/dist/db-100m-full.d.ts +5 -0
- package/dist/db-100m-full.js +78 -0
- package/dist/db-100m-full.js.map +1 -0
- package/dist/db-100m-no-index.d.ts +12 -0
- package/dist/db-100m-no-index.js +119 -0
- package/dist/db-100m-no-index.js.map +1 -0
- package/dist/db-100m-real.d.ts +5 -0
- package/dist/db-100m-real.js +131 -0
- package/dist/db-100m-real.js.map +1 -0
- package/dist/db-100m-streaming.d.ts +15 -0
- package/dist/db-100m-streaming.js +164 -0
- package/dist/db-100m-streaming.js.map +1 -0
- package/dist/db-100m-test.d.ts +5 -0
- package/dist/db-100m-test.js +111 -0
- package/dist/db-100m-test.js.map +1 -0
- package/dist/db-jest.test.d.ts +1 -0
- package/dist/db-jest.test.js +182 -0
- package/dist/db-jest.test.js.map +1 -0
- package/dist/db-runtime.d.ts +24 -0
- package/dist/db-runtime.js +204 -0
- package/dist/db-runtime.js.map +1 -0
- package/dist/db.d.ts +249 -0
- package/dist/db.js +593 -0
- package/dist/db.js.map +1 -0
- package/dist/file-io-jest.test.d.ts +1 -0
- package/dist/file-io-jest.test.js +225 -0
- package/dist/file-io-jest.test.js.map +1 -0
- package/dist/for-of-jest.test.d.ts +1 -0
- package/dist/for-of-jest.test.js +230 -0
- package/dist/for-of-jest.test.js.map +1 -0
- package/dist/for-of.test.d.ts +1 -0
- package/dist/for-of.test.js +305 -0
- package/dist/for-of.test.js.map +1 -0
- package/dist/function-literal-jest.test.d.ts +1 -0
- package/dist/function-literal-jest.test.js +180 -0
- package/dist/function-literal-jest.test.js.map +1 -0
- package/dist/function-literal.test.d.ts +1 -0
- package/dist/function-literal.test.js +245 -0
- package/dist/function-literal.test.js.map +1 -0
- package/dist/generics-jest.test.d.ts +1 -0
- package/dist/generics-jest.test.js +93 -0
- package/dist/generics-jest.test.js.map +1 -0
- package/dist/ir-gen.d.ts +15 -0
- package/dist/ir-gen.js +400 -0
- package/dist/ir-gen.js.map +1 -0
- package/dist/ir.d.ts +114 -0
- package/dist/ir.js +5 -0
- package/dist/ir.js.map +1 -0
- package/dist/lexer.d.ts +110 -0
- package/dist/lexer.js +467 -0
- package/dist/lexer.js.map +1 -0
- package/dist/lexer.test.d.ts +1 -0
- package/dist/lexer.test.js +426 -0
- package/dist/lexer.test.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +241 -0
- package/dist/main.js.map +1 -0
- package/dist/module-jest.test.d.ts +1 -0
- package/dist/module-jest.test.js +123 -0
- package/dist/module-jest.test.js.map +1 -0
- package/dist/parser.d.ts +56 -0
- package/dist/parser.js +1060 -0
- package/dist/parser.js.map +1 -0
- package/dist/parser.test.d.ts +1 -0
- package/dist/parser.test.js +461 -0
- package/dist/parser.test.js.map +1 -0
- package/dist/pattern-matching-jest.test.d.ts +1 -0
- package/dist/pattern-matching-jest.test.js +158 -0
- package/dist/pattern-matching-jest.test.js.map +1 -0
- package/dist/pkg/init.d.ts +1 -0
- package/dist/pkg/init.js +118 -0
- package/dist/pkg/init.js.map +1 -0
- package/dist/pkg/install.d.ts +1 -0
- package/dist/pkg/install.js +77 -0
- package/dist/pkg/install.js.map +1 -0
- package/dist/pkg/registry.d.ts +23 -0
- package/dist/pkg/registry.js +106 -0
- package/dist/pkg/registry.js.map +1 -0
- package/dist/pkg/run.d.ts +1 -0
- package/dist/pkg/run.js +76 -0
- package/dist/pkg/run.js.map +1 -0
- package/dist/pkg/toml.d.ts +5 -0
- package/dist/pkg/toml.js +117 -0
- package/dist/pkg/toml.js.map +1 -0
- package/dist/repl.d.ts +15 -0
- package/dist/repl.js +197 -0
- package/dist/repl.js.map +1 -0
- package/dist/runtime/bytecode.d.ts +92 -0
- package/dist/runtime/bytecode.js +253 -0
- package/dist/runtime/bytecode.js.map +1 -0
- package/dist/runtime/value.d.ts +102 -0
- package/dist/runtime/value.js +302 -0
- package/dist/runtime/value.js.map +1 -0
- package/dist/runtime/vm.d.ts +65 -0
- package/dist/runtime/vm.js +293 -0
- package/dist/runtime/vm.js.map +1 -0
- package/dist/struct-instance-jest.test.d.ts +1 -0
- package/dist/struct-instance-jest.test.js +209 -0
- package/dist/struct-instance-jest.test.js.map +1 -0
- package/dist/struct-instance.test.d.ts +1 -0
- package/dist/struct-instance.test.js +291 -0
- package/dist/struct-instance.test.js.map +1 -0
- package/dist/struct-jest.test.d.ts +1 -0
- package/dist/struct-jest.test.js +176 -0
- package/dist/struct-jest.test.js.map +1 -0
- package/dist/struct.test.d.ts +1 -0
- package/dist/struct.test.js +231 -0
- package/dist/struct.test.js.map +1 -0
- package/dist/trait-jest.test.d.ts +1 -0
- package/dist/trait-jest.test.js +120 -0
- package/dist/trait-jest.test.js.map +1 -0
- package/dist/vm-jest.test.d.ts +1 -0
- package/dist/vm-jest.test.js +569 -0
- package/dist/vm-jest.test.js.map +1 -0
- package/dist/vm.d.ts +81 -0
- package/dist/vm.js +1956 -0
- package/dist/vm.js.map +1 -0
- package/dist/vm.test.d.ts +1 -0
- package/dist/vm.test.js +337 -0
- package/dist/vm.test.js.map +1 -0
- package/dist/web-repl/sandbox.d.ts +11 -0
- package/dist/web-repl/sandbox.js +76 -0
- package/dist/web-repl/sandbox.js.map +1 -0
- package/dist/web-repl/server.d.ts +1 -0
- package/dist/web-repl/server.js +111 -0
- package/dist/web-repl/server.js.map +1 -0
- package/dist/while-loop-jest.test.d.ts +1 -0
- package/dist/while-loop-jest.test.js +201 -0
- package/dist/while-loop-jest.test.js.map +1 -0
- package/dist/while-loop.test.d.ts +1 -0
- package/dist/while-loop.test.js +262 -0
- package/dist/while-loop.test.js.map +1 -0
- package/docs/EXPERIENCE.md +787 -0
- package/docs/README.md +175 -0
- package/docs/V1_V2_V3_ANALYSIS.md +107 -0
- package/docs/_config.yml +36 -0
- package/docs/api-reference.md +459 -0
- package/docs/architecture.md +470 -0
- package/docs/benchmarks.md +295 -0
- package/docs/comparison.md +454 -0
- package/docs/index.md +335 -0
- package/docs/language-completeness.md +228 -0
- package/docs/learning-guide.md +651 -0
- package/package.json +65 -0
- package/src/api/deploy_key.fl +294 -0
- package/src/api/issue.fl +302 -0
- package/src/api/org.fl +356 -0
- package/src/api/repo.fl +394 -0
- package/src/api/team.fl +299 -0
- package/src/api/user.fl +385 -0
- package/src/api/webhook.fl +273 -0
- package/src/ast.ts +158 -0
- package/src/async-basic.test.ts +94 -0
- package/src/async-jest.test.ts +107 -0
- package/src/channel-jest.test.ts +158 -0
- package/src/checker-jest.test.ts +189 -0
- package/src/checker.test.ts +279 -0
- package/src/checker.ts +1861 -0
- package/src/commands/analyze.fl +227 -0
- package/src/commands/auth.fl +315 -0
- package/src/commands/batch.fl +349 -0
- package/src/commands/config.fl +199 -0
- package/src/commands/deploy_key.fl +352 -0
- package/src/commands/issue.fl +275 -0
- package/src/commands/main.fl +492 -0
- package/src/commands/org.fl +425 -0
- package/src/commands/repo.fl +581 -0
- package/src/commands/team.fl +244 -0
- package/src/commands/user.fl +423 -0
- package/src/commands/webhook.fl +400 -0
- package/src/compiler-jest.test.ts +275 -0
- package/src/compiler.test.ts +375 -0
- package/src/compiler.ts +1770 -0
- package/src/config.fl +175 -0
- package/src/core/batch.fl +355 -0
- package/src/core/cache.fl +284 -0
- package/src/core/ensure.fl +324 -0
- package/src/db-100m-full.ts +96 -0
- package/src/db-100m-no-index.ts +133 -0
- package/src/db-100m-real.ts +152 -0
- package/src/db-100m-streaming.ts +154 -0
- package/src/db-100m-test.ts +136 -0
- package/src/db-jest.test.ts +161 -0
- package/src/db-runtime.ts +242 -0
- package/src/db.ts +676 -0
- package/src/errors.fl +134 -0
- package/src/for-of-jest.test.ts +246 -0
- package/src/for-of.test.ts +308 -0
- package/src/function-literal-jest.test.ts +193 -0
- package/src/function-literal.test.ts +248 -0
- package/src/generics-jest.test.ts +104 -0
- package/src/http/client.fl +327 -0
- package/src/ir-gen.ts +459 -0
- package/src/ir.ts +80 -0
- package/src/lexer.test.ts +499 -0
- package/src/lexer.ts +522 -0
- package/src/main.ts +223 -0
- package/src/models.fl +162 -0
- package/src/module-jest.test.ts +145 -0
- package/src/parser.test.ts +542 -0
- package/src/parser.ts +1211 -0
- package/src/pattern-matching-jest.test.ts +170 -0
- package/src/pkg/init.ts +91 -0
- package/src/pkg/install.ts +56 -0
- package/src/pkg/registry.ts +103 -0
- package/src/pkg/run.ts +49 -0
- package/src/pkg/toml.ts +129 -0
- package/src/repl.ts +190 -0
- package/src/runtime/bytecode.ts +291 -0
- package/src/runtime/value.ts +322 -0
- package/src/runtime/vm.ts +354 -0
- package/src/self-host/bootstrap.fl +68 -0
- package/src/self-host/interpreter.fl +361 -0
- package/src/self-host/lexer-simple.fl +22 -0
- package/src/self-host/lexer.fl +305 -0
- package/src/self-host/parser.fl +580 -0
- package/src/struct-instance-jest.test.ts +221 -0
- package/src/struct-instance.test.ts +293 -0
- package/src/struct-jest.test.ts +187 -0
- package/src/struct.test.ts +234 -0
- package/src/trait-jest.test.ts +136 -0
- package/src/vm-jest.test.ts +754 -0
- package/src/vm.ts +1976 -0
- package/src/web-repl/public/index.html +50 -0
- package/src/web-repl/public/main.js +105 -0
- package/src/web-repl/public/style.css +225 -0
- package/src/web-repl/sandbox.ts +88 -0
- package/src/web-repl/server.ts +97 -0
- package/src/while-loop-jest.test.ts +218 -0
- package/src/while-loop.test.ts +267 -0
package/dist/checker.js
ADDED
|
@@ -0,0 +1,1565 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// FreeLang v4 — TypeChecker (SPEC_06, 07, 08 구현)
|
|
3
|
+
// 정적 타입 검사 + Move/Copy 추적 + 스코프 관리
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.TypeChecker = void 0;
|
|
6
|
+
// ============================================================
|
|
7
|
+
// 스코프 (SPEC_08: 3종 — global, function, block)
|
|
8
|
+
// ============================================================
|
|
9
|
+
class Scope {
|
|
10
|
+
constructor(parent) {
|
|
11
|
+
this.vars = new Map();
|
|
12
|
+
this.parent = parent;
|
|
13
|
+
}
|
|
14
|
+
define(name, info) {
|
|
15
|
+
this.vars.set(name, info);
|
|
16
|
+
}
|
|
17
|
+
lookup(name) {
|
|
18
|
+
const v = this.vars.get(name);
|
|
19
|
+
if (v)
|
|
20
|
+
return v;
|
|
21
|
+
if (this.parent)
|
|
22
|
+
return this.parent.lookup(name);
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// ============================================================
|
|
27
|
+
// Copy vs Move (SPEC_07 Q2)
|
|
28
|
+
// ============================================================
|
|
29
|
+
function isCopyType(t) {
|
|
30
|
+
switch (t.kind) {
|
|
31
|
+
case "i32":
|
|
32
|
+
case "i64":
|
|
33
|
+
case "f64":
|
|
34
|
+
case "bool":
|
|
35
|
+
case "string": // string은 immutable이므로 Copy (SPEC_06 Q8)
|
|
36
|
+
case "array": // array는 자동 복사본 전달 (SPEC_09: Copy-on-Pass)
|
|
37
|
+
return true;
|
|
38
|
+
case "option":
|
|
39
|
+
return isCopyType(t.element);
|
|
40
|
+
case "result":
|
|
41
|
+
return isCopyType(t.ok) && isCopyType(t.err);
|
|
42
|
+
case "promise":
|
|
43
|
+
return false; // Promise는 Move 타입
|
|
44
|
+
case "struct":
|
|
45
|
+
return true; // struct도 자동 복사본 전달 (SPEC_09: Copy-on-Pass)
|
|
46
|
+
case "channel":
|
|
47
|
+
case "fn":
|
|
48
|
+
return false; // Move 타입 (채널, 함수는 Move)
|
|
49
|
+
case "type_param":
|
|
50
|
+
return true; // Generic 타입 파라미터는 Copy로 취급 (Type Erasure)
|
|
51
|
+
case "generic_ref":
|
|
52
|
+
return true; // Generic 타입도 기본적으로 Copy
|
|
53
|
+
case "trait":
|
|
54
|
+
return true; // Trait은 기본적으로 Copy
|
|
55
|
+
default:
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// ============================================================
|
|
60
|
+
// 타입 동등 비교 (구조적 — SPEC_06 Q7)
|
|
61
|
+
// ============================================================
|
|
62
|
+
function typesEqual(a, b) {
|
|
63
|
+
// type_param은 항상 compatible (Type Erasure 전략)
|
|
64
|
+
if (a.kind === "type_param" || b.kind === "type_param")
|
|
65
|
+
return true;
|
|
66
|
+
if (a.kind !== b.kind)
|
|
67
|
+
return false;
|
|
68
|
+
switch (a.kind) {
|
|
69
|
+
case "i32":
|
|
70
|
+
case "i64":
|
|
71
|
+
case "f64":
|
|
72
|
+
case "bool":
|
|
73
|
+
case "string":
|
|
74
|
+
case "void":
|
|
75
|
+
case "unknown":
|
|
76
|
+
return true;
|
|
77
|
+
case "array":
|
|
78
|
+
return typesEqual(a.element, b.element);
|
|
79
|
+
case "channel":
|
|
80
|
+
return typesEqual(a.element, b.element);
|
|
81
|
+
case "option":
|
|
82
|
+
return typesEqual(a.element, b.element);
|
|
83
|
+
case "result":
|
|
84
|
+
return typesEqual(a.ok, b.ok) && typesEqual(a.err, b.err);
|
|
85
|
+
case "promise":
|
|
86
|
+
return typesEqual(a.element, b.element);
|
|
87
|
+
case "struct": {
|
|
88
|
+
const bStruct = b;
|
|
89
|
+
if (a.fields.size !== bStruct.fields.size)
|
|
90
|
+
return false;
|
|
91
|
+
for (const [k, v] of a.fields) {
|
|
92
|
+
const bv = bStruct.fields.get(k);
|
|
93
|
+
if (!bv || !typesEqual(v, bv))
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
}
|
|
98
|
+
case "fn": {
|
|
99
|
+
const bFn = b;
|
|
100
|
+
if (a.params.length !== bFn.params.length)
|
|
101
|
+
return false;
|
|
102
|
+
for (let i = 0; i < a.params.length; i++) {
|
|
103
|
+
if (!typesEqual(a.params[i], bFn.params[i]))
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
return typesEqual(a.returnType, bFn.returnType);
|
|
107
|
+
}
|
|
108
|
+
case "generic_ref": {
|
|
109
|
+
const bGen = b;
|
|
110
|
+
if (a.name !== bGen.name)
|
|
111
|
+
return false;
|
|
112
|
+
if (a.typeArgs.length !== bGen.typeArgs.length)
|
|
113
|
+
return false;
|
|
114
|
+
for (let i = 0; i < a.typeArgs.length; i++) {
|
|
115
|
+
if (!typesEqual(a.typeArgs[i], bGen.typeArgs[i]))
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
case "trait": {
|
|
121
|
+
const bTrait = b;
|
|
122
|
+
return a.name === bTrait.name;
|
|
123
|
+
}
|
|
124
|
+
default:
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function typeToString(t) {
|
|
129
|
+
switch (t.kind) {
|
|
130
|
+
case "i32":
|
|
131
|
+
case "i64":
|
|
132
|
+
case "f64":
|
|
133
|
+
case "bool":
|
|
134
|
+
case "string":
|
|
135
|
+
case "void":
|
|
136
|
+
return t.kind;
|
|
137
|
+
case "array": return `[${typeToString(t.element)}]`;
|
|
138
|
+
case "channel": return `channel<${typeToString(t.element)}>`;
|
|
139
|
+
case "option": return `Option<${typeToString(t.element)}>`;
|
|
140
|
+
case "result": return `Result<${typeToString(t.ok)}, ${typeToString(t.err)}>`;
|
|
141
|
+
case "promise": return `Promise<${typeToString(t.element)}>`;
|
|
142
|
+
case "struct": {
|
|
143
|
+
const fields = [...t.fields.entries()].map(([k, v]) => `${k}: ${typeToString(v)}`).join(", ");
|
|
144
|
+
return `{ ${fields} }`;
|
|
145
|
+
}
|
|
146
|
+
case "fn": {
|
|
147
|
+
const paramStr = t.params.map(typeToString).join(", ");
|
|
148
|
+
return `fn(${paramStr}) -> ${typeToString(t.returnType)}`;
|
|
149
|
+
}
|
|
150
|
+
case "type_param":
|
|
151
|
+
return t.name;
|
|
152
|
+
case "generic_ref": {
|
|
153
|
+
const typeArgStr = t.typeArgs.map(typeToString).join(", ");
|
|
154
|
+
return `${t.name}<${typeArgStr}>`;
|
|
155
|
+
}
|
|
156
|
+
case "trait":
|
|
157
|
+
return `trait ${t.name}`;
|
|
158
|
+
case "unknown": return "unknown";
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
// ============================================================
|
|
162
|
+
// 제네릭 타입 치환 — substituteType
|
|
163
|
+
// ============================================================
|
|
164
|
+
function substituteType(t, bindings) {
|
|
165
|
+
switch (t.kind) {
|
|
166
|
+
case "type_param": {
|
|
167
|
+
return bindings.get(t.name) ?? t;
|
|
168
|
+
}
|
|
169
|
+
case "array": {
|
|
170
|
+
return { kind: "array", element: substituteType(t.element, bindings) };
|
|
171
|
+
}
|
|
172
|
+
case "channel": {
|
|
173
|
+
return { kind: "channel", element: substituteType(t.element, bindings) };
|
|
174
|
+
}
|
|
175
|
+
case "option": {
|
|
176
|
+
return { kind: "option", element: substituteType(t.element, bindings) };
|
|
177
|
+
}
|
|
178
|
+
case "result": {
|
|
179
|
+
return { kind: "result", ok: substituteType(t.ok, bindings), err: substituteType(t.err, bindings) };
|
|
180
|
+
}
|
|
181
|
+
case "promise": {
|
|
182
|
+
return { kind: "promise", element: substituteType(t.element, bindings) };
|
|
183
|
+
}
|
|
184
|
+
case "struct": {
|
|
185
|
+
const fields = new Map();
|
|
186
|
+
for (const [k, v] of t.fields) {
|
|
187
|
+
fields.set(k, substituteType(v, bindings));
|
|
188
|
+
}
|
|
189
|
+
return { kind: "struct", fields };
|
|
190
|
+
}
|
|
191
|
+
case "fn": {
|
|
192
|
+
return {
|
|
193
|
+
kind: "fn",
|
|
194
|
+
params: t.params.map(p => substituteType(p, bindings)),
|
|
195
|
+
returnType: substituteType(t.returnType, bindings),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
case "generic_ref": {
|
|
199
|
+
const typeArgs = t.typeArgs.map(ta => substituteType(ta, bindings));
|
|
200
|
+
return { kind: "generic_ref", name: t.name, typeArgs };
|
|
201
|
+
}
|
|
202
|
+
case "trait":
|
|
203
|
+
return t; // Trait types don't have substitutable parts yet
|
|
204
|
+
default:
|
|
205
|
+
return t;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function annotationToType(a, structDefs = new Map(), typeEnv = new Map()) {
|
|
209
|
+
switch (a.kind) {
|
|
210
|
+
case "i32": return { kind: "i32" };
|
|
211
|
+
case "i64": return { kind: "i64" };
|
|
212
|
+
case "f64": return { kind: "f64" };
|
|
213
|
+
case "bool": return { kind: "bool" };
|
|
214
|
+
case "string": return { kind: "string" };
|
|
215
|
+
case "void": return { kind: "void" };
|
|
216
|
+
case "array": return { kind: "array", element: annotationToType(a.element, structDefs, typeEnv) };
|
|
217
|
+
case "channel": return { kind: "channel", element: annotationToType(a.element, structDefs, typeEnv) };
|
|
218
|
+
case "option": return { kind: "option", element: annotationToType(a.element, structDefs, typeEnv) };
|
|
219
|
+
case "result": return { kind: "result", ok: annotationToType(a.ok, structDefs, typeEnv), err: annotationToType(a.err, structDefs, typeEnv) };
|
|
220
|
+
case "promise": return { kind: "promise", element: annotationToType(a.element, structDefs, typeEnv) };
|
|
221
|
+
case "struct_ref": {
|
|
222
|
+
const structType = structDefs.get(a.name);
|
|
223
|
+
return structType || { kind: "unknown" };
|
|
224
|
+
}
|
|
225
|
+
case "fn": {
|
|
226
|
+
const params = a.params.map(p => annotationToType(p, structDefs, typeEnv));
|
|
227
|
+
const returnType = annotationToType(a.returnType, structDefs, typeEnv);
|
|
228
|
+
return { kind: "fn", params, returnType };
|
|
229
|
+
}
|
|
230
|
+
case "type_param": {
|
|
231
|
+
// typeEnv에서 치환된 타입 찾기, 없으면 type_param 유지
|
|
232
|
+
return typeEnv.get(a.name) ?? { kind: "type_param", name: a.name };
|
|
233
|
+
}
|
|
234
|
+
case "generic_ref": {
|
|
235
|
+
const typeArgs = a.typeArgs.map(arg => annotationToType(arg, structDefs, typeEnv));
|
|
236
|
+
return { kind: "generic_ref", name: a.name, typeArgs };
|
|
237
|
+
}
|
|
238
|
+
case "trait_ref":
|
|
239
|
+
return { kind: "unknown" }; // Trait reference not yet fully supported
|
|
240
|
+
case "self_type":
|
|
241
|
+
return { kind: "unknown" }; // Self type not yet fully supported
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// ============================================================
|
|
245
|
+
// TypeChecker
|
|
246
|
+
// ============================================================
|
|
247
|
+
class TypeChecker {
|
|
248
|
+
constructor() {
|
|
249
|
+
this.errors = [];
|
|
250
|
+
this.functions = new Map();
|
|
251
|
+
this.structs = new Map(); // struct 정의 저장소
|
|
252
|
+
this.traits = new Map(); // trait 정의 저장소 (trait_decl)
|
|
253
|
+
this.impls = []; // impl 정의 저장소
|
|
254
|
+
this.genericFunctions = new Map();
|
|
255
|
+
this.genericStructs = new Map();
|
|
256
|
+
this.instantiatedFunctions = new Map(); // 인스턴스화된 함수
|
|
257
|
+
this.instantiatedStructs = new Map(); // 인스턴스화된 구조체
|
|
258
|
+
this.currentReturnType = null;
|
|
259
|
+
this.scope = new Scope(null); // global scope
|
|
260
|
+
}
|
|
261
|
+
check(program) {
|
|
262
|
+
// Builtin 함수 등록
|
|
263
|
+
this.registerBuiltinFunctions();
|
|
264
|
+
// Pass 1: trait과 struct 정의 등록
|
|
265
|
+
for (const stmt of program.stmts) {
|
|
266
|
+
if (stmt.kind === "struct_decl") {
|
|
267
|
+
this.registerStruct(stmt);
|
|
268
|
+
}
|
|
269
|
+
if (stmt.kind === "trait_decl") {
|
|
270
|
+
this.registerTrait(stmt);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// Pass 2: impl 정의 등록
|
|
274
|
+
for (const stmt of program.stmts) {
|
|
275
|
+
if (stmt.kind === "impl_decl") {
|
|
276
|
+
this.registerImpl(stmt);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Pass 3: 함수 전방참조 등록 (SPEC_08 Q5)
|
|
280
|
+
for (const stmt of program.stmts) {
|
|
281
|
+
if (stmt.kind === "fn_decl") {
|
|
282
|
+
this.registerFunction(stmt);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Pass 4: 본문 검사
|
|
286
|
+
for (const stmt of program.stmts) {
|
|
287
|
+
this.checkStmt(stmt);
|
|
288
|
+
}
|
|
289
|
+
return this.errors;
|
|
290
|
+
}
|
|
291
|
+
registerFunction(stmt) {
|
|
292
|
+
const params = stmt.params.map((p) => ({
|
|
293
|
+
name: p.name,
|
|
294
|
+
type: annotationToType(p.type, this.structs),
|
|
295
|
+
}));
|
|
296
|
+
let returnType = annotationToType(stmt.returnType, this.structs);
|
|
297
|
+
// async fn인 경우 반환 타입을 Promise<T>로 자동 변환
|
|
298
|
+
if (stmt.isAsync) {
|
|
299
|
+
returnType = { kind: "promise", element: returnType };
|
|
300
|
+
}
|
|
301
|
+
if (this.functions.has(stmt.name)) {
|
|
302
|
+
this.error(`function '${stmt.name}' already declared`, stmt.line, stmt.col);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
// Generic 함수인 경우 genericFunctions에 등록
|
|
306
|
+
if (stmt.typeParams.length > 0) {
|
|
307
|
+
this.genericFunctions.set(stmt.name, { typeParams: stmt.typeParams, params, returnType });
|
|
308
|
+
}
|
|
309
|
+
else {
|
|
310
|
+
this.functions.set(stmt.name, { params, returnType });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
registerBuiltinFunctions() {
|
|
314
|
+
// Some<T>(value: T) -> Option<T>
|
|
315
|
+
// 실제로는 제네릭 함수이지만, 여기서는 unknown 파라미터로 처리
|
|
316
|
+
this.functions.set("Some", {
|
|
317
|
+
params: [{ name: "value", type: { kind: "unknown" } }],
|
|
318
|
+
returnType: { kind: "option", element: { kind: "unknown" } }
|
|
319
|
+
});
|
|
320
|
+
// None는 값이므로 함수로 등록하지 않음 (상수처럼 취급)
|
|
321
|
+
// Ok<T>(value: T) -> Result<T, E>
|
|
322
|
+
this.functions.set("Ok", {
|
|
323
|
+
params: [{ name: "value", type: { kind: "unknown" } }],
|
|
324
|
+
returnType: { kind: "result", ok: { kind: "unknown" }, err: { kind: "unknown" } }
|
|
325
|
+
});
|
|
326
|
+
// Err<E>(error: E) -> Result<T, E>
|
|
327
|
+
this.functions.set("Err", {
|
|
328
|
+
params: [{ name: "error", type: { kind: "unknown" } }],
|
|
329
|
+
returnType: { kind: "result", ok: { kind: "unknown" }, err: { kind: "unknown" } }
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
registerStruct(stmt) {
|
|
333
|
+
const fields = new Map();
|
|
334
|
+
for (const field of stmt.fields) {
|
|
335
|
+
const fieldType = annotationToType(field.type, this.structs);
|
|
336
|
+
fields.set(field.name, fieldType);
|
|
337
|
+
}
|
|
338
|
+
if (this.structs.has(stmt.name)) {
|
|
339
|
+
this.error(`struct '${stmt.name}' already declared`, stmt.line, stmt.col);
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
// Generic 구조체인 경우 genericStructs에 등록
|
|
343
|
+
if (stmt.typeParams.length > 0) {
|
|
344
|
+
this.genericStructs.set(stmt.name, { typeParams: stmt.typeParams, fields });
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
this.structs.set(stmt.name, { kind: "struct", fields });
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
registerTrait(stmt) {
|
|
351
|
+
const methods = new Map();
|
|
352
|
+
for (const method of stmt.methods) {
|
|
353
|
+
const params = method.params.map(p => annotationToType(p.type, this.structs));
|
|
354
|
+
const returnType = annotationToType(method.returnType, this.structs);
|
|
355
|
+
methods.set(method.name, { params, returnType });
|
|
356
|
+
}
|
|
357
|
+
if (this.traits.has(stmt.name)) {
|
|
358
|
+
this.error(`trait '${stmt.name}' already declared`, stmt.line, stmt.col);
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
this.traits.set(stmt.name, {
|
|
362
|
+
kind: "trait",
|
|
363
|
+
name: stmt.name,
|
|
364
|
+
methods,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
registerImpl(stmt) {
|
|
368
|
+
// forType을 문자열로 간단히 저장 (간소화)
|
|
369
|
+
let forTypeName = "unknown";
|
|
370
|
+
if (stmt.forType.kind === "struct_ref") {
|
|
371
|
+
forTypeName = stmt.forType.name;
|
|
372
|
+
}
|
|
373
|
+
const methods = new Map();
|
|
374
|
+
for (const method of stmt.methods) {
|
|
375
|
+
const params = method.params.map(p => annotationToType(p.type, this.structs));
|
|
376
|
+
const returnType = annotationToType(method.returnType, this.structs);
|
|
377
|
+
methods.set(method.name, { params, returnType });
|
|
378
|
+
}
|
|
379
|
+
// impl 저장
|
|
380
|
+
this.impls.push({
|
|
381
|
+
trait: stmt.trait,
|
|
382
|
+
forType: forTypeName,
|
|
383
|
+
methods,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
// ============================================================
|
|
387
|
+
// 문 검사
|
|
388
|
+
// ============================================================
|
|
389
|
+
checkStmt(stmt) {
|
|
390
|
+
switch (stmt.kind) {
|
|
391
|
+
case "var_decl":
|
|
392
|
+
return this.checkVarDecl(stmt);
|
|
393
|
+
case "fn_decl":
|
|
394
|
+
return this.checkFnDecl(stmt);
|
|
395
|
+
case "struct_decl":
|
|
396
|
+
return; // struct는 Pass 1에서 이미 등록됨
|
|
397
|
+
case "trait_decl":
|
|
398
|
+
return; // trait은 Pass 1에서 이미 등록됨
|
|
399
|
+
case "impl_decl":
|
|
400
|
+
return; // impl은 Pass 2에서 이미 등록됨
|
|
401
|
+
case "if_stmt":
|
|
402
|
+
return this.checkIfStmt(stmt);
|
|
403
|
+
case "match_stmt":
|
|
404
|
+
return this.checkMatchStmt(stmt);
|
|
405
|
+
case "for_stmt":
|
|
406
|
+
return this.checkForStmt(stmt);
|
|
407
|
+
case "for_of_stmt":
|
|
408
|
+
return this.checkForOfStmt(stmt);
|
|
409
|
+
case "while_stmt":
|
|
410
|
+
return this.checkWhileStmt(stmt);
|
|
411
|
+
case "break_stmt":
|
|
412
|
+
return this.checkBreakStmt(stmt);
|
|
413
|
+
case "continue_stmt":
|
|
414
|
+
return this.checkContinueStmt(stmt);
|
|
415
|
+
case "spawn_stmt":
|
|
416
|
+
return this.checkSpawnStmt(stmt);
|
|
417
|
+
case "return_stmt":
|
|
418
|
+
return this.checkReturnStmt(stmt);
|
|
419
|
+
case "expr_stmt":
|
|
420
|
+
return this.checkExprStmt(stmt);
|
|
421
|
+
case "import_decl":
|
|
422
|
+
return this.checkImportDecl(stmt);
|
|
423
|
+
case "export_decl":
|
|
424
|
+
return this.checkExportDecl(stmt);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
checkVarDecl(stmt) {
|
|
428
|
+
const initType = this.checkExpr(stmt.init);
|
|
429
|
+
let declType;
|
|
430
|
+
if (stmt.type) {
|
|
431
|
+
declType = annotationToType(stmt.type, this.structs);
|
|
432
|
+
if (!typesEqual(declType, initType) && initType.kind !== "unknown") {
|
|
433
|
+
this.error(`type mismatch: declared ${typeToString(declType)}, got ${typeToString(initType)}`, stmt.line, stmt.col);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
else {
|
|
437
|
+
// 타입 추론 (SPEC_06 Q3)
|
|
438
|
+
declType = initType;
|
|
439
|
+
}
|
|
440
|
+
// void 변수 금지 (SPEC_06 Q9)
|
|
441
|
+
if (declType.kind === "void") {
|
|
442
|
+
this.error("cannot declare variable of type void", stmt.line, stmt.col);
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
// 스코프에 등록
|
|
446
|
+
if (this.scope.vars.has(stmt.name)) {
|
|
447
|
+
// 섀도잉 허용 (SPEC_08 Q4) — 같은 스코프에서도 재선언 가능
|
|
448
|
+
}
|
|
449
|
+
this.scope.define(stmt.name, {
|
|
450
|
+
type: declType,
|
|
451
|
+
mutable: stmt.mutable,
|
|
452
|
+
moved: false,
|
|
453
|
+
line: stmt.line,
|
|
454
|
+
col: stmt.col,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
checkFnDecl(stmt) {
|
|
458
|
+
const fnInfo = this.functions.get(stmt.name);
|
|
459
|
+
if (!fnInfo)
|
|
460
|
+
return;
|
|
461
|
+
// 새 스코프
|
|
462
|
+
const prevScope = this.scope;
|
|
463
|
+
this.scope = new Scope(prevScope);
|
|
464
|
+
const prevReturn = this.currentReturnType;
|
|
465
|
+
this.currentReturnType = fnInfo.returnType;
|
|
466
|
+
// 매개변수 등록
|
|
467
|
+
for (const p of fnInfo.params) {
|
|
468
|
+
this.scope.define(p.name, {
|
|
469
|
+
type: p.type,
|
|
470
|
+
mutable: false, // 매개변수는 immutable (SPEC_08)
|
|
471
|
+
moved: false,
|
|
472
|
+
line: stmt.line,
|
|
473
|
+
col: stmt.col,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
// 본문 검사
|
|
477
|
+
for (const s of stmt.body) {
|
|
478
|
+
this.checkStmt(s);
|
|
479
|
+
}
|
|
480
|
+
this.currentReturnType = prevReturn;
|
|
481
|
+
this.scope = prevScope;
|
|
482
|
+
}
|
|
483
|
+
checkIfStmt(stmt) {
|
|
484
|
+
const condType = this.checkExpr(stmt.condition);
|
|
485
|
+
if (condType.kind !== "bool" && condType.kind !== "unknown") {
|
|
486
|
+
this.error(`if condition must be bool, got ${typeToString(condType)}`, stmt.line, stmt.col);
|
|
487
|
+
}
|
|
488
|
+
// then 블록
|
|
489
|
+
const prevScope = this.scope;
|
|
490
|
+
this.scope = new Scope(prevScope);
|
|
491
|
+
for (const s of stmt.then)
|
|
492
|
+
this.checkStmt(s);
|
|
493
|
+
this.scope = prevScope;
|
|
494
|
+
// else 블록
|
|
495
|
+
if (stmt.else_) {
|
|
496
|
+
const prevScope2 = this.scope;
|
|
497
|
+
this.scope = new Scope(prevScope2);
|
|
498
|
+
for (const s of stmt.else_)
|
|
499
|
+
this.checkStmt(s);
|
|
500
|
+
this.scope = prevScope2;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
checkMatchStmt(stmt) {
|
|
504
|
+
const subjectType = this.checkExpr(stmt.subject);
|
|
505
|
+
for (const arm of stmt.arms) {
|
|
506
|
+
this.checkMatchArm(arm, subjectType);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
checkMatchArm(arm, subjectType) {
|
|
510
|
+
const prevScope = this.scope;
|
|
511
|
+
this.scope = new Scope(prevScope);
|
|
512
|
+
this.checkPattern(arm.pattern, subjectType);
|
|
513
|
+
// Guard 절 검증: bool 타입이어야 함
|
|
514
|
+
if (arm.guard) {
|
|
515
|
+
const guardType = this.checkExpr(arm.guard);
|
|
516
|
+
if (guardType.kind !== "bool" && guardType.kind !== "unknown") {
|
|
517
|
+
this.error(`guard condition must be bool, got ${typeToString(guardType)}`, 0, 0);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
this.checkExpr(arm.body);
|
|
521
|
+
this.scope = prevScope;
|
|
522
|
+
}
|
|
523
|
+
checkPattern(pattern, expectedType) {
|
|
524
|
+
switch (pattern.kind) {
|
|
525
|
+
case "ident":
|
|
526
|
+
// 바인딩 — 새 변수 생성
|
|
527
|
+
this.scope.define(pattern.name, {
|
|
528
|
+
type: expectedType,
|
|
529
|
+
mutable: false,
|
|
530
|
+
moved: false,
|
|
531
|
+
line: 0, col: 0,
|
|
532
|
+
});
|
|
533
|
+
break;
|
|
534
|
+
case "wildcard":
|
|
535
|
+
break;
|
|
536
|
+
case "none":
|
|
537
|
+
if (expectedType.kind !== "option" && expectedType.kind !== "unknown") {
|
|
538
|
+
this.error(`None pattern on non-Option type ${typeToString(expectedType)}`, 0, 0);
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case "some":
|
|
542
|
+
if (expectedType.kind === "option") {
|
|
543
|
+
this.checkPattern(pattern.inner, expectedType.element);
|
|
544
|
+
}
|
|
545
|
+
else if (expectedType.kind !== "unknown") {
|
|
546
|
+
this.error(`Some pattern on non-Option type ${typeToString(expectedType)}`, 0, 0);
|
|
547
|
+
}
|
|
548
|
+
break;
|
|
549
|
+
case "ok":
|
|
550
|
+
if (expectedType.kind === "result") {
|
|
551
|
+
this.checkPattern(pattern.inner, expectedType.ok);
|
|
552
|
+
}
|
|
553
|
+
else if (expectedType.kind !== "unknown") {
|
|
554
|
+
this.error(`Ok pattern on non-Result type ${typeToString(expectedType)}`, 0, 0);
|
|
555
|
+
}
|
|
556
|
+
break;
|
|
557
|
+
case "err":
|
|
558
|
+
if (expectedType.kind === "result") {
|
|
559
|
+
this.checkPattern(pattern.inner, expectedType.err);
|
|
560
|
+
}
|
|
561
|
+
else if (expectedType.kind !== "unknown") {
|
|
562
|
+
this.error(`Err pattern on non-Result type ${typeToString(expectedType)}`, 0, 0);
|
|
563
|
+
}
|
|
564
|
+
break;
|
|
565
|
+
case "literal":
|
|
566
|
+
// 리터럴 타입은 checkExpr에서 확인
|
|
567
|
+
this.checkExpr(pattern.value);
|
|
568
|
+
break;
|
|
569
|
+
case "struct":
|
|
570
|
+
// 구조체 분해 패턴: Point { x, y }
|
|
571
|
+
if (expectedType.kind !== "struct" && expectedType.kind !== "unknown") {
|
|
572
|
+
this.error(`struct pattern on non-struct type ${typeToString(expectedType)}`, 0, 0);
|
|
573
|
+
}
|
|
574
|
+
if (expectedType.kind === "struct") {
|
|
575
|
+
for (const field of pattern.fields) {
|
|
576
|
+
const fieldType = expectedType.fields.get(field.name);
|
|
577
|
+
if (!fieldType) {
|
|
578
|
+
this.error(`struct ${pattern.name} has no field ${field.name}`, 0, 0);
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
this.checkPattern(field.pattern, fieldType);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
break;
|
|
586
|
+
case "array":
|
|
587
|
+
// 배열 분해 패턴: [a, b, c], [x, .., y]
|
|
588
|
+
if (expectedType.kind !== "array" && expectedType.kind !== "unknown") {
|
|
589
|
+
this.error(`array pattern on non-array type ${typeToString(expectedType)}`, 0, 0);
|
|
590
|
+
}
|
|
591
|
+
if (expectedType.kind === "array") {
|
|
592
|
+
const elementType = expectedType.element;
|
|
593
|
+
for (const element of pattern.elements) {
|
|
594
|
+
this.checkPattern(element, elementType);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
break;
|
|
598
|
+
case "tuple":
|
|
599
|
+
// 튜플 분해 패턴: (a, b, c)
|
|
600
|
+
if (expectedType.kind !== "unknown") {
|
|
601
|
+
this.error(`tuple patterns not yet supported`, 0, 0);
|
|
602
|
+
}
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
checkForStmt(stmt) {
|
|
607
|
+
const iterType = this.checkExpr(stmt.iterable);
|
|
608
|
+
// iterable은 array여야 함
|
|
609
|
+
let elemType = { kind: "unknown" };
|
|
610
|
+
if (iterType.kind === "array") {
|
|
611
|
+
elemType = iterType.element;
|
|
612
|
+
}
|
|
613
|
+
else if (iterType.kind !== "unknown") {
|
|
614
|
+
this.error(`for...in requires array, got ${typeToString(iterType)}`, stmt.line, stmt.col);
|
|
615
|
+
}
|
|
616
|
+
// 루프 스코프
|
|
617
|
+
const prevScope = this.scope;
|
|
618
|
+
this.scope = new Scope(prevScope);
|
|
619
|
+
// 루프 변수 (immutable — SPEC_08 Q6)
|
|
620
|
+
this.scope.define(stmt.variable, {
|
|
621
|
+
type: elemType,
|
|
622
|
+
mutable: false,
|
|
623
|
+
moved: false,
|
|
624
|
+
line: stmt.line,
|
|
625
|
+
col: stmt.col,
|
|
626
|
+
});
|
|
627
|
+
for (const s of stmt.body)
|
|
628
|
+
this.checkStmt(s);
|
|
629
|
+
this.scope = prevScope;
|
|
630
|
+
}
|
|
631
|
+
checkForOfStmt(stmt) {
|
|
632
|
+
const iterType = this.checkExpr(stmt.iterable);
|
|
633
|
+
// iterable은 array 또는 string이어야 함
|
|
634
|
+
let elemType = { kind: "unknown" };
|
|
635
|
+
if (iterType.kind === "array") {
|
|
636
|
+
elemType = iterType.element;
|
|
637
|
+
}
|
|
638
|
+
else if (iterType.kind === "string") {
|
|
639
|
+
// 문자열을 순회하면 각 요소는 string (한 글자)
|
|
640
|
+
elemType = { kind: "string" };
|
|
641
|
+
}
|
|
642
|
+
else if (iterType.kind !== "unknown") {
|
|
643
|
+
this.error(`for...of requires array or string, got ${typeToString(iterType)}`, stmt.line, stmt.col);
|
|
644
|
+
}
|
|
645
|
+
// 루프 스코프
|
|
646
|
+
const prevScope = this.scope;
|
|
647
|
+
this.scope = new Scope(prevScope);
|
|
648
|
+
// 루프 변수 (immutable — SPEC_08 Q6)
|
|
649
|
+
this.scope.define(stmt.variable, {
|
|
650
|
+
type: elemType,
|
|
651
|
+
mutable: false,
|
|
652
|
+
moved: false,
|
|
653
|
+
line: stmt.line,
|
|
654
|
+
col: stmt.col,
|
|
655
|
+
});
|
|
656
|
+
for (const s of stmt.body)
|
|
657
|
+
this.checkStmt(s);
|
|
658
|
+
this.scope = prevScope;
|
|
659
|
+
}
|
|
660
|
+
checkWhileStmt(stmt) {
|
|
661
|
+
const condType = this.checkExpr(stmt.condition);
|
|
662
|
+
// while 조건은 bool이어야 함
|
|
663
|
+
if (condType.kind !== "bool" && condType.kind !== "unknown") {
|
|
664
|
+
this.error(`while condition must be bool, got ${typeToString(condType)}`, stmt.line, stmt.col);
|
|
665
|
+
}
|
|
666
|
+
// 루프 스코프
|
|
667
|
+
const prevScope = this.scope;
|
|
668
|
+
this.scope = new Scope(prevScope);
|
|
669
|
+
for (const s of stmt.body)
|
|
670
|
+
this.checkStmt(s);
|
|
671
|
+
this.scope = prevScope;
|
|
672
|
+
}
|
|
673
|
+
checkBreakStmt(stmt) {
|
|
674
|
+
// break는 루프 내에서만 사용 가능 (현재는 미지원)
|
|
675
|
+
// 나중에 구현할 수 있음
|
|
676
|
+
}
|
|
677
|
+
checkContinueStmt(stmt) {
|
|
678
|
+
// continue는 루프 내에서만 사용 가능 (현재는 미지원)
|
|
679
|
+
// 나중에 구현할 수 있음
|
|
680
|
+
}
|
|
681
|
+
checkSpawnStmt(stmt) {
|
|
682
|
+
// spawn은 새로운 스코프 (외부 변수 접근 가능)
|
|
683
|
+
const prevScope = this.scope;
|
|
684
|
+
this.scope = new Scope(prevScope); // 부모 스코프 유지
|
|
685
|
+
for (const s of stmt.body)
|
|
686
|
+
this.checkStmt(s);
|
|
687
|
+
this.scope = prevScope;
|
|
688
|
+
}
|
|
689
|
+
checkReturnStmt(stmt) {
|
|
690
|
+
if (!this.currentReturnType) {
|
|
691
|
+
this.error("return outside function", stmt.line, stmt.col);
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
if (stmt.value) {
|
|
695
|
+
const valType = this.checkExpr(stmt.value);
|
|
696
|
+
if (!typesEqual(this.currentReturnType, valType) && valType.kind !== "unknown") {
|
|
697
|
+
this.error(`return type mismatch: expected ${typeToString(this.currentReturnType)}, got ${typeToString(valType)}`, stmt.line, stmt.col);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// return; (void)
|
|
702
|
+
if (this.currentReturnType.kind !== "void") {
|
|
703
|
+
this.error(`return without value in non-void function (expected ${typeToString(this.currentReturnType)})`, stmt.line, stmt.col);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
checkExprStmt(stmt) {
|
|
708
|
+
this.checkExpr(stmt.expr);
|
|
709
|
+
}
|
|
710
|
+
checkImportDecl(decl) {
|
|
711
|
+
// 기본 import 검증: 현재는 모듈 로드 없이 항목만 추적
|
|
712
|
+
for (const item of decl.items) {
|
|
713
|
+
const name = item.alias || item.name;
|
|
714
|
+
// import된 항목을 스코프에 등록 (타입은 unknown)
|
|
715
|
+
this.scope.define(name, {
|
|
716
|
+
type: { kind: "unknown" },
|
|
717
|
+
mutable: false,
|
|
718
|
+
moved: false,
|
|
719
|
+
line: decl.line,
|
|
720
|
+
col: decl.col,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
checkExportDecl(decl) {
|
|
725
|
+
// 기본 export 검증: 현재는 항목 추적만 수행
|
|
726
|
+
if (typeof decl.target !== "string") {
|
|
727
|
+
// export fn/struct: 이미 checkStmt에서 처리됨
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
// export { ... }의 경우 항목 이름들이 실제로 정의되어 있는지는
|
|
731
|
+
// 향후 더 정교한 검증이 필요
|
|
732
|
+
}
|
|
733
|
+
// ============================================================
|
|
734
|
+
// 식 검사 — 타입 반환
|
|
735
|
+
// ============================================================
|
|
736
|
+
checkExpr(expr) {
|
|
737
|
+
switch (expr.kind) {
|
|
738
|
+
case "int_lit": return { kind: "i32" };
|
|
739
|
+
case "float_lit": return { kind: "f64" };
|
|
740
|
+
case "string_lit": return { kind: "string" };
|
|
741
|
+
case "bool_lit": return { kind: "bool" };
|
|
742
|
+
case "ident":
|
|
743
|
+
return this.checkIdent(expr);
|
|
744
|
+
case "binary":
|
|
745
|
+
return this.checkBinary(expr);
|
|
746
|
+
case "unary":
|
|
747
|
+
return this.checkUnary(expr);
|
|
748
|
+
case "await":
|
|
749
|
+
return this.checkAwait(expr);
|
|
750
|
+
case "call":
|
|
751
|
+
return this.checkCall(expr);
|
|
752
|
+
case "index":
|
|
753
|
+
return this.checkIndex(expr);
|
|
754
|
+
case "field_access":
|
|
755
|
+
return this.checkFieldAccess(expr);
|
|
756
|
+
case "assign":
|
|
757
|
+
return this.checkAssign(expr);
|
|
758
|
+
case "try":
|
|
759
|
+
return this.checkTry(expr);
|
|
760
|
+
case "if_expr":
|
|
761
|
+
return this.checkIfExpr(expr);
|
|
762
|
+
case "match_expr":
|
|
763
|
+
return this.checkMatchExpr(expr);
|
|
764
|
+
case "array_lit":
|
|
765
|
+
return this.checkArrayLit(expr);
|
|
766
|
+
case "struct_lit":
|
|
767
|
+
return this.checkStructLit(expr);
|
|
768
|
+
case "fn_lit":
|
|
769
|
+
return this.checkFnLit(expr);
|
|
770
|
+
case "block_expr":
|
|
771
|
+
return this.checkBlockExpr(expr);
|
|
772
|
+
case "chan_new":
|
|
773
|
+
return this.checkChanNew(expr);
|
|
774
|
+
case "chan_send":
|
|
775
|
+
return this.checkChanSend(expr);
|
|
776
|
+
case "chan_recv":
|
|
777
|
+
return this.checkChanRecv(expr);
|
|
778
|
+
default:
|
|
779
|
+
return { kind: "unknown" };
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
checkIdent(expr) {
|
|
783
|
+
const info = this.scope.lookup(expr.name);
|
|
784
|
+
if (!info) {
|
|
785
|
+
// 내장 함수 확인
|
|
786
|
+
if (this.isBuiltin(expr.name))
|
|
787
|
+
return { kind: "unknown" };
|
|
788
|
+
// 함수 이름 확인
|
|
789
|
+
if (this.functions.has(expr.name))
|
|
790
|
+
return { kind: "unknown" };
|
|
791
|
+
this.error(`undefined variable: '${expr.name}'`, expr.line, expr.col);
|
|
792
|
+
return { kind: "unknown" };
|
|
793
|
+
}
|
|
794
|
+
// Move 검사 (SPEC_07 Q4)
|
|
795
|
+
if (info.moved) {
|
|
796
|
+
this.error(`use of moved value: '${expr.name}'`, expr.line, expr.col);
|
|
797
|
+
return info.type;
|
|
798
|
+
}
|
|
799
|
+
return info.type;
|
|
800
|
+
}
|
|
801
|
+
checkBinary(expr) {
|
|
802
|
+
const left = this.checkExpr(expr.left);
|
|
803
|
+
const right = this.checkExpr(expr.right);
|
|
804
|
+
// 비교 연산자 → bool (타입 강제 변환 지원)
|
|
805
|
+
if (["==", "!=", "<", ">", "<=", ">="].includes(expr.op)) {
|
|
806
|
+
if (!typesEqual(left, right) && left.kind !== "unknown" && right.kind !== "unknown") {
|
|
807
|
+
// numeric 타입은 강제 변환 허용
|
|
808
|
+
const coerced = this.coerceNumeric(left, right);
|
|
809
|
+
if (!coerced) {
|
|
810
|
+
this.error(`cannot compare ${typeToString(left)} and ${typeToString(right)}`, expr.line, expr.col);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
return { kind: "bool" };
|
|
814
|
+
}
|
|
815
|
+
// 논리 연산자 → bool
|
|
816
|
+
if (expr.op === "&&" || expr.op === "||") {
|
|
817
|
+
if (left.kind !== "bool" && left.kind !== "unknown") {
|
|
818
|
+
this.error(`'${expr.op}' requires bool, got ${typeToString(left)}`, expr.line, expr.col);
|
|
819
|
+
}
|
|
820
|
+
if (right.kind !== "bool" && right.kind !== "unknown") {
|
|
821
|
+
this.error(`'${expr.op}' requires bool, got ${typeToString(right)}`, expr.line, expr.col);
|
|
822
|
+
}
|
|
823
|
+
return { kind: "bool" };
|
|
824
|
+
}
|
|
825
|
+
// 산술 연산자: + 는 문자열 연결도 가능
|
|
826
|
+
if (expr.op === "+") {
|
|
827
|
+
if (left.kind === "string" && right.kind === "string")
|
|
828
|
+
return { kind: "string" };
|
|
829
|
+
}
|
|
830
|
+
// 산술: i32, i64, f64 (타입 강제 변환 지원)
|
|
831
|
+
if (["+", "-", "*", "/", "%"].includes(expr.op)) {
|
|
832
|
+
const numericTypes = ["i32", "i64", "f64"];
|
|
833
|
+
// 타입이 같거나 numeric 타입 강제 변환 가능
|
|
834
|
+
if (!typesEqual(left, right) && left.kind !== "unknown" && right.kind !== "unknown") {
|
|
835
|
+
const coerced = this.coerceNumeric(left, right);
|
|
836
|
+
if (!coerced) {
|
|
837
|
+
this.error(`type mismatch in '${expr.op}': ${typeToString(left)} and ${typeToString(right)}`, expr.line, expr.col);
|
|
838
|
+
return { kind: "unknown" };
|
|
839
|
+
}
|
|
840
|
+
return coerced; // 강제 변환된 타입 반환
|
|
841
|
+
}
|
|
842
|
+
// 한쪽이 unknown이면 다른 쪽 반환
|
|
843
|
+
if (left.kind === "unknown")
|
|
844
|
+
return right;
|
|
845
|
+
if (right.kind === "unknown")
|
|
846
|
+
return left;
|
|
847
|
+
return left;
|
|
848
|
+
}
|
|
849
|
+
return { kind: "unknown" };
|
|
850
|
+
}
|
|
851
|
+
checkUnary(expr) {
|
|
852
|
+
const operand = this.checkExpr(expr.operand);
|
|
853
|
+
if (expr.op === "-") {
|
|
854
|
+
if (!["i32", "i64", "f64", "unknown"].includes(operand.kind)) {
|
|
855
|
+
this.error(`unary '-' requires numeric type, got ${typeToString(operand)}`, expr.line, expr.col);
|
|
856
|
+
}
|
|
857
|
+
return operand;
|
|
858
|
+
}
|
|
859
|
+
if (expr.op === "!") {
|
|
860
|
+
if (operand.kind !== "bool" && operand.kind !== "unknown") {
|
|
861
|
+
this.error(`unary '!' requires bool, got ${typeToString(operand)}`, expr.line, expr.col);
|
|
862
|
+
}
|
|
863
|
+
return { kind: "bool" };
|
|
864
|
+
}
|
|
865
|
+
return { kind: "unknown" };
|
|
866
|
+
}
|
|
867
|
+
checkAwait(expr) {
|
|
868
|
+
const operandType = this.checkExpr(expr.expr);
|
|
869
|
+
// await는 Promise 타입에만 사용 가능
|
|
870
|
+
if (operandType.kind !== "promise" && operandType.kind !== "unknown") {
|
|
871
|
+
this.error(`await requires Promise type, got ${typeToString(operandType)}`, expr.line, expr.col);
|
|
872
|
+
return { kind: "unknown" };
|
|
873
|
+
}
|
|
874
|
+
// Promise<T>이면 T를 반환
|
|
875
|
+
if (operandType.kind === "promise") {
|
|
876
|
+
return operandType.element;
|
|
877
|
+
}
|
|
878
|
+
return { kind: "unknown" };
|
|
879
|
+
}
|
|
880
|
+
checkCall(expr) {
|
|
881
|
+
// 내장 함수 처리
|
|
882
|
+
if (expr.callee.kind === "ident") {
|
|
883
|
+
const name = expr.callee.name;
|
|
884
|
+
// 내장 함수 타입 (SPEC_10)
|
|
885
|
+
const builtinType = this.getBuiltinReturnType(name, expr.args);
|
|
886
|
+
if (builtinType) {
|
|
887
|
+
for (const arg of expr.args)
|
|
888
|
+
this.checkExpr(arg);
|
|
889
|
+
return builtinType;
|
|
890
|
+
}
|
|
891
|
+
// 사용자 함수
|
|
892
|
+
const fn = this.functions.get(name);
|
|
893
|
+
if (fn) {
|
|
894
|
+
if (expr.args.length !== fn.params.length) {
|
|
895
|
+
this.error(`'${name}' expects ${fn.params.length} arguments, got ${expr.args.length}`, expr.line, expr.col);
|
|
896
|
+
}
|
|
897
|
+
for (let i = 0; i < expr.args.length; i++) {
|
|
898
|
+
const argType = this.checkExpr(expr.args[i]);
|
|
899
|
+
if (i < fn.params.length) {
|
|
900
|
+
if (!typesEqual(argType, fn.params[i].type) && argType.kind !== "unknown") {
|
|
901
|
+
this.error(`argument ${i + 1} type mismatch: expected ${typeToString(fn.params[i].type)}, got ${typeToString(argType)}`, expr.line, expr.col);
|
|
902
|
+
}
|
|
903
|
+
// Move semantics: 인자 전달 시 Move (SPEC_07 Q4)
|
|
904
|
+
if (!isCopyType(argType) && expr.args[i].kind === "ident") {
|
|
905
|
+
const varInfo = this.scope.lookup(expr.args[i].name);
|
|
906
|
+
if (varInfo)
|
|
907
|
+
varInfo.moved = true;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
return fn.returnType;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
// 함수 타입 변수 호출 (fn 타입 값)
|
|
915
|
+
const calleeType = this.checkExpr(expr.callee);
|
|
916
|
+
if (calleeType.kind === "fn") {
|
|
917
|
+
// 함수 타입 인자 개수 검사
|
|
918
|
+
if (expr.args.length !== calleeType.params.length) {
|
|
919
|
+
this.error(`function expects ${calleeType.params.length} arguments, got ${expr.args.length}`, expr.line, expr.col);
|
|
920
|
+
}
|
|
921
|
+
// 각 인자의 타입 검사
|
|
922
|
+
for (let i = 0; i < expr.args.length; i++) {
|
|
923
|
+
const argType = this.checkExpr(expr.args[i]);
|
|
924
|
+
if (i < calleeType.params.length) {
|
|
925
|
+
if (!typesEqual(argType, calleeType.params[i]) && argType.kind !== "unknown") {
|
|
926
|
+
this.error(`argument ${i + 1} type mismatch: expected ${typeToString(calleeType.params[i])}, got ${typeToString(argType)}`, expr.line, expr.col);
|
|
927
|
+
}
|
|
928
|
+
// Move semantics for function arguments
|
|
929
|
+
if (!isCopyType(argType) && expr.args[i].kind === "ident") {
|
|
930
|
+
const varInfo = this.scope.lookup(expr.args[i].name);
|
|
931
|
+
if (varInfo)
|
|
932
|
+
varInfo.moved = true;
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
return calleeType.returnType;
|
|
937
|
+
}
|
|
938
|
+
// 메서드 호출 (field_access + call)
|
|
939
|
+
if (expr.callee.kind === "field_access") {
|
|
940
|
+
for (const arg of expr.args)
|
|
941
|
+
this.checkExpr(arg);
|
|
942
|
+
return { kind: "unknown" }; // 메서드 반환 타입은 정적으로 모름
|
|
943
|
+
}
|
|
944
|
+
// 알 수 없는 함수
|
|
945
|
+
for (const arg of expr.args)
|
|
946
|
+
this.checkExpr(arg);
|
|
947
|
+
return { kind: "unknown" };
|
|
948
|
+
}
|
|
949
|
+
checkIndex(expr) {
|
|
950
|
+
const objType = this.checkExpr(expr.object);
|
|
951
|
+
const idxType = this.checkExpr(expr.index);
|
|
952
|
+
if (idxType.kind !== "i32" && idxType.kind !== "unknown") {
|
|
953
|
+
this.error(`array index must be i32, got ${typeToString(idxType)}`, expr.line, expr.col);
|
|
954
|
+
}
|
|
955
|
+
if (objType.kind === "array")
|
|
956
|
+
return objType.element;
|
|
957
|
+
if (objType.kind === "string")
|
|
958
|
+
return { kind: "string" }; // char_at 대체
|
|
959
|
+
if (objType.kind !== "unknown") {
|
|
960
|
+
this.error(`cannot index into ${typeToString(objType)}`, expr.line, expr.col);
|
|
961
|
+
}
|
|
962
|
+
return { kind: "unknown" };
|
|
963
|
+
}
|
|
964
|
+
checkFieldAccess(expr) {
|
|
965
|
+
const objType = this.checkExpr(expr.object);
|
|
966
|
+
if (objType.kind === "struct") {
|
|
967
|
+
// 필드 접근
|
|
968
|
+
const fieldType = objType.fields.get(expr.field);
|
|
969
|
+
if (fieldType) {
|
|
970
|
+
return fieldType;
|
|
971
|
+
}
|
|
972
|
+
// 메서드 호출인지 확인
|
|
973
|
+
const structName = this.findStructName(objType);
|
|
974
|
+
if (structName) {
|
|
975
|
+
const impl = this.findImplMethod(structName, expr.field);
|
|
976
|
+
if (impl) {
|
|
977
|
+
// 메서드를 함수 타입으로 반환
|
|
978
|
+
return { kind: "fn", params: impl.params, returnType: impl.returnType };
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
this.error(`struct has no field or method '${expr.field}'`, expr.line, expr.col);
|
|
982
|
+
return { kind: "unknown" };
|
|
983
|
+
}
|
|
984
|
+
// 메서드 스타일 호출 (ch.recv 등) — unknown 반환
|
|
985
|
+
if (objType.kind !== "unknown") {
|
|
986
|
+
// 채널 메서드
|
|
987
|
+
if (objType.kind === "channel") {
|
|
988
|
+
if (expr.field === "recv" || expr.field === "send")
|
|
989
|
+
return { kind: "unknown" };
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
return { kind: "unknown" };
|
|
993
|
+
}
|
|
994
|
+
findStructName(objType) {
|
|
995
|
+
// 구조체 이름 찾기 (간소화)
|
|
996
|
+
for (const [name, sType] of this.structs) {
|
|
997
|
+
if (typesEqual(sType, objType)) {
|
|
998
|
+
return name;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
findImplMethod(structName, methodName) {
|
|
1004
|
+
// impl에서 메서드 찾기
|
|
1005
|
+
for (const impl of this.impls) {
|
|
1006
|
+
if (impl.forType === structName) {
|
|
1007
|
+
const method = impl.methods.get(methodName);
|
|
1008
|
+
if (method) {
|
|
1009
|
+
return method;
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
return null;
|
|
1014
|
+
}
|
|
1015
|
+
checkAssign(expr) {
|
|
1016
|
+
const valType = this.checkExpr(expr.value);
|
|
1017
|
+
if (expr.target.kind === "ident") {
|
|
1018
|
+
const info = this.scope.lookup(expr.target.name);
|
|
1019
|
+
if (!info) {
|
|
1020
|
+
this.error(`undefined variable: '${expr.target.name}'`, expr.line, expr.col);
|
|
1021
|
+
return { kind: "void" };
|
|
1022
|
+
}
|
|
1023
|
+
if (!info.mutable) {
|
|
1024
|
+
this.error(`cannot assign to immutable variable '${expr.target.name}'`, expr.line, expr.col);
|
|
1025
|
+
return { kind: "void" };
|
|
1026
|
+
}
|
|
1027
|
+
if (!typesEqual(info.type, valType) && valType.kind !== "unknown") {
|
|
1028
|
+
this.error(`assignment type mismatch: ${typeToString(info.type)} = ${typeToString(valType)}`, expr.line, expr.col);
|
|
1029
|
+
}
|
|
1030
|
+
// 재할당으로 Move 복구 (SPEC_07 Q7)
|
|
1031
|
+
info.moved = false;
|
|
1032
|
+
}
|
|
1033
|
+
if (expr.target.kind === "index") {
|
|
1034
|
+
this.checkExpr(expr.target);
|
|
1035
|
+
}
|
|
1036
|
+
if (expr.target.kind === "field_access") {
|
|
1037
|
+
const objType = this.checkExpr(expr.target.object);
|
|
1038
|
+
if (objType.kind === "struct") {
|
|
1039
|
+
const fieldType = objType.fields.get(expr.target.field);
|
|
1040
|
+
if (!fieldType) {
|
|
1041
|
+
this.error(`struct has no field '${expr.target.field}'`, expr.line, expr.col);
|
|
1042
|
+
return { kind: "void" };
|
|
1043
|
+
}
|
|
1044
|
+
if (!typesEqual(fieldType, valType) && valType.kind !== "unknown") {
|
|
1045
|
+
this.error(`field assignment type mismatch: expected ${typeToString(fieldType)}, got ${typeToString(valType)}`, expr.line, expr.col);
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
else if (objType.kind !== "unknown") {
|
|
1049
|
+
this.error(`cannot assign to field of ${typeToString(objType)}`, expr.line, expr.col);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return { kind: "void" };
|
|
1053
|
+
}
|
|
1054
|
+
checkTry(expr) {
|
|
1055
|
+
const operandType = this.checkExpr(expr.operand);
|
|
1056
|
+
// ? 는 Result 또는 Option에만 사용 (SPEC_09 Q6)
|
|
1057
|
+
if (operandType.kind === "result")
|
|
1058
|
+
return operandType.ok;
|
|
1059
|
+
if (operandType.kind === "option")
|
|
1060
|
+
return operandType.element;
|
|
1061
|
+
if (operandType.kind !== "unknown") {
|
|
1062
|
+
this.error(`'?' requires Result or Option, got ${typeToString(operandType)}`, expr.line, expr.col);
|
|
1063
|
+
}
|
|
1064
|
+
return { kind: "unknown" };
|
|
1065
|
+
}
|
|
1066
|
+
checkIfExpr(expr) {
|
|
1067
|
+
const condType = this.checkExpr(expr.condition);
|
|
1068
|
+
if (condType.kind !== "bool" && condType.kind !== "unknown") {
|
|
1069
|
+
this.error(`if condition must be bool, got ${typeToString(condType)}`, expr.line, expr.col);
|
|
1070
|
+
}
|
|
1071
|
+
// then/else 마지막 식의 타입이 일치해야 함 (SPEC_06)
|
|
1072
|
+
let thenType = { kind: "void" };
|
|
1073
|
+
for (const e of expr.then)
|
|
1074
|
+
thenType = this.checkExpr(e);
|
|
1075
|
+
let elseType = { kind: "void" };
|
|
1076
|
+
for (const e of expr.else_)
|
|
1077
|
+
elseType = this.checkExpr(e);
|
|
1078
|
+
if (!typesEqual(thenType, elseType) && thenType.kind !== "unknown" && elseType.kind !== "unknown") {
|
|
1079
|
+
this.error(`if expression branches have different types: ${typeToString(thenType)} vs ${typeToString(elseType)}`, expr.line, expr.col);
|
|
1080
|
+
}
|
|
1081
|
+
return thenType;
|
|
1082
|
+
}
|
|
1083
|
+
checkMatchExpr(expr) {
|
|
1084
|
+
const subjectType = this.checkExpr(expr.subject);
|
|
1085
|
+
let resultType = { kind: "unknown" };
|
|
1086
|
+
for (const arm of expr.arms) {
|
|
1087
|
+
const prevScope = this.scope;
|
|
1088
|
+
this.scope = new Scope(prevScope);
|
|
1089
|
+
this.checkPattern(arm.pattern, subjectType);
|
|
1090
|
+
const armType = this.checkExpr(arm.body);
|
|
1091
|
+
this.scope = prevScope;
|
|
1092
|
+
if (resultType.kind === "unknown") {
|
|
1093
|
+
resultType = armType;
|
|
1094
|
+
}
|
|
1095
|
+
else if (!typesEqual(resultType, armType) && armType.kind !== "unknown") {
|
|
1096
|
+
this.error(`match arms have different types: ${typeToString(resultType)} vs ${typeToString(armType)}`, expr.line, expr.col);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
return resultType;
|
|
1100
|
+
}
|
|
1101
|
+
checkArrayLit(expr) {
|
|
1102
|
+
if (expr.elements.length === 0)
|
|
1103
|
+
return { kind: "array", element: { kind: "unknown" } };
|
|
1104
|
+
const firstType = this.checkExpr(expr.elements[0]);
|
|
1105
|
+
for (let i = 1; i < expr.elements.length; i++) {
|
|
1106
|
+
const elemType = this.checkExpr(expr.elements[i]);
|
|
1107
|
+
if (!typesEqual(firstType, elemType) && elemType.kind !== "unknown") {
|
|
1108
|
+
this.error(`array element type mismatch: expected ${typeToString(firstType)}, got ${typeToString(elemType)}`, expr.line, expr.col);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
return { kind: "array", element: firstType };
|
|
1112
|
+
}
|
|
1113
|
+
checkStructLit(expr) {
|
|
1114
|
+
// struct 정의 확인
|
|
1115
|
+
const structDef = this.structs.get(expr.structName);
|
|
1116
|
+
if (!structDef || structDef.kind !== "struct") {
|
|
1117
|
+
this.error(`undefined struct: '${expr.structName}'`, expr.line, expr.col);
|
|
1118
|
+
return { kind: "unknown" };
|
|
1119
|
+
}
|
|
1120
|
+
// 필드 타입 확인
|
|
1121
|
+
const fields = new Map();
|
|
1122
|
+
for (const f of expr.fields) {
|
|
1123
|
+
const fType = this.checkExpr(f.value);
|
|
1124
|
+
const expectedType = structDef.fields.get(f.name);
|
|
1125
|
+
if (!expectedType) {
|
|
1126
|
+
this.error(`struct '${expr.structName}' has no field '${f.name}'`, expr.line, expr.col);
|
|
1127
|
+
fields.set(f.name, fType);
|
|
1128
|
+
continue;
|
|
1129
|
+
}
|
|
1130
|
+
if (!typesEqual(fType, expectedType) && fType.kind !== "unknown") {
|
|
1131
|
+
this.error(`struct field '${f.name}' type mismatch: expected ${typeToString(expectedType)}, got ${typeToString(fType)}`, expr.line, expr.col);
|
|
1132
|
+
}
|
|
1133
|
+
fields.set(f.name, expectedType);
|
|
1134
|
+
}
|
|
1135
|
+
// 모든 필드가 제공되었는지 확인
|
|
1136
|
+
for (const [fieldName, fieldType] of structDef.fields.entries()) {
|
|
1137
|
+
if (!fields.has(fieldName)) {
|
|
1138
|
+
this.error(`struct '${expr.structName}' is missing field '${fieldName}'`, expr.line, expr.col);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
return structDef;
|
|
1142
|
+
}
|
|
1143
|
+
checkFnLit(expr) {
|
|
1144
|
+
// 함수 리터럴의 매개변수 타입 확인
|
|
1145
|
+
const paramTypes = [];
|
|
1146
|
+
for (const param of expr.params) {
|
|
1147
|
+
if (param.type) {
|
|
1148
|
+
const paramType = annotationToType(param.type, this.structs);
|
|
1149
|
+
paramTypes.push(paramType);
|
|
1150
|
+
}
|
|
1151
|
+
else {
|
|
1152
|
+
// 타입 어노테이션 없으면 unknown (타입 추론 미지원)
|
|
1153
|
+
paramTypes.push({ kind: "unknown" });
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// 반환 타입 확인
|
|
1157
|
+
let returnType;
|
|
1158
|
+
if (expr.returnType) {
|
|
1159
|
+
returnType = annotationToType(expr.returnType, this.structs);
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
// 함수 본체에서 반환 타입 추론
|
|
1163
|
+
const bodyType = this.checkExpr(expr.body);
|
|
1164
|
+
returnType = bodyType;
|
|
1165
|
+
}
|
|
1166
|
+
// 함수 본체 타입 검사 (새로운 스코프에서)
|
|
1167
|
+
const prevScope = this.scope;
|
|
1168
|
+
this.scope = new Scope(prevScope);
|
|
1169
|
+
// 매개변수를 스코프에 등록
|
|
1170
|
+
for (let i = 0; i < expr.params.length; i++) {
|
|
1171
|
+
const param = expr.params[i];
|
|
1172
|
+
const paramType = paramTypes[i];
|
|
1173
|
+
this.scope.define(param.name, {
|
|
1174
|
+
type: paramType,
|
|
1175
|
+
mutable: false,
|
|
1176
|
+
moved: false,
|
|
1177
|
+
line: expr.line,
|
|
1178
|
+
col: expr.col,
|
|
1179
|
+
});
|
|
1180
|
+
}
|
|
1181
|
+
// 함수 본체 타입 검사
|
|
1182
|
+
const actualBodyType = this.checkExpr(expr.body);
|
|
1183
|
+
// 반환 타입과 일치 검사
|
|
1184
|
+
if (expr.returnType && !typesEqual(actualBodyType, returnType) && actualBodyType.kind !== "unknown") {
|
|
1185
|
+
this.error(`function body type mismatch: expected ${typeToString(returnType)}, got ${typeToString(actualBodyType)}`, expr.line, expr.col);
|
|
1186
|
+
}
|
|
1187
|
+
this.scope = prevScope;
|
|
1188
|
+
// 함수 타입 반환
|
|
1189
|
+
return { kind: "fn", params: paramTypes, returnType };
|
|
1190
|
+
}
|
|
1191
|
+
checkBlockExpr(expr) {
|
|
1192
|
+
const prevScope = this.scope;
|
|
1193
|
+
this.scope = new Scope(prevScope);
|
|
1194
|
+
for (const s of expr.stmts)
|
|
1195
|
+
this.checkStmt(s);
|
|
1196
|
+
let result = { kind: "void" };
|
|
1197
|
+
if (expr.expr)
|
|
1198
|
+
result = this.checkExpr(expr.expr);
|
|
1199
|
+
this.scope = prevScope;
|
|
1200
|
+
return result;
|
|
1201
|
+
}
|
|
1202
|
+
// ============================================================
|
|
1203
|
+
// 타입 강제 변환 (Type Coercion)
|
|
1204
|
+
// ============================================================
|
|
1205
|
+
coerceNumeric(left, right) {
|
|
1206
|
+
// 양쪽이 numeric 타입이면 강제 변환 가능
|
|
1207
|
+
const numericTypes = ["i32", "i64", "f64"];
|
|
1208
|
+
if (!numericTypes.includes(left.kind) || !numericTypes.includes(right.kind)) {
|
|
1209
|
+
return null;
|
|
1210
|
+
}
|
|
1211
|
+
// 타입 승격 규칙: f64 > i64 > i32
|
|
1212
|
+
const hierarchy = { i32: 0, i64: 1, f64: 2 };
|
|
1213
|
+
const leftRank = hierarchy[left.kind];
|
|
1214
|
+
const rightRank = hierarchy[right.kind];
|
|
1215
|
+
if (leftRank >= rightRank)
|
|
1216
|
+
return left;
|
|
1217
|
+
else
|
|
1218
|
+
return right;
|
|
1219
|
+
}
|
|
1220
|
+
// ============================================================
|
|
1221
|
+
// 내장 함수 (SPEC_10)
|
|
1222
|
+
// ============================================================
|
|
1223
|
+
isBuiltin(name) {
|
|
1224
|
+
return [
|
|
1225
|
+
"println", "print", "read_line", "read_file", "write_file",
|
|
1226
|
+
"i32", "i64", "f64", "str",
|
|
1227
|
+
"push", "pop", "slice", "clone", "length",
|
|
1228
|
+
"char_at", "char_code", "chr", "contains", "split", "trim", "to_upper", "to_lower",
|
|
1229
|
+
"abs", "min", "max", "pow", "sqrt",
|
|
1230
|
+
"range", "channel", "panic", "typeof", "assert",
|
|
1231
|
+
// Phase 7: 20 Core Libraries
|
|
1232
|
+
"md5", "sha256", "sha512", "base64_encode", "base64_decode", "hmac",
|
|
1233
|
+
"json_parse", "json_stringify", "json_validate", "json_pretty",
|
|
1234
|
+
"starts_with", "ends_with", "replace",
|
|
1235
|
+
"reverse", "sort", "unique",
|
|
1236
|
+
"gcd", "lcm",
|
|
1237
|
+
"uuid", "timestamp",
|
|
1238
|
+
"send", "recv",
|
|
1239
|
+
// Environment (1)
|
|
1240
|
+
"env",
|
|
1241
|
+
// Phase 2: HTTP Client
|
|
1242
|
+
"http_get", "http_post", "http_post_json", "fetch",
|
|
1243
|
+
// HTTP Server & External Commands (gogs-server 지원)
|
|
1244
|
+
"http_server_create", "http_server_accept", "http_server_respond", "exec_command",
|
|
1245
|
+
].includes(name);
|
|
1246
|
+
}
|
|
1247
|
+
getBuiltinReturnType(name, args) {
|
|
1248
|
+
switch (name) {
|
|
1249
|
+
// Basic I/O
|
|
1250
|
+
case "println":
|
|
1251
|
+
case "print": return { kind: "void" };
|
|
1252
|
+
case "read_line": return { kind: "string" };
|
|
1253
|
+
case "read_file": return { kind: "result", ok: { kind: "string" }, err: { kind: "string" } };
|
|
1254
|
+
case "write_file": return { kind: "result", ok: { kind: "void" }, err: { kind: "string" } };
|
|
1255
|
+
// Type conversions
|
|
1256
|
+
case "i32": return { kind: "result", ok: { kind: "i32" }, err: { kind: "string" } };
|
|
1257
|
+
case "i64": return { kind: "result", ok: { kind: "i64" }, err: { kind: "string" } };
|
|
1258
|
+
case "f64": return { kind: "result", ok: { kind: "f64" }, err: { kind: "string" } };
|
|
1259
|
+
case "str": return { kind: "string" };
|
|
1260
|
+
// Array operations
|
|
1261
|
+
case "length": return { kind: "i32" };
|
|
1262
|
+
case "push": return { kind: "void" };
|
|
1263
|
+
case "pop": return { kind: "unknown" };
|
|
1264
|
+
case "clone": return { kind: "unknown" };
|
|
1265
|
+
case "slice": return { kind: "unknown" };
|
|
1266
|
+
case "reverse":
|
|
1267
|
+
case "sort":
|
|
1268
|
+
case "unique":
|
|
1269
|
+
return { kind: "unknown" }; // 배열 타입 복제
|
|
1270
|
+
// String operations
|
|
1271
|
+
case "char_at":
|
|
1272
|
+
case "trim":
|
|
1273
|
+
case "to_upper":
|
|
1274
|
+
case "to_lower":
|
|
1275
|
+
case "chr":
|
|
1276
|
+
return { kind: "string" };
|
|
1277
|
+
case "char_code":
|
|
1278
|
+
return { kind: "i32" };
|
|
1279
|
+
case "contains":
|
|
1280
|
+
case "starts_with":
|
|
1281
|
+
case "ends_with":
|
|
1282
|
+
return { kind: "bool" };
|
|
1283
|
+
case "split": return { kind: "array", element: { kind: "string" } };
|
|
1284
|
+
case "replace": return { kind: "string" };
|
|
1285
|
+
// Range & channel
|
|
1286
|
+
case "range": return { kind: "array", element: { kind: "i32" } };
|
|
1287
|
+
case "channel": return { kind: "unknown" };
|
|
1288
|
+
case "send":
|
|
1289
|
+
case "recv": return { kind: "unknown" };
|
|
1290
|
+
// Control
|
|
1291
|
+
case "panic": return { kind: "void" };
|
|
1292
|
+
case "typeof": return { kind: "string" };
|
|
1293
|
+
case "assert": return { kind: "void" };
|
|
1294
|
+
// Math
|
|
1295
|
+
case "abs":
|
|
1296
|
+
case "min":
|
|
1297
|
+
case "max":
|
|
1298
|
+
case "pow":
|
|
1299
|
+
case "sqrt":
|
|
1300
|
+
return null; // 인자 타입에 의존
|
|
1301
|
+
case "gcd":
|
|
1302
|
+
case "lcm": return { kind: "i32" };
|
|
1303
|
+
// Bitwise Operations (A-1)
|
|
1304
|
+
case "bitand":
|
|
1305
|
+
case "bitor":
|
|
1306
|
+
case "bitxor":
|
|
1307
|
+
case "shl":
|
|
1308
|
+
case "shr":
|
|
1309
|
+
return { kind: "i32" };
|
|
1310
|
+
// Cryptography & Encoding (Phase 7)
|
|
1311
|
+
case "md5":
|
|
1312
|
+
case "sha256":
|
|
1313
|
+
case "sha512":
|
|
1314
|
+
case "hmac":
|
|
1315
|
+
return { kind: "string" };
|
|
1316
|
+
case "base64_encode": return { kind: "string" };
|
|
1317
|
+
case "base64_decode":
|
|
1318
|
+
return { kind: "result", ok: { kind: "string" }, err: { kind: "string" } };
|
|
1319
|
+
// JSON (Phase 7)
|
|
1320
|
+
case "json_parse":
|
|
1321
|
+
return { kind: "result", ok: { kind: "unknown" }, err: { kind: "string" } };
|
|
1322
|
+
case "json_stringify": return { kind: "string" };
|
|
1323
|
+
case "json_validate": return { kind: "bool" };
|
|
1324
|
+
case "json_pretty": return { kind: "string" };
|
|
1325
|
+
// Utils (Phase 7)
|
|
1326
|
+
case "uuid": return { kind: "string" };
|
|
1327
|
+
case "timestamp": return { kind: "f64" };
|
|
1328
|
+
// Environment
|
|
1329
|
+
case "env": return { kind: "string" };
|
|
1330
|
+
// HTTP Client (Phase 2)
|
|
1331
|
+
case "http_get":
|
|
1332
|
+
case "http_post":
|
|
1333
|
+
case "http_post_json":
|
|
1334
|
+
case "fetch":
|
|
1335
|
+
return { kind: "result", ok: { kind: "string" }, err: { kind: "string" } };
|
|
1336
|
+
// Database (Phase 3)
|
|
1337
|
+
case "sqlite_open":
|
|
1338
|
+
case "pg_connect":
|
|
1339
|
+
case "mysql_connect":
|
|
1340
|
+
return { kind: "unknown" }; // Returns database handle
|
|
1341
|
+
case "sqlite_query":
|
|
1342
|
+
case "pg_query":
|
|
1343
|
+
case "mysql_query":
|
|
1344
|
+
return { kind: "result", ok: { kind: "array", element: { kind: "unknown" } }, err: { kind: "string" } };
|
|
1345
|
+
case "sqlite_execute":
|
|
1346
|
+
case "pg_execute":
|
|
1347
|
+
case "mysql_execute":
|
|
1348
|
+
return { kind: "result", ok: { kind: "unknown" }, err: { kind: "string" } };
|
|
1349
|
+
case "sqlite_close":
|
|
1350
|
+
case "sqlite_begin":
|
|
1351
|
+
case "sqlite_commit":
|
|
1352
|
+
case "sqlite_rollback":
|
|
1353
|
+
case "pg_close":
|
|
1354
|
+
case "pg_begin":
|
|
1355
|
+
case "pg_commit":
|
|
1356
|
+
case "pg_rollback":
|
|
1357
|
+
case "mysql_close":
|
|
1358
|
+
case "mysql_begin":
|
|
1359
|
+
case "mysql_commit":
|
|
1360
|
+
case "mysql_rollback":
|
|
1361
|
+
return { kind: "void" };
|
|
1362
|
+
// v4.3 Extensions - Math (7) - B-1
|
|
1363
|
+
case "floor":
|
|
1364
|
+
case "ceil":
|
|
1365
|
+
case "round":
|
|
1366
|
+
return { kind: "i32" };
|
|
1367
|
+
case "random":
|
|
1368
|
+
return { kind: "f64" };
|
|
1369
|
+
case "sin":
|
|
1370
|
+
case "cos":
|
|
1371
|
+
return { kind: "f64" };
|
|
1372
|
+
case "log":
|
|
1373
|
+
return { kind: "result", ok: { kind: "f64" }, err: { kind: "string" } };
|
|
1374
|
+
// v4.3 Extensions - String (3) - B-2
|
|
1375
|
+
case "index_of":
|
|
1376
|
+
return { kind: "option", element: { kind: "i32" } };
|
|
1377
|
+
case "pad_left":
|
|
1378
|
+
case "pad_right":
|
|
1379
|
+
return { kind: "string" };
|
|
1380
|
+
// v4.3 Extensions - Regex (3) - B-3
|
|
1381
|
+
case "regex_match":
|
|
1382
|
+
return { kind: "option", element: { kind: "string" } };
|
|
1383
|
+
case "regex_find_all":
|
|
1384
|
+
return { kind: "array", element: { kind: "string" } };
|
|
1385
|
+
case "regex_replace":
|
|
1386
|
+
return { kind: "result", ok: { kind: "string" }, err: { kind: "string" } };
|
|
1387
|
+
// v4.3 Extensions - CSV (2) - B-4
|
|
1388
|
+
case "csv_parse":
|
|
1389
|
+
return { kind: "array", element: { kind: "array", element: { kind: "string" } } };
|
|
1390
|
+
case "csv_stringify":
|
|
1391
|
+
return { kind: "string" };
|
|
1392
|
+
// v4.3 Extensions - DateTime (3) - B-5
|
|
1393
|
+
case "now":
|
|
1394
|
+
return { kind: "f64" };
|
|
1395
|
+
case "format_date":
|
|
1396
|
+
return { kind: "string" };
|
|
1397
|
+
case "parse_date":
|
|
1398
|
+
return { kind: "result", ok: { kind: "f64" }, err: { kind: "string" } };
|
|
1399
|
+
// v4.3 Extensions - YAML (2)
|
|
1400
|
+
case "yaml_parse":
|
|
1401
|
+
return { kind: "result", ok: { kind: "unknown" }, err: { kind: "string" } };
|
|
1402
|
+
case "yaml_stringify":
|
|
1403
|
+
return { kind: "string" };
|
|
1404
|
+
// HTTP Server & External Commands
|
|
1405
|
+
case "http_server_create":
|
|
1406
|
+
return { kind: "result", ok: { kind: "i32" }, err: { kind: "string" } };
|
|
1407
|
+
case "http_server_accept":
|
|
1408
|
+
return { kind: "result", ok: { kind: "unknown" }, err: { kind: "string" } };
|
|
1409
|
+
case "http_server_respond":
|
|
1410
|
+
return { kind: "void" };
|
|
1411
|
+
case "exec_command":
|
|
1412
|
+
return { kind: "result", ok: { kind: "string" }, err: { kind: "string" } };
|
|
1413
|
+
default: return null;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
// ============================================================
|
|
1417
|
+
// 채널/Actor 관련 검사
|
|
1418
|
+
// ============================================================
|
|
1419
|
+
checkChanNew(expr) {
|
|
1420
|
+
const elementType = annotationToType(expr.element, this.structs);
|
|
1421
|
+
return { kind: "channel", element: elementType };
|
|
1422
|
+
}
|
|
1423
|
+
checkChanSend(expr) {
|
|
1424
|
+
const chanType = this.checkExpr(expr.chan);
|
|
1425
|
+
const valueType = this.checkExpr(expr.value);
|
|
1426
|
+
if (chanType.kind !== "channel" && chanType.kind !== "unknown") {
|
|
1427
|
+
this.error(`cannot send on non-channel type ${typeToString(chanType)}`, expr.line, expr.col);
|
|
1428
|
+
return { kind: "void" };
|
|
1429
|
+
}
|
|
1430
|
+
if (chanType.kind === "channel") {
|
|
1431
|
+
if (!typesEqual(chanType.element, valueType) && valueType.kind !== "unknown") {
|
|
1432
|
+
this.error(`channel element type mismatch: expected ${typeToString(chanType.element)}, got ${typeToString(valueType)}`, expr.line, expr.col);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
return { kind: "void" };
|
|
1436
|
+
}
|
|
1437
|
+
checkChanRecv(expr) {
|
|
1438
|
+
const chanType = this.checkExpr(expr.chan);
|
|
1439
|
+
if (chanType.kind !== "channel" && chanType.kind !== "unknown") {
|
|
1440
|
+
this.error(`cannot receive on non-channel type ${typeToString(chanType)}`, expr.line, expr.col);
|
|
1441
|
+
return { kind: "unknown" };
|
|
1442
|
+
}
|
|
1443
|
+
if (chanType.kind === "channel") {
|
|
1444
|
+
return chanType.element;
|
|
1445
|
+
}
|
|
1446
|
+
return { kind: "unknown" };
|
|
1447
|
+
}
|
|
1448
|
+
// ============================================================
|
|
1449
|
+
// 에러 헬퍼
|
|
1450
|
+
// ============================================================
|
|
1451
|
+
error(message, line, col) {
|
|
1452
|
+
this.errors.push({ message, line, col });
|
|
1453
|
+
}
|
|
1454
|
+
// ============================================================
|
|
1455
|
+
// 제네릭 관련 public 메서드
|
|
1456
|
+
// ============================================================
|
|
1457
|
+
getInstantiatedFunctions() {
|
|
1458
|
+
return this.instantiatedFunctions;
|
|
1459
|
+
}
|
|
1460
|
+
getInstantiatedStructs() {
|
|
1461
|
+
return this.instantiatedStructs;
|
|
1462
|
+
}
|
|
1463
|
+
getGenericFunctions() {
|
|
1464
|
+
return this.genericFunctions;
|
|
1465
|
+
}
|
|
1466
|
+
getGenericStructs() {
|
|
1467
|
+
return this.genericStructs;
|
|
1468
|
+
}
|
|
1469
|
+
getStructs() {
|
|
1470
|
+
return this.structs;
|
|
1471
|
+
}
|
|
1472
|
+
// ============================================================
|
|
1473
|
+
// Name mangling for generic instantiations
|
|
1474
|
+
// ============================================================
|
|
1475
|
+
typeToMangledName(t) {
|
|
1476
|
+
switch (t.kind) {
|
|
1477
|
+
case "i32": return "i32";
|
|
1478
|
+
case "i64": return "i64";
|
|
1479
|
+
case "f64": return "f64";
|
|
1480
|
+
case "bool": return "bool";
|
|
1481
|
+
case "string": return "str";
|
|
1482
|
+
case "void": return "void";
|
|
1483
|
+
case "array":
|
|
1484
|
+
return `arr_${this.typeToMangledName(t.element)}`;
|
|
1485
|
+
case "channel":
|
|
1486
|
+
return `chan_${this.typeToMangledName(t.element)}`;
|
|
1487
|
+
case "option":
|
|
1488
|
+
return `opt_${this.typeToMangledName(t.element)}`;
|
|
1489
|
+
case "result":
|
|
1490
|
+
return `res_${this.typeToMangledName(t.ok)}_${this.typeToMangledName(t.err)}`;
|
|
1491
|
+
case "struct":
|
|
1492
|
+
return `struct_${[...t.fields.keys()].join("_")}`;
|
|
1493
|
+
case "fn": {
|
|
1494
|
+
const paramNames = t.params.map(p => this.typeToMangledName(p)).join("_");
|
|
1495
|
+
const retName = this.typeToMangledName(t.returnType);
|
|
1496
|
+
return `fn_${paramNames}_${retName}`;
|
|
1497
|
+
}
|
|
1498
|
+
case "type_param":
|
|
1499
|
+
return t.name;
|
|
1500
|
+
case "generic_ref":
|
|
1501
|
+
return `gen_${t.name}`;
|
|
1502
|
+
default:
|
|
1503
|
+
return "unknown";
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
mangleFunctionName(baseName, fnType) {
|
|
1507
|
+
const argNames = fnType.params
|
|
1508
|
+
.map(p => this.typeToMangledName(p.type))
|
|
1509
|
+
.join("_");
|
|
1510
|
+
const retName = this.typeToMangledName(fnType.returnType);
|
|
1511
|
+
if (argNames) {
|
|
1512
|
+
return `${baseName}_${argNames}_${retName}`;
|
|
1513
|
+
}
|
|
1514
|
+
else {
|
|
1515
|
+
return `${baseName}_${retName}`;
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
// ============================================================
|
|
1519
|
+
// 제네릭 함수 인스턴시화
|
|
1520
|
+
// ============================================================
|
|
1521
|
+
instantiateFunction(name, typeArgs) {
|
|
1522
|
+
const genericFn = this.genericFunctions.get(name);
|
|
1523
|
+
if (!genericFn)
|
|
1524
|
+
return null;
|
|
1525
|
+
if (typeArgs.length !== genericFn.typeParams.length) {
|
|
1526
|
+
return null;
|
|
1527
|
+
}
|
|
1528
|
+
// 타입 바인딩 생성
|
|
1529
|
+
const bindings = new Map();
|
|
1530
|
+
for (let i = 0; i < genericFn.typeParams.length; i++) {
|
|
1531
|
+
bindings.set(genericFn.typeParams[i], typeArgs[i]);
|
|
1532
|
+
}
|
|
1533
|
+
// 파라미터와 반환 타입 치환
|
|
1534
|
+
const params = genericFn.params.map(p => ({
|
|
1535
|
+
name: p.name,
|
|
1536
|
+
type: substituteType(p.type, bindings),
|
|
1537
|
+
}));
|
|
1538
|
+
const returnType = substituteType(genericFn.returnType, bindings);
|
|
1539
|
+
return { params, returnType };
|
|
1540
|
+
}
|
|
1541
|
+
// ============================================================
|
|
1542
|
+
// 제네릭 구조체 인스턴시화
|
|
1543
|
+
// ============================================================
|
|
1544
|
+
instantiateStruct(name, typeArgs) {
|
|
1545
|
+
const genericStruct = this.genericStructs.get(name);
|
|
1546
|
+
if (!genericStruct)
|
|
1547
|
+
return { kind: "unknown" };
|
|
1548
|
+
if (typeArgs.length !== genericStruct.typeParams.length) {
|
|
1549
|
+
return { kind: "unknown" };
|
|
1550
|
+
}
|
|
1551
|
+
// 타입 바인딩 생성
|
|
1552
|
+
const bindings = new Map();
|
|
1553
|
+
for (let i = 0; i < genericStruct.typeParams.length; i++) {
|
|
1554
|
+
bindings.set(genericStruct.typeParams[i], typeArgs[i]);
|
|
1555
|
+
}
|
|
1556
|
+
// 필드 타입 치환
|
|
1557
|
+
const fields = new Map();
|
|
1558
|
+
for (const [fieldName, fieldType] of genericStruct.fields) {
|
|
1559
|
+
fields.set(fieldName, substituteType(fieldType, bindings));
|
|
1560
|
+
}
|
|
1561
|
+
return { kind: "struct", fields };
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
exports.TypeChecker = TypeChecker;
|
|
1565
|
+
//# sourceMappingURL=checker.js.map
|