c-next 0.2.0 → 0.2.2
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/bin/cnext.js +17 -7
- package/dist/index.js +139747 -0
- package/dist/index.js.map +7 -0
- package/package.json +8 -4
- package/src/cli/Cli.ts +5 -2
- package/src/cli/PathNormalizer.ts +170 -0
- package/src/cli/PlatformIOCommand.ts +51 -3
- package/src/cli/__tests__/Cli.integration.test.ts +100 -0
- package/src/cli/__tests__/Cli.test.ts +17 -12
- package/src/cli/__tests__/PathNormalizer.test.ts +411 -0
- package/src/cli/__tests__/PlatformIOCommand.test.ts +156 -0
- package/src/cli/serve/__tests__/ServeCommand.test.ts +1 -1
- package/src/lib/__tests__/parseWithSymbols.test.ts +228 -0
- package/src/lib/parseCHeader.ts +5 -1
- package/src/lib/parseWithSymbols.ts +62 -5
- package/src/lib/types/ISymbolInfo.ts +4 -0
- package/src/lib/utils/SymbolPathUtils.ts +87 -0
- package/src/lib/utils/__tests__/SymbolPathUtils.test.ts +123 -0
- package/src/transpiler/NodeFileSystem.ts +5 -0
- package/src/transpiler/logic/symbols/SymbolTable.ts +17 -0
- package/src/transpiler/output/codegen/CodeGenerator.ts +27 -32
- package/src/transpiler/output/codegen/TypeResolver.ts +12 -24
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +130 -5
- package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +67 -57
- package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +9 -13
- package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +20 -10
- package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +5 -2
- package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +18 -0
- package/src/transpiler/output/codegen/assignment/handlers/StringHandlers.ts +25 -4
- package/src/transpiler/output/codegen/assignment/handlers/__tests__/handlerTestUtils.ts +18 -0
- package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +51 -2
- package/src/transpiler/output/codegen/generators/expressions/LiteralGenerator.ts +76 -8
- package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts +147 -0
- package/src/transpiler/output/codegen/generators/expressions/__tests__/LiteralGenerator.test.ts +116 -0
- package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +6 -5
- package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +14 -5
- package/src/transpiler/types/IFileSystem.ts +8 -0
|
@@ -18,6 +18,7 @@ import IGeneratorState from "../IGeneratorState";
|
|
|
18
18
|
import IOrchestrator from "../IOrchestrator";
|
|
19
19
|
import CallExprUtils from "./CallExprUtils";
|
|
20
20
|
import CodeGenState from "../../../../state/CodeGenState";
|
|
21
|
+
import C_TYPE_WIDTH from "../../types/C_TYPE_WIDTH";
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Issue #304: Wrap argument with static_cast if it's a C++ enum class
|
|
@@ -58,6 +59,51 @@ interface IResolvedParam {
|
|
|
58
59
|
isCrossFile: boolean;
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Issue #832: Check if parameter expects address-of for typedef'd pointer types.
|
|
64
|
+
*
|
|
65
|
+
* When a parameter type is `T*` and the argument type is `T`, we need to add `&`.
|
|
66
|
+
* This handles cases like `handle_t` (typedef'd pointer) passed to `handle_t*`.
|
|
67
|
+
*
|
|
68
|
+
* IMPORTANT: This should NOT match primitive types like uint8_t, because arrays
|
|
69
|
+
* of primitives decay to pointers naturally (uint8_t[] → uint8_t*).
|
|
70
|
+
* It SHOULD match typedef'd pointer types like handle_t → handle_t*.
|
|
71
|
+
*
|
|
72
|
+
* @param paramType - The parameter's base type (e.g., "handle_t*")
|
|
73
|
+
* @param argType - The argument's type (e.g., "handle_t")
|
|
74
|
+
* @param orchestrator - For type checking (isIntegerType, isFloatType)
|
|
75
|
+
* @returns true if parameter expects `argType*` (address-of needed)
|
|
76
|
+
*/
|
|
77
|
+
const _parameterExpectsAddressOf = (
|
|
78
|
+
paramType: string,
|
|
79
|
+
argType: string,
|
|
80
|
+
orchestrator: IOrchestrator,
|
|
81
|
+
): boolean => {
|
|
82
|
+
// Don't add & for primitive types - arrays decay to pointers naturally
|
|
83
|
+
// e.g., uint8_t[] passed to uint8_t* should NOT get &
|
|
84
|
+
// Check C-Next primitives (u8, i8, etc.)
|
|
85
|
+
if (
|
|
86
|
+
orchestrator.isIntegerType(argType) ||
|
|
87
|
+
orchestrator.isFloatType(argType) ||
|
|
88
|
+
CallExprUtils.isKnownPrimitiveType(argType)
|
|
89
|
+
) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check C standard types (uint8_t, int32_t, etc.)
|
|
94
|
+
if (argType in C_TYPE_WIDTH) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// paramType should end with * (already checked by caller)
|
|
99
|
+
// Remove trailing pointer markers to get the base type
|
|
100
|
+
// Use indexOf/slice instead of regex to avoid ReDoS concerns (SonarCloud S5852)
|
|
101
|
+
const starIndex = paramType.indexOf("*");
|
|
102
|
+
const paramBaseType =
|
|
103
|
+
starIndex >= 0 ? paramType.slice(0, starIndex).trim() : paramType.trim();
|
|
104
|
+
return paramBaseType === argType;
|
|
105
|
+
};
|
|
106
|
+
|
|
61
107
|
/**
|
|
62
108
|
* Generate argument code for a C/C++ function call.
|
|
63
109
|
* Handles automatic address-of (&) for struct arguments passed to pointer params.
|
|
@@ -87,13 +133,16 @@ const _generateCFunctionArg = (
|
|
|
87
133
|
}
|
|
88
134
|
}
|
|
89
135
|
|
|
90
|
-
// Add & if argument
|
|
136
|
+
// Add & if argument needs address-of to match parameter type.
|
|
137
|
+
// Issue #322: struct types passed to pointer params.
|
|
138
|
+
// Issue #832: typedef'd pointer types (e.g., handle_t passed to handle_t*).
|
|
91
139
|
const needsAddressOf =
|
|
92
140
|
argType &&
|
|
93
141
|
!argType.endsWith("*") &&
|
|
94
142
|
!argCode.startsWith("&") &&
|
|
95
143
|
!targetParam.isArray &&
|
|
96
|
-
orchestrator.isStructType(argType)
|
|
144
|
+
(orchestrator.isStructType(argType) ||
|
|
145
|
+
_parameterExpectsAddressOf(targetParam.baseType, argType, orchestrator));
|
|
97
146
|
|
|
98
147
|
if (needsAddressOf) {
|
|
99
148
|
argCode = `&${argCode}`;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* - Boolean literals (true/false) → needs stdbool.h
|
|
6
6
|
* - Float literals with C-Next suffixes (f32 → f, f64 → no suffix)
|
|
7
7
|
* - Integer literals with C-Next suffixes (u64 → ULL, i64 → LL, strip 8/16/32)
|
|
8
|
+
* - MISRA Rule 7.2: Unsigned suffix for unsigned integer types
|
|
8
9
|
* - String and numeric literals pass through unchanged
|
|
9
10
|
*/
|
|
10
11
|
import { LiteralContext } from "../../../../logic/parser/grammar/CNextParser";
|
|
@@ -14,19 +15,63 @@ import IGeneratorInput from "../IGeneratorInput";
|
|
|
14
15
|
import IGeneratorState from "../IGeneratorState";
|
|
15
16
|
import IOrchestrator from "../IOrchestrator";
|
|
16
17
|
|
|
18
|
+
/**
|
|
19
|
+
* Unsigned type patterns for MISRA Rule 7.2 compliance.
|
|
20
|
+
* Includes both C-Next types (u8, u16, u32, u64) and C types (uint8_t, etc.)
|
|
21
|
+
*/
|
|
22
|
+
const UNSIGNED_64_TYPES = new Set(["u64", "uint64_t"]);
|
|
23
|
+
const UNSIGNED_TYPES = new Set([
|
|
24
|
+
"u8",
|
|
25
|
+
"u16",
|
|
26
|
+
"u32",
|
|
27
|
+
"uint8_t",
|
|
28
|
+
"uint16_t",
|
|
29
|
+
"uint32_t",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check if a literal is a numeric integer (decimal, hex, octal, or binary).
|
|
34
|
+
* Excludes strings, floats, and booleans.
|
|
35
|
+
*/
|
|
36
|
+
function isNumericIntegerLiteral(text: string): boolean {
|
|
37
|
+
// Exclude strings (quoted)
|
|
38
|
+
if (text.startsWith('"') || text.startsWith("'")) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
// Exclude booleans
|
|
42
|
+
if (text === "true" || text === "false") {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Exclude floats (contain decimal point or exponent without 0x prefix)
|
|
46
|
+
if (!text.startsWith("0x") && !text.startsWith("0X")) {
|
|
47
|
+
if (text.includes(".") || /[eE][+-]?\d/.test(text)) {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// Must start with digit or be hex/binary/octal
|
|
52
|
+
return /^\d/.test(text) || /^0[xXbBoO]/.test(text);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if a literal already has a C unsigned suffix (U, UL, ULL).
|
|
57
|
+
*/
|
|
58
|
+
function hasUnsignedSuffix(text: string): boolean {
|
|
59
|
+
return /[uU]([lL]{0,2})$/.test(text);
|
|
60
|
+
}
|
|
61
|
+
|
|
17
62
|
/**
|
|
18
63
|
* Generate C code for a literal value.
|
|
19
64
|
*
|
|
20
65
|
* @param node - The LiteralContext AST node
|
|
21
66
|
* @param _input - Read-only context (unused for literals)
|
|
22
|
-
* @param
|
|
67
|
+
* @param state - Current generation state (contains expectedType)
|
|
23
68
|
* @param _orchestrator - For delegating to other generators (unused for literals)
|
|
24
69
|
* @returns Generated code and effects (stdbool include for bool literals)
|
|
25
70
|
*/
|
|
26
71
|
const generateLiteral = (
|
|
27
72
|
node: LiteralContext,
|
|
28
73
|
_input: IGeneratorInput,
|
|
29
|
-
|
|
74
|
+
state: IGeneratorState,
|
|
30
75
|
_orchestrator: IOrchestrator,
|
|
31
76
|
): IGeneratorOutput => {
|
|
32
77
|
const effects: TGeneratorEffect[] = [];
|
|
@@ -35,30 +80,53 @@ const generateLiteral = (
|
|
|
35
80
|
// Track boolean literal usage to include stdbool.h
|
|
36
81
|
if (literalText === "true" || literalText === "false") {
|
|
37
82
|
effects.push({ type: "include", header: "stdbool" });
|
|
83
|
+
return { code: literalText, effects };
|
|
38
84
|
}
|
|
39
85
|
|
|
40
86
|
// ADR-024: Transform C-Next float suffixes to standard C syntax
|
|
41
87
|
// 3.14f32 -> 3.14f (C float)
|
|
42
88
|
// 3.14f64 -> 3.14 (C double, no suffix needed)
|
|
43
|
-
if (/[fF]32$/.
|
|
89
|
+
if (/[fF]32$/.test(literalText)) {
|
|
44
90
|
literalText = literalText.replace(/[fF]32$/, "f");
|
|
45
|
-
|
|
91
|
+
return { code: literalText, effects };
|
|
92
|
+
}
|
|
93
|
+
if (/[fF]64$/.test(literalText)) {
|
|
46
94
|
literalText = literalText.replace(/[fF]64$/, "");
|
|
95
|
+
return { code: literalText, effects };
|
|
47
96
|
}
|
|
48
97
|
|
|
49
98
|
// Issue #130: Transform C-Next integer suffixes to standard C syntax
|
|
50
99
|
// u8/u16/u32 and i8/i16/i32 suffixes are stripped (C infers from context)
|
|
51
100
|
// u64 -> ULL suffix for 64-bit unsigned
|
|
52
101
|
// i64 -> LL suffix for 64-bit signed
|
|
53
|
-
if (/[uU]64$/.
|
|
102
|
+
if (/[uU]64$/.test(literalText)) {
|
|
54
103
|
literalText = literalText.replace(/[uU]64$/, "ULL");
|
|
55
|
-
|
|
104
|
+
return { code: literalText, effects };
|
|
105
|
+
}
|
|
106
|
+
if (/[iI]64$/.test(literalText)) {
|
|
56
107
|
literalText = literalText.replace(/[iI]64$/, "LL");
|
|
57
|
-
|
|
58
|
-
|
|
108
|
+
return { code: literalText, effects };
|
|
109
|
+
}
|
|
110
|
+
if (/[uUiI](8|16|32)$/.test(literalText)) {
|
|
111
|
+
// Strip 8/16/32-bit suffixes - will add U below if needed for MISRA
|
|
59
112
|
literalText = literalText.replace(/[uUiI](8|16|32)$/, "");
|
|
60
113
|
}
|
|
61
114
|
|
|
115
|
+
// MISRA Rule 7.2: Add unsigned suffix based on expectedType
|
|
116
|
+
// Only applies to numeric integer literals without existing unsigned suffix
|
|
117
|
+
const expectedType = state?.expectedType;
|
|
118
|
+
if (
|
|
119
|
+
expectedType &&
|
|
120
|
+
isNumericIntegerLiteral(literalText) &&
|
|
121
|
+
!hasUnsignedSuffix(literalText)
|
|
122
|
+
) {
|
|
123
|
+
if (UNSIGNED_64_TYPES.has(expectedType)) {
|
|
124
|
+
literalText = literalText + "ULL";
|
|
125
|
+
} else if (UNSIGNED_TYPES.has(expectedType)) {
|
|
126
|
+
literalText = literalText + "U";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
62
130
|
return { code: literalText, effects };
|
|
63
131
|
};
|
|
64
132
|
|
package/src/transpiler/output/codegen/generators/expressions/__tests__/CallExprGenerator.test.ts
CHANGED
|
@@ -1270,4 +1270,151 @@ describe("CallExprGenerator", () => {
|
|
|
1270
1270
|
expect(markParameterModified).not.toHaveBeenCalled();
|
|
1271
1271
|
});
|
|
1272
1272
|
});
|
|
1273
|
+
|
|
1274
|
+
// ========================================================================
|
|
1275
|
+
// Issue #832: Auto-reference for typedef pointer output parameters
|
|
1276
|
+
// ========================================================================
|
|
1277
|
+
describe("Issue #832: typedef pointer output parameters", () => {
|
|
1278
|
+
it("adds & when typedef pointer type is passed to pointer-to-typedef param", () => {
|
|
1279
|
+
// handle_t is typedef'd pointer, create_handle expects handle_t*
|
|
1280
|
+
const argExprs = [createMockExpressionContext("my_handle")];
|
|
1281
|
+
const argCtx = createMockArgListContext(argExprs);
|
|
1282
|
+
const sigs = new Map([
|
|
1283
|
+
[
|
|
1284
|
+
"create_handle",
|
|
1285
|
+
{
|
|
1286
|
+
name: "create_handle",
|
|
1287
|
+
parameters: [
|
|
1288
|
+
{
|
|
1289
|
+
name: "out",
|
|
1290
|
+
baseType: "handle_t*",
|
|
1291
|
+
isConst: false,
|
|
1292
|
+
isArray: false,
|
|
1293
|
+
},
|
|
1294
|
+
],
|
|
1295
|
+
},
|
|
1296
|
+
],
|
|
1297
|
+
]);
|
|
1298
|
+
const typeRegistry = new Map([
|
|
1299
|
+
[
|
|
1300
|
+
"my_handle",
|
|
1301
|
+
{ baseType: "handle_t", bitWidth: 0, isArray: false, isConst: false },
|
|
1302
|
+
],
|
|
1303
|
+
]);
|
|
1304
|
+
const input = createMockInput({ functionSignatures: sigs, typeRegistry });
|
|
1305
|
+
const state = createMockState();
|
|
1306
|
+
const orchestrator = createMockOrchestrator({
|
|
1307
|
+
isCNextFunction: vi.fn(() => false),
|
|
1308
|
+
getExpressionType: vi.fn(() => "handle_t"),
|
|
1309
|
+
isStructType: vi.fn(() => false), // typedef pointer is not a struct
|
|
1310
|
+
isIntegerType: vi.fn(() => false),
|
|
1311
|
+
isFloatType: vi.fn(() => false),
|
|
1312
|
+
});
|
|
1313
|
+
|
|
1314
|
+
const result = generateFunctionCall(
|
|
1315
|
+
"create_handle",
|
|
1316
|
+
argCtx,
|
|
1317
|
+
input,
|
|
1318
|
+
state,
|
|
1319
|
+
orchestrator,
|
|
1320
|
+
);
|
|
1321
|
+
|
|
1322
|
+
expect(result.code).toBe("create_handle(&my_handle)");
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it("does not add & for primitive types passed to pointer params (array decay)", () => {
|
|
1326
|
+
// uint8_t[] passed to uint8_t* should NOT get &
|
|
1327
|
+
const argExprs = [createMockExpressionContext("data")];
|
|
1328
|
+
const argCtx = createMockArgListContext(argExprs);
|
|
1329
|
+
const sigs = new Map([
|
|
1330
|
+
[
|
|
1331
|
+
"send_data",
|
|
1332
|
+
{
|
|
1333
|
+
name: "send_data",
|
|
1334
|
+
parameters: [
|
|
1335
|
+
{
|
|
1336
|
+
name: "buf",
|
|
1337
|
+
baseType: "uint8_t*",
|
|
1338
|
+
isConst: false,
|
|
1339
|
+
isArray: false,
|
|
1340
|
+
},
|
|
1341
|
+
],
|
|
1342
|
+
},
|
|
1343
|
+
],
|
|
1344
|
+
]);
|
|
1345
|
+
const typeRegistry = new Map([
|
|
1346
|
+
[
|
|
1347
|
+
"data",
|
|
1348
|
+
{ baseType: "uint8_t", bitWidth: 8, isArray: true, isConst: false },
|
|
1349
|
+
],
|
|
1350
|
+
]);
|
|
1351
|
+
const input = createMockInput({ functionSignatures: sigs, typeRegistry });
|
|
1352
|
+
const state = createMockState();
|
|
1353
|
+
const orchestrator = createMockOrchestrator({
|
|
1354
|
+
isCNextFunction: vi.fn(() => false),
|
|
1355
|
+
getExpressionType: vi.fn(() => "uint8_t"),
|
|
1356
|
+
isStructType: vi.fn(() => false),
|
|
1357
|
+
isIntegerType: vi.fn(() => false), // uint8_t is C type, not in INTEGER_TYPES
|
|
1358
|
+
isFloatType: vi.fn(() => false),
|
|
1359
|
+
});
|
|
1360
|
+
|
|
1361
|
+
const result = generateFunctionCall(
|
|
1362
|
+
"send_data",
|
|
1363
|
+
argCtx,
|
|
1364
|
+
input,
|
|
1365
|
+
state,
|
|
1366
|
+
orchestrator,
|
|
1367
|
+
);
|
|
1368
|
+
|
|
1369
|
+
// Should NOT add & because uint8_t is in C_TYPE_WIDTH (primitive)
|
|
1370
|
+
expect(result.code).toBe("send_data(data)");
|
|
1371
|
+
});
|
|
1372
|
+
|
|
1373
|
+
it("does not add & when typedef type is passed directly (not to pointer)", () => {
|
|
1374
|
+
// use_handle expects handle_t, not handle_t*
|
|
1375
|
+
const argExprs = [createMockExpressionContext("my_handle")];
|
|
1376
|
+
const argCtx = createMockArgListContext(argExprs);
|
|
1377
|
+
const sigs = new Map([
|
|
1378
|
+
[
|
|
1379
|
+
"use_handle",
|
|
1380
|
+
{
|
|
1381
|
+
name: "use_handle",
|
|
1382
|
+
parameters: [
|
|
1383
|
+
{
|
|
1384
|
+
name: "h",
|
|
1385
|
+
baseType: "handle_t", // NOT a pointer
|
|
1386
|
+
isConst: false,
|
|
1387
|
+
isArray: false,
|
|
1388
|
+
},
|
|
1389
|
+
],
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1392
|
+
]);
|
|
1393
|
+
const typeRegistry = new Map([
|
|
1394
|
+
[
|
|
1395
|
+
"my_handle",
|
|
1396
|
+
{ baseType: "handle_t", bitWidth: 0, isArray: false, isConst: false },
|
|
1397
|
+
],
|
|
1398
|
+
]);
|
|
1399
|
+
const input = createMockInput({ functionSignatures: sigs, typeRegistry });
|
|
1400
|
+
const state = createMockState();
|
|
1401
|
+
const orchestrator = createMockOrchestrator({
|
|
1402
|
+
isCNextFunction: vi.fn(() => false),
|
|
1403
|
+
getExpressionType: vi.fn(() => "handle_t"),
|
|
1404
|
+
isStructType: vi.fn(() => false),
|
|
1405
|
+
isIntegerType: vi.fn(() => false),
|
|
1406
|
+
isFloatType: vi.fn(() => false),
|
|
1407
|
+
});
|
|
1408
|
+
|
|
1409
|
+
const result = generateFunctionCall(
|
|
1410
|
+
"use_handle",
|
|
1411
|
+
argCtx,
|
|
1412
|
+
input,
|
|
1413
|
+
state,
|
|
1414
|
+
orchestrator,
|
|
1415
|
+
);
|
|
1416
|
+
|
|
1417
|
+
expect(result.code).toBe("use_handle(my_handle)");
|
|
1418
|
+
});
|
|
1419
|
+
});
|
|
1273
1420
|
});
|
package/src/transpiler/output/codegen/generators/expressions/__tests__/LiteralGenerator.test.ts
CHANGED
|
@@ -165,4 +165,120 @@ describe("LiteralGenerator", () => {
|
|
|
165
165
|
expect(result.effects).toEqual([]);
|
|
166
166
|
});
|
|
167
167
|
});
|
|
168
|
+
|
|
169
|
+
describe("MISRA Rule 7.2: unsigned suffix for unsigned types", () => {
|
|
170
|
+
/**
|
|
171
|
+
* Create mock state with expectedType set.
|
|
172
|
+
*/
|
|
173
|
+
function createStateWithExpectedType(
|
|
174
|
+
expectedType: string | null,
|
|
175
|
+
): IGeneratorState {
|
|
176
|
+
return { expectedType } as IGeneratorState;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
it("should add U suffix to decimal literal when expectedType is u8", () => {
|
|
180
|
+
const node = createMockLiteral("255");
|
|
181
|
+
const state = createStateWithExpectedType("u8");
|
|
182
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
183
|
+
|
|
184
|
+
expect(result.code).toBe("255U");
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("should add U suffix to decimal literal when expectedType is u16", () => {
|
|
188
|
+
const node = createMockLiteral("60000");
|
|
189
|
+
const state = createStateWithExpectedType("u16");
|
|
190
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
191
|
+
|
|
192
|
+
expect(result.code).toBe("60000U");
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it("should add U suffix to decimal literal when expectedType is u32", () => {
|
|
196
|
+
const node = createMockLiteral("4000000000");
|
|
197
|
+
const state = createStateWithExpectedType("u32");
|
|
198
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
199
|
+
|
|
200
|
+
expect(result.code).toBe("4000000000U");
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("should add ULL suffix to decimal literal when expectedType is u64", () => {
|
|
204
|
+
const node = createMockLiteral("42");
|
|
205
|
+
const state = createStateWithExpectedType("u64");
|
|
206
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
207
|
+
|
|
208
|
+
expect(result.code).toBe("42ULL");
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("should add U suffix to hex literal when expectedType is u8", () => {
|
|
212
|
+
const node = createMockLiteral("0xFF");
|
|
213
|
+
const state = createStateWithExpectedType("u8");
|
|
214
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
215
|
+
|
|
216
|
+
expect(result.code).toBe("0xFFU");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it("should add U suffix to binary literal when expectedType is u8", () => {
|
|
220
|
+
const node = createMockLiteral("0b11110000");
|
|
221
|
+
const state = createStateWithExpectedType("u8");
|
|
222
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
223
|
+
|
|
224
|
+
expect(result.code).toBe("0b11110000U");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("should NOT add U suffix when expectedType is signed (i32)", () => {
|
|
228
|
+
const node = createMockLiteral("42");
|
|
229
|
+
const state = createStateWithExpectedType("i32");
|
|
230
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
231
|
+
|
|
232
|
+
expect(result.code).toBe("42");
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should NOT add U suffix when expectedType is null", () => {
|
|
236
|
+
const node = createMockLiteral("42");
|
|
237
|
+
const state = createStateWithExpectedType(null);
|
|
238
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
239
|
+
|
|
240
|
+
expect(result.code).toBe("42");
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it("should NOT add U suffix to string literals even with unsigned expectedType", () => {
|
|
244
|
+
const node = createMockLiteral('"hello"');
|
|
245
|
+
const state = createStateWithExpectedType("u8");
|
|
246
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
247
|
+
|
|
248
|
+
expect(result.code).toBe('"hello"');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("should NOT add U suffix to float literals even with unsigned expectedType", () => {
|
|
252
|
+
const node = createMockLiteral("3.14");
|
|
253
|
+
const state = createStateWithExpectedType("u32");
|
|
254
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
255
|
+
|
|
256
|
+
expect(result.code).toBe("3.14");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should NOT double-add U suffix if already present via explicit suffix", () => {
|
|
260
|
+
const node = createMockLiteral("42u32");
|
|
261
|
+
const state = createStateWithExpectedType("u32");
|
|
262
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
263
|
+
|
|
264
|
+
// u32 suffix is stripped, then U is added based on expectedType
|
|
265
|
+
expect(result.code).toBe("42U");
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should handle uint8_t C type as unsigned", () => {
|
|
269
|
+
const node = createMockLiteral("42");
|
|
270
|
+
const state = createStateWithExpectedType("uint8_t");
|
|
271
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
272
|
+
|
|
273
|
+
expect(result.code).toBe("42U");
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("should handle uint32_t C type as unsigned", () => {
|
|
277
|
+
const node = createMockLiteral("0x80000000");
|
|
278
|
+
const state = createStateWithExpectedType("uint32_t");
|
|
279
|
+
const result = generateLiteral(node, mockInput, state, mockOrchestrator);
|
|
280
|
+
|
|
281
|
+
expect(result.code).toBe("0x80000000U");
|
|
282
|
+
});
|
|
283
|
+
});
|
|
168
284
|
});
|
|
@@ -116,17 +116,18 @@ class AssignmentExpectedTypeResolver {
|
|
|
116
116
|
let currentStructType: string | undefined = rootTypeInfo.baseType;
|
|
117
117
|
|
|
118
118
|
// Walk through each member in the chain to find the final field's type
|
|
119
|
+
// Issue #831: Use SymbolTable as single source of truth for struct fields
|
|
119
120
|
for (let i = 1; i < identifiers.length && currentStructType; i++) {
|
|
120
121
|
const memberName = identifiers[i];
|
|
121
|
-
const
|
|
122
|
-
|
|
122
|
+
const memberType = CodeGenState.symbolTable?.getStructFieldType(
|
|
123
|
+
currentStructType,
|
|
124
|
+
memberName,
|
|
125
|
+
);
|
|
123
126
|
|
|
124
|
-
if (!
|
|
127
|
+
if (!memberType) {
|
|
125
128
|
break;
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
const memberType: string = structFieldTypes.get(memberName)!;
|
|
129
|
-
|
|
130
131
|
if (i === identifiers.length - 1) {
|
|
131
132
|
// Last field in chain - this is the assignment target's type
|
|
132
133
|
return { expectedType: memberType, assignmentContext: null };
|
package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach } from "vitest";
|
|
|
2
2
|
import AssignmentExpectedTypeResolver from "../AssignmentExpectedTypeResolver.js";
|
|
3
3
|
import CNextSourceParser from "../../../../logic/parser/CNextSourceParser.js";
|
|
4
4
|
import CodeGenState from "../../../../state/CodeGenState.js";
|
|
5
|
+
import SymbolTable from "../../../../logic/symbols/SymbolTable.js";
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Create a mock assignment target context by parsing a minimal assignment statement.
|
|
@@ -18,12 +19,24 @@ function parseAssignmentTarget(target: string) {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Helper to set up struct fields in CodeGenState.
|
|
22
|
+
* Helper to set up struct fields in CodeGenState.symbolTable
|
|
23
|
+
* Issue #831: SymbolTable is now the single source of truth for struct fields
|
|
22
24
|
*/
|
|
23
25
|
function setupStructFields(
|
|
24
26
|
structName: string,
|
|
25
27
|
fields: Map<string, string>,
|
|
26
28
|
): void {
|
|
29
|
+
// Initialize symbolTable if not set
|
|
30
|
+
if (!CodeGenState.symbolTable) {
|
|
31
|
+
CodeGenState.symbolTable = new SymbolTable();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Register struct fields in SymbolTable
|
|
35
|
+
for (const [fieldName, fieldType] of fields) {
|
|
36
|
+
CodeGenState.symbolTable.addStructField(structName, fieldName, fieldType);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Also mark struct as known (for isKnownStruct checks)
|
|
27
40
|
if (!CodeGenState.symbols) {
|
|
28
41
|
CodeGenState.symbols = {
|
|
29
42
|
knownStructs: new Set(),
|
|
@@ -54,10 +67,6 @@ function setupStructFields(
|
|
|
54
67
|
};
|
|
55
68
|
}
|
|
56
69
|
(CodeGenState.symbols.knownStructs as Set<string>).add(structName);
|
|
57
|
-
(CodeGenState.symbols.structFields as Map<string, Map<string, string>>).set(
|
|
58
|
-
structName,
|
|
59
|
-
fields,
|
|
60
|
-
);
|
|
61
70
|
}
|
|
62
71
|
|
|
63
72
|
describe("AssignmentExpectedTypeResolver", () => {
|
|
@@ -58,6 +58,14 @@ interface IFileSystem {
|
|
|
58
58
|
* @throws Error if file doesn't exist or can't be read
|
|
59
59
|
*/
|
|
60
60
|
stat(path: string): { mtimeMs: number };
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Resolve symlinks to get the real path.
|
|
64
|
+
* Optional - if not provided, symlink loop detection is skipped.
|
|
65
|
+
* @returns The resolved real path
|
|
66
|
+
* @throws Error if path doesn't exist
|
|
67
|
+
*/
|
|
68
|
+
realpath?(path: string): string;
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
export default IFileSystem;
|