c-next 0.2.16 → 0.2.17

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 (56) hide show
  1. package/README.md +16 -0
  2. package/dist/index.js +1347 -414
  3. package/dist/index.js.map +3 -3
  4. package/grammar/CNext.g4 +4 -0
  5. package/package.json +5 -1
  6. package/src/transpiler/Transpiler.ts +90 -22
  7. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  8. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +57 -10
  9. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +186 -14
  10. package/src/transpiler/logic/analysis/SignedShiftAnalyzer.ts +124 -12
  11. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +200 -0
  12. package/src/transpiler/logic/analysis/__tests__/InitializationAnalyzer.test.ts +386 -1
  13. package/src/transpiler/logic/analysis/__tests__/SignedShiftAnalyzer.test.ts +211 -0
  14. package/src/transpiler/logic/parser/grammar/CNext.interp +1 -1
  15. package/src/transpiler/logic/parser/grammar/CNextParser.ts +154 -86
  16. package/src/transpiler/logic/symbols/SymbolTable.ts +17 -2
  17. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +6 -4
  18. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +15 -2
  19. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +64 -50
  20. package/src/transpiler/output/codegen/CodeGenerator.ts +151 -94
  21. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +165 -17
  22. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +150 -34
  23. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  24. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +11 -0
  25. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +26 -7
  26. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +86 -0
  27. package/src/transpiler/output/codegen/generators/expressions/BinaryExprGenerator.ts +43 -24
  28. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +48 -43
  29. package/src/transpiler/output/codegen/generators/expressions/ExpressionGenerator.ts +9 -2
  30. package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +44 -0
  31. package/src/transpiler/output/codegen/generators/expressions/__tests__/ExpressionGenerator.test.ts +82 -1
  32. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +17 -3
  33. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +17 -4
  34. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +227 -32
  35. package/src/transpiler/output/codegen/helpers/SymbolLookupHelper.ts +0 -21
  36. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +60 -39
  37. package/src/transpiler/output/codegen/helpers/TypeRegistrationEngine.ts +170 -36
  38. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +37 -39
  39. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +117 -0
  40. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +94 -2
  41. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +268 -1
  42. package/src/transpiler/output/codegen/helpers/__tests__/SymbolLookupHelper.test.ts +0 -64
  43. package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +101 -0
  44. package/src/transpiler/output/codegen/helpers/__tests__/VariableDeclHelper.test.ts +29 -5
  45. package/src/transpiler/output/codegen/types/ICallbackTypeInfo.ts +2 -1
  46. package/src/transpiler/output/codegen/types/IParameterInput.ts +7 -0
  47. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +8 -0
  48. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +75 -0
  49. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +280 -0
  50. package/src/transpiler/output/headers/generators/IHeaderTypeInput.ts +13 -0
  51. package/src/transpiler/output/headers/generators/generateStructHeader.ts +48 -28
  52. package/src/transpiler/state/CodeGenState.ts +71 -6
  53. package/src/transpiler/state/__tests__/CodeGenState.test.ts +253 -11
  54. package/src/utils/LiteralUtils.ts +23 -0
  55. package/src/utils/__tests__/LiteralUtils.test.ts +101 -0
  56. package/src/utils/types/IParameterSymbol.ts +1 -0
@@ -310,9 +310,11 @@ describe("StringDeclHelper", () => {
310
310
  });
311
311
 
312
312
  it("allows assignment when source capacity fits", () => {
313
+ // Issue #1030: String variable initialization now uses strcpy
313
314
  const callbacks = {
314
315
  ...defaultCallbacks,
315
316
  getStringExprCapacity: vi.fn(() => 20),
317
+ generateExpression: vi.fn(() => "smallString"),
316
318
  };
317
319
 
318
320
  const typeCtx = {
@@ -336,7 +338,42 @@ describe("StringDeclHelper", () => {
336
338
  );
337
339
 
338
340
  expect(result.handled).toBe(true);
339
- expect(result.code).toContain("char dest[33] = smallString;");
341
+ // Issue #1030: String variable initialization uses strcpy
342
+ expect(result.code).toContain('char dest[33] = "";');
343
+ expect(result.code).toContain("strcpy(dest, smallString)");
344
+ });
345
+
346
+ it("throws error for string variable initialization at global scope", () => {
347
+ // Issue #1030: String variable initialization requires function body
348
+ CodeGenState.inFunctionBody = false;
349
+ const callbacks = {
350
+ ...defaultCallbacks,
351
+ getStringExprCapacity: vi.fn(() => 20),
352
+ };
353
+
354
+ const typeCtx = {
355
+ stringType: () => ({
356
+ INTEGER_LITERAL: () => ({ getText: () => "32" }),
357
+ }),
358
+ } as never;
359
+
360
+ const expression = {
361
+ getText: () => "sourceVar",
362
+ } as never;
363
+
364
+ expect(() =>
365
+ StringDeclHelper.generateStringDecl(
366
+ typeCtx,
367
+ "dest",
368
+ expression,
369
+ [],
370
+ { extern: "", const: "", atomic: "", volatile: "" },
371
+ false,
372
+ callbacks,
373
+ ),
374
+ ).toThrow(
375
+ "String initialization from variable cannot be used at global scope",
376
+ );
340
377
  });
341
378
  });
342
379
 
@@ -1154,4 +1191,234 @@ describe("StringDeclHelper", () => {
1154
1191
  expect(CodeGenState.localArrays.has("tracked")).toBe(true);
1155
1192
  });
1156
1193
  });
1194
+
1195
+ describe("string arrays from arrayType syntax (Issue #1029)", () => {
1196
+ it("generates string array from arrayType without initializer", () => {
1197
+ // Simulates: string<32>[4] items;
1198
+ const typeCtx = {
1199
+ stringType: () => null, // Not directly on typeCtx
1200
+ arrayType: () => ({
1201
+ stringType: () => ({
1202
+ INTEGER_LITERAL: () => ({ getText: () => "32" }),
1203
+ }),
1204
+ arrayTypeDimension: () => [
1205
+ { expression: () => ({ getText: () => "4" }) },
1206
+ ],
1207
+ }),
1208
+ } as never;
1209
+
1210
+ const result = StringDeclHelper.generateStringDecl(
1211
+ typeCtx,
1212
+ "items",
1213
+ null,
1214
+ [],
1215
+ { extern: "", const: "", atomic: "", volatile: "" },
1216
+ false,
1217
+ defaultCallbacks,
1218
+ );
1219
+
1220
+ expect(result.handled).toBe(true);
1221
+ expect(result.code).toBe("char items[4][33] = {0};");
1222
+ });
1223
+
1224
+ it("generates string array from arrayType with initializer", () => {
1225
+ const callbacks = {
1226
+ ...defaultCallbacks,
1227
+ generateExpression: vi.fn(() => {
1228
+ CodeGenState.lastArrayInitCount = 2;
1229
+ CodeGenState.lastArrayFillValue = undefined;
1230
+ return '{"One", "Two"}';
1231
+ }),
1232
+ };
1233
+
1234
+ // Simulates: string<10>[2] labels <- ["One", "Two"];
1235
+ const typeCtx = {
1236
+ stringType: () => null,
1237
+ arrayType: () => ({
1238
+ stringType: () => ({
1239
+ INTEGER_LITERAL: () => ({ getText: () => "10" }),
1240
+ }),
1241
+ arrayTypeDimension: () => [
1242
+ { expression: () => ({ getText: () => "2" }) },
1243
+ ],
1244
+ }),
1245
+ } as never;
1246
+
1247
+ const expression = {
1248
+ getText: () => '["One", "Two"]',
1249
+ } as never;
1250
+
1251
+ const result = StringDeclHelper.generateStringDecl(
1252
+ typeCtx,
1253
+ "labels",
1254
+ expression,
1255
+ [],
1256
+ { extern: "", const: "", atomic: "", volatile: "" },
1257
+ false,
1258
+ callbacks,
1259
+ );
1260
+
1261
+ expect(result.handled).toBe(true);
1262
+ expect(result.code).toContain("[2]");
1263
+ expect(result.code).toContain("[11]"); // capacity + 1
1264
+ expect(result.code).toContain('{"One", "Two"}');
1265
+ });
1266
+
1267
+ it("generates string array from arrayType with trailing dimensions", () => {
1268
+ // Simulates: string<10>[2] matrix[3]; (2D string array)
1269
+ const typeCtx = {
1270
+ stringType: () => null,
1271
+ arrayType: () => ({
1272
+ stringType: () => ({
1273
+ INTEGER_LITERAL: () => ({ getText: () => "10" }),
1274
+ }),
1275
+ arrayTypeDimension: () => [
1276
+ { expression: () => ({ getText: () => "2" }) },
1277
+ ],
1278
+ }),
1279
+ } as never;
1280
+
1281
+ const trailingDims = [
1282
+ { expression: () => ({ getText: () => "3" }) },
1283
+ ] as never[];
1284
+
1285
+ const result = StringDeclHelper.generateStringDecl(
1286
+ typeCtx,
1287
+ "matrix",
1288
+ null,
1289
+ trailingDims,
1290
+ { extern: "", const: "", atomic: "", volatile: "" },
1291
+ false,
1292
+ defaultCallbacks,
1293
+ );
1294
+
1295
+ expect(result.handled).toBe(true);
1296
+ expect(result.code).toBe("char matrix[2][3][11] = {0};");
1297
+ });
1298
+
1299
+ it("throws error for unsized string array from arrayType", () => {
1300
+ // Simulates: string[4] items; (missing capacity)
1301
+ const typeCtx = {
1302
+ stringType: () => null,
1303
+ arrayType: () => ({
1304
+ stringType: () => ({
1305
+ INTEGER_LITERAL: () => null, // No capacity
1306
+ }),
1307
+ arrayTypeDimension: () => [
1308
+ { expression: () => ({ getText: () => "4" }) },
1309
+ ],
1310
+ }),
1311
+ } as never;
1312
+
1313
+ expect(() =>
1314
+ StringDeclHelper.generateStringDecl(
1315
+ typeCtx,
1316
+ "items",
1317
+ null,
1318
+ [],
1319
+ { extern: "", const: "", atomic: "", volatile: "" },
1320
+ false,
1321
+ defaultCallbacks,
1322
+ ),
1323
+ ).toThrow("String arrays require explicit capacity");
1324
+ });
1325
+
1326
+ it("validates element count matches declared size from arrayType", () => {
1327
+ const callbacks = {
1328
+ ...defaultCallbacks,
1329
+ generateExpression: vi.fn(() => {
1330
+ CodeGenState.lastArrayInitCount = 2; // Only 2 elements
1331
+ CodeGenState.lastArrayFillValue = undefined;
1332
+ return '{"One", "Two"}';
1333
+ }),
1334
+ };
1335
+
1336
+ // Simulates: string<10>[4] items <- ["One", "Two"]; (size mismatch)
1337
+ const typeCtx = {
1338
+ stringType: () => null,
1339
+ arrayType: () => ({
1340
+ stringType: () => ({
1341
+ INTEGER_LITERAL: () => ({ getText: () => "10" }),
1342
+ }),
1343
+ arrayTypeDimension: () => [
1344
+ { expression: () => ({ getText: () => "4" }) }, // Declared size = 4
1345
+ ],
1346
+ }),
1347
+ } as never;
1348
+
1349
+ const expression = {
1350
+ getText: () => '["One", "Two"]',
1351
+ } as never;
1352
+
1353
+ expect(() =>
1354
+ StringDeclHelper.generateStringDecl(
1355
+ typeCtx,
1356
+ "items",
1357
+ expression,
1358
+ [],
1359
+ { extern: "", const: "", atomic: "", volatile: "" },
1360
+ false,
1361
+ callbacks,
1362
+ ),
1363
+ ).toThrow("Array size mismatch - declared [4] but got 2 elements");
1364
+ });
1365
+
1366
+ it("tracks local arrays from arrayType in localArrays set", () => {
1367
+ const typeCtx = {
1368
+ stringType: () => null,
1369
+ arrayType: () => ({
1370
+ stringType: () => ({
1371
+ INTEGER_LITERAL: () => ({ getText: () => "20" }),
1372
+ }),
1373
+ arrayTypeDimension: () => [
1374
+ { expression: () => ({ getText: () => "3" }) },
1375
+ ],
1376
+ }),
1377
+ } as never;
1378
+
1379
+ StringDeclHelper.generateStringDecl(
1380
+ typeCtx,
1381
+ "tracked",
1382
+ null,
1383
+ [],
1384
+ { extern: "", const: "", atomic: "", volatile: "" },
1385
+ false,
1386
+ defaultCallbacks,
1387
+ );
1388
+
1389
+ expect(CodeGenState.localArrays.has("tracked")).toBe(true);
1390
+ });
1391
+
1392
+ it("generates string array from arrayType with modifiers", () => {
1393
+ const typeCtx = {
1394
+ stringType: () => null,
1395
+ arrayType: () => ({
1396
+ stringType: () => ({
1397
+ INTEGER_LITERAL: () => ({ getText: () => "8" }),
1398
+ }),
1399
+ arrayTypeDimension: () => [
1400
+ { expression: () => ({ getText: () => "2" }) },
1401
+ ],
1402
+ }),
1403
+ } as never;
1404
+
1405
+ const result = StringDeclHelper.generateStringDecl(
1406
+ typeCtx,
1407
+ "data",
1408
+ null,
1409
+ [],
1410
+ {
1411
+ extern: "extern ",
1412
+ const: "const ",
1413
+ atomic: "",
1414
+ volatile: "volatile ",
1415
+ },
1416
+ true,
1417
+ defaultCallbacks,
1418
+ );
1419
+
1420
+ expect(result.handled).toBe(true);
1421
+ expect(result.code).toContain("extern const volatile char data[2][9]");
1422
+ });
1423
+ });
1157
1424
  });
@@ -203,70 +203,6 @@ describe("SymbolLookupHelper", () => {
203
203
  });
204
204
  });
205
205
 
206
- describe("isCppType", () => {
207
- it("returns false when symbolTable is null", () => {
208
- expect(SymbolLookupHelper.isCppType(null, "MyType")).toBe(false);
209
- });
210
-
211
- it("returns false when symbolTable is undefined", () => {
212
- expect(SymbolLookupHelper.isCppType(undefined, "MyType")).toBe(false);
213
- });
214
-
215
- it("returns true for C++ struct", () => {
216
- const mockTable = {
217
- getOverloads: () => [
218
- { kind: "struct" as const, sourceLanguage: ESourceLanguage.Cpp },
219
- ],
220
- };
221
- expect(SymbolLookupHelper.isCppType(mockTable, "MyStruct")).toBe(true);
222
- });
223
-
224
- it("returns true for C++ class", () => {
225
- const mockTable = {
226
- getOverloads: () => [
227
- { kind: "class" as const, sourceLanguage: ESourceLanguage.Cpp },
228
- ],
229
- };
230
- expect(SymbolLookupHelper.isCppType(mockTable, "MyClass")).toBe(true);
231
- });
232
-
233
- it("returns true for C++ type alias", () => {
234
- const mockTable = {
235
- getOverloads: () => [
236
- { kind: "type" as const, sourceLanguage: ESourceLanguage.Cpp },
237
- ],
238
- };
239
- expect(SymbolLookupHelper.isCppType(mockTable, "MyType")).toBe(true);
240
- });
241
-
242
- it("returns false for C struct", () => {
243
- const mockTable = {
244
- getOverloads: () => [
245
- { kind: "struct" as const, sourceLanguage: ESourceLanguage.C },
246
- ],
247
- };
248
- expect(SymbolLookupHelper.isCppType(mockTable, "MyStruct")).toBe(false);
249
- });
250
-
251
- it("returns false for C++ function", () => {
252
- const mockTable = {
253
- getOverloads: () => [
254
- { kind: "function" as const, sourceLanguage: ESourceLanguage.Cpp },
255
- ],
256
- };
257
- expect(SymbolLookupHelper.isCppType(mockTable, "myFunc")).toBe(false);
258
- });
259
-
260
- it("returns false for C++ enum", () => {
261
- const mockTable = {
262
- getOverloads: () => [
263
- { kind: "enum" as const, sourceLanguage: ESourceLanguage.Cpp },
264
- ],
265
- };
266
- expect(SymbolLookupHelper.isCppType(mockTable, "MyEnum")).toBe(false);
267
- });
268
- });
269
-
270
206
  describe("isCNextFunction", () => {
271
207
  it("returns false when symbolTable is null", () => {
272
208
  expect(SymbolLookupHelper.isCNextFunction(null, "myFunc")).toBe(false);
@@ -183,5 +183,106 @@ describe("TypeRegistrationEngine", () => {
183
183
 
184
184
  expect(mockCallbacks.requireInclude).toHaveBeenCalledWith("string");
185
185
  });
186
+
187
+ it("registers string array types with correct dimensions (Issue #1029)", () => {
188
+ const source = `string<32>[4] items;`;
189
+ const tree = CNextSourceParser.parse(source).tree;
190
+
191
+ TypeRegistrationEngine.register(tree, mockCallbacks);
192
+
193
+ const info = CodeGenState.getVariableTypeInfo("items");
194
+ expect(info).not.toBeNull();
195
+ expect(info?.baseType).toBe("char");
196
+ expect(info?.isArray).toBe(true);
197
+ expect(info?.isString).toBe(true);
198
+ expect(info?.stringCapacity).toBe(32);
199
+ // Dimensions: [4] for array, [33] for string capacity + null terminator
200
+ expect(info?.arrayDimensions).toEqual([4, 33]);
201
+ });
202
+
203
+ it("registers multi-dimensional string arrays (Issue #1029)", () => {
204
+ const source = `string<16>[2][3] matrix;`;
205
+ const tree = CNextSourceParser.parse(source).tree;
206
+
207
+ TypeRegistrationEngine.register(tree, mockCallbacks);
208
+
209
+ const info = CodeGenState.getVariableTypeInfo("matrix");
210
+ expect(info).not.toBeNull();
211
+ expect(info?.baseType).toBe("char");
212
+ expect(info?.isArray).toBe(true);
213
+ expect(info?.isString).toBe(true);
214
+ expect(info?.stringCapacity).toBe(16);
215
+ // Dimensions: [2][3] for array, [17] for string capacity + null terminator
216
+ expect(info?.arrayDimensions).toEqual([2, 3, 17]);
217
+ });
218
+
219
+ it("requires string include for string array types (Issue #1029)", () => {
220
+ const source = `string<32>[4] items;`;
221
+ const tree = CNextSourceParser.parse(source).tree;
222
+
223
+ TypeRegistrationEngine.register(tree, mockCallbacks);
224
+
225
+ expect(mockCallbacks.requireInclude).toHaveBeenCalledWith("string");
226
+ });
227
+
228
+ it("registers global type arrays", () => {
229
+ // Set up a scope context to test global.Type[N] pattern
230
+ CodeGenState.currentScope = "Motor";
231
+
232
+ const source = `
233
+ scope Motor {
234
+ enum State { OFF, ON }
235
+ global.State[3] states;
236
+ }
237
+ `;
238
+ const tree = CNextSourceParser.parse(source).tree;
239
+
240
+ TypeRegistrationEngine.register(tree, mockCallbacks);
241
+
242
+ const info = CodeGenState.getVariableTypeInfo("Motor_states");
243
+ expect(info).not.toBeNull();
244
+ expect(info?.baseType).toBe("State");
245
+ expect(info?.isArray).toBe(true);
246
+
247
+ CodeGenState.currentScope = null;
248
+ });
249
+
250
+ it("registers qualified type arrays (Scope.Type[N])", () => {
251
+ const source = `
252
+ scope Motor {
253
+ public enum State { OFF, ON }
254
+ }
255
+ Motor.State[4] allStates;
256
+ `;
257
+ const tree = CNextSourceParser.parse(source).tree;
258
+
259
+ TypeRegistrationEngine.register(tree, mockCallbacks);
260
+
261
+ const info = CodeGenState.getVariableTypeInfo("allStates");
262
+ expect(info).not.toBeNull();
263
+ expect(info?.baseType).toBe("Motor_State");
264
+ expect(info?.isArray).toBe(true);
265
+ });
266
+
267
+ it("registers scoped type arrays (this.Type[N])", () => {
268
+ CodeGenState.currentScope = "Motor";
269
+
270
+ const source = `
271
+ scope Motor {
272
+ enum State { OFF, ON }
273
+ this.State[2] localStates;
274
+ }
275
+ `;
276
+ const tree = CNextSourceParser.parse(source).tree;
277
+
278
+ TypeRegistrationEngine.register(tree, mockCallbacks);
279
+
280
+ const info = CodeGenState.getVariableTypeInfo("Motor_localStates");
281
+ expect(info).not.toBeNull();
282
+ expect(info?.baseType).toBe("Motor_State");
283
+ expect(info?.isArray).toBe(true);
284
+
285
+ CodeGenState.currentScope = null;
286
+ });
186
287
  });
187
288
  });
@@ -127,8 +127,8 @@ describe("VariableDeclHelper", () => {
127
127
  }).not.toThrow();
128
128
  });
129
129
 
130
- it("allows empty dimension for size inference", () => {
131
- const varDecl = parseVarDecl("u8 arr[] <- [1, 2, 3];");
130
+ it("allows empty dimension for size inference in arrayType", () => {
131
+ const varDecl = parseVarDecl("u8[] arr <- [1, 2, 3];");
132
132
  const typeCtx = varDecl.type();
133
133
  expect(() => {
134
134
  VariableDeclHelper.validateArrayDeclarationSyntax(
@@ -139,7 +139,19 @@ describe("VariableDeclHelper", () => {
139
139
  }).not.toThrow();
140
140
  });
141
141
 
142
- it("allows multi-dimensional C-style", () => {
142
+ it("rejects empty dimension with C-style trailing brackets (Issue #1017)", () => {
143
+ const varDecl = parseVarDecl("u8 arr[] <- [1, 2, 3];");
144
+ const typeCtx = varDecl.type();
145
+ expect(() => {
146
+ VariableDeclHelper.validateArrayDeclarationSyntax(
147
+ varDecl,
148
+ typeCtx,
149
+ "arr",
150
+ );
151
+ }).toThrow("C-style array declaration is not allowed");
152
+ });
153
+
154
+ it("rejects multi-dimensional C-style (Issue #1014)", () => {
143
155
  const varDecl = parseVarDecl("u8 matrix[4][4];");
144
156
  const typeCtx = varDecl.type();
145
157
  expect(() => {
@@ -148,10 +160,22 @@ describe("VariableDeclHelper", () => {
148
160
  typeCtx,
149
161
  "matrix",
150
162
  );
163
+ }).toThrow("C-style array declaration is not allowed");
164
+ });
165
+
166
+ it("allows string type with arrayType syntax", () => {
167
+ const varDecl = parseVarDecl("string<32>[4] names;");
168
+ const typeCtx = varDecl.type();
169
+ expect(() => {
170
+ VariableDeclHelper.validateArrayDeclarationSyntax(
171
+ varDecl,
172
+ typeCtx,
173
+ "names",
174
+ );
151
175
  }).not.toThrow();
152
176
  });
153
177
 
154
- it("allows string type with C-style", () => {
178
+ it("rejects string type with C-style trailing brackets (Issue #1016)", () => {
155
179
  const varDecl = parseVarDecl("string<32> names[4];");
156
180
  const typeCtx = varDecl.type();
157
181
  expect(() => {
@@ -160,7 +184,7 @@ describe("VariableDeclHelper", () => {
160
184
  typeCtx,
161
185
  "names",
162
186
  );
163
- }).not.toThrow();
187
+ }).toThrow("C-style array declaration is not allowed");
164
188
  });
165
189
 
166
190
  it("rejects C-style single dimension for primitives", () => {
@@ -10,7 +10,8 @@ interface ICallbackTypeInfo {
10
10
  name: string;
11
11
  type: string; // C type
12
12
  isConst: boolean;
13
- isPointer: boolean; // Non-array params become pointers
13
+ isPointer: boolean; // Non-array params become pointers (C mode only for structs)
14
+ isStruct: boolean; // True if parameter type is a struct (ADR-006 reference semantics)
14
15
  isArray: boolean; // Array parameters pass naturally as pointers
15
16
  arrayDims: string; // Array dimensions if applicable
16
17
  }>;
@@ -66,6 +66,13 @@ interface IParameterInput {
66
66
  * When the C typedef has `const T*`, this preserves const on the generated param.
67
67
  */
68
68
  forceConst?: boolean;
69
+
70
+ /**
71
+ * Issue #995: Whether the parameter type is an opaque handle (incomplete struct typedef).
72
+ * This is a pass-through flag; the rule (suppress auto-const, force pointer) is applied
73
+ * in ParameterSignatureBuilder to avoid dual code paths.
74
+ */
75
+ isOpaqueHandle?: boolean;
69
76
  }
70
77
 
71
78
  export default IParameterInput;
@@ -116,6 +116,14 @@ abstract class BaseHeaderGenerator {
116
116
  ...HeaderGeneratorUtils.generateEnumSection(groups.enums, typeInput),
117
117
  ...HeaderGeneratorUtils.generateBitmapSection(groups.bitmaps, typeInput),
118
118
  ...HeaderGeneratorUtils.generateTypeAliasSection(groups.types),
119
+ ...HeaderGeneratorUtils.generateCallbackStructForwardDecls(
120
+ groups.structs,
121
+ typeInput,
122
+ ),
123
+ ...HeaderGeneratorUtils.generateCallbackTypedefSection(
124
+ typeInput,
125
+ options.cppMode,
126
+ ),
119
127
  ...HeaderGeneratorUtils.generateStructSection(
120
128
  groups.structs,
121
129
  groups.classes,
@@ -416,6 +416,81 @@ class HeaderGeneratorUtils {
416
416
  return lines;
417
417
  }
418
418
 
419
+ /**
420
+ * ADR-029: Generate forward declarations for structs used in callback typedefs.
421
+ * This ensures struct types can be used in callback parameter types before
422
+ * the full struct definition.
423
+ */
424
+ static generateCallbackStructForwardDecls(
425
+ structs: IHeaderSymbol[],
426
+ typeInput?: IHeaderTypeInput,
427
+ ): string[] {
428
+ if (!typeInput?.callbackTypes || typeInput.callbackTypes.size === 0) {
429
+ return [];
430
+ }
431
+
432
+ // Collect struct types that are used in callback parameters
433
+ const usedStructTypes = new Set<string>();
434
+ for (const [, cbInfo] of typeInput.callbackTypes) {
435
+ for (const p of cbInfo.parameters) {
436
+ if (p.isStruct) {
437
+ usedStructTypes.add(p.type);
438
+ }
439
+ }
440
+ }
441
+
442
+ if (usedStructTypes.size === 0) {
443
+ return [];
444
+ }
445
+
446
+ // Get local struct names to only forward-declare those
447
+ const localStructNames = new Set(structs.map((s) => s.name));
448
+
449
+ const lines: string[] = [];
450
+ for (const structType of usedStructTypes) {
451
+ if (localStructNames.has(structType)) {
452
+ lines.push(`typedef struct ${structType} ${structType};`);
453
+ }
454
+ }
455
+
456
+ return lines.length > 0 ? [...lines, ""] : [];
457
+ }
458
+
459
+ /**
460
+ * ADR-029: Generate callback typedef section
461
+ * Generates function pointer typedefs for callbacks used as struct field types
462
+ */
463
+ static generateCallbackTypedefSection(
464
+ typeInput?: IHeaderTypeInput,
465
+ isCppMode?: boolean,
466
+ ): string[] {
467
+ if (!typeInput?.callbackTypes || typeInput.callbackTypes.size === 0) {
468
+ return [];
469
+ }
470
+
471
+ const lines: string[] = ["/* Callback typedefs */"];
472
+ for (const [, cbInfo] of typeInput.callbackTypes) {
473
+ const params =
474
+ cbInfo.parameters.length > 0
475
+ ? cbInfo.parameters
476
+ .map((p) => {
477
+ // ADR-006: Struct parameters become pointers (C) or references (C++)
478
+ if (p.isStruct) {
479
+ const ptrOrRef = isCppMode ? "&" : "*";
480
+ return `${p.type}${ptrOrRef}`;
481
+ }
482
+ return p.type;
483
+ })
484
+ .join(", ")
485
+ : "void";
486
+ lines.push(
487
+ `typedef ${cbInfo.returnType} (*${cbInfo.typedefName})(${params});`,
488
+ );
489
+ }
490
+ lines.push("");
491
+ return lines;
492
+ }
493
+
419
494
  /**
420
495
  * Generate struct and class definitions section
421
496
  */