c-next 0.2.2 → 0.2.4
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 +59 -57
- package/dist/index.js +641 -191
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/cli/Runner.ts +1 -1
- package/src/cli/__tests__/Runner.test.ts +8 -8
- package/src/cli/serve/ServeCommand.ts +29 -9
- package/src/transpiler/Transpiler.ts +105 -200
- package/src/transpiler/__tests__/DualCodePaths.test.ts +117 -68
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +87 -51
- package/src/transpiler/__tests__/Transpiler.test.ts +150 -48
- package/src/transpiler/__tests__/determineProjectRoot.test.ts +2 -2
- package/src/transpiler/data/IncludeResolver.ts +11 -3
- package/src/transpiler/data/__tests__/IncludeResolver.test.ts +2 -2
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +346 -0
- package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +170 -14
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +545 -0
- package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +327 -0
- package/src/transpiler/logic/analysis/runAnalyzers.ts +9 -2
- package/src/transpiler/logic/analysis/types/IArrayIndexTypeError.ts +15 -0
- package/src/transpiler/logic/symbols/TransitiveEnumCollector.ts +1 -1
- package/src/transpiler/logic/symbols/c/index.ts +50 -1
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +99 -2
- package/src/transpiler/logic/symbols/c/utils/__tests__/DeclaratorUtils.test.ts +128 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +31 -5
- package/src/transpiler/output/codegen/TypeValidator.ts +10 -7
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +49 -36
- package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +9 -3
- package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +90 -25
- package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +3 -1
- package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +43 -29
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +5 -2
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +23 -14
- package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +10 -7
- package/src/transpiler/output/codegen/generators/statements/ControlFlowGenerator.ts +12 -3
- package/src/transpiler/output/codegen/generators/statements/SwitchGenerator.ts +10 -1
- package/src/transpiler/output/codegen/generators/statements/__tests__/ControlFlowGenerator.test.ts +4 -4
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +6 -3
- package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +36 -22
- package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +9 -1
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +8 -6
- package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +34 -18
- package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +5 -2
- package/src/transpiler/state/CodeGenState.ts +6 -0
- package/src/transpiler/types/IPipelineFile.ts +2 -2
- package/src/transpiler/types/IPipelineInput.ts +1 -1
- package/src/transpiler/types/TTranspileInput.ts +18 -0
- package/src/utils/constants/TypeConstants.ts +22 -0
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for ArrayIndexTypeAnalyzer
|
|
3
|
+
* Tests detection of signed/float types used as array and bit subscript indexes (ADR-054)
|
|
4
|
+
*/
|
|
5
|
+
import { describe, it, expect, afterEach } from "vitest";
|
|
6
|
+
import { CharStream, CommonTokenStream } from "antlr4ng";
|
|
7
|
+
import { CNextLexer } from "../../parser/grammar/CNextLexer";
|
|
8
|
+
import { CNextParser } from "../../parser/grammar/CNextParser";
|
|
9
|
+
import ArrayIndexTypeAnalyzer from "../ArrayIndexTypeAnalyzer";
|
|
10
|
+
import CodeGenState from "../../../state/CodeGenState";
|
|
11
|
+
import ICodeGenSymbols from "../../../types/ICodeGenSymbols";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Helper to parse C-Next code and return the AST
|
|
15
|
+
*/
|
|
16
|
+
function parse(source: string) {
|
|
17
|
+
const charStream = CharStream.fromString(source);
|
|
18
|
+
const lexer = new CNextLexer(charStream);
|
|
19
|
+
const tokenStream = new CommonTokenStream(lexer);
|
|
20
|
+
const parser = new CNextParser(tokenStream);
|
|
21
|
+
return parser.program();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Create a minimal mock ICodeGenSymbols with default empty collections.
|
|
26
|
+
*/
|
|
27
|
+
function createMockSymbols(
|
|
28
|
+
overrides: Partial<{
|
|
29
|
+
knownEnums: Set<string>;
|
|
30
|
+
knownStructs: Set<string>;
|
|
31
|
+
structFields: Map<string, Map<string, string>>;
|
|
32
|
+
functionReturnTypes: Map<string, string>;
|
|
33
|
+
}> = {},
|
|
34
|
+
): ICodeGenSymbols {
|
|
35
|
+
return {
|
|
36
|
+
knownScopes: new Set(),
|
|
37
|
+
knownEnums: overrides.knownEnums ?? new Set(),
|
|
38
|
+
knownBitmaps: new Set(),
|
|
39
|
+
knownStructs: overrides.knownStructs ?? new Set(),
|
|
40
|
+
knownRegisters: new Set(),
|
|
41
|
+
scopeMembers: new Map(),
|
|
42
|
+
scopeMemberVisibility: new Map(),
|
|
43
|
+
structFields: overrides.structFields ?? new Map(),
|
|
44
|
+
structFieldArrays: new Map(),
|
|
45
|
+
structFieldDimensions: new Map(),
|
|
46
|
+
enumMembers: new Map(),
|
|
47
|
+
bitmapFields: new Map(),
|
|
48
|
+
bitmapBackingType: new Map(),
|
|
49
|
+
bitmapBitWidth: new Map(),
|
|
50
|
+
scopedRegisters: new Map(),
|
|
51
|
+
registerMemberAccess: new Map(),
|
|
52
|
+
registerMemberTypes: new Map(),
|
|
53
|
+
registerBaseAddresses: new Map(),
|
|
54
|
+
registerMemberOffsets: new Map(),
|
|
55
|
+
registerMemberCTypes: new Map(),
|
|
56
|
+
scopeVariableUsage: new Map(),
|
|
57
|
+
scopePrivateConstValues: new Map(),
|
|
58
|
+
functionReturnTypes: overrides.functionReturnTypes ?? new Map(),
|
|
59
|
+
getSingleFunctionForVariable: () => null,
|
|
60
|
+
hasPublicSymbols: () => false,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
describe("ArrayIndexTypeAnalyzer", () => {
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
CodeGenState.reset();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ========================================================================
|
|
70
|
+
// Allowed types (0 errors expected)
|
|
71
|
+
// ========================================================================
|
|
72
|
+
|
|
73
|
+
describe("allowed unsigned types", () => {
|
|
74
|
+
it("should allow u8 variable as array index", () => {
|
|
75
|
+
const tree = parse(
|
|
76
|
+
`void main() { u8[10] arr; u8 idx <- 0; arr[idx] <- 1; }`,
|
|
77
|
+
);
|
|
78
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
79
|
+
const errors = analyzer.analyze(tree);
|
|
80
|
+
expect(errors).toHaveLength(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should allow u16 variable as array index", () => {
|
|
84
|
+
const tree = parse(
|
|
85
|
+
`void main() { u8[10] arr; u16 idx <- 0; arr[idx] <- 1; }`,
|
|
86
|
+
);
|
|
87
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
88
|
+
const errors = analyzer.analyze(tree);
|
|
89
|
+
expect(errors).toHaveLength(0);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
it("should allow u32 variable as array index", () => {
|
|
93
|
+
const tree = parse(
|
|
94
|
+
`void main() { u8[10] arr; u32 idx <- 0; arr[idx] <- 1; }`,
|
|
95
|
+
);
|
|
96
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
97
|
+
const errors = analyzer.analyze(tree);
|
|
98
|
+
expect(errors).toHaveLength(0);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("should allow u64 variable as array index", () => {
|
|
102
|
+
const tree = parse(
|
|
103
|
+
`void main() { u8[10] arr; u64 idx <- 0; arr[idx] <- 1; }`,
|
|
104
|
+
);
|
|
105
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
106
|
+
const errors = analyzer.analyze(tree);
|
|
107
|
+
expect(errors).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should allow bool variable as array index", () => {
|
|
111
|
+
const tree = parse(
|
|
112
|
+
`void main() { u8[2] arr; bool idx <- 0; arr[idx] <- 1; }`,
|
|
113
|
+
);
|
|
114
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
115
|
+
const errors = analyzer.analyze(tree);
|
|
116
|
+
expect(errors).toHaveLength(0);
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should allow integer literal as array index", () => {
|
|
120
|
+
const tree = parse(`void main() { u8[10] arr; arr[3] <- 1; }`);
|
|
121
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
122
|
+
const errors = analyzer.analyze(tree);
|
|
123
|
+
expect(errors).toHaveLength(0);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should allow enum member as array index", () => {
|
|
127
|
+
const tree = parse(`
|
|
128
|
+
enum EColor { RED, GREEN, BLUE, COUNT }
|
|
129
|
+
void main() { u8[4] arr; arr[EColor.RED] <- 1; }
|
|
130
|
+
`);
|
|
131
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
132
|
+
CodeGenState.symbols = createMockSymbols({
|
|
133
|
+
knownEnums: new Set(["EColor"]),
|
|
134
|
+
});
|
|
135
|
+
const errors = analyzer.analyze(tree);
|
|
136
|
+
expect(errors).toHaveLength(0);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("should allow unsigned for-loop variable as index", () => {
|
|
140
|
+
const tree = parse(`
|
|
141
|
+
void main() {
|
|
142
|
+
u8[10] arr;
|
|
143
|
+
for (u32 i <- 0; i < 10; i <- i + 1) {
|
|
144
|
+
arr[i] <- 0;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
`);
|
|
148
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
149
|
+
const errors = analyzer.analyze(tree);
|
|
150
|
+
expect(errors).toHaveLength(0);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should allow unsigned function parameter as index", () => {
|
|
154
|
+
const tree = parse(`
|
|
155
|
+
void setElement(u8[10] arr, u32 idx) {
|
|
156
|
+
arr[idx] <- 1;
|
|
157
|
+
}
|
|
158
|
+
`);
|
|
159
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
160
|
+
const errors = analyzer.analyze(tree);
|
|
161
|
+
expect(errors).toHaveLength(0);
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// ========================================================================
|
|
166
|
+
// Rejected signed types (E0850)
|
|
167
|
+
// ========================================================================
|
|
168
|
+
|
|
169
|
+
describe("rejected signed types", () => {
|
|
170
|
+
it("should reject i8 variable as array index", () => {
|
|
171
|
+
const tree = parse(
|
|
172
|
+
`void main() { u8[10] arr; i8 idx <- 0; arr[idx] <- 1; }`,
|
|
173
|
+
);
|
|
174
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
175
|
+
const errors = analyzer.analyze(tree);
|
|
176
|
+
expect(errors).toHaveLength(1);
|
|
177
|
+
expect(errors[0].code).toBe("E0850");
|
|
178
|
+
expect(errors[0].actualType).toBe("i8");
|
|
179
|
+
expect(errors[0].message).toContain("unsigned integer type");
|
|
180
|
+
expect(errors[0].message).toContain("i8");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("should reject i16 variable as array index", () => {
|
|
184
|
+
const tree = parse(
|
|
185
|
+
`void main() { u8[10] arr; i16 idx <- 0; arr[idx] <- 1; }`,
|
|
186
|
+
);
|
|
187
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
188
|
+
const errors = analyzer.analyze(tree);
|
|
189
|
+
expect(errors).toHaveLength(1);
|
|
190
|
+
expect(errors[0].code).toBe("E0850");
|
|
191
|
+
expect(errors[0].actualType).toBe("i16");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("should reject i32 variable as array index", () => {
|
|
195
|
+
const tree = parse(
|
|
196
|
+
`void main() { u8[10] arr; i32 idx <- 0; arr[idx] <- 1; }`,
|
|
197
|
+
);
|
|
198
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
199
|
+
const errors = analyzer.analyze(tree);
|
|
200
|
+
expect(errors).toHaveLength(1);
|
|
201
|
+
expect(errors[0].code).toBe("E0850");
|
|
202
|
+
expect(errors[0].actualType).toBe("i32");
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("should reject i64 variable as array index", () => {
|
|
206
|
+
const tree = parse(
|
|
207
|
+
`void main() { u8[10] arr; i64 idx <- 0; arr[idx] <- 1; }`,
|
|
208
|
+
);
|
|
209
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
210
|
+
const errors = analyzer.analyze(tree);
|
|
211
|
+
expect(errors).toHaveLength(1);
|
|
212
|
+
expect(errors[0].code).toBe("E0850");
|
|
213
|
+
expect(errors[0].actualType).toBe("i64");
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("should reject signed for-loop variable as index", () => {
|
|
217
|
+
const tree = parse(`
|
|
218
|
+
void main() {
|
|
219
|
+
u8[10] arr;
|
|
220
|
+
for (i32 i <- 0; i < 10; i <- i + 1) {
|
|
221
|
+
arr[i] <- 0;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
`);
|
|
225
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
226
|
+
const errors = analyzer.analyze(tree);
|
|
227
|
+
expect(errors).toHaveLength(1);
|
|
228
|
+
expect(errors[0].code).toBe("E0850");
|
|
229
|
+
expect(errors[0].actualType).toBe("i32");
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should reject signed function parameter as index", () => {
|
|
233
|
+
const tree = parse(`
|
|
234
|
+
void setElement(u8[10] arr, i32 idx) {
|
|
235
|
+
arr[idx] <- 1;
|
|
236
|
+
}
|
|
237
|
+
`);
|
|
238
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
239
|
+
const errors = analyzer.analyze(tree);
|
|
240
|
+
expect(errors).toHaveLength(1);
|
|
241
|
+
expect(errors[0].code).toBe("E0850");
|
|
242
|
+
expect(errors[0].actualType).toBe("i32");
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// ========================================================================
|
|
247
|
+
// Rejected float types (E0851)
|
|
248
|
+
// ========================================================================
|
|
249
|
+
|
|
250
|
+
describe("rejected float types", () => {
|
|
251
|
+
it("should reject f32 variable as array index", () => {
|
|
252
|
+
const tree = parse(
|
|
253
|
+
`void main() { u8[10] arr; f32 idx <- 0.0; arr[idx] <- 1; }`,
|
|
254
|
+
);
|
|
255
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
256
|
+
const errors = analyzer.analyze(tree);
|
|
257
|
+
expect(errors).toHaveLength(1);
|
|
258
|
+
expect(errors[0].code).toBe("E0851");
|
|
259
|
+
expect(errors[0].actualType).toBe("f32");
|
|
260
|
+
expect(errors[0].message).toContain("unsigned integer type");
|
|
261
|
+
expect(errors[0].message).toContain("f32");
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("should reject f64 variable as array index", () => {
|
|
265
|
+
const tree = parse(
|
|
266
|
+
`void main() { u8[10] arr; f64 idx <- 0.0; arr[idx] <- 1; }`,
|
|
267
|
+
);
|
|
268
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
269
|
+
const errors = analyzer.analyze(tree);
|
|
270
|
+
expect(errors).toHaveLength(1);
|
|
271
|
+
expect(errors[0].code).toBe("E0851");
|
|
272
|
+
expect(errors[0].actualType).toBe("f64");
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// ========================================================================
|
|
277
|
+
// Bit indexing (same rules apply)
|
|
278
|
+
// ========================================================================
|
|
279
|
+
|
|
280
|
+
describe("bit indexing", () => {
|
|
281
|
+
it("should reject signed type for single bit access", () => {
|
|
282
|
+
const tree = parse(
|
|
283
|
+
`void main() { u32 flags <- 0; i32 bit <- 0; u8 val <- flags[bit]; }`,
|
|
284
|
+
);
|
|
285
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
286
|
+
const errors = analyzer.analyze(tree);
|
|
287
|
+
expect(errors).toHaveLength(1);
|
|
288
|
+
expect(errors[0].code).toBe("E0850");
|
|
289
|
+
expect(errors[0].actualType).toBe("i32");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should reject signed type for bit range start", () => {
|
|
293
|
+
const tree = parse(
|
|
294
|
+
`void main() { u32 flags <- 0; i32 start <- 0; u8 val <- flags[start, 4]; }`,
|
|
295
|
+
);
|
|
296
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
297
|
+
const errors = analyzer.analyze(tree);
|
|
298
|
+
expect(errors).toHaveLength(1);
|
|
299
|
+
expect(errors[0].code).toBe("E0850");
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it("should reject signed type for bit range width", () => {
|
|
303
|
+
const tree = parse(
|
|
304
|
+
`void main() { u32 flags <- 0; i32 width <- 4; u8 val <- flags[0, width]; }`,
|
|
305
|
+
);
|
|
306
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
307
|
+
const errors = analyzer.analyze(tree);
|
|
308
|
+
expect(errors).toHaveLength(1);
|
|
309
|
+
expect(errors[0].code).toBe("E0850");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should allow unsigned type for bit access", () => {
|
|
313
|
+
const tree = parse(
|
|
314
|
+
`void main() { u32 flags <- 0; u8 bit <- 0; u8 val <- flags[bit]; }`,
|
|
315
|
+
);
|
|
316
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
317
|
+
const errors = analyzer.analyze(tree);
|
|
318
|
+
expect(errors).toHaveLength(0);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
// ========================================================================
|
|
323
|
+
// Complex expressions (arithmetic, parenthesized)
|
|
324
|
+
// ========================================================================
|
|
325
|
+
|
|
326
|
+
describe("complex expressions", () => {
|
|
327
|
+
it("should reject arr[x + 1] where x is i32", () => {
|
|
328
|
+
const tree = parse(`
|
|
329
|
+
void main() {
|
|
330
|
+
u8[10] arr;
|
|
331
|
+
i32 x <- 2;
|
|
332
|
+
arr[x + 1] <- 5;
|
|
333
|
+
}
|
|
334
|
+
`);
|
|
335
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
336
|
+
const errors = analyzer.analyze(tree);
|
|
337
|
+
expect(errors).toHaveLength(1);
|
|
338
|
+
expect(errors[0].code).toBe("E0850");
|
|
339
|
+
expect(errors[0].actualType).toBe("i32");
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
it("should reject arr[(x)] where x is i32 (parenthesized)", () => {
|
|
343
|
+
const tree = parse(`
|
|
344
|
+
void main() {
|
|
345
|
+
u8[10] arr;
|
|
346
|
+
i32 x <- 2;
|
|
347
|
+
arr[(x)] <- 5;
|
|
348
|
+
}
|
|
349
|
+
`);
|
|
350
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
351
|
+
const errors = analyzer.analyze(tree);
|
|
352
|
+
expect(errors).toHaveLength(1);
|
|
353
|
+
expect(errors[0].code).toBe("E0850");
|
|
354
|
+
expect(errors[0].actualType).toBe("i32");
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it("should reject arr[x * 2 + y] where x is i32, y is u32", () => {
|
|
358
|
+
const tree = parse(`
|
|
359
|
+
void main() {
|
|
360
|
+
u8[10] arr;
|
|
361
|
+
i32 x <- 1;
|
|
362
|
+
u32 y <- 0;
|
|
363
|
+
arr[x * 2 + y] <- 5;
|
|
364
|
+
}
|
|
365
|
+
`);
|
|
366
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
367
|
+
const errors = analyzer.analyze(tree);
|
|
368
|
+
expect(errors).toHaveLength(1);
|
|
369
|
+
expect(errors[0].code).toBe("E0850");
|
|
370
|
+
expect(errors[0].actualType).toBe("i32");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("should allow arr[x + 1] where x is u32", () => {
|
|
374
|
+
const tree = parse(`
|
|
375
|
+
void main() {
|
|
376
|
+
u8[10] arr;
|
|
377
|
+
u32 x <- 2;
|
|
378
|
+
arr[x + 1] <- 5;
|
|
379
|
+
}
|
|
380
|
+
`);
|
|
381
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
382
|
+
const errors = analyzer.analyze(tree);
|
|
383
|
+
expect(errors).toHaveLength(0);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("should reject float literal in arithmetic", () => {
|
|
387
|
+
const tree = parse(`
|
|
388
|
+
void main() {
|
|
389
|
+
u8[10] arr;
|
|
390
|
+
arr[1 + 2.0] <- 5;
|
|
391
|
+
}
|
|
392
|
+
`);
|
|
393
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
394
|
+
const errors = analyzer.analyze(tree);
|
|
395
|
+
expect(errors).toHaveLength(1);
|
|
396
|
+
expect(errors[0].code).toBe("E0851");
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
// ========================================================================
|
|
401
|
+
// State-based type resolution (CodeGenState)
|
|
402
|
+
// ========================================================================
|
|
403
|
+
|
|
404
|
+
describe("state-based type resolution", () => {
|
|
405
|
+
it("should reject arr[config.value] where Config.value is i32", () => {
|
|
406
|
+
CodeGenState.symbols = createMockSymbols({
|
|
407
|
+
knownStructs: new Set(["Config"]),
|
|
408
|
+
structFields: new Map([["Config", new Map([["value", "i32"]])]]),
|
|
409
|
+
});
|
|
410
|
+
const tree = parse(`
|
|
411
|
+
void main() {
|
|
412
|
+
u8[10] arr;
|
|
413
|
+
Config config;
|
|
414
|
+
arr[config.value] <- 5;
|
|
415
|
+
}
|
|
416
|
+
`);
|
|
417
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
418
|
+
const errors = analyzer.analyze(tree);
|
|
419
|
+
expect(errors).toHaveLength(1);
|
|
420
|
+
expect(errors[0].code).toBe("E0850");
|
|
421
|
+
expect(errors[0].actualType).toBe("i32");
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
it("should allow arr[config.index] where Config.index is u32", () => {
|
|
425
|
+
CodeGenState.symbols = createMockSymbols({
|
|
426
|
+
knownStructs: new Set(["Config"]),
|
|
427
|
+
structFields: new Map([["Config", new Map([["index", "u32"]])]]),
|
|
428
|
+
});
|
|
429
|
+
const tree = parse(`
|
|
430
|
+
void main() {
|
|
431
|
+
u8[10] arr;
|
|
432
|
+
Config config;
|
|
433
|
+
arr[config.index] <- 5;
|
|
434
|
+
}
|
|
435
|
+
`);
|
|
436
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
437
|
+
const errors = analyzer.analyze(tree);
|
|
438
|
+
expect(errors).toHaveLength(0);
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
it("should reject arr[getIndex()] where getIndex returns i32", () => {
|
|
442
|
+
CodeGenState.symbols = createMockSymbols({
|
|
443
|
+
functionReturnTypes: new Map([["getIndex", "i32"]]),
|
|
444
|
+
});
|
|
445
|
+
const tree = parse(`
|
|
446
|
+
i32 getIndex() { return 0; }
|
|
447
|
+
void main() {
|
|
448
|
+
u8[10] arr;
|
|
449
|
+
arr[getIndex()] <- 1;
|
|
450
|
+
}
|
|
451
|
+
`);
|
|
452
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
453
|
+
const errors = analyzer.analyze(tree);
|
|
454
|
+
expect(errors).toHaveLength(1);
|
|
455
|
+
expect(errors[0].code).toBe("E0850");
|
|
456
|
+
expect(errors[0].actualType).toBe("i32");
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
it("should allow arr[getIndex()] where getIndex returns u32", () => {
|
|
460
|
+
CodeGenState.symbols = createMockSymbols({
|
|
461
|
+
functionReturnTypes: new Map([["getIndex", "u32"]]),
|
|
462
|
+
});
|
|
463
|
+
const tree = parse(`
|
|
464
|
+
u32 getIndex() { return 0; }
|
|
465
|
+
void main() {
|
|
466
|
+
u8[10] arr;
|
|
467
|
+
arr[getIndex()] <- 1;
|
|
468
|
+
}
|
|
469
|
+
`);
|
|
470
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
471
|
+
const errors = analyzer.analyze(tree);
|
|
472
|
+
expect(errors).toHaveLength(0);
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
it("should allow arr[EColor.RED] via CodeGenState.isKnownEnum", () => {
|
|
476
|
+
CodeGenState.symbols = createMockSymbols({
|
|
477
|
+
knownEnums: new Set(["EColor"]),
|
|
478
|
+
});
|
|
479
|
+
const tree = parse(`
|
|
480
|
+
enum EColor { RED, GREEN, BLUE }
|
|
481
|
+
void main() {
|
|
482
|
+
u8[4] arr;
|
|
483
|
+
arr[EColor.RED] <- 1;
|
|
484
|
+
}
|
|
485
|
+
`);
|
|
486
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
487
|
+
const errors = analyzer.analyze(tree);
|
|
488
|
+
expect(errors).toHaveLength(0);
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
it("should pass through unresolvable function call without CodeGenState", () => {
|
|
492
|
+
const tree = parse(`
|
|
493
|
+
u32 getIndex() { return 0; }
|
|
494
|
+
void main() {
|
|
495
|
+
u8[10] arr;
|
|
496
|
+
arr[getIndex()] <- 1;
|
|
497
|
+
}
|
|
498
|
+
`);
|
|
499
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
500
|
+
const errors = analyzer.analyze(tree);
|
|
501
|
+
expect(errors).toHaveLength(0);
|
|
502
|
+
});
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
// ========================================================================
|
|
506
|
+
// Edge cases
|
|
507
|
+
// ========================================================================
|
|
508
|
+
|
|
509
|
+
describe("edge cases", () => {
|
|
510
|
+
it("should report multiple errors in same function", () => {
|
|
511
|
+
const tree = parse(`
|
|
512
|
+
void main() {
|
|
513
|
+
u8[10] arr;
|
|
514
|
+
i32 a <- 0;
|
|
515
|
+
i32 b <- 1;
|
|
516
|
+
arr[a] <- 1;
|
|
517
|
+
arr[b] <- 2;
|
|
518
|
+
}
|
|
519
|
+
`);
|
|
520
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
521
|
+
const errors = analyzer.analyze(tree);
|
|
522
|
+
expect(errors).toHaveLength(2);
|
|
523
|
+
expect(errors[0].code).toBe("E0850");
|
|
524
|
+
expect(errors[1].code).toBe("E0850");
|
|
525
|
+
});
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
// ========================================================================
|
|
529
|
+
// getErrors() accessor
|
|
530
|
+
// ========================================================================
|
|
531
|
+
|
|
532
|
+
describe("getErrors accessor", () => {
|
|
533
|
+
it("should access errors via getErrors method", () => {
|
|
534
|
+
const tree = parse(
|
|
535
|
+
`void main() { u8[10] arr; i32 idx <- 0; arr[idx] <- 1; }`,
|
|
536
|
+
);
|
|
537
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
538
|
+
analyzer.analyze(tree);
|
|
539
|
+
|
|
540
|
+
const errors = analyzer.getErrors();
|
|
541
|
+
expect(errors).toHaveLength(1);
|
|
542
|
+
expect(errors[0].code).toBe("E0850");
|
|
543
|
+
});
|
|
544
|
+
});
|
|
545
|
+
});
|