c-next 0.2.11 → 0.2.13

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 (75) hide show
  1. package/README.md +80 -649
  2. package/dist/index.js +8273 -6357
  3. package/dist/index.js.map +4 -4
  4. package/grammar/C.g4 +17 -4
  5. package/package.json +3 -3
  6. package/src/__tests__/index.test.ts +1 -1
  7. package/src/cli/CleanCommand.ts +8 -12
  8. package/src/cli/Cli.ts +29 -6
  9. package/src/cli/Runner.ts +42 -62
  10. package/src/cli/__tests__/CleanCommand.test.ts +10 -10
  11. package/src/cli/__tests__/Cli.test.ts +59 -7
  12. package/src/cli/__tests__/ConfigPrinter.test.ts +12 -12
  13. package/src/cli/__tests__/PathNormalizer.test.ts +5 -5
  14. package/src/cli/__tests__/Runner.test.ts +108 -82
  15. package/src/cli/serve/ServeCommand.ts +1 -1
  16. package/src/cli/types/ICliConfig.ts +2 -2
  17. package/src/lib/parseWithSymbols.ts +21 -21
  18. package/src/transpiler/Transpiler.ts +99 -46
  19. package/src/transpiler/__tests__/DualCodePaths.test.ts +29 -29
  20. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +244 -72
  21. package/src/transpiler/__tests__/Transpiler.test.ts +32 -72
  22. package/src/transpiler/__tests__/determineProjectRoot.test.ts +30 -28
  23. package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +1 -1
  24. package/src/transpiler/data/CNextMarkerDetector.ts +34 -0
  25. package/src/transpiler/data/CppEntryPointScanner.ts +174 -0
  26. package/src/transpiler/data/FileDiscovery.ts +2 -105
  27. package/src/transpiler/data/InputExpansion.ts +37 -81
  28. package/src/transpiler/data/__tests__/CNextMarkerDetector.test.ts +62 -0
  29. package/src/transpiler/data/__tests__/CppEntryPointScanner.test.ts +239 -0
  30. package/src/transpiler/data/__tests__/FileDiscovery.test.ts +45 -191
  31. package/src/transpiler/data/__tests__/InputExpansion.test.ts +36 -204
  32. package/src/transpiler/logic/analysis/InitializationAnalyzer.ts +2 -2
  33. package/src/transpiler/logic/analysis/PassByValueAnalyzer.ts +4 -5
  34. package/src/transpiler/logic/parser/c/grammar/C.interp +33 -3
  35. package/src/transpiler/logic/parser/c/grammar/C.tokens +237 -207
  36. package/src/transpiler/logic/parser/c/grammar/CLexer.interp +48 -3
  37. package/src/transpiler/logic/parser/c/grammar/CLexer.tokens +237 -207
  38. package/src/transpiler/logic/parser/c/grammar/CLexer.ts +702 -611
  39. package/src/transpiler/logic/parser/c/grammar/CParser.ts +1221 -1107
  40. package/src/transpiler/logic/symbols/SymbolTable.ts +147 -73
  41. package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +157 -14
  42. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +3 -3
  43. package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +7 -37
  44. package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +6 -6
  45. package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +28 -27
  46. package/src/transpiler/logic/symbols/cnext/index.ts +4 -4
  47. package/src/transpiler/logic/symbols/cnext/utils/SymbolNameUtils.ts +5 -5
  48. package/src/transpiler/output/codegen/CodeGenerator.ts +16 -1
  49. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +15 -0
  50. package/src/transpiler/output/codegen/__tests__/ExpressionWalker.test.ts +3 -3
  51. package/src/transpiler/output/codegen/__tests__/RequireInclude.test.ts +14 -14
  52. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +2 -2
  53. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +15 -3
  54. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +14 -6
  55. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +1 -0
  56. package/src/transpiler/output/codegen/types/IFunctionContextCallbacks.ts +2 -0
  57. package/src/transpiler/output/codegen/utils/QualifiedNameGenerator.ts +7 -7
  58. package/src/transpiler/output/codegen/utils/__tests__/QualifiedNameGenerator.test.ts +3 -3
  59. package/src/transpiler/output/headers/BaseHeaderGenerator.ts +10 -1
  60. package/src/transpiler/output/headers/HeaderGenerator.ts +3 -0
  61. package/src/transpiler/output/headers/HeaderGeneratorUtils.ts +6 -2
  62. package/src/transpiler/output/headers/__tests__/HeaderGeneratorUtils.test.ts +16 -0
  63. package/src/transpiler/output/headers/adapters/HeaderSymbolAdapter.ts +19 -19
  64. package/src/transpiler/output/headers/adapters/__tests__/HeaderSymbolAdapter.test.ts +5 -5
  65. package/src/transpiler/state/SymbolRegistry.ts +10 -12
  66. package/src/transpiler/state/__tests__/SymbolRegistry.test.ts +11 -13
  67. package/src/transpiler/types/ICachedFileEntry.ts +4 -0
  68. package/src/transpiler/types/IPipelineFile.ts +3 -0
  69. package/src/transpiler/types/ITranspilerConfig.ts +2 -2
  70. package/src/transpiler/types/symbols/IScopeSymbol.ts +1 -1
  71. package/src/utils/FunctionUtils.ts +3 -3
  72. package/src/utils/__tests__/FunctionUtils.test.ts +6 -4
  73. package/src/utils/cache/CacheManager.ts +28 -15
  74. package/src/utils/cache/__tests__/CacheManager.test.ts +6 -4
  75. package/src/transpiler/data/types/IDiscoveryOptions.ts +0 -15
@@ -8,6 +8,7 @@
8
8
  * - TCppSymbol: C++ header symbols (string types)
9
9
  */
10
10
 
11
+ import { produce, enableMapSet } from "immer";
11
12
  import ESourceLanguage from "../../../utils/types/ESourceLanguage";
12
13
  import LiteralUtils from "../../../utils/LiteralUtils";
13
14
  import IConflict from "../../types/IConflict";
@@ -23,6 +24,38 @@ import IVariableSymbol from "../../types/symbols/IVariableSymbol";
23
24
  import TypeResolver from "../../../utils/TypeResolver";
24
25
  import SymbolNameUtils from "./cnext/utils/SymbolNameUtils";
25
26
 
27
+ // Enable immer support for Map and Set (must be called once at module scope)
28
+ enableMapSet();
29
+
30
+ /**
31
+ * Issue #958: Immutable struct symbol state managed via immer produce().
32
+ * All mutations are additive-only — no unmark/delete operations.
33
+ * Resolution (e.g., "is this type truly opaque?") happens at query time.
34
+ */
35
+ interface IStructSymbolState {
36
+ /** Typedef names declared with forward-declared structs (additive only) */
37
+ opaqueTypes: Set<string>;
38
+ /** ALL typedef struct types from C headers: name → sourceFile (additive only) */
39
+ typedefStructTypes: Map<string, string>;
40
+ /** Struct tag → typedef name (e.g., "_widget_t" → "widget_t") */
41
+ structTagAliases: Map<string, string>;
42
+ /** Typedef name → struct tag (reverse of structTagAliases) */
43
+ typedefToTag: Map<string, string>;
44
+ /** Struct tags that have full definitions (bodies) */
45
+ structTagsWithBodies: Set<string>;
46
+ }
47
+
48
+ /** Create a fresh initial struct symbol state */
49
+ function createInitialStructState(): IStructSymbolState {
50
+ return {
51
+ opaqueTypes: new Set(),
52
+ typedefStructTypes: new Map(),
53
+ structTagAliases: new Map(),
54
+ typedefToTag: new Map(),
55
+ structTagsWithBodies: new Set(),
56
+ };
57
+ }
58
+
26
59
  /**
27
60
  * Central symbol table for cross-language interoperability
28
61
  *
@@ -78,27 +111,10 @@ class SymbolTable {
78
111
  private readonly needsStructKeyword: Set<string> = new Set();
79
112
 
80
113
  /**
81
- * Issue #948: Track typedef names that alias incomplete (forward-declared) struct types.
82
- * These are "opaque" types that can only be used as pointers.
114
+ * Issue #958: Immutable struct symbol state additive only, query-time resolution.
115
+ * Replaces separate opaqueTypes, typedefStructTypes, structTagAliases fields.
83
116
  */
84
- private readonly opaqueTypes: Set<string> = new Set();
85
-
86
- /**
87
- * Issue #958: Track typedef struct type names with their source files.
88
- * Maps typeName -> sourceFile. Used for scope variables which should be pointers
89
- * when the struct definition comes from a different file than the typedef.
90
- * If definition is in the same file as typedef, the entry is removed (value type).
91
- * If definition is in a different file, the entry remains (pointer type).
92
- */
93
- private readonly typedefStructTypes: Map<string, string> = new Map();
94
-
95
- /**
96
- * Issue #948: Track struct tag -> typedef name relationships.
97
- * When a typedef declares an alias for a struct tag (e.g., typedef struct _foo foo_t),
98
- * we record structTagAliases["_foo"] = "foo_t". This allows us to unmark the typedef
99
- * as opaque when the full struct definition is later found.
100
- */
101
- private readonly structTagAliases: Map<string, string> = new Map();
117
+ private structState: IStructSymbolState = createInitialStructState();
102
118
 
103
119
  /**
104
120
  * Issue #208: Track enum backing type bit widths
@@ -141,7 +157,7 @@ class SymbolTable {
141
157
  * Called automatically when adding struct symbols.
142
158
  */
143
159
  private registerStructFields(struct: IStructSymbol): void {
144
- const mangledName = SymbolNameUtils.getMangledName(struct);
160
+ const cName = SymbolNameUtils.getTranspiledCName(struct);
145
161
 
146
162
  for (const [fieldName, fieldInfo] of struct.fields) {
147
163
  // Convert TType to string for structFields map
@@ -153,7 +169,7 @@ class SymbolTable {
153
169
  );
154
170
 
155
171
  this.addStructField(
156
- mangledName,
172
+ cName,
157
173
  fieldName,
158
174
  typeString,
159
175
  numericDims && numericDims.length > 0 ? numericDims : undefined,
@@ -635,17 +651,29 @@ class SymbolTable {
635
651
  (s) => s.sourceLanguage === ESourceLanguage.Cpp,
636
652
  );
637
653
 
638
- if (cnextDefs.length > 0 && (cDefs.length > 0 || cppDefs.length > 0)) {
639
- const locations = globalDefinitions.map(
654
+ // Issue #967: Only global-scope C-Next symbols can conflict with C/C++ symbols.
655
+ // Scoped symbols (e.g., Touch.read) live in a namespace and don't compete
656
+ // with C's global symbols (e.g., POSIX read()).
657
+ const conflictingCnextDefs = cnextDefs.filter((s) => {
658
+ const tSymbol = s as TSymbol;
659
+ return tSymbol.scope.name === "";
660
+ });
661
+
662
+ if (
663
+ conflictingCnextDefs.length > 0 &&
664
+ (cDefs.length > 0 || cppDefs.length > 0)
665
+ ) {
666
+ const conflictingDefs = [...conflictingCnextDefs, ...cDefs, ...cppDefs];
667
+ const locations = conflictingDefs.map(
640
668
  (s) =>
641
669
  `${s.sourceLanguage.toUpperCase()} (${s.sourceFile}:${s.sourceLine})`,
642
670
  );
643
671
 
644
672
  return {
645
- symbolName: globalDefinitions[0].name,
646
- definitions: globalDefinitions,
673
+ symbolName: conflictingDefs[0].name,
674
+ definitions: conflictingDefs,
647
675
  severity: "error",
648
- message: `Symbol conflict: '${globalDefinitions[0].name}' is defined in multiple languages:\n ${locations.join("\n ")}\nRename the C-Next symbol to resolve.`,
676
+ message: `Symbol conflict: '${conflictingDefs[0].name}' is defined in multiple languages:\n ${locations.join("\n ")}\nRename the C-Next symbol to resolve.`,
649
677
  };
650
678
  }
651
679
 
@@ -879,7 +907,7 @@ class SymbolTable {
879
907
  }
880
908
 
881
909
  // ========================================================================
882
- // Opaque Type Tracking (Issue #948)
910
+ // Struct Symbol State (Issue #948, #958) — immer-managed, additive only
883
911
  // ========================================================================
884
912
 
885
913
  /**
@@ -887,33 +915,32 @@ class SymbolTable {
887
915
  * @param typeName Typedef name (e.g., "widget_t")
888
916
  */
889
917
  markOpaqueType(typeName: string): void {
890
- this.opaqueTypes.add(typeName);
891
- }
892
-
893
- /**
894
- * Issue #948: Unmark a typedef when full struct definition is found.
895
- * Handles edge case: typedef before definition.
896
- * @param typeName Typedef name
897
- */
898
- unmarkOpaqueType(typeName: string): void {
899
- this.opaqueTypes.delete(typeName);
918
+ this.structState = produce(this.structState, (draft) => {
919
+ draft.opaqueTypes.add(typeName);
920
+ });
900
921
  }
901
922
 
902
923
  /**
903
- * Issue #948: Check if a typedef aliases an opaque struct type.
924
+ * Issue #948/#958: Check if a typedef aliases a truly opaque struct type.
925
+ * Query-time resolution: if the underlying struct tag has a body, it's not opaque.
904
926
  * @param typeName Typedef name
905
- * @returns true if the type is opaque (forward-declared)
927
+ * @returns true if the type is opaque (forward-declared with no body found)
906
928
  */
907
929
  isOpaqueType(typeName: string): boolean {
908
- return this.opaqueTypes.has(typeName);
930
+ if (!this.structState.opaqueTypes.has(typeName)) return false;
931
+ // Resolve: if the underlying struct tag has a body, it's not truly opaque
932
+ const tag = this.structState.typedefToTag.get(typeName);
933
+ if (tag && this.structState.structTagsWithBodies.has(tag)) return false;
934
+ return true;
909
935
  }
910
936
 
911
937
  /**
912
938
  * Issue #948: Get all opaque type names for cache serialization.
939
+ * Returns the raw set — resolution happens at query time via isOpaqueType().
913
940
  * @returns Array of opaque typedef names
914
941
  */
915
942
  getAllOpaqueTypes(): string[] {
916
- return Array.from(this.opaqueTypes);
943
+ return Array.from(this.structState.opaqueTypes);
917
944
  }
918
945
 
919
946
  /**
@@ -921,20 +948,25 @@ class SymbolTable {
921
948
  * @param typeNames Array of opaque typedef names
922
949
  */
923
950
  restoreOpaqueTypes(typeNames: string[]): void {
924
- for (const name of typeNames) {
925
- this.opaqueTypes.add(name);
926
- }
951
+ this.structState = produce(this.structState, (draft) => {
952
+ for (const name of typeNames) {
953
+ draft.opaqueTypes.add(name);
954
+ }
955
+ });
927
956
  }
928
957
 
929
958
  /**
930
959
  * Issue #948: Register a struct tag -> typedef name relationship.
931
960
  * Called when processing: typedef struct _foo foo_t;
932
- * This allows unmarking foo_t when struct _foo { ... } is later defined.
961
+ * Populates both forward (tag→typedef) and reverse (typedef→tag) maps.
933
962
  * @param structTag The struct tag name (e.g., "_foo")
934
963
  * @param typedefName The typedef alias name (e.g., "foo_t")
935
964
  */
936
965
  registerStructTagAlias(structTag: string, typedefName: string): void {
937
- this.structTagAliases.set(structTag, typedefName);
966
+ this.structState = produce(this.structState, (draft) => {
967
+ draft.structTagAliases.set(structTag, typedefName);
968
+ draft.typedefToTag.set(typedefName, structTag);
969
+ });
938
970
  }
939
971
 
940
972
  /**
@@ -943,7 +975,59 @@ class SymbolTable {
943
975
  * @returns The typedef alias name, or undefined if none registered
944
976
  */
945
977
  getStructTagAlias(structTag: string): string | undefined {
946
- return this.structTagAliases.get(structTag);
978
+ return this.structState.structTagAliases.get(structTag);
979
+ }
980
+
981
+ /**
982
+ * Issue #958: Record that a struct tag has a full definition (body).
983
+ * Used by query-time resolution: opaque types with bodies are not truly opaque.
984
+ * @param structTag The struct tag name (e.g., "_widget_t")
985
+ */
986
+ markStructTagHasBody(structTag: string): void {
987
+ this.structState = produce(this.structState, (draft) => {
988
+ draft.structTagsWithBodies.add(structTag);
989
+ });
990
+ }
991
+
992
+ /**
993
+ * Issue #958: Get all struct tags with bodies for cache serialization.
994
+ * @returns Array of struct tag names
995
+ */
996
+ getAllStructTagsWithBodies(): string[] {
997
+ return Array.from(this.structState.structTagsWithBodies);
998
+ }
999
+
1000
+ /**
1001
+ * Issue #958: Restore struct tags with bodies from cache.
1002
+ * @param tags Array of struct tag names
1003
+ */
1004
+ restoreStructTagsWithBodies(tags: string[]): void {
1005
+ this.structState = produce(this.structState, (draft) => {
1006
+ for (const tag of tags) {
1007
+ draft.structTagsWithBodies.add(tag);
1008
+ }
1009
+ });
1010
+ }
1011
+
1012
+ /**
1013
+ * Issue #958: Get all struct tag aliases for cache serialization.
1014
+ * @returns Array of [structTag, typedefName] pairs
1015
+ */
1016
+ getAllStructTagAliases(): Array<[string, string]> {
1017
+ return Array.from(this.structState.structTagAliases.entries());
1018
+ }
1019
+
1020
+ /**
1021
+ * Issue #958: Restore struct tag aliases from cache.
1022
+ * @param entries Array of [structTag, typedefName] pairs
1023
+ */
1024
+ restoreStructTagAliases(entries: Array<[string, string]>): void {
1025
+ this.structState = produce(this.structState, (draft) => {
1026
+ for (const [tag, typedefName] of entries) {
1027
+ draft.structTagAliases.set(tag, typedefName);
1028
+ draft.typedefToTag.set(typedefName, tag);
1029
+ }
1030
+ });
947
1031
  }
948
1032
 
949
1033
  // ========================================================================
@@ -952,36 +1036,26 @@ class SymbolTable {
952
1036
 
953
1037
  /**
954
1038
  * Issue #958: Mark a typedef as aliasing a struct type.
955
- * Records the source file to enable same-file vs cross-file distinction.
1039
+ * Records the source file. Additive only never removed.
956
1040
  * @param typedefName The typedef name (e.g., "widget_t")
957
1041
  * @param sourceFile The file where the typedef was declared
958
1042
  */
959
1043
  markTypedefStructType(typedefName: string, sourceFile: string): void {
960
- this.typedefStructTypes.set(typedefName, sourceFile);
961
- }
962
-
963
- /**
964
- * Issue #958: Unmark a typedef struct type when full definition is found.
965
- * Only unmarks if the definition is in the SAME file as the typedef declaration.
966
- * Cross-file definitions keep the typedef marked (pointer semantics).
967
- * @param typeName The typedef name to unmark
968
- * @param sourceFile The file where the definition was found
969
- */
970
- unmarkTypedefStructType(typeName: string, sourceFile: string): void {
971
- const typedefFile = this.typedefStructTypes.get(typeName);
972
- if (typedefFile === sourceFile) {
973
- this.typedefStructTypes.delete(typeName);
974
- }
1044
+ this.structState = produce(this.structState, (draft) => {
1045
+ draft.typedefStructTypes.set(typedefName, sourceFile);
1046
+ });
975
1047
  }
976
1048
 
977
1049
  /**
978
1050
  * Issue #958: Check if a typedef aliases a struct type.
979
- * Used for scope variables which should be pointers for external struct types.
1051
+ * Used for scope variables, function parameters, and local variables
1052
+ * which should be pointers for C-header struct types.
980
1053
  * @param typeName The type name to check
981
1054
  * @returns true if this is a typedef'd struct type from C headers
982
1055
  */
983
1056
  isTypedefStructType(typeName: string): boolean {
984
- return this.typedefStructTypes.has(typeName);
1057
+ const result = this.structState.typedefStructTypes.has(typeName);
1058
+ return result;
985
1059
  }
986
1060
 
987
1061
  /**
@@ -989,7 +1063,7 @@ class SymbolTable {
989
1063
  * @returns Map entries as [typeName, sourceFile] pairs
990
1064
  */
991
1065
  getAllTypedefStructTypes(): Array<[string, string]> {
992
- return Array.from(this.typedefStructTypes.entries());
1066
+ return Array.from(this.structState.typedefStructTypes.entries());
993
1067
  }
994
1068
 
995
1069
  /**
@@ -997,9 +1071,11 @@ class SymbolTable {
997
1071
  * @param entries Array of [typeName, sourceFile] pairs
998
1072
  */
999
1073
  restoreTypedefStructTypes(entries: Array<[string, string]>): void {
1000
- for (const [name, sourceFile] of entries) {
1001
- this.typedefStructTypes.set(name, sourceFile);
1002
- }
1074
+ this.structState = produce(this.structState, (draft) => {
1075
+ for (const [name, sourceFile] of entries) {
1076
+ draft.typedefStructTypes.set(name, sourceFile);
1077
+ }
1078
+ });
1003
1079
  }
1004
1080
 
1005
1081
  // ========================================================================
@@ -1151,10 +1227,8 @@ class SymbolTable {
1151
1227
  // Auxiliary
1152
1228
  this.structFields.clear();
1153
1229
  this.needsStructKeyword.clear();
1154
- this.opaqueTypes.clear();
1155
- this.structTagAliases.clear();
1230
+ this.structState = createInitialStructState();
1156
1231
  this.enumBitWidth.clear();
1157
- this.typedefStructTypes.clear();
1158
1232
  }
1159
1233
  }
1160
1234
 
@@ -478,6 +478,84 @@ describe("SymbolTable", () => {
478
478
  // Two globals with same name IS a conflict
479
479
  expect(symbolTable.hasConflict("globalVar")).toBe(true);
480
480
  });
481
+
482
+ // Issue #967: Scoped C-Next symbols live in a namespace and don't conflict
483
+ // with C's global symbols. Only global-scope C-Next symbols can conflict.
484
+ it("should NOT detect conflict for scoped C-Next method vs C function with same bare name", () => {
485
+ const globalScope = TestScopeUtils.createMockGlobalScope();
486
+ const touchScope = TestScopeUtils.createMockScope("Touch", globalScope);
487
+
488
+ // Add C-Next scoped function 'read' in scope 'Touch'
489
+ // This transpiles to Touch_read()
490
+ symbolTable.addTSymbol({
491
+ kind: "function",
492
+ name: "read",
493
+ sourceFile: "touch.cnx",
494
+ sourceLine: 28,
495
+ sourceLanguage: ESourceLanguage.CNext,
496
+ isExported: true,
497
+ returnType: TTypeUtils.createPrimitive("u8"),
498
+ parameters: [],
499
+ scope: touchScope,
500
+ visibility: "public",
501
+ body: null,
502
+ } as IFunctionSymbol);
503
+
504
+ // Add C function 'read' from POSIX headers
505
+ // This stays as read()
506
+ symbolTable.addCSymbol({
507
+ kind: "function",
508
+ name: "read",
509
+ sourceFile: "lv_pthread.h",
510
+ sourceLine: 80,
511
+ sourceLanguage: ESourceLanguage.C,
512
+ isExported: true,
513
+ type: "ssize_t",
514
+ parameters: [
515
+ { name: "fd", type: "int", isConst: false, isArray: false },
516
+ { name: "buf", type: "void*", isConst: false, isArray: false },
517
+ { name: "count", type: "size_t", isConst: false, isArray: false },
518
+ ],
519
+ });
520
+
521
+ // Touch.read() is in a namespace — does NOT conflict with C's global read()
522
+ expect(symbolTable.hasConflict("read")).toBe(false);
523
+ });
524
+
525
+ // Issue #967: Global C-Next functions SHOULD still conflict with C functions
526
+ it("should detect conflict for global C-Next function vs C function", () => {
527
+ const globalScope = TestScopeUtils.createMockGlobalScope();
528
+
529
+ // Add global C-Next function 'read'
530
+ symbolTable.addTSymbol({
531
+ kind: "function",
532
+ name: "read",
533
+ sourceFile: "utils.cnx",
534
+ sourceLine: 5,
535
+ sourceLanguage: ESourceLanguage.CNext,
536
+ isExported: true,
537
+ returnType: TTypeUtils.createPrimitive("u8"),
538
+ parameters: [],
539
+ scope: globalScope,
540
+ visibility: "public",
541
+ body: null,
542
+ } as IFunctionSymbol);
543
+
544
+ // Add C function 'read'
545
+ symbolTable.addCSymbol({
546
+ kind: "function",
547
+ name: "read",
548
+ sourceFile: "unistd.h",
549
+ sourceLine: 100,
550
+ sourceLanguage: ESourceLanguage.C,
551
+ isExported: true,
552
+ type: "ssize_t",
553
+ parameters: [],
554
+ });
555
+
556
+ // Global C-Next read() DOES conflict with C's read()
557
+ expect(symbolTable.hasConflict("read")).toBe(true);
558
+ });
481
559
  });
482
560
 
483
561
  // ========================================================================
@@ -621,13 +699,25 @@ describe("SymbolTable", () => {
621
699
  expect(symbolTable.isOpaqueType("other_t")).toBe(false);
622
700
  });
623
701
 
624
- it("should unmark opaque types when full definition found", () => {
702
+ it("should resolve opaque type as non-opaque when struct tag has body", () => {
703
+ // typedef struct _point point_t; → mark opaque + register alias
625
704
  symbolTable.markOpaqueType("point_t");
705
+ symbolTable.registerStructTagAlias("_point", "point_t");
626
706
  expect(symbolTable.isOpaqueType("point_t")).toBe(true);
627
- symbolTable.unmarkOpaqueType("point_t");
707
+
708
+ // struct _point { int x; int y; }; → mark body
709
+ symbolTable.markStructTagHasBody("_point");
710
+ // Query-time resolution: no longer opaque
628
711
  expect(symbolTable.isOpaqueType("point_t")).toBe(false);
629
712
  });
630
713
 
714
+ it("should keep opaque type when no body found for struct tag", () => {
715
+ symbolTable.markOpaqueType("handle_t");
716
+ symbolTable.registerStructTagAlias("_handle", "handle_t");
717
+ // No markStructTagHasBody call → stays opaque
718
+ expect(symbolTable.isOpaqueType("handle_t")).toBe(true);
719
+ });
720
+
631
721
  it("should get all opaque types", () => {
632
722
  symbolTable.markOpaqueType("handle_t");
633
723
  symbolTable.markOpaqueType("context_t");
@@ -662,20 +752,14 @@ describe("SymbolTable", () => {
662
752
  expect(symbolTable.isTypedefStructType("other_t")).toBe(false);
663
753
  });
664
754
 
665
- it("should unmark typedef struct type when definition is in same file", () => {
755
+ it("should always return true for typedef struct types (additive only)", () => {
756
+ // Issue #958: typedef struct types are never unmarked
666
757
  symbolTable.markTypedefStructType("point_t", "point.h");
667
758
  expect(symbolTable.isTypedefStructType("point_t")).toBe(true);
668
- // Same file - should unmark
669
- symbolTable.unmarkTypedefStructType("point_t", "point.h");
670
- expect(symbolTable.isTypedefStructType("point_t")).toBe(false);
671
- });
672
-
673
- it("should retain typedef struct type when definition is in different file", () => {
674
- symbolTable.markTypedefStructType("widget_t", "widget_types.h");
675
- expect(symbolTable.isTypedefStructType("widget_t")).toBe(true);
676
- // Different file - should NOT unmark (cross-file pattern)
677
- symbolTable.unmarkTypedefStructType("widget_t", "widget_private.h");
678
- expect(symbolTable.isTypedefStructType("widget_t")).toBe(true);
759
+ // Even after marking the body, typedef struct type stays marked
760
+ symbolTable.registerStructTagAlias("_point", "point_t");
761
+ symbolTable.markStructTagHasBody("_point");
762
+ expect(symbolTable.isTypedefStructType("point_t")).toBe(true);
679
763
  });
680
764
 
681
765
  it("should get all typedef struct types", () => {
@@ -704,6 +788,65 @@ describe("SymbolTable", () => {
704
788
  });
705
789
  });
706
790
 
791
+ // ========================================================================
792
+ // Struct Tag Aliases and Body Tracking (Issue #958)
793
+ // ========================================================================
794
+
795
+ describe("Struct Tag Aliases and Body Tracking", () => {
796
+ it("should register and retrieve struct tag aliases", () => {
797
+ symbolTable.registerStructTagAlias("_widget", "widget_t");
798
+ expect(symbolTable.getStructTagAlias("_widget")).toBe("widget_t");
799
+ expect(symbolTable.getStructTagAlias("_unknown")).toBeUndefined();
800
+ });
801
+
802
+ it("should populate forward and reverse alias maps", () => {
803
+ symbolTable.registerStructTagAlias("_foo", "foo_t");
804
+ const aliases = symbolTable.getAllStructTagAliases();
805
+ expect(aliases).toContainEqual(["_foo", "foo_t"]);
806
+ });
807
+
808
+ it("should track struct tags with bodies", () => {
809
+ symbolTable.markStructTagHasBody("_widget");
810
+ const bodies = symbolTable.getAllStructTagsWithBodies();
811
+ expect(bodies).toContain("_widget");
812
+ });
813
+
814
+ it("should restore struct tag aliases from cache", () => {
815
+ symbolTable.restoreStructTagAliases([
816
+ ["_foo", "foo_t"],
817
+ ["_bar", "bar_t"],
818
+ ]);
819
+ expect(symbolTable.getStructTagAlias("_foo")).toBe("foo_t");
820
+ expect(symbolTable.getStructTagAlias("_bar")).toBe("bar_t");
821
+ });
822
+
823
+ it("should restore reverse map (typedefToTag) so isOpaqueType resolves after cache restore", () => {
824
+ // Simulate cache restore: aliases + opaque types + bodies
825
+ symbolTable.restoreStructTagAliases([["_widget", "widget_t"]]);
826
+ symbolTable.restoreOpaqueTypes(["widget_t"]);
827
+ // widget_t is opaque (no body for _widget)
828
+ expect(symbolTable.isOpaqueType("widget_t")).toBe(true);
829
+ // Now restore body — isOpaqueType should resolve via typedefToTag
830
+ symbolTable.restoreStructTagsWithBodies(["_widget"]);
831
+ expect(symbolTable.isOpaqueType("widget_t")).toBe(false);
832
+ });
833
+
834
+ it("should restore struct tags with bodies from cache", () => {
835
+ symbolTable.restoreStructTagsWithBodies(["_foo", "_bar"]);
836
+ const bodies = symbolTable.getAllStructTagsWithBodies();
837
+ expect(bodies).toContain("_foo");
838
+ expect(bodies).toContain("_bar");
839
+ });
840
+
841
+ it("should clear all struct state on clear()", () => {
842
+ symbolTable.registerStructTagAlias("_foo", "foo_t");
843
+ symbolTable.markStructTagHasBody("_foo");
844
+ symbolTable.clear();
845
+ expect(symbolTable.getStructTagAlias("_foo")).toBeUndefined();
846
+ expect(symbolTable.getAllStructTagsWithBodies()).toHaveLength(0);
847
+ });
848
+ });
849
+
707
850
  // ========================================================================
708
851
  // Clear
709
852
  // ========================================================================
@@ -664,7 +664,7 @@ describe("CResolver - Opaque Type Detection (Issue #948)", () => {
664
664
  expect(symbolTable.isOpaqueType("point_t")).toBe(false);
665
665
  });
666
666
 
667
- it("unmarks opaque type when full definition is found in separate parse", () => {
667
+ it("resolves opaque type as non-opaque when full definition is found in separate parse", () => {
668
668
  // Forward declaration first
669
669
  const tree1 = TestHelpers.parseC(`typedef struct _foo foo_t;`);
670
670
  const symbolTable = new SymbolTable();
@@ -672,14 +672,14 @@ describe("CResolver - Opaque Type Detection (Issue #948)", () => {
672
672
 
673
673
  expect(symbolTable.isOpaqueType("foo_t")).toBe(true);
674
674
 
675
- // Full definition later
675
+ // Full definition later — query-time resolution sees body
676
676
  const tree2 = TestHelpers.parseC(`struct _foo { int value; };`);
677
677
  CResolver.resolve(tree2!, "test.h", symbolTable);
678
678
 
679
679
  expect(symbolTable.isOpaqueType("foo_t")).toBe(false);
680
680
  });
681
681
 
682
- it("unmarks opaque type when full definition follows typedef in same parse", () => {
682
+ it("resolves opaque type as non-opaque when full definition follows typedef in same parse", () => {
683
683
  const tree = TestHelpers.parseC(`
684
684
  typedef struct _point_t point_t;
685
685
  struct _point_t { int x; int y; };
@@ -137,47 +137,17 @@ class StructCollector {
137
137
  }
138
138
  }
139
139
 
140
- // Issue #958: Track ALL typedef struct types (forward-declared OR complete)
141
- // Records source file to enable same-file vs cross-file distinction
140
+ // Issue #958: Track forward-declared typedef struct types (no body).
141
+ // These always need pointer semantics (ADR-006). Inline typedefs with
142
+ // bodies (typedef struct { ... } X;) are concrete value types.
142
143
  // Issue #957: Don't track pointer typedefs - they're already pointers.
143
- if (isTypedef && typedefName && !isPointerTypedef) {
144
+ if (isTypedef && !hasBody && typedefName && !isPointerTypedef) {
144
145
  symbolTable.markTypedefStructType(typedefName, sourceFile);
145
146
  }
146
147
 
147
- // Issue #948: Unmark opaque type if full definition is found
148
- if (hasBody) {
149
- StructCollector.unmarkOpaqueTypesOnDefinition(
150
- symbolTable,
151
- sourceFile,
152
- structTag,
153
- typedefName,
154
- );
155
- }
156
- }
157
-
158
- /**
159
- * Unmark opaque and typedef struct types when a full struct definition is encountered.
160
- * Handles: typedef struct _foo foo; struct _foo { ... };
161
- * Issue #958: Only unmarks typedefStructTypes when definition is in SAME file.
162
- */
163
- private static unmarkOpaqueTypesOnDefinition(
164
- symbolTable: SymbolTable,
165
- sourceFile: string,
166
- structTag?: string,
167
- typedefName?: string,
168
- ): void {
169
- if (structTag) {
170
- const typedefAlias = symbolTable.getStructTagAlias(structTag);
171
- symbolTable.unmarkOpaqueType(structTag);
172
- symbolTable.unmarkTypedefStructType(structTag, sourceFile);
173
- if (typedefAlias) {
174
- symbolTable.unmarkOpaqueType(typedefAlias);
175
- symbolTable.unmarkTypedefStructType(typedefAlias, sourceFile);
176
- }
177
- }
178
- if (typedefName) {
179
- symbolTable.unmarkOpaqueType(typedefName);
180
- symbolTable.unmarkTypedefStructType(typedefName, sourceFile);
148
+ // Issue #958: Record struct tag body for query-time opaque resolution
149
+ if (hasBody && structTag) {
150
+ symbolTable.markStructTagHasBody(structTag);
181
151
  }
182
152
  }
183
153