c-next 0.2.14 → 0.2.15

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "c-next",
3
- "version": "0.2.14",
3
+ "version": "0.2.15",
4
4
  "description": "A safer C for embedded systems development. Transpiles to clean, readable C.",
5
5
  "packageManager": "npm@11.9.0",
6
6
  "type": "module",
@@ -182,6 +182,34 @@ describe("CResolver - Variable Declarations", () => {
182
182
  }
183
183
  });
184
184
  });
185
+
186
+ it("detects pointer type in variable declaration (Issue #978)", () => {
187
+ const tree = TestHelpers.parseC(
188
+ `typedef struct { int x; } point_t;\nextern const point_t *origin;`,
189
+ );
190
+ const result = CResolver.resolve(tree!, "test.h");
191
+
192
+ const varSymbol = result.symbols.find((s) => s.name === "origin");
193
+ expect(varSymbol).toBeDefined();
194
+ expect(varSymbol?.kind).toBe("variable");
195
+ if (varSymbol?.kind === "variable") {
196
+ expect(varSymbol.type).toBe("point_t*");
197
+ }
198
+ });
199
+
200
+ it("does not add pointer suffix for non-pointer variables (Issue #978)", () => {
201
+ const tree = TestHelpers.parseC(
202
+ `typedef struct { int x; } point_t;\nextern const point_t origin;`,
203
+ );
204
+ const result = CResolver.resolve(tree!, "test.h");
205
+
206
+ const varSymbol = result.symbols.find((s) => s.name === "origin");
207
+ expect(varSymbol).toBeDefined();
208
+ expect(varSymbol?.kind).toBe("variable");
209
+ if (varSymbol?.kind === "variable") {
210
+ expect(varSymbol.type).toBe("point_t");
211
+ }
212
+ });
185
213
  });
186
214
 
187
215
  describe("CResolver - Typedefs", () => {
@@ -32,6 +32,13 @@ class VariableCollector {
32
32
  ? DeclaratorUtils.extractArrayDimensions(declarator)
33
33
  : [];
34
34
 
35
+ // Issue #978: Detect pointer variables (e.g., `font_t *ptr`).
36
+ // C grammar puts `*` in the declarator, not the type specifier.
37
+ // Same pattern as FunctionCollector._resolveReturnType().
38
+ const hasPointer =
39
+ declarator?.pointer?.() !== null && declarator?.pointer?.() !== undefined;
40
+ const resolvedType = hasPointer ? `${baseType}*` : baseType;
41
+
35
42
  return {
36
43
  kind: "variable",
37
44
  name,
@@ -39,7 +46,7 @@ class VariableCollector {
39
46
  sourceLine: line,
40
47
  sourceLanguage: ESourceLanguage.C,
41
48
  isExported: !isExtern,
42
- type: baseType,
49
+ type: resolvedType,
43
50
  isArray: arrayDimensions.length > 0,
44
51
  arrayDimensions: arrayDimensions.length > 0 ? arrayDimensions : undefined,
45
52
  isExtern,
@@ -50,6 +57,10 @@ class VariableCollector {
50
57
  * Collect a variable from declaration specifiers (when identifier appears as typedefName).
51
58
  * This handles the C grammar ambiguity where variable names can be parsed as typedef names.
52
59
  *
60
+ * Note: No pointer detection here — this path handles declarations without an
61
+ * initDeclaratorList. Pointer declarations (e.g., `font_t *ptr`) always produce
62
+ * an initDeclaratorList (the `*` creates a declarator), so they go through collect().
63
+ *
53
64
  * @param name Variable name
54
65
  * @param baseType Variable type
55
66
  * @param sourceFile Source file path
@@ -554,13 +554,31 @@ export default class CodeGenState {
554
554
  }
555
555
 
556
556
  // ADR-055 Phase 7: Fall back to SymbolTable for cross-file C-Next variables only.
557
- // C/C++ header symbols don't have complete type info (e.g., isArray),
558
- // so we only use C-Next TSymbols from SymbolTable.
559
557
  const symbol = this.symbolTable.getTSymbol(name);
560
558
  if (symbol?.kind === "variable" && symbol.type) {
561
559
  return this.convertTSymbolToTypeInfo(symbol);
562
560
  }
563
561
 
562
+ // Issue #978: Fall back to C symbols for external struct globals from .h headers.
563
+ // Only return type info for struct-typed variables — returning info for all
564
+ // C types would cause regressions (e.g., array indexing misread as bit extraction).
565
+ const cSymbol = this.symbolTable.getCSymbol(name);
566
+ if (cSymbol?.kind === "variable" && cSymbol.type) {
567
+ const baseType = CodeGenState.stripTrailingPointers(cSymbol.type);
568
+ if (
569
+ this.symbolTable.isTypedefStructType(baseType) ||
570
+ this.symbolTable.getStructFields(baseType)
571
+ ) {
572
+ return {
573
+ baseType,
574
+ bitWidth: 0,
575
+ isArray: cSymbol.isArray || false,
576
+ isConst: cSymbol.isConst || false,
577
+ isPointer: cSymbol.type.endsWith("*"),
578
+ };
579
+ }
580
+ }
581
+
564
582
  return undefined;
565
583
  }
566
584
 
@@ -581,7 +599,19 @@ export default class CodeGenState {
581
599
  return true;
582
600
  }
583
601
  const symbol = this.symbolTable.getTSymbol(name);
584
- return symbol?.kind === "variable" && symbol.type !== undefined;
602
+ if (symbol?.kind === "variable" && symbol.type !== undefined) {
603
+ return true;
604
+ }
605
+ // Issue #978: Check C symbols for external struct globals only
606
+ const cSymbol = this.symbolTable.getCSymbol(name);
607
+ if (cSymbol?.kind === "variable" && cSymbol.type) {
608
+ const baseType = CodeGenState.stripTrailingPointers(cSymbol.type);
609
+ return (
610
+ this.symbolTable.isTypedefStructType(baseType) ||
611
+ !!this.symbolTable.getStructFields(baseType)
612
+ );
613
+ }
614
+ return false;
585
615
  }
586
616
 
587
617
  /**
@@ -611,6 +641,18 @@ export default class CodeGenState {
611
641
  * Convert a TSymbol IVariableSymbol to TTypeInfo for unified type lookups.
612
642
  * ADR-055 Phase 7: Works with typed TSymbol instead of ISymbol.
613
643
  */
644
+ /**
645
+ * Strip trailing pointer stars from a C type string (e.g., "font_t*" → "font_t").
646
+ * Uses string operations instead of regex to avoid SonarCloud ReDoS flag (S5852).
647
+ */
648
+ private static stripTrailingPointers(type: string): string {
649
+ let end = type.length;
650
+ while (end > 0 && type[end - 1] === "*") {
651
+ end--;
652
+ }
653
+ return type.slice(0, end).trim();
654
+ }
655
+
614
656
  private static convertTSymbolToTypeInfo(
615
657
  symbol: import("../types/symbols/IVariableSymbol").default,
616
658
  ): TTypeInfo {
@@ -105,6 +105,7 @@ function createMockSymbols(
105
105
  describe("CodeGenState", () => {
106
106
  beforeEach(() => {
107
107
  CodeGenState.reset();
108
+ CodeGenState.symbolTable.clear();
108
109
  });
109
110
 
110
111
  describe("reset()", () => {
@@ -435,8 +436,8 @@ describe("CodeGenState", () => {
435
436
  expect(result?.arrayDimensions).toEqual([10]);
436
437
  });
437
438
 
438
- it("getVariableTypeInfo does not use C header symbols", () => {
439
- // Add a C header variable (should NOT be used)
439
+ it("getVariableTypeInfo does not use C header symbols for primitive types", () => {
440
+ // Add a C header variable with primitive type (should NOT be used)
440
441
  CodeGenState.symbolTable.addCSymbol(
441
442
  createCVariableSymbol({
442
443
  name: "cHeaderVar",
@@ -447,6 +448,110 @@ describe("CodeGenState", () => {
447
448
  expect(CodeGenState.getVariableTypeInfo("cHeaderVar")).toBeUndefined();
448
449
  });
449
450
 
451
+ it("getVariableTypeInfo returns type info for C header struct variables (Issue #978)", () => {
452
+ // Register font_t as a typedef struct type
453
+ CodeGenState.symbolTable.markTypedefStructType("font_t", "fake_lib.h");
454
+
455
+ // Add a C header variable with struct type
456
+ CodeGenState.symbolTable.addCSymbol(
457
+ createCVariableSymbol({
458
+ name: "big_font",
459
+ type: "font_t",
460
+ isConst: true,
461
+ }),
462
+ );
463
+
464
+ const result = CodeGenState.getVariableTypeInfo("big_font");
465
+ expect(result).toBeDefined();
466
+ expect(result?.baseType).toBe("font_t");
467
+ expect(result?.isConst).toBe(true);
468
+ expect(result?.bitWidth).toBe(0);
469
+ expect(result?.isPointer).toBe(false);
470
+ });
471
+
472
+ it("getVariableTypeInfo returns type info for C struct via getStructFields path (Issue #978)", () => {
473
+ // Register struct fields directly (non-typedef struct, e.g., `struct point`)
474
+ CodeGenState.symbolTable.addStructField("point", "x", "int32_t");
475
+ CodeGenState.symbolTable.addStructField("point", "y", "int32_t");
476
+
477
+ CodeGenState.symbolTable.addCSymbol(
478
+ createCVariableSymbol({
479
+ name: "origin",
480
+ type: "point",
481
+ }),
482
+ );
483
+
484
+ const result = CodeGenState.getVariableTypeInfo("origin");
485
+ expect(result).toBeDefined();
486
+ expect(result?.baseType).toBe("point");
487
+ expect(result?.bitWidth).toBe(0);
488
+ });
489
+
490
+ it("getVariableTypeInfo detects pointer type from C symbol (Issue #978)", () => {
491
+ // Register font_t as a struct
492
+ CodeGenState.symbolTable.markTypedefStructType("font_t", "lib.h");
493
+
494
+ // Pointer variable: type includes * (set by VariableCollector)
495
+ CodeGenState.symbolTable.addCSymbol(
496
+ createCVariableSymbol({
497
+ name: "font_ptr",
498
+ type: "font_t*",
499
+ }),
500
+ );
501
+
502
+ const result = CodeGenState.getVariableTypeInfo("font_ptr");
503
+ expect(result).toBeDefined();
504
+ expect(result?.baseType).toBe("font_t");
505
+ expect(result?.isPointer).toBe(true);
506
+ });
507
+
508
+ it("getVariableTypeInfo ignores C array variables with primitive types", () => {
509
+ CodeGenState.symbolTable.addCSymbol(
510
+ createCVariableSymbol({
511
+ name: "lookup_table",
512
+ type: "uint8_t",
513
+ isArray: true,
514
+ arrayDimensions: [16],
515
+ }),
516
+ );
517
+
518
+ expect(CodeGenState.getVariableTypeInfo("lookup_table")).toBeUndefined();
519
+ });
520
+
521
+ it("getVariableTypeInfo ignores C volatile register variables", () => {
522
+ CodeGenState.symbolTable.addCSymbol(
523
+ createCVariableSymbol({
524
+ name: "status_reg",
525
+ type: "uint32_t",
526
+ }),
527
+ );
528
+
529
+ expect(CodeGenState.getVariableTypeInfo("status_reg")).toBeUndefined();
530
+ });
531
+
532
+ it("getVariableTypeInfo prefers TSymbol over CSymbol with same name (Issue #978)", () => {
533
+ // Both C-Next and C symbols exist with same name
534
+ CodeGenState.symbolTable.markTypedefStructType("config_t", "config.h");
535
+
536
+ CodeGenState.symbolTable.addTSymbol(
537
+ createCNextVariableSymbol({
538
+ name: "config",
539
+ type: TTypeUtils.createPrimitive("u32"),
540
+ }),
541
+ );
542
+
543
+ CodeGenState.symbolTable.addCSymbol(
544
+ createCVariableSymbol({
545
+ name: "config",
546
+ type: "config_t",
547
+ }),
548
+ );
549
+
550
+ // TSymbol should win (checked first in priority order)
551
+ const result = CodeGenState.getVariableTypeInfo("config");
552
+ expect(result?.baseType).toBe("u32");
553
+ });
554
+
450
555
  it("getVariableTypeInfo prefers local registry over SymbolTable", () => {
451
556
  // Add both local and SymbolTable version
452
557
  const localInfo: TTypeInfo = {
@@ -496,7 +601,7 @@ describe("CodeGenState", () => {
496
601
  expect(CodeGenState.hasVariableTypeInfo("unknownVar")).toBe(false);
497
602
  });
498
603
 
499
- it("hasVariableTypeInfo returns false for C header variable", () => {
604
+ it("hasVariableTypeInfo returns false for C header primitive variable", () => {
500
605
  CodeGenState.symbolTable.addCSymbol(
501
606
  createCVariableSymbol({
502
607
  name: "cVar",
@@ -507,6 +612,43 @@ describe("CodeGenState", () => {
507
612
  expect(CodeGenState.hasVariableTypeInfo("cVar")).toBe(false);
508
613
  });
509
614
 
615
+ it("hasVariableTypeInfo returns true for C header struct variable (Issue #978)", () => {
616
+ CodeGenState.symbolTable.markTypedefStructType("widget_t", "widget.h");
617
+ CodeGenState.symbolTable.addCSymbol(
618
+ createCVariableSymbol({
619
+ name: "my_widget",
620
+ type: "widget_t",
621
+ }),
622
+ );
623
+
624
+ expect(CodeGenState.hasVariableTypeInfo("my_widget")).toBe(true);
625
+ });
626
+
627
+ it("hasVariableTypeInfo returns true for C struct via getStructFields path (Issue #978)", () => {
628
+ CodeGenState.symbolTable.addStructField("vec2", "x", "float");
629
+ CodeGenState.symbolTable.addStructField("vec2", "y", "float");
630
+
631
+ CodeGenState.symbolTable.addCSymbol(
632
+ createCVariableSymbol({
633
+ name: "position",
634
+ type: "vec2",
635
+ }),
636
+ );
637
+
638
+ expect(CodeGenState.hasVariableTypeInfo("position")).toBe(true);
639
+ });
640
+
641
+ it("hasVariableTypeInfo returns false for C pointer to non-struct type", () => {
642
+ CodeGenState.symbolTable.addCSymbol(
643
+ createCVariableSymbol({
644
+ name: "data_ptr",
645
+ type: "uint8_t*",
646
+ }),
647
+ );
648
+
649
+ expect(CodeGenState.hasVariableTypeInfo("data_ptr")).toBe(false);
650
+ });
651
+
510
652
  it("setVariableTypeInfo and deleteVariableTypeInfo work correctly", () => {
511
653
  const typeInfo: TTypeInfo = {
512
654
  baseType: "f32",