c-next 0.2.9 → 0.2.11
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/dist/index.js +941 -210
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
- package/src/transpiler/Transpiler.ts +102 -7
- package/src/transpiler/__tests__/Transpiler.coverage.test.ts +170 -0
- package/src/transpiler/__tests__/needsConditionalPreprocessing.test.ts +246 -0
- package/src/transpiler/logic/analysis/ArrayIndexTypeAnalyzer.ts +12 -1
- package/src/transpiler/logic/analysis/__tests__/ArrayIndexTypeAnalyzer.test.ts +172 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +150 -0
- package/src/transpiler/logic/symbols/__tests__/SymbolTable.test.ts +98 -0
- package/src/transpiler/logic/symbols/__tests__/TransitiveEnumCollector.test.ts +1 -0
- package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +98 -1
- package/src/transpiler/logic/symbols/c/__tests__/PointerTypedef.test.ts +47 -0
- package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +23 -5
- package/src/transpiler/logic/symbols/c/collectors/StructCollector.ts +118 -8
- package/src/transpiler/logic/symbols/c/index.ts +107 -30
- package/src/transpiler/logic/symbols/c/utils/DeclaratorUtils.ts +35 -0
- package/src/transpiler/logic/symbols/cnext/__tests__/TSymbolInfoAdapter.test.ts +90 -0
- package/src/transpiler/logic/symbols/cnext/adapters/TSymbolInfoAdapter.ts +40 -39
- package/src/transpiler/output/codegen/CodeGenerator.ts +32 -1
- package/src/transpiler/output/codegen/TypeResolver.ts +14 -0
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +19 -14
- package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +7 -7
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +1 -0
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +27 -4
- package/src/transpiler/output/codegen/assignment/AssignmentKind.ts +6 -0
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +73 -0
- package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +4 -0
- package/src/transpiler/output/codegen/assignment/handlers/SimpleHandler.ts +92 -0
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +5 -2
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +1 -0
- package/src/transpiler/output/codegen/generators/IOrchestrator.ts +20 -0
- package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +23 -2
- package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +51 -0
- package/src/transpiler/output/codegen/generators/expressions/AccessExprGenerator.ts +17 -5
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +5 -0
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +16 -0
- package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +41 -5
- package/src/transpiler/output/codegen/generators/expressions/UnaryExprGenerator.ts +25 -1
- package/src/transpiler/output/codegen/generators/statements/AtomicGenerator.ts +2 -17
- package/src/transpiler/output/codegen/helpers/ArrayAccessHelper.ts +3 -0
- package/src/transpiler/output/codegen/helpers/BitRangeHelper.ts +19 -3
- package/src/transpiler/output/codegen/helpers/NarrowingCastHelper.ts +191 -0
- package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +35 -4
- package/src/transpiler/output/codegen/helpers/__tests__/ArrayAccessHelper.test.ts +131 -1
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/BitRangeHelper.test.ts +53 -2
- package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +1 -0
- package/src/transpiler/output/codegen/helpers/__tests__/NarrowingCastHelper.test.ts +159 -0
- package/src/transpiler/output/codegen/helpers/__tests__/TypeRegistrationEngine.test.ts +1 -0
- package/src/transpiler/output/codegen/types/COMPOUND_TO_BINARY.ts +21 -0
- package/src/transpiler/output/codegen/types/IArrayAccessInfo.ts +2 -0
- package/src/transpiler/state/CodeGenState.ts +58 -0
- package/src/transpiler/state/__tests__/CodeGenState.test.ts +53 -0
- package/src/transpiler/state/__tests__/TranspilerState.test.ts +1 -0
- package/src/transpiler/types/ICachedFileEntry.ts +4 -0
- package/src/transpiler/types/ICodeGenSymbols.ts +8 -0
- package/src/utils/BitUtils.ts +33 -4
- package/src/utils/cache/CacheManager.ts +17 -1
- package/src/utils/cache/__tests__/CacheManager.test.ts +1 -1
|
@@ -56,6 +56,7 @@ function createMockSymbols(
|
|
|
56
56
|
scopeVariableUsage: new Map(),
|
|
57
57
|
scopePrivateConstValues: new Map(),
|
|
58
58
|
functionReturnTypes: overrides.functionReturnTypes ?? new Map(),
|
|
59
|
+
opaqueTypes: new Set(),
|
|
59
60
|
getSingleFunctionForVariable: () => null,
|
|
60
61
|
hasPublicSymbols: () => false,
|
|
61
62
|
};
|
|
@@ -136,6 +137,38 @@ describe("ArrayIndexTypeAnalyzer", () => {
|
|
|
136
137
|
expect(errors).toHaveLength(0);
|
|
137
138
|
});
|
|
138
139
|
|
|
140
|
+
it("should allow enum-typed variable as array index (Issue #949)", () => {
|
|
141
|
+
const tree = parse(`
|
|
142
|
+
enum EColor { RED, GREEN, BLUE, COUNT }
|
|
143
|
+
void getValue(EColor color) {
|
|
144
|
+
u8[4] arr;
|
|
145
|
+
arr[color] <- 1;
|
|
146
|
+
}
|
|
147
|
+
`);
|
|
148
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
149
|
+
CodeGenState.symbols = createMockSymbols({
|
|
150
|
+
knownEnums: new Set(["EColor"]),
|
|
151
|
+
});
|
|
152
|
+
const errors = analyzer.analyze(tree);
|
|
153
|
+
expect(errors).toHaveLength(0);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("should allow enum-typed parameter as array index (Issue #949)", () => {
|
|
157
|
+
const tree = parse(`
|
|
158
|
+
enum EState { IDLE, RUNNING, STOPPED }
|
|
159
|
+
u32[3] stateCounts;
|
|
160
|
+
void incrementState(EState state) {
|
|
161
|
+
stateCounts[state] <- stateCounts[state] + 1;
|
|
162
|
+
}
|
|
163
|
+
`);
|
|
164
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
165
|
+
CodeGenState.symbols = createMockSymbols({
|
|
166
|
+
knownEnums: new Set(["EState"]),
|
|
167
|
+
});
|
|
168
|
+
const errors = analyzer.analyze(tree);
|
|
169
|
+
expect(errors).toHaveLength(0);
|
|
170
|
+
});
|
|
171
|
+
|
|
139
172
|
it("should allow unsigned for-loop variable as index", () => {
|
|
140
173
|
const tree = parse(`
|
|
141
174
|
void main() {
|
|
@@ -525,6 +558,145 @@ describe("ArrayIndexTypeAnalyzer", () => {
|
|
|
525
558
|
});
|
|
526
559
|
});
|
|
527
560
|
|
|
561
|
+
// ========================================================================
|
|
562
|
+
// Nested array subscript (Issue #950)
|
|
563
|
+
// ========================================================================
|
|
564
|
+
|
|
565
|
+
describe("nested array subscript", () => {
|
|
566
|
+
it("should allow arr[data[i]] where data is u8[8] (Issue #950)", () => {
|
|
567
|
+
const tree = parse(`
|
|
568
|
+
void main() {
|
|
569
|
+
u8[10] arr;
|
|
570
|
+
u8[8] data <- [0, 0, 3, 0, 0, 0, 0, 0];
|
|
571
|
+
arr[data[2]] <- 5;
|
|
572
|
+
}
|
|
573
|
+
`);
|
|
574
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
575
|
+
const errors = analyzer.analyze(tree);
|
|
576
|
+
expect(errors).toHaveLength(0);
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
it("should allow arr[data[i] - 1] where data is u8[8] (Issue #950)", () => {
|
|
580
|
+
const tree = parse(`
|
|
581
|
+
void main() {
|
|
582
|
+
u8[10] arr;
|
|
583
|
+
u8[8] data <- [0, 0, 3, 0, 0, 0, 0, 0];
|
|
584
|
+
arr[data[2] - 1] <- 5;
|
|
585
|
+
}
|
|
586
|
+
`);
|
|
587
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
588
|
+
const errors = analyzer.analyze(tree);
|
|
589
|
+
expect(errors).toHaveLength(0);
|
|
590
|
+
});
|
|
591
|
+
|
|
592
|
+
it("should allow nested subscript with const array param (Issue #950)", () => {
|
|
593
|
+
const tree = parse(`
|
|
594
|
+
void process(const u8[8] data) {
|
|
595
|
+
u8[10] inputs;
|
|
596
|
+
inputs[data[2] - 1] <- data[1];
|
|
597
|
+
}
|
|
598
|
+
`);
|
|
599
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
600
|
+
const errors = analyzer.analyze(tree);
|
|
601
|
+
expect(errors).toHaveLength(0);
|
|
602
|
+
});
|
|
603
|
+
|
|
604
|
+
it("should reject nested subscript when inner array is signed", () => {
|
|
605
|
+
const tree = parse(`
|
|
606
|
+
void main() {
|
|
607
|
+
u8[10] arr;
|
|
608
|
+
i8[8] data;
|
|
609
|
+
arr[data[2]] <- 5;
|
|
610
|
+
}
|
|
611
|
+
`);
|
|
612
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
613
|
+
const errors = analyzer.analyze(tree);
|
|
614
|
+
expect(errors).toHaveLength(1);
|
|
615
|
+
expect(errors[0].code).toBe("E0850");
|
|
616
|
+
expect(errors[0].actualType).toBe("i8");
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
it("should allow multi-dimensional array index access", () => {
|
|
620
|
+
const tree = parse(`
|
|
621
|
+
void main() {
|
|
622
|
+
u8[10] arr;
|
|
623
|
+
u8[4][4] matrix;
|
|
624
|
+
arr[matrix[1][2]] <- 5;
|
|
625
|
+
}
|
|
626
|
+
`);
|
|
627
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
628
|
+
const errors = analyzer.analyze(tree);
|
|
629
|
+
expect(errors).toHaveLength(0);
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it("should handle constant-dimension arrays (PR #951 review)", () => {
|
|
633
|
+
// Regression test: regex must handle non-numeric dimensions
|
|
634
|
+
// e.g., "u8[DEVICE_COUNT]" should strip to "u8"
|
|
635
|
+
const tree = parse(`
|
|
636
|
+
void main() {
|
|
637
|
+
u8[10] arr;
|
|
638
|
+
u8[4] lookup;
|
|
639
|
+
arr[lookup[0]] <- 5;
|
|
640
|
+
}
|
|
641
|
+
`);
|
|
642
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
643
|
+
const errors = analyzer.analyze(tree);
|
|
644
|
+
expect(errors).toHaveLength(0);
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("should reject unknown type as index with E0852 (branch coverage)", () => {
|
|
648
|
+
// Tests the branch where isKnownEnum returns false for non-enum types
|
|
649
|
+
// This triggers E0852 for types that aren't signed, float, unsigned, or enum
|
|
650
|
+
const tree = parse(`
|
|
651
|
+
void main() {
|
|
652
|
+
u8[10] arr;
|
|
653
|
+
UnknownType x;
|
|
654
|
+
arr[x] <- 5;
|
|
655
|
+
}
|
|
656
|
+
`);
|
|
657
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
658
|
+
// No mock symbols - UnknownType is not a known enum
|
|
659
|
+
const errors = analyzer.analyze(tree);
|
|
660
|
+
expect(errors).toHaveLength(1);
|
|
661
|
+
expect(errors[0].code).toBe("E0852");
|
|
662
|
+
expect(errors[0].actualType).toBe("UnknownType");
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("should handle struct type in subscript chain (branch coverage)", () => {
|
|
666
|
+
// Tests the branch where array stripping doesn't find brackets
|
|
667
|
+
// Covers the case where strippedType === currentType (not an array)
|
|
668
|
+
CodeGenState.symbols = createMockSymbols({
|
|
669
|
+
knownStructs: new Set(["Wrapper"]),
|
|
670
|
+
structFields: new Map([["Wrapper", new Map([["idx", "u32"]])]]),
|
|
671
|
+
});
|
|
672
|
+
const tree = parse(`
|
|
673
|
+
void main() {
|
|
674
|
+
u8[10] arr;
|
|
675
|
+
Wrapper w;
|
|
676
|
+
arr[w.idx] <- 5;
|
|
677
|
+
}
|
|
678
|
+
`);
|
|
679
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
680
|
+
const errors = analyzer.analyze(tree);
|
|
681
|
+
expect(errors).toHaveLength(0);
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it("should allow bit index from signed integer as array index (branch coverage)", () => {
|
|
685
|
+
// Tests the SIGNED_TYPES branch in resolvePostfixOpType for LBRACKET
|
|
686
|
+
// i32[bit] returns "bool", which is valid for array indexing
|
|
687
|
+
const tree = parse(`
|
|
688
|
+
void main() {
|
|
689
|
+
u8[2] arr;
|
|
690
|
+
i32 signedFlags <- 1;
|
|
691
|
+
arr[signedFlags[0]] <- 5;
|
|
692
|
+
}
|
|
693
|
+
`);
|
|
694
|
+
const analyzer = new ArrayIndexTypeAnalyzer();
|
|
695
|
+
const errors = analyzer.analyze(tree);
|
|
696
|
+
expect(errors).toHaveLength(0);
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
|
|
528
700
|
// ========================================================================
|
|
529
701
|
// getErrors() accessor
|
|
530
702
|
// ========================================================================
|
|
@@ -77,6 +77,29 @@ class SymbolTable {
|
|
|
77
77
|
*/
|
|
78
78
|
private readonly needsStructKeyword: Set<string> = new Set();
|
|
79
79
|
|
|
80
|
+
/**
|
|
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.
|
|
83
|
+
*/
|
|
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();
|
|
102
|
+
|
|
80
103
|
/**
|
|
81
104
|
* Issue #208: Track enum backing type bit widths
|
|
82
105
|
* C++14 typed enums: enum Name : uint8_t { ... } have explicit bit widths
|
|
@@ -855,6 +878,130 @@ class SymbolTable {
|
|
|
855
878
|
}
|
|
856
879
|
}
|
|
857
880
|
|
|
881
|
+
// ========================================================================
|
|
882
|
+
// Opaque Type Tracking (Issue #948)
|
|
883
|
+
// ========================================================================
|
|
884
|
+
|
|
885
|
+
/**
|
|
886
|
+
* Issue #948: Mark a typedef as aliasing an opaque (forward-declared) struct type.
|
|
887
|
+
* @param typeName Typedef name (e.g., "widget_t")
|
|
888
|
+
*/
|
|
889
|
+
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);
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
/**
|
|
903
|
+
* Issue #948: Check if a typedef aliases an opaque struct type.
|
|
904
|
+
* @param typeName Typedef name
|
|
905
|
+
* @returns true if the type is opaque (forward-declared)
|
|
906
|
+
*/
|
|
907
|
+
isOpaqueType(typeName: string): boolean {
|
|
908
|
+
return this.opaqueTypes.has(typeName);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Issue #948: Get all opaque type names for cache serialization.
|
|
913
|
+
* @returns Array of opaque typedef names
|
|
914
|
+
*/
|
|
915
|
+
getAllOpaqueTypes(): string[] {
|
|
916
|
+
return Array.from(this.opaqueTypes);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Issue #948: Restore opaque types from cache.
|
|
921
|
+
* @param typeNames Array of opaque typedef names
|
|
922
|
+
*/
|
|
923
|
+
restoreOpaqueTypes(typeNames: string[]): void {
|
|
924
|
+
for (const name of typeNames) {
|
|
925
|
+
this.opaqueTypes.add(name);
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Issue #948: Register a struct tag -> typedef name relationship.
|
|
931
|
+
* Called when processing: typedef struct _foo foo_t;
|
|
932
|
+
* This allows unmarking foo_t when struct _foo { ... } is later defined.
|
|
933
|
+
* @param structTag The struct tag name (e.g., "_foo")
|
|
934
|
+
* @param typedefName The typedef alias name (e.g., "foo_t")
|
|
935
|
+
*/
|
|
936
|
+
registerStructTagAlias(structTag: string, typedefName: string): void {
|
|
937
|
+
this.structTagAliases.set(structTag, typedefName);
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* Issue #948: Get the typedef alias for a struct tag, if any.
|
|
942
|
+
* @param structTag The struct tag name
|
|
943
|
+
* @returns The typedef alias name, or undefined if none registered
|
|
944
|
+
*/
|
|
945
|
+
getStructTagAlias(structTag: string): string | undefined {
|
|
946
|
+
return this.structTagAliases.get(structTag);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ========================================================================
|
|
950
|
+
// Issue #958: Typedef Struct Type Tracking
|
|
951
|
+
// ========================================================================
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Issue #958: Mark a typedef as aliasing a struct type.
|
|
955
|
+
* Records the source file to enable same-file vs cross-file distinction.
|
|
956
|
+
* @param typedefName The typedef name (e.g., "widget_t")
|
|
957
|
+
* @param sourceFile The file where the typedef was declared
|
|
958
|
+
*/
|
|
959
|
+
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
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* Issue #958: Check if a typedef aliases a struct type.
|
|
979
|
+
* Used for scope variables which should be pointers for external struct types.
|
|
980
|
+
* @param typeName The type name to check
|
|
981
|
+
* @returns true if this is a typedef'd struct type from C headers
|
|
982
|
+
*/
|
|
983
|
+
isTypedefStructType(typeName: string): boolean {
|
|
984
|
+
return this.typedefStructTypes.has(typeName);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Issue #958: Get all typedef struct types for cache serialization.
|
|
989
|
+
* @returns Map entries as [typeName, sourceFile] pairs
|
|
990
|
+
*/
|
|
991
|
+
getAllTypedefStructTypes(): Array<[string, string]> {
|
|
992
|
+
return Array.from(this.typedefStructTypes.entries());
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
/**
|
|
996
|
+
* Issue #958: Restore typedef struct types from cache.
|
|
997
|
+
* @param entries Array of [typeName, sourceFile] pairs
|
|
998
|
+
*/
|
|
999
|
+
restoreTypedefStructTypes(entries: Array<[string, string]>): void {
|
|
1000
|
+
for (const [name, sourceFile] of entries) {
|
|
1001
|
+
this.typedefStructTypes.set(name, sourceFile);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
|
|
858
1005
|
// ========================================================================
|
|
859
1006
|
// Enum Bit Width Tracking
|
|
860
1007
|
// ========================================================================
|
|
@@ -1004,7 +1151,10 @@ class SymbolTable {
|
|
|
1004
1151
|
// Auxiliary
|
|
1005
1152
|
this.structFields.clear();
|
|
1006
1153
|
this.needsStructKeyword.clear();
|
|
1154
|
+
this.opaqueTypes.clear();
|
|
1155
|
+
this.structTagAliases.clear();
|
|
1007
1156
|
this.enumBitWidth.clear();
|
|
1157
|
+
this.typedefStructTypes.clear();
|
|
1008
1158
|
}
|
|
1009
1159
|
}
|
|
1010
1160
|
|
|
@@ -610,6 +610,100 @@ describe("SymbolTable", () => {
|
|
|
610
610
|
});
|
|
611
611
|
});
|
|
612
612
|
|
|
613
|
+
// ========================================================================
|
|
614
|
+
// Opaque Type Tracking (Issue #948)
|
|
615
|
+
// ========================================================================
|
|
616
|
+
|
|
617
|
+
describe("Opaque Type Tracking", () => {
|
|
618
|
+
it("should mark and check opaque types", () => {
|
|
619
|
+
symbolTable.markOpaqueType("widget_t");
|
|
620
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(true);
|
|
621
|
+
expect(symbolTable.isOpaqueType("other_t")).toBe(false);
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
it("should unmark opaque types when full definition found", () => {
|
|
625
|
+
symbolTable.markOpaqueType("point_t");
|
|
626
|
+
expect(symbolTable.isOpaqueType("point_t")).toBe(true);
|
|
627
|
+
symbolTable.unmarkOpaqueType("point_t");
|
|
628
|
+
expect(symbolTable.isOpaqueType("point_t")).toBe(false);
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
it("should get all opaque types", () => {
|
|
632
|
+
symbolTable.markOpaqueType("handle_t");
|
|
633
|
+
symbolTable.markOpaqueType("context_t");
|
|
634
|
+
const all = symbolTable.getAllOpaqueTypes();
|
|
635
|
+
expect(all).toContain("handle_t");
|
|
636
|
+
expect(all).toContain("context_t");
|
|
637
|
+
expect(all).toHaveLength(2);
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("should clear opaque types on clear()", () => {
|
|
641
|
+
symbolTable.markOpaqueType("widget_t");
|
|
642
|
+
symbolTable.clear();
|
|
643
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(false);
|
|
644
|
+
});
|
|
645
|
+
|
|
646
|
+
it("should restore opaque types from cache", () => {
|
|
647
|
+
symbolTable.restoreOpaqueTypes(["widget_t", "handle_t"]);
|
|
648
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(true);
|
|
649
|
+
expect(symbolTable.isOpaqueType("handle_t")).toBe(true);
|
|
650
|
+
expect(symbolTable.getAllOpaqueTypes()).toHaveLength(2);
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// ========================================================================
|
|
655
|
+
// Typedef Struct Type Tracking (Issue #958)
|
|
656
|
+
// ========================================================================
|
|
657
|
+
|
|
658
|
+
describe("Typedef Struct Type Tracking", () => {
|
|
659
|
+
it("should mark and check typedef struct types", () => {
|
|
660
|
+
symbolTable.markTypedefStructType("widget_t", "widget_types.h");
|
|
661
|
+
expect(symbolTable.isTypedefStructType("widget_t")).toBe(true);
|
|
662
|
+
expect(symbolTable.isTypedefStructType("other_t")).toBe(false);
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
it("should unmark typedef struct type when definition is in same file", () => {
|
|
666
|
+
symbolTable.markTypedefStructType("point_t", "point.h");
|
|
667
|
+
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);
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
it("should get all typedef struct types", () => {
|
|
682
|
+
symbolTable.markTypedefStructType("handle_t", "handle.h");
|
|
683
|
+
symbolTable.markTypedefStructType("context_t", "context.h");
|
|
684
|
+
const all = symbolTable.getAllTypedefStructTypes();
|
|
685
|
+
expect(all).toHaveLength(2);
|
|
686
|
+
expect(all).toContainEqual(["handle_t", "handle.h"]);
|
|
687
|
+
expect(all).toContainEqual(["context_t", "context.h"]);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
it("should clear typedef struct types on clear()", () => {
|
|
691
|
+
symbolTable.markTypedefStructType("widget_t", "widget.h");
|
|
692
|
+
symbolTable.clear();
|
|
693
|
+
expect(symbolTable.isTypedefStructType("widget_t")).toBe(false);
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
it("should restore typedef struct types from cache", () => {
|
|
697
|
+
symbolTable.restoreTypedefStructTypes([
|
|
698
|
+
["widget_t", "widget_types.h"],
|
|
699
|
+
["handle_t", "handle.h"],
|
|
700
|
+
]);
|
|
701
|
+
expect(symbolTable.isTypedefStructType("widget_t")).toBe(true);
|
|
702
|
+
expect(symbolTable.isTypedefStructType("handle_t")).toBe(true);
|
|
703
|
+
expect(symbolTable.getAllTypedefStructTypes()).toHaveLength(2);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
|
|
613
707
|
// ========================================================================
|
|
614
708
|
// Clear
|
|
615
709
|
// ========================================================================
|
|
@@ -632,14 +726,18 @@ describe("SymbolTable", () => {
|
|
|
632
726
|
|
|
633
727
|
symbolTable.addStructField("Point", "x", "int");
|
|
634
728
|
symbolTable.markNeedsStructKeyword("RawStruct");
|
|
729
|
+
symbolTable.markOpaqueType("widget_t");
|
|
635
730
|
symbolTable.addEnumBitWidth("SmallEnum", 8);
|
|
731
|
+
symbolTable.markTypedefStructType("handle_t", "handle.h");
|
|
636
732
|
|
|
637
733
|
symbolTable.clear();
|
|
638
734
|
|
|
639
735
|
expect(symbolTable.getAllSymbols().length).toBe(0);
|
|
640
736
|
expect(symbolTable.getStructFieldType("Point", "x")).toBeUndefined();
|
|
641
737
|
expect(symbolTable.checkNeedsStructKeyword("RawStruct")).toBe(false);
|
|
738
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(false);
|
|
642
739
|
expect(symbolTable.getEnumBitWidth("SmallEnum")).toBeUndefined();
|
|
740
|
+
expect(symbolTable.isTypedefStructType("handle_t")).toBe(false);
|
|
643
741
|
});
|
|
644
742
|
});
|
|
645
743
|
});
|
|
@@ -573,6 +573,33 @@ describe("CResolver - Additional Edge Cases", () => {
|
|
|
573
573
|
expect(result.symbols[0].kind).toBe("function");
|
|
574
574
|
});
|
|
575
575
|
|
|
576
|
+
it("collects pointer return type from function definition (Issue #945)", () => {
|
|
577
|
+
// Tests that collectFromDefinition detects pointer return types
|
|
578
|
+
const tree = TestHelpers.parseC(
|
|
579
|
+
`widget_t *create_label(void *parent) { return 0; }`,
|
|
580
|
+
);
|
|
581
|
+
const result = CResolver.resolve(tree!, "test.h");
|
|
582
|
+
|
|
583
|
+
expect(result.symbols).toHaveLength(1);
|
|
584
|
+
const symbol = result.symbols[0];
|
|
585
|
+
expect(symbol.name).toBe("create_label");
|
|
586
|
+
expect(symbol.kind).toBe("function");
|
|
587
|
+
if (symbol.kind === "function") {
|
|
588
|
+
expect(symbol.type).toBe("widget_t*");
|
|
589
|
+
expect(symbol.isDeclaration).toBe(false);
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
it("collects non-pointer return type from function definition (Issue #945)", () => {
|
|
594
|
+
const tree = TestHelpers.parseC(`int getValue(void) { return 42; }`);
|
|
595
|
+
const result = CResolver.resolve(tree!, "test.h");
|
|
596
|
+
|
|
597
|
+
if (result.symbols[0].kind === "function") {
|
|
598
|
+
expect(result.symbols[0].type).toBe("int");
|
|
599
|
+
expect(result.symbols[0].isDeclaration).toBe(false);
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
576
603
|
it("collects pointer return type from function prototype (Issue #895 Bug B)", () => {
|
|
577
604
|
const tree = TestHelpers.parseC(`widget_t *widget_create(int w, int h);`);
|
|
578
605
|
const result = CResolver.resolve(tree!, "test.h");
|
|
@@ -611,4 +638,74 @@ describe("CResolver - Without SymbolTable", () => {
|
|
|
611
638
|
});
|
|
612
639
|
});
|
|
613
640
|
|
|
614
|
-
|
|
641
|
+
describe("CResolver - Opaque Type Detection (Issue #948)", () => {
|
|
642
|
+
it("marks typedef struct forward declaration as opaque", () => {
|
|
643
|
+
const tree = TestHelpers.parseC(`typedef struct _widget_t widget_t;`);
|
|
644
|
+
const symbolTable = new SymbolTable();
|
|
645
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
646
|
+
|
|
647
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(true);
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
it("does not mark typedef struct with named body as opaque", () => {
|
|
651
|
+
const tree = TestHelpers.parseC(
|
|
652
|
+
`typedef struct _point { int x; int y; } point_t;`,
|
|
653
|
+
);
|
|
654
|
+
const symbolTable = new SymbolTable();
|
|
655
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
656
|
+
|
|
657
|
+
expect(symbolTable.isOpaqueType("point_t")).toBe(false);
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("does not mark typedef struct with anonymous body as opaque", () => {
|
|
661
|
+
const tree = TestHelpers.parseC(`typedef struct { int x; } point_t;`);
|
|
662
|
+
const symbolTable = new SymbolTable();
|
|
663
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
664
|
+
expect(symbolTable.isOpaqueType("point_t")).toBe(false);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it("unmarks opaque type when full definition is found in separate parse", () => {
|
|
668
|
+
// Forward declaration first
|
|
669
|
+
const tree1 = TestHelpers.parseC(`typedef struct _foo foo_t;`);
|
|
670
|
+
const symbolTable = new SymbolTable();
|
|
671
|
+
CResolver.resolve(tree1!, "test.h", symbolTable);
|
|
672
|
+
|
|
673
|
+
expect(symbolTable.isOpaqueType("foo_t")).toBe(true);
|
|
674
|
+
|
|
675
|
+
// Full definition later
|
|
676
|
+
const tree2 = TestHelpers.parseC(`struct _foo { int value; };`);
|
|
677
|
+
CResolver.resolve(tree2!, "test.h", symbolTable);
|
|
678
|
+
|
|
679
|
+
expect(symbolTable.isOpaqueType("foo_t")).toBe(false);
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
it("unmarks opaque type when full definition follows typedef in same parse", () => {
|
|
683
|
+
const tree = TestHelpers.parseC(`
|
|
684
|
+
typedef struct _point_t point_t;
|
|
685
|
+
struct _point_t { int x; int y; };
|
|
686
|
+
`);
|
|
687
|
+
const symbolTable = new SymbolTable();
|
|
688
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
689
|
+
expect(symbolTable.isOpaqueType("point_t")).toBe(false);
|
|
690
|
+
});
|
|
691
|
+
|
|
692
|
+
it("handles multiple forward declarations", () => {
|
|
693
|
+
const tree = TestHelpers.parseC(`
|
|
694
|
+
typedef struct _widget_t widget_t;
|
|
695
|
+
typedef struct _display_t display_t;
|
|
696
|
+
`);
|
|
697
|
+
const symbolTable = new SymbolTable();
|
|
698
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
699
|
+
|
|
700
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(true);
|
|
701
|
+
expect(symbolTable.isOpaqueType("display_t")).toBe(true);
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
it("registers struct tag to typedef alias relationship", () => {
|
|
705
|
+
const tree = TestHelpers.parseC(`typedef struct _widget_t widget_t;`);
|
|
706
|
+
const symbolTable = new SymbolTable();
|
|
707
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
708
|
+
|
|
709
|
+
expect(symbolTable.getStructTagAlias("_widget_t")).toBe("widget_t");
|
|
710
|
+
});
|
|
711
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue #957: Test pointer typedef detection
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import CResolver from "../index";
|
|
7
|
+
import TestHelpers from "./testHelpers";
|
|
8
|
+
import SymbolTable from "../../SymbolTable";
|
|
9
|
+
|
|
10
|
+
describe("Issue #957 - Pointer Typedef Detection", () => {
|
|
11
|
+
it("should NOT mark pointer typedef as opaque", () => {
|
|
12
|
+
const symbolTable = new SymbolTable();
|
|
13
|
+
const tree = TestHelpers.parseC(
|
|
14
|
+
`typedef struct spi_device_t *spi_device_handle_t;`,
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
18
|
+
|
|
19
|
+
// spi_device_handle_t should NOT be marked as opaque
|
|
20
|
+
// because the typedef has a pointer declarator (*spi_device_handle_t)
|
|
21
|
+
expect(symbolTable.isOpaqueType("spi_device_handle_t")).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should mark forward-declared struct typedef as opaque", () => {
|
|
25
|
+
const symbolTable = new SymbolTable();
|
|
26
|
+
const tree = TestHelpers.parseC(`typedef struct _widget_t widget_t;`);
|
|
27
|
+
|
|
28
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
29
|
+
|
|
30
|
+
// widget_t SHOULD be marked as opaque because it's a forward declaration
|
|
31
|
+
// without a pointer declarator
|
|
32
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("should unmark opaque when full definition follows", () => {
|
|
36
|
+
const symbolTable = new SymbolTable();
|
|
37
|
+
const tree = TestHelpers.parseC(`
|
|
38
|
+
typedef struct _widget_t widget_t;
|
|
39
|
+
struct _widget_t { int x; };
|
|
40
|
+
`);
|
|
41
|
+
|
|
42
|
+
CResolver.resolve(tree!, "test.h", symbolTable);
|
|
43
|
+
|
|
44
|
+
// widget_t should NOT be opaque because full definition was found
|
|
45
|
+
expect(symbolTable.isOpaqueType("widget_t")).toBe(false);
|
|
46
|
+
});
|
|
47
|
+
});
|