c-next 0.2.4 → 0.2.6

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 (51) hide show
  1. package/dist/index.js +561 -78
  2. package/dist/index.js.map +3 -3
  3. package/package.json +3 -1
  4. package/src/transpiler/Transpiler.ts +1 -1
  5. package/src/transpiler/logic/analysis/FunctionCallAnalyzer.ts +194 -8
  6. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +140 -0
  7. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +41 -0
  8. package/src/transpiler/logic/symbols/c/collectors/FunctionCollector.ts +11 -5
  9. package/src/transpiler/output/codegen/CodeGenerator.ts +195 -17
  10. package/src/transpiler/output/codegen/TypeResolver.ts +1 -1
  11. package/src/transpiler/output/codegen/__tests__/CodeGenerator.coverage.test.ts +129 -0
  12. package/src/transpiler/output/codegen/__tests__/CodeGenerator.test.ts +30 -5
  13. package/src/transpiler/output/codegen/assignment/handlers/AccessPatternHandlers.ts +2 -2
  14. package/src/transpiler/output/codegen/assignment/handlers/BitAccessHandlers.ts +2 -2
  15. package/src/transpiler/output/codegen/assignment/handlers/BitmapHandlers.ts +2 -2
  16. package/src/transpiler/output/codegen/assignment/handlers/RegisterHandlers.ts +4 -4
  17. package/src/transpiler/output/codegen/assignment/handlers/__tests__/AccessPatternHandlers.test.ts +4 -4
  18. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitAccessHandlers.test.ts +8 -8
  19. package/src/transpiler/output/codegen/assignment/handlers/__tests__/BitmapHandlers.test.ts +5 -5
  20. package/src/transpiler/output/codegen/assignment/handlers/__tests__/RegisterHandlers.test.ts +4 -4
  21. package/src/transpiler/output/codegen/generators/expressions/CallExprGenerator.ts +8 -1
  22. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +15 -3
  23. package/src/transpiler/output/codegen/helpers/ArgumentGenerator.ts +5 -0
  24. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +63 -10
  25. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +28 -6
  26. package/src/transpiler/output/codegen/helpers/ParameterDereferenceResolver.ts +12 -0
  27. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +30 -2
  28. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +15 -7
  29. package/src/transpiler/output/codegen/helpers/StringDeclHelper.ts +8 -1
  30. package/src/transpiler/output/codegen/helpers/StringOperationsHelper.ts +1 -1
  31. package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +220 -0
  32. package/src/transpiler/output/codegen/helpers/VariableDeclHelper.ts +11 -0
  33. package/src/transpiler/output/codegen/helpers/VariableModifierBuilder.ts +16 -1
  34. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +5 -5
  35. package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +48 -36
  36. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +37 -0
  37. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +63 -0
  38. package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +209 -0
  39. package/src/transpiler/output/codegen/helpers/__tests__/VariableModifierBuilder.test.ts +34 -2
  40. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
  41. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
  42. package/src/transpiler/output/codegen/types/IParameterInput.ts +13 -0
  43. package/src/transpiler/output/codegen/types/ISeparatorContext.ts +7 -0
  44. package/src/transpiler/output/codegen/types/TParameterInfo.ts +12 -0
  45. package/src/transpiler/output/codegen/types/TTypeInfo.ts +1 -0
  46. package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +1 -1
  47. package/src/transpiler/state/CodeGenState.ts +21 -2
  48. package/src/utils/BitUtils.ts +17 -13
  49. package/src/{transpiler/output/codegen/utils → utils}/ExpressionUnwrapper.ts +1 -1
  50. package/src/utils/__tests__/BitUtils.test.ts +56 -56
  51. package/src/{transpiler/output/codegen/utils → utils}/__tests__/ExpressionUnwrapper.test.ts +2 -2
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { hideBin } from "yargs/helpers";
10
10
  // package.json
11
11
  var package_default = {
12
12
  name: "c-next",
13
- version: "0.2.4",
13
+ version: "0.2.6",
14
14
  description: "A safer C for embedded systems development. Transpiles to clean, readable C.",
15
15
  packageManager: "npm@11.9.0",
16
16
  type: "module",
@@ -32,6 +32,8 @@ var package_default = {
32
32
  "test:cli": "node scripts/test-cli.js",
33
33
  "test:q": "tsx scripts/test.ts -q",
34
34
  "test:update": "tsx scripts/test.ts --update",
35
+ "integration:transpile": "tsx scripts/test.ts --transpile-only",
36
+ "integration:execute": "tsx scripts/test.ts --execute-only",
35
37
  analyze: "./scripts/static-analysis.sh",
36
38
  clean: "rm -rf dist src/transpiler/logic/parser/grammar src/transpiler/logic/parser/c/grammar src/transpiler/logic/parser/cpp/grammar",
37
39
  "prettier:check": "prettier --check .",
@@ -110764,8 +110766,12 @@ var CodeGenState = class {
110764
110766
  static callbackTypes = /* @__PURE__ */ new Map();
110765
110767
  /** Callback field types: "Struct.field" -> callbackTypeName */
110766
110768
  static callbackFieldTypes = /* @__PURE__ */ new Map();
110767
- /** Functions that need C-callback-compatible (by-value) struct parameters */
110768
- static callbackCompatibleFunctions = /* @__PURE__ */ new Set();
110769
+ /**
110770
+ * Functions that are assigned to C callback typedefs.
110771
+ * Maps function name -> typedef name (e.g., "my_flush" -> "flush_cb_t")
110772
+ * Issue #895: We need the typedef name to look up parameter types.
110773
+ */
110774
+ static callbackCompatibleFunctions = /* @__PURE__ */ new Map();
110769
110775
  // ===========================================================================
110770
110776
  // PASS-BY-VALUE ANALYSIS (Issue #269)
110771
110777
  // ===========================================================================
@@ -111133,6 +111139,20 @@ var CodeGenState = class {
111133
111139
  static getCallbackType(name) {
111134
111140
  return this.callbackTypes.get(name);
111135
111141
  }
111142
+ /**
111143
+ * Issue #895: Get the typedef type string for a C typedef by name.
111144
+ * Used to look up function pointer typedef signatures for callback-compatible functions.
111145
+ *
111146
+ * @param typedefName - Name of the typedef (e.g., "flush_cb_t")
111147
+ * @returns The type string (e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)") or undefined
111148
+ */
111149
+ static getTypedefType(typedefName) {
111150
+ const symbol = this.symbolTable.getCSymbol(typedefName);
111151
+ if (symbol?.kind === "type") {
111152
+ return symbol.type;
111153
+ }
111154
+ return void 0;
111155
+ }
111136
111156
  /**
111137
111157
  * Check if a type name is a known C-Next function.
111138
111158
  */
@@ -112906,7 +112926,7 @@ var TYPE_RANGES = {
112906
112926
  };
112907
112927
  var TYPE_RANGES_default = TYPE_RANGES;
112908
112928
 
112909
- // src/transpiler/output/codegen/utils/ExpressionUnwrapper.ts
112929
+ // src/utils/ExpressionUnwrapper.ts
112910
112930
  var ExpressionUnwrapper = class {
112911
112931
  /**
112912
112932
  * Navigate from ExpressionContext to ShiftExpressionContext.
@@ -115145,13 +115165,17 @@ var _generateCFunctionArg = (e, targetParam, input, orchestrator) => {
115145
115165
  return wrapWithCppEnumCast(argCode, e, targetParam?.baseType, orchestrator);
115146
115166
  }
115147
115167
  let argType = orchestrator.getExpressionType(e);
115168
+ let isPointerVariable = false;
115169
+ const typeInfo = CodeGenState.getVariableTypeInfo(argCode);
115148
115170
  if (!argType && !argCode.startsWith("&")) {
115149
- const typeInfo = CodeGenState.getVariableTypeInfo(argCode);
115150
115171
  if (typeInfo) {
115151
115172
  argType = typeInfo.baseType;
115152
115173
  }
115153
115174
  }
115154
- const needsAddressOf = argType && !argType.endsWith("*") && !argCode.startsWith("&") && !targetParam.isArray && (orchestrator.isStructType(argType) || _parameterExpectsAddressOf(targetParam.baseType, argType, orchestrator));
115175
+ if (typeInfo?.isPointer) {
115176
+ isPointerVariable = true;
115177
+ }
115178
+ const needsAddressOf = argType && !argType.endsWith("*") && !argCode.startsWith("&") && !targetParam.isArray && !isPointerVariable && (orchestrator.isStructType(argType) || _parameterExpectsAddressOf(targetParam.baseType, argType, orchestrator));
115155
115179
  if (needsAddressOf) {
115156
115180
  argCode = `&${argCode}`;
115157
115181
  }
@@ -115744,6 +115768,7 @@ var generatePostfixExpression = (ctx, input, state, orchestrator) => {
115744
115768
  const rootIdentifier = primary.IDENTIFIER()?.getText();
115745
115769
  const paramInfo = rootIdentifier ? state.currentParameters.get(rootIdentifier) : null;
115746
115770
  const isStructParam = paramInfo?.isStruct ?? false;
115771
+ const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
115747
115772
  const hasSubscriptOps = ops.some((op) => op.expression().length > 0);
115748
115773
  const isNonArrayParamWithSubscript = paramInfo && !paramInfo.isArray && !paramInfo.isStruct && hasSubscriptOps;
115749
115774
  let result;
@@ -115764,6 +115789,7 @@ var generatePostfixExpression = (ctx, input, state, orchestrator) => {
115764
115789
  const postfixCtx = {
115765
115790
  rootIdentifier,
115766
115791
  isStructParam,
115792
+ forcePointerSemantics,
115767
115793
  input,
115768
115794
  state,
115769
115795
  orchestrator,
@@ -115855,6 +115881,7 @@ var handleMemberOp = (memberName, tracking, ctx) => {
115855
115881
  memberName,
115856
115882
  rootIdentifier: ctx.rootIdentifier,
115857
115883
  isStructParam: ctx.isStructParam,
115884
+ forcePointerSemantics: ctx.forcePointerSemantics,
115858
115885
  isGlobalAccess: tracking.isGlobalAccess,
115859
115886
  isCppAccessChain: tracking.isCppAccessChain,
115860
115887
  currentStructType: tracking.currentStructType,
@@ -116589,7 +116616,7 @@ var tryStructParamAccess = (ctx, orchestrator) => {
116589
116616
  if (!ctx.isStructParam || ctx.result !== ctx.rootIdentifier) {
116590
116617
  return null;
116591
116618
  }
116592
- const structParamSep = memberAccessChain_default.getStructParamSeparator({
116619
+ const structParamSep = ctx.forcePointerSemantics ? "->" : memberAccessChain_default.getStructParamSeparator({
116593
116620
  cppMode: orchestrator.isCppMode()
116594
116621
  });
116595
116622
  const output = initializeMemberOutput(ctx);
@@ -116838,15 +116865,18 @@ var VariableModifierBuilder = class {
116838
116865
  *
116839
116866
  * @param ctx - Parser context with modifier methods
116840
116867
  * @param inFunctionBody - Whether we're inside a function body (affects extern)
116868
+ * @param hasInitializer - Whether the variable has an initializer (affects extern in C mode)
116869
+ * @param cppMode - Whether we're generating C++ code (affects extern behavior)
116841
116870
  * @returns Modifier strings ready for use in generated code
116842
116871
  * @throws Error if both atomic and volatile are specified
116843
116872
  */
116844
- static build(ctx, inFunctionBody) {
116873
+ static build(ctx, inFunctionBody, hasInitializer = false, cppMode = false) {
116845
116874
  const hasConst = ctx.constModifier?.() ?? false;
116846
116875
  const constMod = hasConst ? "const " : "";
116847
116876
  const atomicMod = ctx.atomicModifier() ? "volatile " : "";
116848
116877
  const volatileMod = ctx.volatileModifier() ? "volatile " : "";
116849
- const externMod = hasConst && !inFunctionBody ? "extern " : "";
116878
+ const needsExtern = hasConst && !inFunctionBody && (cppMode || !hasInitializer);
116879
+ const externMod = needsExtern ? "extern " : "";
116850
116880
  if (ctx.atomicModifier() && ctx.volatileModifier()) {
116851
116881
  const line = ctx.start?.line ?? 0;
116852
116882
  throw new Error(
@@ -118034,16 +118064,17 @@ var ScopeGenerator_default = generateScope;
118034
118064
  // src/utils/BitUtils.ts
118035
118065
  var BitUtils = class _BitUtils {
118036
118066
  /**
118037
- * Convert a boolean expression to an integer (0 or 1).
118067
+ * Convert a boolean expression to an unsigned integer (0U or 1U).
118038
118068
  * Handles literal "true"/"false" and generates ternary for expressions.
118069
+ * Uses unsigned literals for MISRA C:2012 Rule 10.1 compliance.
118039
118070
  *
118040
118071
  * @param expr - The expression to convert
118041
- * @returns C code string representing the integer value
118072
+ * @returns C code string representing the unsigned integer value
118042
118073
  */
118043
118074
  static boolToInt(expr) {
118044
- if (expr === "true") return "1";
118045
- if (expr === "false") return "0";
118046
- return `(${expr} ? 1 : 0)`;
118075
+ if (expr === "true") return "1U";
118076
+ if (expr === "false") return "0U";
118077
+ return `(${expr} ? 1U : 0U)`;
118047
118078
  }
118048
118079
  /**
118049
118080
  * Generate a bit mask for the given width.
@@ -118100,24 +118131,26 @@ var BitUtils = class _BitUtils {
118100
118131
  }
118101
118132
  }
118102
118133
  /**
118103
- * Return the appropriate "1" literal for a given type.
118104
- * Uses "1ULL" for 64-bit types to avoid undefined behavior on large shifts.
118134
+ * Return the appropriate unsigned "1" literal for a given type.
118135
+ * Uses "1ULL" for 64-bit types, "1U" for others.
118136
+ * MISRA C:2012 Rule 10.1 requires unsigned operands for bitwise operations.
118105
118137
  *
118106
118138
  * @param typeName - The C-Next type name (e.g., "u64", "i32")
118107
- * @returns "1ULL" for 64-bit types, "1" otherwise
118139
+ * @returns "1ULL" for 64-bit types, "1U" otherwise
118108
118140
  */
118109
118141
  static oneForType(typeName) {
118110
- return typeName === "u64" || typeName === "i64" ? "1ULL" : "1";
118142
+ return typeName === "u64" || typeName === "i64" ? "1ULL" : "1U";
118111
118143
  }
118112
118144
  /**
118113
- * Format a number as an uppercase hex string (e.g., 255 -> "0xFF").
118145
+ * Format a number as an unsigned uppercase hex string (e.g., 255 -> "0xFFU").
118114
118146
  * Used for generating hex mask literals in generated C code.
118147
+ * Includes U suffix for MISRA C:2012 Rule 10.1 compliance.
118115
118148
  *
118116
118149
  * @param value - The numeric value to format
118117
- * @returns Hex string like "0xFF" or "0x1F"
118150
+ * @returns Hex string like "0xFFU" or "0x1FU"
118118
118151
  */
118119
118152
  static formatHex(value) {
118120
- return `0x${value.toString(16).toUpperCase()}`;
118153
+ return `0x${value.toString(16).toUpperCase()}U`;
118121
118154
  }
118122
118155
  /**
118123
118156
  * Generate code to read a single bit from a value.
@@ -118163,7 +118196,7 @@ var BitUtils = class _BitUtils {
118163
118196
  static singleBitWrite(target, offset, value, targetType) {
118164
118197
  const intValue = _BitUtils.boolToInt(value);
118165
118198
  const is64Bit = targetType === "u64" || targetType === "i64";
118166
- const one = is64Bit ? "1ULL" : "1";
118199
+ const one = is64Bit ? "1ULL" : "1U";
118167
118200
  const valueShift = is64Bit ? `((uint64_t)${intValue} << ${offset})` : `(${intValue} << ${offset})`;
118168
118201
  return `${target} = (${target} & ~(${one} << ${offset})) | ${valueShift};`;
118169
118202
  }
@@ -120847,7 +120880,7 @@ function getBitmapFieldInfo(bitmapType, fieldName, ctx) {
120847
120880
  function generateBitmapWrite(target, fieldInfo, value) {
120848
120881
  const { maskHex } = calculateMask(fieldInfo.width);
120849
120882
  if (fieldInfo.width === 1) {
120850
- return `${target} = (${target} & ~(1 << ${fieldInfo.offset})) | (${BitUtils_default.boolToInt(value)} << ${fieldInfo.offset});`;
120883
+ return `${target} = (${target} & ~(1U << ${fieldInfo.offset})) | (${BitUtils_default.boolToInt(value)} << ${fieldInfo.offset});`;
120851
120884
  } else {
120852
120885
  return `${target} = (${target} & ~(${maskHex} << ${fieldInfo.offset})) | ((${value} & ${maskHex}) << ${fieldInfo.offset});`;
120853
120886
  }
@@ -121100,9 +121133,9 @@ function handleRegisterBit(ctx) {
121100
121133
  bitIndex,
121101
121134
  true
121102
121135
  );
121103
- return `${fullName} = (1 << ${bitIndex});`;
121136
+ return `${fullName} = (1U << ${bitIndex});`;
121104
121137
  }
121105
- return `${fullName} = (${fullName} & ~(1 << ${bitIndex})) | (${BitUtils_default.boolToInt(ctx.generatedValue)} << ${bitIndex});`;
121138
+ return `${fullName} = (${fullName} & ~(1U << ${bitIndex})) | (${BitUtils_default.boolToInt(ctx.generatedValue)} << ${bitIndex});`;
121106
121139
  }
121107
121140
  function handleRegisterBitRange(ctx) {
121108
121141
  AssignmentHandlerUtils_default.validateNoCompoundForBitAccess(
@@ -121168,9 +121201,9 @@ function handleScopedRegisterBit(ctx) {
121168
121201
  bitIndex,
121169
121202
  true
121170
121203
  );
121171
- return `${regName} = (1 << ${bitIndex});`;
121204
+ return `${regName} = (1U << ${bitIndex});`;
121172
121205
  }
121173
- return `${regName} = (${regName} & ~(1 << ${bitIndex})) | (${BitUtils_default.boolToInt(ctx.generatedValue)} << ${bitIndex});`;
121206
+ return `${regName} = (${regName} & ~(1U << ${bitIndex})) | (${BitUtils_default.boolToInt(ctx.generatedValue)} << ${bitIndex});`;
121174
121207
  }
121175
121208
  function handleScopedRegisterBitRange(ctx) {
121176
121209
  AssignmentHandlerUtils_default.validateScopeContext(CodeGenState.currentScope);
@@ -121416,7 +121449,7 @@ function handleStructMemberBit(ctx) {
121416
121449
  validateNotCompound2(ctx);
121417
121450
  const target = gen6().generateAssignmentTarget(ctx.targetCtx);
121418
121451
  const bitIndex = gen6().generateExpression(ctx.subscripts.at(-1));
121419
- const one = "1";
121452
+ const one = "1U";
121420
121453
  const intValue = BitUtils_default.boolToInt(ctx.generatedValue);
121421
121454
  return `${target} = (${target} & ~(${one} << ${bitIndex})) | (${intValue} << ${bitIndex});`;
121422
121455
  }
@@ -121664,9 +121697,9 @@ function handleGlobalRegisterBit(ctx) {
121664
121697
  `Cannot assign false to write-only register bit ${regName}[${bitIndex}]. Use the corresponding CLEAR register to clear bits.`
121665
121698
  );
121666
121699
  }
121667
- return `${regName} = (1 << ${bitIndex});`;
121700
+ return `${regName} = (1U << ${bitIndex});`;
121668
121701
  }
121669
- return `${regName} = (${regName} & ~(1 << ${bitIndex})) | (${BitUtils_default.boolToInt(ctx.generatedValue)} << ${bitIndex});`;
121702
+ return `${regName} = (${regName} & ~(1U << ${bitIndex})) | (${BitUtils_default.boolToInt(ctx.generatedValue)} << ${bitIndex});`;
121670
121703
  }
121671
121704
  function handleMemberChain(ctx) {
121672
121705
  const bitAnalysis = gen9().analyzeMemberChainForBitAccess(ctx.targetCtx);
@@ -122816,6 +122849,9 @@ var ArgumentGenerator = class _ArgumentGenerator {
122816
122849
  if (typeInfo?.isArray && !typeInfo.isString) {
122817
122850
  return id;
122818
122851
  }
122852
+ if (typeInfo?.isPointer) {
122853
+ return id;
122854
+ }
122819
122855
  if (CodeGenState.currentScope) {
122820
122856
  const members = CodeGenState.getScopeMembers(CodeGenState.currentScope);
122821
122857
  if (members?.has(id)) {
@@ -124082,7 +124118,11 @@ var StringDeclHelper = class _StringDeclHelper {
124082
124118
  initValue,
124083
124119
  arrayDims
124084
124120
  );
124085
- return { code: `${decl} = ${finalInitValue};`, handled: true };
124121
+ const suppression = "// cppcheck-suppress misra-c2012-9.3\n// cppcheck-suppress misra-c2012-9.4\n";
124122
+ return {
124123
+ code: `${suppression}${decl} = ${finalInitValue};`,
124124
+ handled: true
124125
+ };
124086
124126
  }
124087
124127
  /**
124088
124128
  * Handle size inference for empty array dimension.
@@ -124558,15 +124598,21 @@ ${assignments}`;
124558
124598
  { generateType: callbacks.generateType }
124559
124599
  );
124560
124600
  }
124601
+ const hasInitializer = ctx.expression() !== null;
124561
124602
  const modifiers = VariableModifierBuilder_default.build(
124562
124603
  ctx,
124563
- CodeGenState.inFunctionBody
124604
+ CodeGenState.inFunctionBody,
124605
+ hasInitializer,
124606
+ CodeGenState.cppMode
124564
124607
  );
124565
124608
  const name = ctx.IDENTIFIER().getText();
124566
124609
  const typeCtx = ctx.type();
124567
124610
  _VariableDeclHelper.validateArrayDeclarationSyntax(ctx, typeCtx, name);
124568
124611
  const type = callbacks.inferVariableType(ctx, name);
124569
124612
  callbacks.trackLocalVariable(ctx, name);
124613
+ if (type.endsWith("*")) {
124614
+ callbacks.markVariableAsPointer(name);
124615
+ }
124570
124616
  const stringResult = StringDeclHelper_default.generateStringDecl(
124571
124617
  typeCtx,
124572
124618
  name,
@@ -124794,7 +124840,16 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124794
124840
  /**
124795
124841
  * Build the separator context for a member access chain
124796
124842
  */
124797
- static buildContext(firstId, hasGlobal, hasThis, currentScope, isStructParam, deps, isCppAccess) {
124843
+ static buildContext(input, deps) {
124844
+ const {
124845
+ firstId,
124846
+ hasGlobal,
124847
+ hasThis,
124848
+ currentScope,
124849
+ isStructParam,
124850
+ isCppAccess,
124851
+ forcePointerSemantics
124852
+ } = input;
124798
124853
  const isCrossScope = hasGlobal && (deps.isKnownScope(firstId) || deps.isKnownRegister(firstId));
124799
124854
  const scopedRegName = hasThis && currentScope ? `${currentScope}_${firstId}` : null;
124800
124855
  const isScopedRegister = scopedRegName !== null && deps.isKnownRegister(scopedRegName);
@@ -124804,7 +124859,8 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124804
124859
  isStructParam,
124805
124860
  isCppAccess,
124806
124861
  scopedRegName,
124807
- isScopedRegister
124862
+ isScopedRegister,
124863
+ forcePointerSemantics
124808
124864
  };
124809
124865
  }
124810
124866
  /**
@@ -124815,6 +124871,9 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124815
124871
  return "::";
124816
124872
  }
124817
124873
  if (ctx.isStructParam) {
124874
+ if (ctx.forcePointerSemantics) {
124875
+ return "->";
124876
+ }
124818
124877
  return deps.getStructParamSeparator();
124819
124878
  }
124820
124879
  if (ctx.isCrossScope) {
@@ -124875,6 +124934,9 @@ var ParameterDereferenceResolver = class _ParameterDereferenceResolver {
124875
124934
  * Determine if a parameter should be passed by value (no dereference needed)
124876
124935
  */
124877
124936
  static isPassByValue(paramInfo, deps) {
124937
+ if (paramInfo.isCallbackPointerPrimitive) {
124938
+ return false;
124939
+ }
124878
124940
  if (paramInfo.isCallback) {
124879
124941
  return true;
124880
124942
  }
@@ -124913,6 +124975,9 @@ var ParameterDereferenceResolver = class _ParameterDereferenceResolver {
124913
124975
  if (_ParameterDereferenceResolver.isPassByValue(paramInfo, deps)) {
124914
124976
  return id;
124915
124977
  }
124978
+ if (paramInfo.forcePointerSemantics) {
124979
+ return `(*${id})`;
124980
+ }
124916
124981
  return deps.maybeDereference(id);
124917
124982
  }
124918
124983
  };
@@ -125455,6 +125520,137 @@ var CastValidator = class _CastValidator {
125455
125520
  };
125456
125521
  var CastValidator_default = CastValidator;
125457
125522
 
125523
+ // src/transpiler/output/codegen/helpers/TypedefParamParser.ts
125524
+ var TypedefParamParser = class _TypedefParamParser {
125525
+ /**
125526
+ * Parse a function pointer typedef type string.
125527
+ *
125528
+ * @param typedefType - The type string, e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)"
125529
+ * @returns Parsed result with return type and parameters, or null if parsing fails
125530
+ */
125531
+ static parse(typedefType) {
125532
+ const funcPtrIndex = typedefType.indexOf("(*)");
125533
+ if (funcPtrIndex === -1) {
125534
+ return null;
125535
+ }
125536
+ const returnType = typedefType.substring(0, funcPtrIndex).trim();
125537
+ const afterFuncPtr = typedefType.substring(funcPtrIndex + 3).trim();
125538
+ if (!afterFuncPtr.startsWith("(")) {
125539
+ return null;
125540
+ }
125541
+ const paramsStr = _TypedefParamParser.extractParenContent(afterFuncPtr);
125542
+ if (paramsStr === null) {
125543
+ return null;
125544
+ }
125545
+ if (!paramsStr || paramsStr === "void") {
125546
+ return { returnType, params: [] };
125547
+ }
125548
+ const paramStrings = _TypedefParamParser.splitParams(paramsStr);
125549
+ const params = paramStrings.map((p) => _TypedefParamParser.parseParam(p));
125550
+ return { returnType, params };
125551
+ }
125552
+ /**
125553
+ * Extract content between matching parentheses, handling arbitrary nesting.
125554
+ * @param str - String starting with '('
125555
+ * @returns Content between outer parens, or null if no match
125556
+ */
125557
+ static extractParenContent(str) {
125558
+ if (!str.startsWith("(")) {
125559
+ return null;
125560
+ }
125561
+ let depth = 0;
125562
+ for (let i = 0; i < str.length; i++) {
125563
+ if (str[i] === "(") {
125564
+ depth++;
125565
+ } else if (str[i] === ")") {
125566
+ depth--;
125567
+ if (depth === 0) {
125568
+ return str.substring(1, i);
125569
+ }
125570
+ }
125571
+ }
125572
+ return null;
125573
+ }
125574
+ /**
125575
+ * Split parameter string by commas, respecting nested parentheses.
125576
+ */
125577
+ static splitParams(paramsStr) {
125578
+ const params = [];
125579
+ let current = "";
125580
+ let depth = 0;
125581
+ for (const char of paramsStr) {
125582
+ if (char === "(") {
125583
+ depth++;
125584
+ current += char;
125585
+ } else if (char === ")") {
125586
+ depth--;
125587
+ current += char;
125588
+ } else if (char === "," && depth === 0) {
125589
+ params.push(current.trim());
125590
+ current = "";
125591
+ } else {
125592
+ current += char;
125593
+ }
125594
+ }
125595
+ if (current.trim()) {
125596
+ params.push(current.trim());
125597
+ }
125598
+ return params;
125599
+ }
125600
+ /**
125601
+ * Parse a single parameter type string.
125602
+ */
125603
+ static parseParam(paramStr) {
125604
+ const trimmed = paramStr.trim();
125605
+ const isPointer = trimmed.includes("*");
125606
+ const isConst = /\bconst\b/.test(trimmed) || trimmed.startsWith("const");
125607
+ let baseType = trimmed.replaceAll(/\bconst\b/g, "").replace(/^const/, "").replaceAll("*", "").replaceAll(/\s+/g, " ").trim();
125608
+ if (baseType.includes(" ")) {
125609
+ baseType = baseType.replace(/\s+\w+$/, "");
125610
+ }
125611
+ if (baseType.startsWith("struct ")) {
125612
+ baseType = baseType.substring(7);
125613
+ }
125614
+ return {
125615
+ type: trimmed,
125616
+ isPointer,
125617
+ isConst,
125618
+ baseType
125619
+ };
125620
+ }
125621
+ /**
125622
+ * Get the parameter info at a given index, or null if not found.
125623
+ */
125624
+ static getParamAt(typedefType, paramIndex) {
125625
+ const parsed = _TypedefParamParser.parse(typedefType);
125626
+ if (!parsed || paramIndex >= parsed.params.length) {
125627
+ return null;
125628
+ }
125629
+ return parsed.params[paramIndex];
125630
+ }
125631
+ /**
125632
+ * Check if a parameter at a given index should be a pointer based on the typedef.
125633
+ *
125634
+ * @param typedefType - The typedef type string
125635
+ * @param paramIndex - The parameter index (0-based)
125636
+ * @returns true if the param should be a pointer, false for value, null if unknown
125637
+ */
125638
+ static shouldBePointer(typedefType, paramIndex) {
125639
+ return _TypedefParamParser.getParamAt(typedefType, paramIndex)?.isPointer ?? null;
125640
+ }
125641
+ /**
125642
+ * Check if a parameter at a given index should be const based on the typedef.
125643
+ *
125644
+ * @param typedefType - The typedef type string
125645
+ * @param paramIndex - The parameter index (0-based)
125646
+ * @returns true if the param should be const, false otherwise, null if unknown
125647
+ */
125648
+ static shouldBeConst(typedefType, paramIndex) {
125649
+ return _TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null;
125650
+ }
125651
+ };
125652
+ var TypedefParamParser_default = TypedefParamParser;
125653
+
125458
125654
  // src/transpiler/output/codegen/helpers/FunctionContextManager.ts
125459
125655
  var FunctionContextManager = class _FunctionContextManager {
125460
125656
  /**
@@ -125515,14 +125711,15 @@ var FunctionContextManager = class _FunctionContextManager {
125515
125711
  static processParameterList(params, callbacks) {
125516
125712
  CodeGenState.currentParameters.clear();
125517
125713
  if (!params) return;
125518
- for (const param of params.parameter()) {
125519
- _FunctionContextManager.processParameter(param, callbacks);
125714
+ const paramList = params.parameter();
125715
+ for (let i = 0; i < paramList.length; i++) {
125716
+ _FunctionContextManager.processParameter(paramList[i], callbacks, i);
125520
125717
  }
125521
125718
  }
125522
125719
  /**
125523
125720
  * Process a single parameter declaration.
125524
125721
  */
125525
- static processParameter(param, callbacks) {
125722
+ static processParameter(param, callbacks, paramIndex) {
125526
125723
  const name = param.IDENTIFIER().getText();
125527
125724
  const isArray = param.arrayDimension().length > 0 || param.type().arrayType() !== null;
125528
125725
  const isConst = param.constModifier() !== null;
@@ -125531,17 +125728,21 @@ var FunctionContextManager = class _FunctionContextManager {
125531
125728
  typeCtx,
125532
125729
  callbacks
125533
125730
  );
125534
- const isCallbackCompat = CodeGenState.currentFunctionName !== null && CodeGenState.callbackCompatibleFunctions.has(
125535
- CodeGenState.currentFunctionName
125536
- );
125731
+ const callbackTypedefInfo = _FunctionContextManager.getCallbackTypedefParamInfo(paramIndex);
125732
+ const isCallbackPointerParam = callbackTypedefInfo?.shouldBePointer ?? false;
125733
+ const isStruct = callbackTypedefInfo ? isCallbackPointerParam && typeInfo.isStruct : typeInfo.isStruct;
125734
+ const isCallbackPointerPrimitive = isCallbackPointerParam && !typeInfo.isStruct && !isArray;
125537
125735
  const paramInfo = {
125538
125736
  name,
125539
125737
  baseType: typeInfo.typeName,
125540
125738
  isArray,
125541
- isStruct: isCallbackCompat ? false : typeInfo.isStruct,
125739
+ isStruct,
125542
125740
  isConst,
125543
125741
  isCallback: typeInfo.isCallback,
125544
- isString: typeInfo.isString
125742
+ isString: typeInfo.isString,
125743
+ isCallbackPointerPrimitive,
125744
+ // Issue #895: Callback-compatible params need pointer semantics even in C++ mode
125745
+ forcePointerSemantics: isCallbackPointerParam
125545
125746
  };
125546
125747
  CodeGenState.currentParameters.set(name, paramInfo);
125547
125748
  _FunctionContextManager.registerParameterType(
@@ -125704,6 +125905,32 @@ var FunctionContextManager = class _FunctionContextManager {
125704
125905
  }
125705
125906
  return dimensions;
125706
125907
  }
125908
+ /**
125909
+ * Issue #895: Get callback typedef parameter info from the C header.
125910
+ * Returns null if not callback-compatible or index is invalid.
125911
+ */
125912
+ static getCallbackTypedefParamInfo(paramIndex) {
125913
+ if (CodeGenState.currentFunctionName === null) return null;
125914
+ const typedefName = CodeGenState.callbackCompatibleFunctions.get(
125915
+ CodeGenState.currentFunctionName
125916
+ );
125917
+ if (!typedefName) return null;
125918
+ const typedefType = CodeGenState.getTypedefType(typedefName);
125919
+ if (!typedefType) return null;
125920
+ const shouldBePointer = TypedefParamParser_default.shouldBePointer(
125921
+ typedefType,
125922
+ paramIndex
125923
+ );
125924
+ const shouldBeConst = TypedefParamParser_default.shouldBeConst(
125925
+ typedefType,
125926
+ paramIndex
125927
+ );
125928
+ if (shouldBePointer === null) return null;
125929
+ return {
125930
+ shouldBePointer,
125931
+ shouldBeConst: shouldBeConst ?? false
125932
+ };
125933
+ }
125707
125934
  /**
125708
125935
  * Extract string capacity from a string type context.
125709
125936
  */
@@ -126496,7 +126723,8 @@ var ParameterInputAdapter = class {
126496
126723
  }
126497
126724
  const isKnownStruct = deps.isKnownStruct(typeName);
126498
126725
  const isKnownPrimitive = !!deps.typeMap[typeName];
126499
- const isAutoConst = !deps.isModified && !isConst;
126726
+ const isAutoConst = !deps.isCallbackCompatible && !deps.isModified && !isConst;
126727
+ const isPassByReference = deps.forcePassByReference || isKnownStruct || isKnownPrimitive;
126500
126728
  return {
126501
126729
  name,
126502
126730
  baseType: typeName,
@@ -126507,7 +126735,12 @@ var ParameterInputAdapter = class {
126507
126735
  isCallback: false,
126508
126736
  isString: false,
126509
126737
  isPassByValue: deps.isPassByValue,
126510
- isPassByReference: isKnownStruct || isKnownPrimitive
126738
+ isPassByReference,
126739
+ // Issue #895: Force pointer syntax in C++ mode for callback-compatible functions
126740
+ // because C callback typedefs expect pointers, not C++ references
126741
+ forcePointerSyntax: deps.forcePassByReference,
126742
+ // Issue #895: Preserve const from callback typedef signature
126743
+ forceConst: deps.forceConst
126511
126744
  };
126512
126745
  }
126513
126746
  /**
@@ -126721,11 +126954,15 @@ var ParameterSignatureBuilder = class {
126721
126954
  /**
126722
126955
  * Build pass-by-reference parameter signature.
126723
126956
  * C mode: const Point* p
126724
- * C++ mode: const Point& p
126957
+ * C++ mode: const Point& p (unless forcePointerSyntax)
126958
+ *
126959
+ * Issue #895: When forcePointerSyntax is set, always use pointer syntax
126960
+ * because C callback typedefs expect pointers, not C++ references.
126725
126961
  */
126726
126962
  static _buildRefParam(param, refSuffix) {
126727
126963
  const constPrefix = this._getConstPrefix(param);
126728
- return `${constPrefix}${param.mappedType}${refSuffix} ${param.name}`;
126964
+ const actualSuffix = param.forcePointerSyntax ? "*" : refSuffix;
126965
+ return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
126729
126966
  }
126730
126967
  /**
126731
126968
  * Build unknown type parameter (pass by value, standard C semantics).
@@ -126735,13 +126972,15 @@ var ParameterSignatureBuilder = class {
126735
126972
  return `${constMod}${param.mappedType} ${param.name}`;
126736
126973
  }
126737
126974
  /**
126738
- * Get const prefix combining explicit const and auto-const.
126739
- * Auto-const is applied to unmodified parameters.
126975
+ * Get const prefix combining explicit const, auto-const, and forced const.
126976
+ * Priority: forceConst > isConst > isAutoConst
126977
+ * Issue #895: forceConst preserves const from callback typedef signature.
126740
126978
  */
126741
126979
  static _getConstPrefix(param) {
126742
- const autoConst = param.isAutoConst && !param.isConst ? "const " : "";
126743
- const explicitConst = param.isConst ? "const " : "";
126744
- return `${autoConst}${explicitConst}`;
126980
+ if (param.forceConst || param.isConst || param.isAutoConst) {
126981
+ return "const ";
126982
+ }
126983
+ return "";
126745
126984
  }
126746
126985
  };
126747
126986
  var ParameterSignatureBuilder_default = ParameterSignatureBuilder;
@@ -127717,7 +127956,7 @@ var CodeGenerator = class _CodeGenerator {
127717
127956
  }
127718
127957
  /** Generate parameter list for function signature */
127719
127958
  generateParameterList(ctx) {
127720
- return ctx.parameter().map((p) => this.generateParameter(p)).join(", ");
127959
+ return ctx.parameter().map((p, index) => this.generateParameter(p, index)).join(", ");
127721
127960
  }
127722
127961
  /** Get the raw type name without C conversion */
127723
127962
  getTypeName(ctx) {
@@ -129463,13 +129702,17 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129463
129702
  const typedef = this.generateCallbackTypedef(name);
129464
129703
  return typedef ? functionCode + typedef : functionCode;
129465
129704
  }
129466
- generateParameter(ctx) {
129705
+ generateParameter(ctx, paramIndex) {
129467
129706
  const typeName = this.getTypeName(ctx.type());
129468
129707
  const name = ctx.IDENTIFIER().getText();
129469
129708
  this._validateCStyleArrayParam(ctx, typeName, name);
129470
129709
  this._validateUnboundedArrayParam(ctx);
129471
129710
  const isModified = this._isCurrentParameterModified(name);
129472
- const isPassByValue = this._isPassByValueType(typeName, name);
129711
+ const callbackInfo = paramIndex === void 0 ? null : FunctionContextManager_default.getCallbackTypedefParamInfo(paramIndex);
129712
+ const isPassByValue = callbackInfo ? !callbackInfo.shouldBePointer : this._isPassByValueType(typeName, name);
129713
+ const isCallbackCompatible = callbackInfo !== null;
129714
+ const forcePassByReference = callbackInfo?.shouldBePointer ?? false;
129715
+ const forceConst = callbackInfo?.shouldBeConst ?? false;
129473
129716
  const input = ParameterInputAdapter_default.fromAST(ctx, {
129474
129717
  getTypeName: (t) => this.getTypeName(t),
129475
129718
  generateType: (t) => this.generateType(t),
@@ -129478,7 +129721,10 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129478
129721
  isKnownStruct: (t) => this.isKnownStruct(t),
129479
129722
  typeMap: TYPE_MAP_default,
129480
129723
  isModified,
129481
- isPassByValue
129724
+ isPassByValue,
129725
+ isCallbackCompatible,
129726
+ forcePassByReference,
129727
+ forceConst
129482
129728
  });
129483
129729
  return ParameterSignatureBuilder_default.build(input, CppModeHelper_default.refOrPtr());
129484
129730
  }
@@ -129548,6 +129794,7 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129548
129794
  getExpressionType: (exprCtx) => this.getExpressionType(exprCtx),
129549
129795
  inferVariableType: (varCtx, name) => this._inferVariableType(varCtx, name),
129550
129796
  trackLocalVariable: (varCtx, name) => this._trackLocalVariable(varCtx, name),
129797
+ markVariableAsPointer: (name) => this._markVariableAsPointer(name),
129551
129798
  getStringConcatOperands: (concatCtx) => this._getStringConcatOperands(concatCtx),
129552
129799
  getSubstringOperands: (substrCtx) => this._getSubstringOperands(substrCtx),
129553
129800
  getStringExprCapacity: (exprCode) => this.getStringExprCapacity(exprCode),
@@ -129556,20 +129803,116 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129556
129803
  }
129557
129804
  /**
129558
129805
  * Issue #696: Infer variable type, handling nullable C pointer types.
129806
+ * Issue #895 Bug B: Infer pointer type from C function return type.
129559
129807
  */
129560
129808
  _inferVariableType(ctx, name) {
129561
- let type = this.generateType(ctx.type());
129562
- if (!name.startsWith("c_") || !ctx.expression()) {
129809
+ const type = this.generateType(ctx.type());
129810
+ if (!ctx.expression()) {
129563
129811
  return type;
129564
129812
  }
129565
- const exprText = ctx.expression().getText();
129566
- for (const funcName of NullCheckAnalyzer_default.getStructPointerFunctions()) {
129567
- if (exprText.includes(`${funcName}(`)) {
129568
- return `${type}*`;
129813
+ const pointerType = this._inferPointerTypeFromFunctionCall(
129814
+ ctx.expression(),
129815
+ type
129816
+ );
129817
+ if (pointerType) {
129818
+ return pointerType;
129819
+ }
129820
+ if (name.startsWith("c_")) {
129821
+ const exprText = ctx.expression().getText();
129822
+ for (const funcName of NullCheckAnalyzer_default.getStructPointerFunctions()) {
129823
+ if (exprText.includes(`${funcName}(`)) {
129824
+ return `${type}*`;
129825
+ }
129569
129826
  }
129570
129827
  }
129571
129828
  return type;
129572
129829
  }
129830
+ /**
129831
+ * Issue #895 Bug B: Infer pointer type from C function return type.
129832
+ * If initializer is a call to a C function that returns T*, and declared
129833
+ * type is T, return T* instead of T.
129834
+ */
129835
+ _inferPointerTypeFromFunctionCall(expr, declaredType) {
129836
+ const funcName = this._extractCFunctionName(expr);
129837
+ if (!funcName) {
129838
+ return null;
129839
+ }
129840
+ const cFunc = CodeGenState.symbolTable?.getCSymbol(funcName);
129841
+ if (cFunc?.kind !== "function") {
129842
+ return null;
129843
+ }
129844
+ const returnType = cFunc.type;
129845
+ if (!returnType.endsWith("*")) {
129846
+ return null;
129847
+ }
129848
+ const returnBaseType = returnType.replace(/\s*\*\s*$/, "").trim();
129849
+ if (returnBaseType === declaredType) {
129850
+ return `${declaredType}*`;
129851
+ }
129852
+ return null;
129853
+ }
129854
+ /**
129855
+ * Extract C function name from expression patterns.
129856
+ * Handles both:
129857
+ * - global.funcName(...) - explicit global access
129858
+ * - funcName(...) - direct call (if funcName is a known C function)
129859
+ * Returns null if expression doesn't match these patterns.
129860
+ */
129861
+ _extractCFunctionName(expr) {
129862
+ const postfix = ExpressionUnwrapper_default.getPostfixExpression(expr);
129863
+ if (!postfix) {
129864
+ return null;
129865
+ }
129866
+ const primary = postfix.primaryExpression();
129867
+ const ops = postfix.postfixOp();
129868
+ if (primary.GLOBAL()) {
129869
+ return this._extractGlobalPatternFuncName(ops);
129870
+ }
129871
+ const identifier = primary.IDENTIFIER();
129872
+ if (identifier) {
129873
+ return this._extractDirectCallFuncName(identifier.getText(), ops);
129874
+ }
129875
+ return null;
129876
+ }
129877
+ /**
129878
+ * Extract function name from global.funcName(...) pattern.
129879
+ */
129880
+ _extractGlobalPatternFuncName(ops) {
129881
+ if (ops.length < 2) {
129882
+ return null;
129883
+ }
129884
+ const memberOp = ops[0];
129885
+ if (!memberOp.IDENTIFIER()) {
129886
+ return null;
129887
+ }
129888
+ const callOp = ops[1];
129889
+ if (!this._isCallOp(callOp)) {
129890
+ return null;
129891
+ }
129892
+ return memberOp.IDENTIFIER().getText();
129893
+ }
129894
+ /**
129895
+ * Extract function name from direct funcName(...) call if it's a C function.
129896
+ */
129897
+ _extractDirectCallFuncName(funcName, ops) {
129898
+ if (ops.length < 1) {
129899
+ return null;
129900
+ }
129901
+ if (!this._isCallOp(ops[0])) {
129902
+ return null;
129903
+ }
129904
+ const cFunc = CodeGenState.symbolTable?.getCSymbol(funcName);
129905
+ if (cFunc?.kind === "function") {
129906
+ return funcName;
129907
+ }
129908
+ return null;
129909
+ }
129910
+ /**
129911
+ * Check if a postfix op is a function call.
129912
+ */
129913
+ _isCallOp(op) {
129914
+ return Boolean(op.argumentList() || op.getText().startsWith("("));
129915
+ }
129573
129916
  /**
129574
129917
  * Issue #696: Track local variable for type registry and const values.
129575
129918
  */
@@ -129590,6 +129933,20 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129590
129933
  }
129591
129934
  }
129592
129935
  }
129936
+ /**
129937
+ * Issue #895 Bug B: Mark variable as a pointer in the type registry.
129938
+ * Called when type inference detects that a variable should be a pointer
129939
+ * (e.g., initialized from a C function returning T*).
129940
+ */
129941
+ _markVariableAsPointer(name) {
129942
+ const typeInfo = CodeGenState.getVariableTypeInfo(name);
129943
+ if (typeInfo) {
129944
+ CodeGenState.setVariableTypeInfo(name, {
129945
+ ...typeInfo,
129946
+ isPointer: true
129947
+ });
129948
+ }
129949
+ }
129593
129950
  // Issue #792: Methods _handleArrayDeclaration, _getArrayTypeDimension, _parseArrayTypeDimension,
129594
129951
  // _parseFirstArrayDimension, _validateArrayDeclarationSyntax, _extractBaseTypeName,
129595
129952
  // _generateVariableInitializer, _validateIntegerInitializer, _finalizeCppClassAssignments,
@@ -129816,14 +130173,18 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129816
130173
  const isStructParam = paramInfo?.isStruct ?? false;
129817
130174
  const isCppAccess = hasGlobal && this.isCppScopeSymbol(firstId);
129818
130175
  const separatorDeps = this._buildMemberSeparatorDeps();
130176
+ const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
129819
130177
  const separatorCtx = MemberSeparatorResolver_default.buildContext(
129820
- firstId,
129821
- hasGlobal,
129822
- hasThis,
129823
- CodeGenState.currentScope,
129824
- isStructParam,
129825
- separatorDeps,
129826
- isCppAccess
130178
+ {
130179
+ firstId,
130180
+ hasGlobal,
130181
+ hasThis,
130182
+ currentScope: CodeGenState.currentScope,
130183
+ isStructParam,
130184
+ isCppAccess,
130185
+ forcePointerSemantics
130186
+ },
130187
+ separatorDeps
129827
130188
  );
129828
130189
  return {
129829
130190
  generateExpression: (expr) => this.generateExpression(expr),
@@ -133162,6 +133523,8 @@ var FunctionCollector2 = class _FunctionCollector {
133162
133523
  const parameters = _FunctionCollector._mapParameters(
133163
133524
  DeclaratorUtils_default.extractFunctionParameters(declarator)
133164
133525
  );
133526
+ const hasPointer = declarator.pointer() !== null;
133527
+ const returnType = hasPointer ? `${baseType}*` : baseType;
133165
133528
  return {
133166
133529
  kind: "function",
133167
133530
  name,
@@ -133169,7 +133532,7 @@ var FunctionCollector2 = class _FunctionCollector {
133169
133532
  sourceLine: line,
133170
133533
  sourceLanguage: ESourceLanguage_default.C,
133171
133534
  isExported: !isExtern,
133172
- type: baseType,
133535
+ type: returnType,
133173
133536
  parameters: parameters.length > 0 ? parameters : void 0,
133174
133537
  isDeclaration: true
133175
133538
  };
@@ -136858,6 +137221,8 @@ var FunctionCallAnalyzer = class {
136858
137221
  if (sym?.kind !== "type") return false;
136859
137222
  return "type" in sym && typeof sym.type === "string" && sym.type.includes("(*)");
136860
137223
  }
137224
+ /** Current scope name during callback assignment scanning */
137225
+ scanCurrentScope = null;
136861
137226
  /**
136862
137227
  * Detect functions assigned to C function pointer typedefs.
136863
137228
  * When `PointCallback cb <- my_handler;` is found and PointCallback
@@ -136866,12 +137231,46 @@ var FunctionCallAnalyzer = class {
136866
137231
  collectCallbackCompatibleFunctions(tree) {
136867
137232
  for (const decl of tree.declaration()) {
136868
137233
  const funcDecl = decl.functionDeclaration();
136869
- if (!funcDecl) continue;
136870
- const block = funcDecl.block();
136871
- if (!block) continue;
136872
- this.scanBlockForCallbackAssignments(block);
137234
+ if (funcDecl) {
137235
+ this.scanStandaloneFunctionForCallbacks(funcDecl);
137236
+ continue;
137237
+ }
137238
+ const scopeDecl = decl.scopeDeclaration();
137239
+ if (scopeDecl) {
137240
+ this.scanScopeForCallbacks(scopeDecl);
137241
+ }
136873
137242
  }
136874
137243
  }
137244
+ /**
137245
+ * Scan a standalone function declaration for callback assignments.
137246
+ */
137247
+ scanStandaloneFunctionForCallbacks(funcDecl) {
137248
+ const block = funcDecl.block();
137249
+ if (!block) return;
137250
+ this.scanCurrentScope = null;
137251
+ this.scanBlockForCallbackAssignments(block);
137252
+ }
137253
+ /**
137254
+ * Scan all member functions in a scope for callback assignments (Issue #895).
137255
+ */
137256
+ scanScopeForCallbacks(scopeDecl) {
137257
+ const scopeName = scopeDecl.IDENTIFIER().getText();
137258
+ for (const member of scopeDecl.scopeMember()) {
137259
+ this.scanScopeMemberForCallbacks(member, scopeName);
137260
+ }
137261
+ this.scanCurrentScope = null;
137262
+ }
137263
+ /**
137264
+ * Scan a single scope member for callback assignments.
137265
+ */
137266
+ scanScopeMemberForCallbacks(member, scopeName) {
137267
+ const memberFunc = member.functionDeclaration();
137268
+ if (!memberFunc) return;
137269
+ const block = memberFunc.block();
137270
+ if (!block) return;
137271
+ this.scanCurrentScope = scopeName;
137272
+ this.scanBlockForCallbackAssignments(block);
137273
+ }
136875
137274
  /**
136876
137275
  * Recursively scan all statements in a block for callback typedef assignments.
136877
137276
  */
@@ -136890,6 +137289,11 @@ var FunctionCallAnalyzer = class {
136890
137289
  this.checkVarDeclForCallbackAssignment(varDecl);
136891
137290
  return;
136892
137291
  }
137292
+ const exprStmt = stmt.expressionStatement();
137293
+ if (exprStmt) {
137294
+ this.checkExpressionForCallbackArgs(exprStmt.expression());
137295
+ return;
137296
+ }
136893
137297
  const ifStmt = stmt.ifStatement();
136894
137298
  if (ifStmt) {
136895
137299
  for (const child of ifStmt.statement()) {
@@ -136945,22 +137349,101 @@ var FunctionCallAnalyzer = class {
136945
137349
  if (!funcRef) return;
136946
137350
  const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
136947
137351
  if (this.allLocalFunctions.has(lookupName)) {
136948
- CodeGenState.callbackCompatibleFunctions.add(lookupName);
137352
+ CodeGenState.callbackCompatibleFunctions.set(lookupName, typeName);
136949
137353
  }
136950
137354
  }
136951
137355
  /**
136952
137356
  * Extract a function reference from an expression context.
136953
- * Matches bare identifiers (e.g., "my_handler") and qualified scope
136954
- * names (e.g., "MyScope.handler").
137357
+ * Matches:
137358
+ * - Bare identifiers: "my_handler"
137359
+ * - Qualified scope names: "MyScope.handler"
137360
+ * - Self-scope reference: "this.handler" (resolved using scanCurrentScope)
137361
+ * - Global scope reference: "global.ScopeName.handler"
136955
137362
  * Returns null if the expression is not a function reference.
136956
137363
  */
136957
137364
  extractFunctionReference(expr) {
136958
137365
  const text = expr.getText();
136959
- if (/^\w+(\.\w+)?$/.test(text)) {
137366
+ const thisPattern = /^this\.(\w+)$/;
137367
+ const thisMatch = thisPattern.exec(text);
137368
+ if (thisMatch) {
137369
+ if (!this.scanCurrentScope) {
137370
+ return null;
137371
+ }
137372
+ return this.scanCurrentScope + "." + thisMatch[1];
137373
+ }
137374
+ const globalPattern = /^global\.(\w+)\.(\w+)$/;
137375
+ const globalMatch = globalPattern.exec(text);
137376
+ if (globalMatch) {
137377
+ return globalMatch[1] + "." + globalMatch[2];
137378
+ }
137379
+ const simplePattern = /^\w+(\.\w+)?$/;
137380
+ if (simplePattern.test(text)) {
136960
137381
  return text;
136961
137382
  }
136962
137383
  return null;
136963
137384
  }
137385
+ /**
137386
+ * Issue #895: Check expression for function calls that pass C-Next functions
137387
+ * to C function pointer parameters.
137388
+ *
137389
+ * Pattern: `global.widget_set_flush_cb(w, my_flush)`
137390
+ * Where widget_set_flush_cb's 2nd param is a C function pointer typedef.
137391
+ */
137392
+ checkExpressionForCallbackArgs(expr) {
137393
+ const postfix = this.findPostfixExpression(expr);
137394
+ if (!postfix) return;
137395
+ const callInfo = this.extractCallInfo(postfix);
137396
+ if (!callInfo) return;
137397
+ const cFunc = this.symbolTable?.getCSymbol(callInfo.funcName);
137398
+ if (cFunc?.kind !== "function" || !cFunc.parameters) return;
137399
+ for (let i = 0; i < callInfo.args.length; i++) {
137400
+ const param = cFunc.parameters[i];
137401
+ if (!param) continue;
137402
+ if (!this.isCFunctionPointerTypedef(param.type)) continue;
137403
+ const funcRef = this.extractFunctionReference(callInfo.args[i]);
137404
+ if (!funcRef) continue;
137405
+ const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
137406
+ if (this.allLocalFunctions.has(lookupName)) {
137407
+ CodeGenState.callbackCompatibleFunctions.set(lookupName, param.type);
137408
+ }
137409
+ }
137410
+ }
137411
+ /**
137412
+ * Find the postfix expression within an expression tree.
137413
+ * Uses ExpressionUnwrapper which validates that expression is "simple"
137414
+ * (single term at each level), returning null for complex expressions.
137415
+ */
137416
+ findPostfixExpression(expr) {
137417
+ return ExpressionUnwrapper_default.getPostfixExpression(expr);
137418
+ }
137419
+ /**
137420
+ * Extract function name and arguments from a postfix expression.
137421
+ * Returns null if not a function call.
137422
+ */
137423
+ extractCallInfo(postfix) {
137424
+ const primary = postfix.primaryExpression();
137425
+ const ops = postfix.postfixOp();
137426
+ const ident = primary.IDENTIFIER();
137427
+ const globalKw = primary.GLOBAL();
137428
+ if (!ident && !globalKw) {
137429
+ return null;
137430
+ }
137431
+ let funcName = ident ? ident.getText() : "";
137432
+ let argListOp = null;
137433
+ for (const op of ops) {
137434
+ if (op.IDENTIFIER()) {
137435
+ const member = op.IDENTIFIER().getText();
137436
+ funcName = funcName ? `${funcName}_${member}` : member;
137437
+ } else if (op.argumentList() || op.getText().startsWith("(")) {
137438
+ argListOp = op;
137439
+ break;
137440
+ }
137441
+ }
137442
+ if (!argListOp || !funcName) return null;
137443
+ const argList = argListOp.argumentList();
137444
+ const args = argList?.expression() ?? [];
137445
+ return { funcName, args };
137446
+ }
136964
137447
  /**
136965
137448
  * ADR-040: Register a variable that holds a callable (ISR or callback)
136966
137449
  */
@@ -138627,7 +139110,7 @@ var Transpiler = class {
138627
139110
  this.state.reset();
138628
139111
  CodeGenState.symbolTable.clear();
138629
139112
  SymbolRegistry_default.reset();
138630
- CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Set();
139113
+ CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Map();
138631
139114
  }
138632
139115
  /**
138633
139116
  * Ensure output directories exist