c-next 0.1.67 → 0.1.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/package.json +1 -1
  2. package/src/transpiler/Transpiler.ts +5 -13
  3. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +41 -36
  4. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +29 -28
  5. package/src/transpiler/logic/analysis/__tests__/runAnalyzers.test.ts +32 -19
  6. package/src/transpiler/logic/analysis/runAnalyzers.ts +8 -14
  7. package/src/transpiler/output/codegen/CodeGenerator.ts +125 -135
  8. package/src/transpiler/output/codegen/TypeValidator.ts +2 -2
  9. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +2 -2
  10. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +29 -1
  11. package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +1 -3
  12. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +3 -1
  13. package/src/transpiler/output/codegen/assignment/AssignmentContextBuilder.ts +49 -0
  14. package/src/transpiler/output/codegen/assignment/IAssignmentContext.ts +15 -0
  15. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +7 -0
  16. package/src/transpiler/output/codegen/assignment/handlers/ArrayHandlers.ts +24 -17
  17. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +16 -5
  18. package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +9 -1
  19. package/src/transpiler/output/codegen/assignment/handlers/__tests__/ArrayHandlers.test.ts +18 -1
  20. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +9 -1
  21. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +9 -1
  22. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +10 -1
  23. package/src/transpiler/output/codegen/assignment/handlers/__tests__/SpecialHandlers.test.ts +9 -1
  24. package/src/transpiler/output/codegen/assignment/handlers/__tests__/StringHandlers.test.ts +9 -1
  25. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +31 -10
  26. package/src/transpiler/output/codegen/generators/expressions/__tests__/PostfixExpressionGenerator.test.ts +66 -4
  27. package/src/transpiler/output/codegen/helpers/MemberAccessValidator.ts +47 -6
  28. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +6 -1
  29. package/src/transpiler/output/codegen/helpers/__tests__/MemberAccessValidator.test.ts +109 -3
  30. package/src/transpiler/state/CodeGenState.ts +75 -1
  31. package/src/transpiler/state/__tests__/CodeGenState.test.ts +124 -5
  32. package/src/transpiler/logic/analysis/AnalyzerContextBuilder.ts +0 -58
  33. package/src/transpiler/logic/analysis/__tests__/AnalyzerContextBuilder.test.ts +0 -137
@@ -118,7 +118,8 @@ describe("MemberAccessValidator", () => {
118
118
  });
119
119
 
120
120
  describe("validateGlobalEntityAccess", () => {
121
- it("throws when in scope and entity does not belong to current scope", () => {
121
+ it("throws when in scope and entity name conflicts with scope member", () => {
122
+ const scopeMembers = new Map([["Motor", new Set(["Color", "speed"])]]);
122
123
  expect(() =>
123
124
  MemberAccessValidator.validateGlobalEntityAccess(
124
125
  "Color",
@@ -126,13 +127,29 @@ describe("MemberAccessValidator", () => {
126
127
  "enum",
127
128
  "Motor",
128
129
  false,
130
+ { scopeMembers },
129
131
  ),
130
132
  ).toThrow(
131
133
  "Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor'",
132
134
  );
133
135
  });
134
136
 
135
- it("does NOT throw when isGlobalAccess is true", () => {
137
+ it("does NOT throw when in scope but no naming conflict", () => {
138
+ const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
139
+ expect(() =>
140
+ MemberAccessValidator.validateGlobalEntityAccess(
141
+ "Color",
142
+ "Red",
143
+ "enum",
144
+ "Motor",
145
+ false,
146
+ { scopeMembers },
147
+ ),
148
+ ).not.toThrow();
149
+ });
150
+
151
+ it("does NOT throw when isGlobalAccess is true even with conflict", () => {
152
+ const scopeMembers = new Map([["Motor", new Set(["Color"])]]);
136
153
  expect(() =>
137
154
  MemberAccessValidator.validateGlobalEntityAccess(
138
155
  "Color",
@@ -140,6 +157,7 @@ describe("MemberAccessValidator", () => {
140
157
  "enum",
141
158
  "Motor",
142
159
  true,
160
+ { scopeMembers },
143
161
  ),
144
162
  ).not.toThrow();
145
163
  });
@@ -168,7 +186,8 @@ describe("MemberAccessValidator", () => {
168
186
  ).not.toThrow();
169
187
  });
170
188
 
171
- it("works with register entity type", () => {
189
+ it("throws for register with naming conflict", () => {
190
+ const scopeMembers = new Map([["Motor", new Set(["GPIO"])]]);
172
191
  expect(() =>
173
192
  MemberAccessValidator.validateGlobalEntityAccess(
174
193
  "GPIO",
@@ -176,10 +195,97 @@ describe("MemberAccessValidator", () => {
176
195
  "register",
177
196
  "Motor",
178
197
  false,
198
+ { scopeMembers },
179
199
  ),
180
200
  ).toThrow(
181
201
  "Use 'global.GPIO.PIN0' to access register 'GPIO' from inside scope 'Motor'",
182
202
  );
183
203
  });
204
+
205
+ it("does NOT throw for register without naming conflict", () => {
206
+ const scopeMembers = new Map([["Motor", new Set(["speed"])]]);
207
+ expect(() =>
208
+ MemberAccessValidator.validateGlobalEntityAccess(
209
+ "GPIO",
210
+ "PIN0",
211
+ "register",
212
+ "Motor",
213
+ false,
214
+ { scopeMembers },
215
+ ),
216
+ ).not.toThrow();
217
+ });
218
+
219
+ it("does NOT throw when options is undefined", () => {
220
+ expect(() =>
221
+ MemberAccessValidator.validateGlobalEntityAccess(
222
+ "Color",
223
+ "Red",
224
+ "enum",
225
+ "Motor",
226
+ false,
227
+ undefined,
228
+ ),
229
+ ).not.toThrow();
230
+ });
231
+
232
+ // Shadowing detection tests (using rootIdentifier and knownEnums)
233
+ it("throws when scope member shadows global enum (resolved identifier differs)", () => {
234
+ const knownEnums = new Set(["Color"]);
235
+ expect(() =>
236
+ MemberAccessValidator.validateGlobalEntityAccess(
237
+ "Motor_Color", // resolved to scope member
238
+ "Red",
239
+ "enum",
240
+ "Motor",
241
+ false,
242
+ { rootIdentifier: "Color", knownEnums },
243
+ ),
244
+ ).toThrow(
245
+ "Use 'global.Color.Red' to access enum 'Color' from inside scope 'Motor' (scope member 'Color' shadows the global enum)",
246
+ );
247
+ });
248
+
249
+ it("does NOT throw for shadowing when isGlobalAccess is true", () => {
250
+ const knownEnums = new Set(["Color"]);
251
+ expect(() =>
252
+ MemberAccessValidator.validateGlobalEntityAccess(
253
+ "Motor_Color",
254
+ "Red",
255
+ "enum",
256
+ "Motor",
257
+ true,
258
+ { rootIdentifier: "Color", knownEnums },
259
+ ),
260
+ ).not.toThrow();
261
+ });
262
+
263
+ it("does NOT throw when root is not a known enum", () => {
264
+ const knownEnums = new Set(["OtherEnum"]);
265
+ expect(() =>
266
+ MemberAccessValidator.validateGlobalEntityAccess(
267
+ "Motor_Color",
268
+ "Red",
269
+ "enum",
270
+ "Motor",
271
+ false,
272
+ { rootIdentifier: "Color", knownEnums },
273
+ ),
274
+ ).not.toThrow();
275
+ });
276
+
277
+ it("does NOT throw when resolved name IS a known enum", () => {
278
+ const knownEnums = new Set(["Color", "Motor_Color"]);
279
+ expect(() =>
280
+ MemberAccessValidator.validateGlobalEntityAccess(
281
+ "Motor_Color",
282
+ "Red",
283
+ "enum",
284
+ "Motor",
285
+ false,
286
+ { rootIdentifier: "Color", knownEnums },
287
+ ),
288
+ ).not.toThrow();
289
+ });
184
290
  });
185
291
  });
@@ -90,6 +90,13 @@ export default class CodeGenState {
90
90
  */
91
91
  static symbolTable: SymbolTable = new SymbolTable();
92
92
 
93
+ /**
94
+ * External struct fields from C/C++ headers for initialization analysis.
95
+ * Maps struct name -> Set of non-array field names.
96
+ * Persists across per-file reset() calls, cleared at start of run.
97
+ */
98
+ static externalStructFields: Map<string, Set<string>> = new Map();
99
+
93
100
  // ===========================================================================
94
101
  // TYPE TRACKING
95
102
  // ===========================================================================
@@ -180,7 +187,7 @@ export default class CodeGenState {
180
187
  static localArrays: Set<string> = new Set();
181
188
 
182
189
  /** Scope member names: scope -> Set of member names */
183
- static scopeMembers: Map<string, Set<string>> = new Map();
190
+ private static scopeMembers: Map<string, Set<string>> = new Map();
184
191
 
185
192
  /** Float bit indexing: declared shadow variables */
186
193
  static floatBitShadows: Set<string> = new Set();
@@ -456,6 +463,26 @@ export default class CodeGenState {
456
463
  return this.modifiedParameters.get(funcName)?.has(paramName) ?? false;
457
464
  }
458
465
 
466
+ /**
467
+ * Compute unmodified parameters for all functions on-demand.
468
+ * Returns a map of function name -> Set of parameter names NOT modified.
469
+ * Computed from functionSignatures and modifiedParameters (no cached state).
470
+ */
471
+ static getUnmodifiedParameters(): Map<string, Set<string>> {
472
+ const result = new Map<string, Set<string>>();
473
+ for (const [funcName, signature] of this.functionSignatures) {
474
+ const modifiedSet = this.modifiedParameters.get(funcName);
475
+ const unmodified = new Set<string>();
476
+ for (const param of signature.parameters) {
477
+ if (!modifiedSet?.has(param.name)) {
478
+ unmodified.add(param.name);
479
+ }
480
+ }
481
+ result.set(funcName, unmodified);
482
+ }
483
+ return result;
484
+ }
485
+
459
486
  /**
460
487
  * Check if a parameter should pass by value.
461
488
  */
@@ -535,6 +562,20 @@ export default class CodeGenState {
535
562
  return this.scopeMembers.get(scopeName);
536
563
  }
537
564
 
565
+ /**
566
+ * Set members of a scope.
567
+ */
568
+ static setScopeMembers(scopeName: string, members: Set<string>): void {
569
+ this.scopeMembers.set(scopeName, members);
570
+ }
571
+
572
+ /**
573
+ * Get all scope members (for IGeneratorState).
574
+ */
575
+ static getAllScopeMembers(): ReadonlyMap<string, ReadonlySet<string>> {
576
+ return this.scopeMembers;
577
+ }
578
+
538
579
  /**
539
580
  * Check if an identifier is a member of the current scope.
540
581
  */
@@ -650,6 +691,39 @@ export default class CodeGenState {
650
691
  return this.symbols?.enumMembers.get(enumName);
651
692
  }
652
693
 
694
+ /**
695
+ * Build external struct fields from the symbol table.
696
+ * Called once per run after all headers are processed.
697
+ * Issue #355: Excludes array fields from init checking.
698
+ */
699
+ static buildExternalStructFields(): void {
700
+ this.externalStructFields.clear();
701
+ const allStructFields = this.symbolTable.getAllStructFields();
702
+
703
+ for (const [structName, fieldMap] of allStructFields) {
704
+ const nonArrayFields = new Set<string>();
705
+ for (const [fieldName, fieldInfo] of fieldMap) {
706
+ // Only include non-array fields in init checking
707
+ if (
708
+ !fieldInfo.arrayDimensions ||
709
+ fieldInfo.arrayDimensions.length === 0
710
+ ) {
711
+ nonArrayFields.add(fieldName);
712
+ }
713
+ }
714
+ if (nonArrayFields.size > 0) {
715
+ this.externalStructFields.set(structName, nonArrayFields);
716
+ }
717
+ }
718
+ }
719
+
720
+ /**
721
+ * Get external struct fields for initialization analysis.
722
+ */
723
+ static getExternalStructFields(): Map<string, Set<string>> {
724
+ return this.externalStructFields;
725
+ }
726
+
653
727
  /**
654
728
  * Get function return type.
655
729
  */
@@ -110,7 +110,7 @@ describe("CodeGenState", () => {
110
110
 
111
111
  it("getScopeMembers returns members for known scope", () => {
112
112
  const members = new Set(["member1", "member2"]);
113
- CodeGenState.scopeMembers.set("TestScope", members);
113
+ CodeGenState.setScopeMembers("TestScope", members);
114
114
 
115
115
  expect(CodeGenState.getScopeMembers("TestScope")).toBe(members);
116
116
  });
@@ -122,14 +122,14 @@ describe("CodeGenState", () => {
122
122
 
123
123
  it("isCurrentScopeMember returns false for non-member", () => {
124
124
  CodeGenState.currentScope = "TestScope";
125
- CodeGenState.scopeMembers.set("TestScope", new Set(["member1"]));
125
+ CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
126
126
 
127
127
  expect(CodeGenState.isCurrentScopeMember("nonMember")).toBe(false);
128
128
  });
129
129
 
130
130
  it("isCurrentScopeMember returns true for member", () => {
131
131
  CodeGenState.currentScope = "TestScope";
132
- CodeGenState.scopeMembers.set("TestScope", new Set(["member1"]));
132
+ CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
133
133
 
134
134
  expect(CodeGenState.isCurrentScopeMember("member1")).toBe(true);
135
135
  });
@@ -143,14 +143,14 @@ describe("CodeGenState", () => {
143
143
 
144
144
  it("returns identifier unchanged when not a scope member", () => {
145
145
  CodeGenState.currentScope = "TestScope";
146
- CodeGenState.scopeMembers.set("TestScope", new Set(["member1"]));
146
+ CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
147
147
 
148
148
  expect(CodeGenState.resolveIdentifier("varName")).toBe("varName");
149
149
  });
150
150
 
151
151
  it("returns scoped name for scope member", () => {
152
152
  CodeGenState.currentScope = "TestScope";
153
- CodeGenState.scopeMembers.set("TestScope", new Set(["member1"]));
153
+ CodeGenState.setScopeMembers("TestScope", new Set(["member1"]));
154
154
 
155
155
  expect(CodeGenState.resolveIdentifier("member1")).toBe(
156
156
  "TestScope_member1",
@@ -453,4 +453,123 @@ describe("CodeGenState", () => {
453
453
  expect(CodeGenState.isLocalArray("myArr")).toBe(true);
454
454
  });
455
455
  });
456
+
457
+ describe("buildExternalStructFields", () => {
458
+ it("returns empty map when no struct fields exist", () => {
459
+ CodeGenState.buildExternalStructFields();
460
+ const result = CodeGenState.getExternalStructFields();
461
+ expect(result.size).toBe(0);
462
+ });
463
+
464
+ it("includes non-array fields in result", () => {
465
+ // Manually add struct fields to the symbol table
466
+ const structFields = new Map<
467
+ string,
468
+ Map<string, { type: string; arrayDimensions?: number[] }>
469
+ >();
470
+ const pointFields = new Map<
471
+ string,
472
+ { type: string; arrayDimensions?: number[] }
473
+ >();
474
+ pointFields.set("x", { type: "i32" });
475
+ pointFields.set("y", { type: "i32" });
476
+ structFields.set("Point", pointFields);
477
+
478
+ // Use restoreStructFields to populate the symbol table
479
+ CodeGenState.symbolTable.restoreStructFields(structFields);
480
+
481
+ CodeGenState.buildExternalStructFields();
482
+ const result = CodeGenState.getExternalStructFields();
483
+
484
+ expect(result.has("Point")).toBe(true);
485
+ const fields = result.get("Point");
486
+ expect(fields?.has("x")).toBe(true);
487
+ expect(fields?.has("y")).toBe(true);
488
+ });
489
+
490
+ it("excludes array fields from result (Issue #355)", () => {
491
+ const structFields = new Map<
492
+ string,
493
+ Map<string, { type: string; arrayDimensions?: number[] }>
494
+ >();
495
+ const bufferFields = new Map<
496
+ string,
497
+ { type: string; arrayDimensions?: number[] }
498
+ >();
499
+ bufferFields.set("size", { type: "u32" }); // Non-array
500
+ bufferFields.set("data", { type: "u8", arrayDimensions: [256] }); // Array
501
+
502
+ structFields.set("Buffer", bufferFields);
503
+ CodeGenState.symbolTable.restoreStructFields(structFields);
504
+
505
+ CodeGenState.buildExternalStructFields();
506
+ const result = CodeGenState.getExternalStructFields();
507
+
508
+ expect(result.has("Buffer")).toBe(true);
509
+ const fields = result.get("Buffer");
510
+ expect(fields?.has("size")).toBe(true); // Non-array included
511
+ expect(fields?.has("data")).toBe(false); // Array excluded
512
+ });
513
+
514
+ it("excludes structs with only array fields", () => {
515
+ const structFields = new Map<
516
+ string,
517
+ Map<string, { type: string; arrayDimensions?: number[] }>
518
+ >();
519
+ const arrayOnlyFields = new Map<
520
+ string,
521
+ { type: string; arrayDimensions?: number[] }
522
+ >();
523
+ arrayOnlyFields.set("items", { type: "u8", arrayDimensions: [10] });
524
+ arrayOnlyFields.set("values", { type: "i32", arrayDimensions: [5] });
525
+
526
+ structFields.set("ArrayOnly", arrayOnlyFields);
527
+ CodeGenState.symbolTable.restoreStructFields(structFields);
528
+
529
+ CodeGenState.buildExternalStructFields();
530
+ const result = CodeGenState.getExternalStructFields();
531
+
532
+ // Struct should not be included since all fields are arrays
533
+ expect(result.has("ArrayOnly")).toBe(false);
534
+ });
535
+
536
+ it("handles mixed structs correctly", () => {
537
+ const structFields = new Map<
538
+ string,
539
+ Map<string, { type: string; arrayDimensions?: number[] }>
540
+ >();
541
+
542
+ // Struct with mixed fields
543
+ const mixedFields = new Map<
544
+ string,
545
+ { type: string; arrayDimensions?: number[] }
546
+ >();
547
+ mixedFields.set("id", { type: "u32" });
548
+ mixedFields.set("name", { type: "string", arrayDimensions: [32] });
549
+ mixedFields.set("count", { type: "u16" });
550
+
551
+ // Struct with only non-array
552
+ const simpleFields = new Map<
553
+ string,
554
+ { type: string; arrayDimensions?: number[] }
555
+ >();
556
+ simpleFields.set("value", { type: "f32" });
557
+
558
+ structFields.set("Mixed", mixedFields);
559
+ structFields.set("Simple", simpleFields);
560
+ CodeGenState.symbolTable.restoreStructFields(structFields);
561
+
562
+ CodeGenState.buildExternalStructFields();
563
+ const result = CodeGenState.getExternalStructFields();
564
+
565
+ // Mixed struct should have only non-array fields
566
+ expect(result.get("Mixed")?.size).toBe(2);
567
+ expect(result.get("Mixed")?.has("id")).toBe(true);
568
+ expect(result.get("Mixed")?.has("count")).toBe(true);
569
+ expect(result.get("Mixed")?.has("name")).toBe(false);
570
+
571
+ // Simple struct should have its field
572
+ expect(result.get("Simple")?.has("value")).toBe(true);
573
+ });
574
+ });
456
575
  });
@@ -1,58 +0,0 @@
1
- /**
2
- * AnalyzerContextBuilder
3
- * Issue #591: Extracted from Transpiler.transpileSource() to reduce complexity
4
- *
5
- * Builds the context needed for running semantic analyzers, specifically
6
- * converting struct field information from the symbol table format to
7
- * the analyzer-compatible format.
8
- */
9
-
10
- import SymbolTable from "../symbols/SymbolTable";
11
-
12
- /**
13
- * Builds analyzer context from symbol table data.
14
- *
15
- * The analyzer requires struct fields as Map<string, Set<string>> (structName -> fieldNames)
16
- * while the symbol table stores more detailed IStructFieldInfo. This builder handles
17
- * the conversion, including Issue #355's requirement to exclude array fields.
18
- */
19
- class AnalyzerContextBuilder {
20
- /**
21
- * Build external struct fields map for analyzer from symbol table.
22
- *
23
- * Converts the symbol table's detailed struct field info to the simpler
24
- * format needed by InitializationAnalyzer.
25
- *
26
- * Issue #355: Excludes array fields from init checking since the analyzer
27
- * can't prove loop-based array initialization is complete.
28
- *
29
- * @param symbolTable - Symbol table containing struct definitions
30
- * @returns Map of struct name to set of non-array field names
31
- */
32
- static buildExternalStructFields(
33
- symbolTable: SymbolTable,
34
- ): Map<string, Set<string>> {
35
- const externalStructFields = new Map<string, Set<string>>();
36
- const allStructFields = symbolTable.getAllStructFields();
37
-
38
- for (const [structName, fieldMap] of allStructFields) {
39
- const nonArrayFields = new Set<string>();
40
- for (const [fieldName, fieldInfo] of fieldMap) {
41
- // Only include non-array fields in init checking
42
- if (
43
- !fieldInfo.arrayDimensions ||
44
- fieldInfo.arrayDimensions.length === 0
45
- ) {
46
- nonArrayFields.add(fieldName);
47
- }
48
- }
49
- if (nonArrayFields.size > 0) {
50
- externalStructFields.set(structName, nonArrayFields);
51
- }
52
- }
53
-
54
- return externalStructFields;
55
- }
56
- }
57
-
58
- export default AnalyzerContextBuilder;
@@ -1,137 +0,0 @@
1
- /**
2
- * Unit tests for AnalyzerContextBuilder
3
- * Issue #591: Tests for struct field conversion utility
4
- */
5
-
6
- import { describe, it, expect, beforeEach } from "vitest";
7
-
8
- import AnalyzerContextBuilder from "../AnalyzerContextBuilder";
9
- import SymbolTable from "../../symbols/SymbolTable";
10
-
11
- describe("AnalyzerContextBuilder", () => {
12
- let symbolTable: SymbolTable;
13
-
14
- beforeEach(() => {
15
- symbolTable = new SymbolTable();
16
- });
17
-
18
- describe("buildExternalStructFields", () => {
19
- it("returns empty map when no struct fields exist", () => {
20
- const result =
21
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
22
-
23
- expect(result.size).toBe(0);
24
- });
25
-
26
- it("includes non-array fields in result", () => {
27
- // Manually add struct fields to the symbol table
28
- const structFields = new Map<
29
- string,
30
- Map<string, { type: string; arrayDimensions?: number[] }>
31
- >();
32
- const pointFields = new Map<
33
- string,
34
- { type: string; arrayDimensions?: number[] }
35
- >();
36
- pointFields.set("x", { type: "i32" });
37
- pointFields.set("y", { type: "i32" });
38
- structFields.set("Point", pointFields);
39
-
40
- // Use restoreStructFields to populate the symbol table
41
- symbolTable.restoreStructFields(structFields);
42
-
43
- const result =
44
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
45
-
46
- expect(result.has("Point")).toBe(true);
47
- const fields = result.get("Point");
48
- expect(fields?.has("x")).toBe(true);
49
- expect(fields?.has("y")).toBe(true);
50
- });
51
-
52
- it("excludes array fields from result (Issue #355)", () => {
53
- const structFields = new Map<
54
- string,
55
- Map<string, { type: string; arrayDimensions?: number[] }>
56
- >();
57
- const bufferFields = new Map<
58
- string,
59
- { type: string; arrayDimensions?: number[] }
60
- >();
61
- bufferFields.set("size", { type: "u32" }); // Non-array
62
- bufferFields.set("data", { type: "u8", arrayDimensions: [256] }); // Array
63
-
64
- structFields.set("Buffer", bufferFields);
65
- symbolTable.restoreStructFields(structFields);
66
-
67
- const result =
68
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
69
-
70
- expect(result.has("Buffer")).toBe(true);
71
- const fields = result.get("Buffer");
72
- expect(fields?.has("size")).toBe(true); // Non-array included
73
- expect(fields?.has("data")).toBe(false); // Array excluded
74
- });
75
-
76
- it("excludes structs with only array fields", () => {
77
- const structFields = new Map<
78
- string,
79
- Map<string, { type: string; arrayDimensions?: number[] }>
80
- >();
81
- const arrayOnlyFields = new Map<
82
- string,
83
- { type: string; arrayDimensions?: number[] }
84
- >();
85
- arrayOnlyFields.set("items", { type: "u8", arrayDimensions: [10] });
86
- arrayOnlyFields.set("values", { type: "i32", arrayDimensions: [5] });
87
-
88
- structFields.set("ArrayOnly", arrayOnlyFields);
89
- symbolTable.restoreStructFields(structFields);
90
-
91
- const result =
92
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
93
-
94
- // Struct should not be included since all fields are arrays
95
- expect(result.has("ArrayOnly")).toBe(false);
96
- });
97
-
98
- it("handles mixed structs correctly", () => {
99
- const structFields = new Map<
100
- string,
101
- Map<string, { type: string; arrayDimensions?: number[] }>
102
- >();
103
-
104
- // Struct with mixed fields
105
- const mixedFields = new Map<
106
- string,
107
- { type: string; arrayDimensions?: number[] }
108
- >();
109
- mixedFields.set("id", { type: "u32" });
110
- mixedFields.set("name", { type: "string", arrayDimensions: [32] });
111
- mixedFields.set("count", { type: "u16" });
112
-
113
- // Struct with only non-array
114
- const simpleFields = new Map<
115
- string,
116
- { type: string; arrayDimensions?: number[] }
117
- >();
118
- simpleFields.set("value", { type: "f32" });
119
-
120
- structFields.set("Mixed", mixedFields);
121
- structFields.set("Simple", simpleFields);
122
- symbolTable.restoreStructFields(structFields);
123
-
124
- const result =
125
- AnalyzerContextBuilder.buildExternalStructFields(symbolTable);
126
-
127
- // Mixed struct should have only non-array fields
128
- expect(result.get("Mixed")?.size).toBe(2);
129
- expect(result.get("Mixed")?.has("id")).toBe(true);
130
- expect(result.get("Mixed")?.has("count")).toBe(true);
131
- expect(result.get("Mixed")?.has("name")).toBe(false);
132
-
133
- // Simple struct should have its field
134
- expect(result.get("Simple")?.has("value")).toBe(true);
135
- });
136
- });
137
- });