c-next 0.1.62 → 0.1.63

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 (55) hide show
  1. package/README.md +86 -63
  2. package/package.json +1 -1
  3. package/src/transpiler/Transpiler.ts +3 -2
  4. package/src/transpiler/__tests__/DualCodePaths.test.ts +1 -1
  5. package/src/transpiler/__tests__/Transpiler.coverage.test.ts +1 -1
  6. package/src/transpiler/__tests__/Transpiler.test.ts +0 -23
  7. package/src/transpiler/logic/symbols/cnext/collectors/StructCollector.ts +156 -70
  8. package/src/transpiler/logic/symbols/cnext/collectors/VariableCollector.ts +31 -6
  9. package/src/transpiler/logic/symbols/cnext/utils/TypeUtils.ts +43 -11
  10. package/src/transpiler/output/codegen/CodeGenState.ts +811 -0
  11. package/src/transpiler/output/codegen/CodeGenerator.ts +817 -1377
  12. package/src/transpiler/output/codegen/TypeResolver.ts +193 -149
  13. package/src/transpiler/output/codegen/TypeValidator.ts +148 -370
  14. package/src/transpiler/output/codegen/__tests__/CodeGenState.test.ts +446 -0
  15. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +326 -60
  16. package/src/transpiler/output/codegen/__tests__/TrackVariableTypeHelpers.test.ts +1 -1
  17. package/src/transpiler/output/codegen/__tests__/TypeResolver.test.ts +435 -196
  18. package/src/transpiler/output/codegen/__tests__/TypeValidator.resolution.test.ts +51 -67
  19. package/src/transpiler/output/codegen/__tests__/TypeValidator.test.ts +495 -471
  20. package/src/transpiler/output/codegen/analysis/MemberChainAnalyzer.ts +39 -43
  21. package/src/transpiler/output/codegen/analysis/StringLengthCounter.ts +52 -55
  22. package/src/transpiler/output/codegen/analysis/__tests__/MemberChainAnalyzer.test.ts +122 -62
  23. package/src/transpiler/output/codegen/analysis/__tests__/StringLengthCounter.test.ts +101 -144
  24. package/src/transpiler/output/codegen/assignment/AssignmentClassifier.ts +143 -126
  25. package/src/transpiler/output/codegen/assignment/__tests__/AssignmentClassifier.test.ts +287 -320
  26. package/src/transpiler/output/codegen/generators/GeneratorRegistry.ts +12 -0
  27. package/src/transpiler/output/codegen/generators/__tests__/GeneratorRegistry.test.ts +28 -1
  28. package/src/transpiler/output/codegen/generators/declarationGenerators/ArrayDimensionUtils.ts +67 -0
  29. package/src/transpiler/output/codegen/generators/declarationGenerators/ScopeGenerator.ts +121 -51
  30. package/src/transpiler/output/codegen/generators/declarationGenerators/StructGenerator.ts +100 -23
  31. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ArrayDimensionUtils.test.ts +125 -0
  32. package/src/transpiler/output/codegen/generators/declarationGenerators/__tests__/ScopeGenerator.test.ts +157 -4
  33. package/src/transpiler/output/codegen/generators/support/HelperGenerator.ts +23 -22
  34. package/src/transpiler/output/codegen/helpers/ArrayInitHelper.ts +54 -61
  35. package/src/transpiler/output/codegen/helpers/AssignmentExpectedTypeResolver.ts +21 -30
  36. package/src/transpiler/output/codegen/helpers/AssignmentValidator.ts +56 -53
  37. package/src/transpiler/output/codegen/helpers/CppModeHelper.ts +22 -30
  38. package/src/transpiler/output/codegen/helpers/EnumAssignmentValidator.ts +108 -50
  39. package/src/transpiler/output/codegen/helpers/FloatBitHelper.ts +16 -31
  40. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +103 -96
  41. package/src/transpiler/output/codegen/helpers/TypeGenerationHelper.ts +9 -0
  42. package/src/transpiler/output/codegen/helpers/__tests__/ArrayInitHelper.test.ts +58 -103
  43. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentExpectedTypeResolver.test.ts +97 -40
  44. package/src/transpiler/output/codegen/helpers/__tests__/AssignmentValidator.test.ts +223 -128
  45. package/src/transpiler/output/codegen/helpers/__tests__/CppModeHelper.test.ts +68 -41
  46. package/src/transpiler/output/codegen/helpers/__tests__/EnumAssignmentValidator.test.ts +198 -47
  47. package/src/transpiler/output/codegen/helpers/__tests__/FloatBitHelper.test.ts +39 -37
  48. package/src/transpiler/output/codegen/helpers/__tests__/StringDeclHelper.test.ts +191 -453
  49. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +229 -0
  50. package/src/transpiler/output/codegen/resolution/ScopeResolver.ts +60 -0
  51. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +177 -0
  52. package/src/transpiler/output/codegen/resolution/__tests__/EnumTypeResolver.test.ts +336 -0
  53. package/src/transpiler/output/codegen/resolution/__tests__/SizeofResolver.test.ts +201 -0
  54. package/src/transpiler/output/codegen/types/ITypeResolverDeps.ts +0 -23
  55. package/src/transpiler/output/codegen/types/ITypeValidatorDeps.ts +0 -53
@@ -1,13 +1,9 @@
1
1
  /**
2
2
  * TypeResolver - Handles type inference, classification, and validation
3
- * Extracted from CodeGenerator for better separation of concerns
4
- * Issue #61: Now independent of CodeGenerator
3
+ * Static class that reads from CodeGenState directly.
5
4
  */
6
5
  import * as Parser from "../../logic/parser/grammar/CNextParser";
7
- import ICodeGenSymbols from "../../types/ICodeGenSymbols";
8
- import SymbolTable from "../../logic/symbols/SymbolTable";
9
- import TTypeInfo from "./types/TTypeInfo";
10
- import ITypeResolverDeps from "./types/ITypeResolverDeps";
6
+ import CodeGenState from "./CodeGenState";
11
7
  import INTEGER_TYPES from "./types/INTEGER_TYPES";
12
8
  import FLOAT_TYPES from "./types/FLOAT_TYPES";
13
9
  import SIGNED_TYPES from "./types/SIGNED_TYPES";
@@ -16,64 +12,68 @@ import TYPE_WIDTH from "./types/TYPE_WIDTH";
16
12
  import TYPE_RANGES from "./types/TYPE_RANGES";
17
13
  import ExpressionUnwrapper from "./utils/ExpressionUnwrapper";
18
14
 
15
+ /**
16
+ * Internal type info tracked through postfix suffix chains.
17
+ * Preserves isArray so indexing can distinguish array access from bit indexing.
18
+ */
19
+ type InternalTypeInfo = { baseType: string; isArray: boolean };
20
+
21
+ /**
22
+ * Discriminated union for postfix suffix processing results.
23
+ * stop=true: return type immediately (terminal suffix like bit indexing).
24
+ * stop=false: continue chain with updated InternalTypeInfo.
25
+ */
26
+ type SuffixResult =
27
+ | { stop: true; type: string | null }
28
+ | { stop: false; info: InternalTypeInfo };
29
+
19
30
  class TypeResolver {
20
- private readonly symbols: ICodeGenSymbols | null;
21
- private readonly symbolTable: SymbolTable | null;
22
- private readonly typeRegistry: Map<string, TTypeInfo>;
23
- private readonly resolveIdentifierFn: (name: string) => string;
24
-
25
- constructor(deps: ITypeResolverDeps) {
26
- this.symbols = deps.symbols;
27
- this.symbolTable = deps.symbolTable;
28
- this.typeRegistry = deps.typeRegistry;
29
- this.resolveIdentifierFn = deps.resolveIdentifier;
30
- }
31
+ /** Sentinel value for `global` keyword in postfix expression type resolution */
32
+ private static readonly GLOBAL_SENTINEL = "__global__";
33
+ /** Sentinel value for `this` keyword in postfix expression type resolution */
34
+ private static readonly THIS_SENTINEL = "__this__";
31
35
 
32
36
  /**
33
37
  * ADR-024: Check if a type is any integer (signed or unsigned)
34
38
  */
35
- isIntegerType(typeName: string): boolean {
39
+ static isIntegerType(typeName: string): boolean {
36
40
  return (INTEGER_TYPES as readonly string[]).includes(typeName);
37
41
  }
38
42
 
39
43
  /**
40
44
  * ADR-024: Check if a type is a floating point type
41
45
  */
42
- isFloatType(typeName: string): boolean {
46
+ static isFloatType(typeName: string): boolean {
43
47
  return (FLOAT_TYPES as readonly string[]).includes(typeName);
44
48
  }
45
49
 
46
50
  /**
47
51
  * ADR-024: Check if a type is a signed integer
48
52
  */
49
- isSignedType(typeName: string): boolean {
53
+ static isSignedType(typeName: string): boolean {
50
54
  return (SIGNED_TYPES as readonly string[]).includes(typeName);
51
55
  }
52
56
 
53
57
  /**
54
58
  * ADR-024: Check if a type is an unsigned integer
55
59
  */
56
- isUnsignedType(typeName: string): boolean {
60
+ static isUnsignedType(typeName: string): boolean {
57
61
  return (UNSIGNED_TYPES as readonly string[]).includes(typeName);
58
62
  }
59
63
 
60
64
  /**
61
65
  * Check if a type is a user-defined struct (C-Next or C header).
62
66
  * Issue #103: Now checks both knownStructs AND SymbolTable.
63
- * Issue #60: Uses SymbolCollector for C-Next structs.
64
- * Issue #61: Uses injected dependencies instead of CodeGenerator.
65
67
  */
66
- isStructType(typeName: string): boolean {
67
- // Check C-Next structs first (Issue #60: use SymbolCollector)
68
- if (this.symbols?.knownStructs.has(typeName)) {
68
+ static isStructType(typeName: string): boolean {
69
+ if (CodeGenState.symbols?.knownStructs.has(typeName)) {
69
70
  return true;
70
71
  }
71
72
  // Issue #551: Bitmaps are struct-like (use pass-by-reference with -> access)
72
- if (this.symbols?.knownBitmaps.has(typeName)) {
73
+ if (CodeGenState.symbols?.knownBitmaps.has(typeName)) {
73
74
  return true;
74
75
  }
75
- // Check SymbolTable for C header structs
76
- if (this.symbolTable?.getStructFields(typeName)) {
76
+ if (CodeGenState.symbolTable?.getStructFields(typeName)) {
77
77
  return true;
78
78
  }
79
79
  return false;
@@ -81,14 +81,16 @@ class TypeResolver {
81
81
 
82
82
  /**
83
83
  * ADR-024: Check if conversion from sourceType to targetType is narrowing
84
- * Narrowing occurs when target type has fewer bits than source type
85
84
  */
86
- isNarrowingConversion(sourceType: string, targetType: string): boolean {
85
+ static isNarrowingConversion(
86
+ sourceType: string,
87
+ targetType: string,
88
+ ): boolean {
87
89
  const sourceWidth = TYPE_WIDTH[sourceType] || 0;
88
90
  const targetWidth = TYPE_WIDTH[targetType] || 0;
89
91
 
90
92
  if (sourceWidth === 0 || targetWidth === 0) {
91
- return false; // Can't determine for unknown types
93
+ return false;
92
94
  }
93
95
 
94
96
  return targetWidth < sourceWidth;
@@ -96,13 +98,12 @@ class TypeResolver {
96
98
 
97
99
  /**
98
100
  * ADR-024: Check if conversion involves a sign change
99
- * Sign change occurs when converting between signed and unsigned types
100
101
  */
101
- isSignConversion(sourceType: string, targetType: string): boolean {
102
- const sourceIsSigned = this.isSignedType(sourceType);
103
- const sourceIsUnsigned = this.isUnsignedType(sourceType);
104
- const targetIsSigned = this.isSignedType(targetType);
105
- const targetIsUnsigned = this.isUnsignedType(targetType);
102
+ static isSignConversion(sourceType: string, targetType: string): boolean {
103
+ const sourceIsSigned = TypeResolver.isSignedType(sourceType);
104
+ const sourceIsUnsigned = TypeResolver.isUnsignedType(sourceType);
105
+ const targetIsSigned = TypeResolver.isSignedType(targetType);
106
+ const targetIsUnsigned = TypeResolver.isUnsignedType(targetType);
106
107
 
107
108
  return (
108
109
  (sourceIsSigned && targetIsUnsigned) ||
@@ -113,47 +114,41 @@ class TypeResolver {
113
114
  /**
114
115
  * ADR-024: Validate that a literal value fits within the target type's range.
115
116
  * Throws an error if the value doesn't fit.
116
- * @param literalText The literal text (e.g., "256", "-1", "0xFF")
117
- * @param targetType The target type (e.g., "u8", "i32")
118
117
  */
119
- validateLiteralFitsType(literalText: string, targetType: string): void {
118
+ static validateLiteralFitsType(
119
+ literalText: string,
120
+ targetType: string,
121
+ ): void {
120
122
  const range = TYPE_RANGES[targetType];
121
123
  if (!range) {
122
- return; // No validation for unknown types (floats, bools, etc.)
124
+ return;
123
125
  }
124
126
 
125
- // Parse the literal value
126
127
  let value: bigint;
127
128
  try {
128
129
  const cleanText = literalText.trim();
129
130
 
130
131
  if (/^-?\d+$/.exec(cleanText)) {
131
- // Decimal integer
132
132
  value = BigInt(cleanText);
133
133
  } else if (/^0[xX][0-9a-fA-F]+$/.exec(cleanText)) {
134
- // Hex literal
135
134
  value = BigInt(cleanText);
136
135
  } else if (/^0[bB][01]+$/.exec(cleanText)) {
137
- // Binary literal
138
136
  value = BigInt(cleanText);
139
137
  } else {
140
- // Not an integer literal we can validate
141
138
  return;
142
139
  }
143
140
  } catch {
144
- return; // Can't parse, skip validation
141
+ return;
145
142
  }
146
143
 
147
144
  const [min, max] = range;
148
145
 
149
- // Check if value is negative for unsigned type
150
- if (this.isUnsignedType(targetType) && value < 0n) {
146
+ if (TypeResolver.isUnsignedType(targetType) && value < 0n) {
151
147
  throw new Error(
152
148
  `Error: Negative value ${literalText} cannot be assigned to unsigned type ${targetType}`,
153
149
  );
154
150
  }
155
151
 
156
- // Check if value is out of range
157
152
  if (value < min || value > max) {
158
153
  throw new Error(
159
154
  `Error: Value ${literalText} exceeds ${targetType} range (${min} to ${max})`,
@@ -163,15 +158,12 @@ class TypeResolver {
163
158
 
164
159
  /**
165
160
  * ADR-024: Get the type from a literal (suffixed or unsuffixed).
166
- * Returns the explicit suffix type, or null for unsuffixed literals.
167
161
  */
168
- getLiteralType(ctx: Parser.LiteralContext): string | null {
162
+ static getLiteralType(ctx: Parser.LiteralContext): string | null {
169
163
  const text = ctx.getText();
170
164
 
171
- // Boolean literals
172
165
  if (text === "true" || text === "false") return "bool";
173
166
 
174
- // Check for type suffix on numeric literals
175
167
  const suffixMatch = /([uUiI])(8|16|32|64)$/.exec(text);
176
168
  if (suffixMatch) {
177
169
  const signChar = suffixMatch[1].toLowerCase();
@@ -179,194 +171,252 @@ class TypeResolver {
179
171
  return (signChar === "u" ? "u" : "i") + width;
180
172
  }
181
173
 
182
- // Float suffix
183
174
  const floatMatch = /[fF](32|64)$/.exec(text);
184
175
  if (floatMatch) {
185
176
  return "f" + floatMatch[1];
186
177
  }
187
178
 
188
- // Unsuffixed literal - type depends on context (handled by caller)
189
179
  return null;
190
180
  }
191
181
 
192
182
  /**
193
183
  * ADR-024: Get the type of an expression for type checking.
194
- * Returns the inferred type or null if type cannot be determined.
195
- * Issue #61: Uses ExpressionUnwrapper utility for tree navigation.
196
184
  */
197
- getExpressionType(ctx: Parser.ExpressionContext): string | null {
198
- // Navigate through expression tree to get the actual value
185
+ static getExpressionType(ctx: Parser.ExpressionContext): string | null {
199
186
  const postfix = ExpressionUnwrapper.getPostfixExpression(ctx);
200
187
  if (postfix) {
201
- return this.getPostfixExpressionType(postfix);
188
+ return TypeResolver.getPostfixExpressionType(postfix);
202
189
  }
203
190
 
204
- // For more complex expressions (binary ops, etc.), try to infer type
205
191
  const ternary = ctx.ternaryExpression();
206
192
  const orExprs = ternary.orExpression();
207
- // If it's a ternary, we can't easily determine the type
208
193
  if (orExprs.length > 1) {
209
194
  return null;
210
195
  }
211
196
  const or = orExprs[0];
212
197
  if (or.andExpression().length > 1) {
213
- return "bool"; // Logical OR returns bool
198
+ return "bool";
214
199
  }
215
200
 
216
201
  const and = or.andExpression()[0];
217
202
  if (and.equalityExpression().length > 1) {
218
- return "bool"; // Logical AND returns bool
203
+ return "bool";
219
204
  }
220
205
 
221
206
  const eq = and.equalityExpression()[0];
222
207
  if (eq.relationalExpression().length > 1) {
223
- return "bool"; // Equality comparison returns bool
208
+ return "bool";
224
209
  }
225
210
 
226
211
  const rel = eq.relationalExpression()[0];
227
212
  if (rel.bitwiseOrExpression().length > 1) {
228
- return "bool"; // Relational comparison returns bool
213
+ return "bool";
229
214
  }
230
215
 
231
- // For arithmetic expressions, we'd need to track operand types
232
- // For now, return null for complex expressions
233
216
  return null;
234
217
  }
235
218
 
236
219
  /**
237
220
  * ADR-024: Get the type of a postfix expression.
238
- * Issue #304: Enhanced to track type through member access chains (e.g., cfg.mode)
239
- * SonarCloud S3776: Refactored to use processSuffix helper.
221
+ * Tracks InternalTypeInfo (baseType + isArray) through the suffix chain
222
+ * so that array indexing is correctly distinguished from bit indexing.
240
223
  */
241
- getPostfixExpressionType(
224
+ static getPostfixExpressionType(
242
225
  ctx: Parser.PostfixExpressionContext,
243
226
  ): string | null {
244
227
  const primary = ctx.primaryExpression();
245
228
  if (!primary) return null;
246
229
 
247
- // Get base type from primary expression
248
- let currentType = this.getPrimaryExpressionType(primary);
249
- if (!currentType) return null;
230
+ let current = TypeResolver.getPrimaryExpressionTypeInfo(primary);
231
+ if (!current) return null;
250
232
 
251
- // Check for postfix operations: member access, array indexing, bit indexing
252
233
  const suffixes = ctx.children?.slice(1) || [];
253
234
  for (const suffix of suffixes) {
254
- const result = this.processPostfixSuffix(suffix.getText(), currentType);
235
+ const result = TypeResolver.processPostfixSuffix(
236
+ suffix.getText(),
237
+ current,
238
+ );
255
239
  if (result.stop) {
256
240
  return result.type;
257
241
  }
258
- currentType = result.type!;
242
+ current = result.info;
259
243
  }
260
244
 
261
- return currentType;
245
+ return current.baseType;
262
246
  }
263
247
 
264
248
  /**
265
249
  * Process a single postfix suffix and determine the resulting type.
266
- * Returns { type, stop } where stop=true means to return immediately.
250
+ * Returns { type, stop, info } where stop=true means return type immediately.
267
251
  */
268
- private processPostfixSuffix(
252
+ private static processPostfixSuffix(
269
253
  text: string,
270
- currentType: string,
271
- ): { type: string | null; stop: boolean } {
272
- // Member access: .fieldName
254
+ current: InternalTypeInfo,
255
+ ): SuffixResult {
273
256
  if (text.startsWith(".")) {
274
- const memberName = text.slice(1);
275
- const memberInfo = this.getMemberTypeInfo(currentType, memberName);
276
- if (!memberInfo) {
277
- return { type: null, stop: true };
278
- }
279
- return { type: memberInfo.baseType, stop: false };
257
+ return TypeResolver.processMemberSuffix(text.slice(1), current);
280
258
  }
281
259
 
282
- // Array or bit indexing: [index] or [start, width]
283
260
  if (text.startsWith("[") && text.endsWith("]")) {
284
- return this.processIndexingSuffix(text, currentType);
261
+ return TypeResolver.processIndexingSuffix(text, current);
285
262
  }
286
263
 
287
- // Unknown suffix, continue with current type
288
- return { type: currentType, stop: false };
264
+ return { stop: false, info: current };
265
+ }
266
+
267
+ /**
268
+ * Process a member access suffix (.name) and resolve the resulting type.
269
+ * Handles global/this sentinel values and regular struct member lookups.
270
+ */
271
+ private static processMemberSuffix(
272
+ memberName: string,
273
+ current: InternalTypeInfo,
274
+ ): SuffixResult {
275
+ // Handle global.X — resolve X as a global variable name
276
+ if (current.baseType === TypeResolver.GLOBAL_SENTINEL) {
277
+ return TypeResolver.resolveRegistryLookup(memberName);
278
+ }
279
+
280
+ // Handle this.X — resolve X as a scope member variable
281
+ if (
282
+ current.baseType === TypeResolver.THIS_SENTINEL &&
283
+ CodeGenState.currentScope
284
+ ) {
285
+ const scopedName = `${CodeGenState.currentScope}_${memberName}`;
286
+ return TypeResolver.resolveRegistryLookup(scopedName);
287
+ }
288
+
289
+ const memberInfo = TypeResolver.getMemberTypeInfo(
290
+ current.baseType,
291
+ memberName,
292
+ );
293
+ if (!memberInfo) {
294
+ return { stop: true, type: null };
295
+ }
296
+ return {
297
+ stop: false,
298
+ info: { baseType: memberInfo.baseType, isArray: memberInfo.isArray },
299
+ };
300
+ }
301
+
302
+ /**
303
+ * Look up a variable name in the type registry and return a SuffixResult.
304
+ */
305
+ private static resolveRegistryLookup(name: string): SuffixResult {
306
+ const typeInfo = CodeGenState.typeRegistry.get(name);
307
+ if (typeInfo) {
308
+ return {
309
+ stop: false,
310
+ info: { baseType: typeInfo.baseType, isArray: typeInfo.isArray },
311
+ };
312
+ }
313
+ return { stop: true, type: null };
289
314
  }
290
315
 
291
316
  /**
292
317
  * Process array or bit indexing suffix.
318
+ * Checks isArray BEFORE isIntegerType to correctly distinguish
319
+ * array element access from bit indexing.
293
320
  */
294
- private processIndexingSuffix(
321
+ private static processIndexingSuffix(
295
322
  text: string,
296
- currentType: string,
297
- ): { type: string | null; stop: boolean } {
323
+ current: InternalTypeInfo,
324
+ ): SuffixResult {
298
325
  const inner = text.slice(1, -1);
299
326
 
300
- // Range indexing: [start, width]
301
- // ADR-024: Return null for bit indexing to skip type conversion validation
327
+ // Range indexing: [start, width] - always bit extraction
302
328
  if (inner.includes(",")) {
303
- return { type: null, stop: true };
329
+ return { stop: true, type: null };
330
+ }
331
+
332
+ // Array access: if current type is known to be an array, index yields element
333
+ if (current.isArray) {
334
+ return {
335
+ stop: false,
336
+ info: { baseType: current.baseType, isArray: false },
337
+ };
304
338
  }
305
339
 
306
- // Single index: array access or bit indexing
307
- // For single bit on integer, returns bool
308
- if (this.isIntegerType(currentType)) {
309
- return { type: "bool", stop: true };
340
+ // Bit indexing on integer: single bit returns bool
341
+ if (TypeResolver.isIntegerType(current.baseType)) {
342
+ return { stop: true, type: "bool" };
310
343
  }
311
344
 
312
- // For arrays, currentType is already the element type
313
- return { type: currentType, stop: false };
345
+ // Unknown indexing - preserve current state
346
+ return { stop: false, info: current };
314
347
  }
315
348
 
316
349
  /**
317
- * ADR-024: Get the type of a primary expression.
318
- * Issue #61: Uses injected dependencies instead of CodeGenerator.
350
+ * Get full InternalTypeInfo from a primary expression (preserves isArray).
319
351
  */
320
- getPrimaryExpressionType(
352
+ private static getPrimaryExpressionTypeInfo(
321
353
  ctx: Parser.PrimaryExpressionContext,
322
- ): string | null {
323
- // Check for identifier
354
+ ): InternalTypeInfo | null {
324
355
  const id = ctx.IDENTIFIER();
325
356
  if (id) {
326
357
  const name = id.getText();
327
- const scopedName = this.resolveIdentifierFn(name);
328
- const typeInfo = this.typeRegistry.get(scopedName);
358
+ const scopedName = CodeGenState.resolveIdentifier(name);
359
+ const typeInfo = CodeGenState.typeRegistry.get(scopedName);
329
360
  if (typeInfo) {
330
- return typeInfo.baseType;
361
+ return { baseType: typeInfo.baseType, isArray: typeInfo.isArray };
331
362
  }
332
363
  return null;
333
364
  }
334
365
 
335
- // Check for literal
366
+ // Handle global.X and this.X — these are scope qualifiers, not types.
367
+ // The actual variable name is the first .suffix after the keyword.
368
+ // Return a sentinel so getPostfixExpressionType knows to consume one suffix.
369
+ if (ctx.GLOBAL()) {
370
+ return { baseType: TypeResolver.GLOBAL_SENTINEL, isArray: false };
371
+ }
372
+ if (ctx.THIS()) {
373
+ return { baseType: TypeResolver.THIS_SENTINEL, isArray: false };
374
+ }
375
+
336
376
  const literal = ctx.literal();
337
377
  if (literal) {
338
- return this.getLiteralType(literal);
378
+ const litType = TypeResolver.getLiteralType(literal);
379
+ return litType ? { baseType: litType, isArray: false } : null;
339
380
  }
340
381
 
341
- // Check for parenthesized expression
342
382
  const expr = ctx.expression();
343
383
  if (expr) {
344
- return this.getExpressionType(expr);
384
+ const exprType = TypeResolver.getExpressionType(expr);
385
+ return exprType ? { baseType: exprType, isArray: false } : null;
345
386
  }
346
387
 
347
- // Check for cast expression
348
388
  const cast = ctx.castExpression();
349
389
  if (cast) {
350
- return cast.type().getText();
390
+ return { baseType: cast.type().getText(), isArray: false };
351
391
  }
352
392
 
353
393
  return null;
354
394
  }
355
395
 
396
+ /**
397
+ * ADR-024: Get the type of a primary expression (public API, returns baseType only).
398
+ */
399
+ static getPrimaryExpressionType(
400
+ ctx: Parser.PrimaryExpressionContext,
401
+ ): string | null {
402
+ const info = TypeResolver.getPrimaryExpressionTypeInfo(ctx);
403
+ return info?.baseType ?? null;
404
+ }
405
+
356
406
  /**
357
407
  * ADR-024: Get the type of a unary expression (for cast validation).
358
408
  */
359
- getUnaryExpressionType(ctx: Parser.UnaryExpressionContext): string | null {
360
- // Check for unary operators - type doesn't change for !, ~, -, +
409
+ static getUnaryExpressionType(
410
+ ctx: Parser.UnaryExpressionContext,
411
+ ): string | null {
361
412
  const postfix = ctx.postfixExpression();
362
413
  if (postfix) {
363
- return this.getPostfixExpressionType(postfix);
414
+ return TypeResolver.getPostfixExpressionType(postfix);
364
415
  }
365
416
 
366
- // Check for recursive unary expression
367
417
  const unary = ctx.unaryExpression();
368
418
  if (unary) {
369
- return this.getUnaryExpressionType(unary);
419
+ return TypeResolver.getUnaryExpressionType(unary);
370
420
  }
371
421
 
372
422
  return null;
@@ -374,21 +424,21 @@ class TypeResolver {
374
424
 
375
425
  /**
376
426
  * ADR-024: Validate that a type conversion is allowed.
377
- * Throws error for narrowing or sign-changing conversions.
378
427
  */
379
- validateTypeConversion(targetType: string, sourceType: string | null): void {
380
- // If we can't determine source type, skip validation
428
+ static validateTypeConversion(
429
+ targetType: string,
430
+ sourceType: string | null,
431
+ ): void {
381
432
  if (!sourceType) return;
382
-
383
- // Skip if types are the same
384
433
  if (sourceType === targetType) return;
385
434
 
386
- // Only validate integer-to-integer conversions
387
- if (!this.isIntegerType(sourceType) || !this.isIntegerType(targetType))
435
+ if (
436
+ !TypeResolver.isIntegerType(sourceType) ||
437
+ !TypeResolver.isIntegerType(targetType)
438
+ )
388
439
  return;
389
440
 
390
- // Check for narrowing conversion
391
- if (this.isNarrowingConversion(sourceType, targetType)) {
441
+ if (TypeResolver.isNarrowingConversion(sourceType, targetType)) {
392
442
  const targetWidth = TYPE_WIDTH[targetType] || 0;
393
443
  throw new Error(
394
444
  `Error: Cannot assign ${sourceType} to ${targetType} (narrowing). ` +
@@ -396,8 +446,7 @@ class TypeResolver {
396
446
  );
397
447
  }
398
448
 
399
- // Check for sign conversion
400
- if (this.isSignConversion(sourceType, targetType)) {
449
+ if (TypeResolver.isSignConversion(sourceType, targetType)) {
401
450
  const targetWidth = TYPE_WIDTH[targetType] || 0;
402
451
  throw new Error(
403
452
  `Error: Cannot assign ${sourceType} to ${targetType} (sign change). ` +
@@ -407,18 +456,15 @@ class TypeResolver {
407
456
  }
408
457
 
409
458
  /**
410
- * Get type info for a struct member field
411
- * Used to track types through member access chains like buf.data[0]
412
- * Issue #103: Now checks SymbolTable first for C header structs
413
- * Issue #61: Uses injected dependencies instead of CodeGenerator.
459
+ * Get type info for a struct member field.
460
+ * Issue #103: Checks SymbolTable first for C header structs.
414
461
  */
415
- getMemberTypeInfo(
462
+ static getMemberTypeInfo(
416
463
  structType: string,
417
464
  memberName: string,
418
465
  ): { isArray: boolean; baseType: string } | undefined {
419
- // First check SymbolTable (C header structs) - Issue #103 fix
420
- if (this.symbolTable) {
421
- const fieldInfo = this.symbolTable.getStructFieldInfo(
466
+ if (CodeGenState.symbolTable) {
467
+ const fieldInfo = CodeGenState.symbolTable.getStructFieldInfo(
422
468
  structType,
423
469
  memberName,
424
470
  );
@@ -432,14 +478,12 @@ class TypeResolver {
432
478
  }
433
479
  }
434
480
 
435
- // Fall back to local C-Next struct fields (Issue #60: use SymbolCollector)
436
- const fieldType = this.symbols?.structFields
481
+ const fieldType = CodeGenState.symbols?.structFields
437
482
  .get(structType)
438
483
  ?.get(memberName);
439
484
  if (!fieldType) return undefined;
440
485
 
441
- // Check if this field is marked as an array (Issue #60: use SymbolCollector)
442
- const arrayFields = this.symbols?.structFieldArrays.get(structType);
486
+ const arrayFields = CodeGenState.symbols?.structFieldArrays.get(structType);
443
487
  const isArray = arrayFields?.has(memberName) ?? false;
444
488
 
445
489
  return { isArray, baseType: fieldType };