c-next 0.2.4 → 0.2.5

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 (31) hide show
  1. package/dist/index.js +331 -37
  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 +112 -1
  6. package/src/transpiler/logic/analysis/__tests__/FunctionCallAnalyzer.test.ts +48 -0
  7. package/src/transpiler/logic/symbols/c/__tests__/CResolver.integration.test.ts +18 -0
  8. package/src/transpiler/output/codegen/CodeGenerator.ts +38 -10
  9. package/src/transpiler/output/codegen/TypeResolver.ts +1 -1
  10. package/src/transpiler/output/codegen/generators/expressions/PostfixExpressionGenerator.ts +15 -3
  11. package/src/transpiler/output/codegen/helpers/FunctionContextManager.ts +63 -10
  12. package/src/transpiler/output/codegen/helpers/MemberSeparatorResolver.ts +28 -6
  13. package/src/transpiler/output/codegen/helpers/ParameterDereferenceResolver.ts +12 -0
  14. package/src/transpiler/output/codegen/helpers/ParameterInputAdapter.ts +30 -2
  15. package/src/transpiler/output/codegen/helpers/ParameterSignatureBuilder.ts +15 -7
  16. package/src/transpiler/output/codegen/helpers/StringOperationsHelper.ts +1 -1
  17. package/src/transpiler/output/codegen/helpers/TypedefParamParser.ts +220 -0
  18. package/src/transpiler/output/codegen/helpers/__tests__/FunctionContextManager.test.ts +5 -5
  19. package/src/transpiler/output/codegen/helpers/__tests__/MemberSeparatorResolver.test.ts +48 -36
  20. package/src/transpiler/output/codegen/helpers/__tests__/ParameterInputAdapter.test.ts +37 -0
  21. package/src/transpiler/output/codegen/helpers/__tests__/ParameterSignatureBuilder.test.ts +63 -0
  22. package/src/transpiler/output/codegen/helpers/__tests__/TypedefParamParser.test.ts +209 -0
  23. package/src/transpiler/output/codegen/resolution/EnumTypeResolver.ts +1 -1
  24. package/src/transpiler/output/codegen/resolution/SizeofResolver.ts +1 -1
  25. package/src/transpiler/output/codegen/types/IParameterInput.ts +13 -0
  26. package/src/transpiler/output/codegen/types/ISeparatorContext.ts +7 -0
  27. package/src/transpiler/output/codegen/types/TParameterInfo.ts +12 -0
  28. package/src/transpiler/output/codegen/utils/CodegenParserUtils.ts +1 -1
  29. package/src/transpiler/state/CodeGenState.ts +21 -2
  30. package/src/{transpiler/output/codegen/utils → utils}/ExpressionUnwrapper.ts +1 -1
  31. 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.5",
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.
@@ -115744,6 +115764,7 @@ var generatePostfixExpression = (ctx, input, state, orchestrator) => {
115744
115764
  const rootIdentifier = primary.IDENTIFIER()?.getText();
115745
115765
  const paramInfo = rootIdentifier ? state.currentParameters.get(rootIdentifier) : null;
115746
115766
  const isStructParam = paramInfo?.isStruct ?? false;
115767
+ const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
115747
115768
  const hasSubscriptOps = ops.some((op) => op.expression().length > 0);
115748
115769
  const isNonArrayParamWithSubscript = paramInfo && !paramInfo.isArray && !paramInfo.isStruct && hasSubscriptOps;
115749
115770
  let result;
@@ -115764,6 +115785,7 @@ var generatePostfixExpression = (ctx, input, state, orchestrator) => {
115764
115785
  const postfixCtx = {
115765
115786
  rootIdentifier,
115766
115787
  isStructParam,
115788
+ forcePointerSemantics,
115767
115789
  input,
115768
115790
  state,
115769
115791
  orchestrator,
@@ -115855,6 +115877,7 @@ var handleMemberOp = (memberName, tracking, ctx) => {
115855
115877
  memberName,
115856
115878
  rootIdentifier: ctx.rootIdentifier,
115857
115879
  isStructParam: ctx.isStructParam,
115880
+ forcePointerSemantics: ctx.forcePointerSemantics,
115858
115881
  isGlobalAccess: tracking.isGlobalAccess,
115859
115882
  isCppAccessChain: tracking.isCppAccessChain,
115860
115883
  currentStructType: tracking.currentStructType,
@@ -116589,7 +116612,7 @@ var tryStructParamAccess = (ctx, orchestrator) => {
116589
116612
  if (!ctx.isStructParam || ctx.result !== ctx.rootIdentifier) {
116590
116613
  return null;
116591
116614
  }
116592
- const structParamSep = memberAccessChain_default.getStructParamSeparator({
116615
+ const structParamSep = ctx.forcePointerSemantics ? "->" : memberAccessChain_default.getStructParamSeparator({
116593
116616
  cppMode: orchestrator.isCppMode()
116594
116617
  });
116595
116618
  const output = initializeMemberOutput(ctx);
@@ -124794,7 +124817,16 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124794
124817
  /**
124795
124818
  * Build the separator context for a member access chain
124796
124819
  */
124797
- static buildContext(firstId, hasGlobal, hasThis, currentScope, isStructParam, deps, isCppAccess) {
124820
+ static buildContext(input, deps) {
124821
+ const {
124822
+ firstId,
124823
+ hasGlobal,
124824
+ hasThis,
124825
+ currentScope,
124826
+ isStructParam,
124827
+ isCppAccess,
124828
+ forcePointerSemantics
124829
+ } = input;
124798
124830
  const isCrossScope = hasGlobal && (deps.isKnownScope(firstId) || deps.isKnownRegister(firstId));
124799
124831
  const scopedRegName = hasThis && currentScope ? `${currentScope}_${firstId}` : null;
124800
124832
  const isScopedRegister = scopedRegName !== null && deps.isKnownRegister(scopedRegName);
@@ -124804,7 +124836,8 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124804
124836
  isStructParam,
124805
124837
  isCppAccess,
124806
124838
  scopedRegName,
124807
- isScopedRegister
124839
+ isScopedRegister,
124840
+ forcePointerSemantics
124808
124841
  };
124809
124842
  }
124810
124843
  /**
@@ -124815,6 +124848,9 @@ var MemberSeparatorResolver = class _MemberSeparatorResolver {
124815
124848
  return "::";
124816
124849
  }
124817
124850
  if (ctx.isStructParam) {
124851
+ if (ctx.forcePointerSemantics) {
124852
+ return "->";
124853
+ }
124818
124854
  return deps.getStructParamSeparator();
124819
124855
  }
124820
124856
  if (ctx.isCrossScope) {
@@ -124875,6 +124911,9 @@ var ParameterDereferenceResolver = class _ParameterDereferenceResolver {
124875
124911
  * Determine if a parameter should be passed by value (no dereference needed)
124876
124912
  */
124877
124913
  static isPassByValue(paramInfo, deps) {
124914
+ if (paramInfo.isCallbackPointerPrimitive) {
124915
+ return false;
124916
+ }
124878
124917
  if (paramInfo.isCallback) {
124879
124918
  return true;
124880
124919
  }
@@ -124913,6 +124952,9 @@ var ParameterDereferenceResolver = class _ParameterDereferenceResolver {
124913
124952
  if (_ParameterDereferenceResolver.isPassByValue(paramInfo, deps)) {
124914
124953
  return id;
124915
124954
  }
124955
+ if (paramInfo.forcePointerSemantics) {
124956
+ return `(*${id})`;
124957
+ }
124916
124958
  return deps.maybeDereference(id);
124917
124959
  }
124918
124960
  };
@@ -125455,6 +125497,137 @@ var CastValidator = class _CastValidator {
125455
125497
  };
125456
125498
  var CastValidator_default = CastValidator;
125457
125499
 
125500
+ // src/transpiler/output/codegen/helpers/TypedefParamParser.ts
125501
+ var TypedefParamParser = class _TypedefParamParser {
125502
+ /**
125503
+ * Parse a function pointer typedef type string.
125504
+ *
125505
+ * @param typedefType - The type string, e.g., "void (*)(widget_t *, const rect_t *, uint8_t *)"
125506
+ * @returns Parsed result with return type and parameters, or null if parsing fails
125507
+ */
125508
+ static parse(typedefType) {
125509
+ const funcPtrIndex = typedefType.indexOf("(*)");
125510
+ if (funcPtrIndex === -1) {
125511
+ return null;
125512
+ }
125513
+ const returnType = typedefType.substring(0, funcPtrIndex).trim();
125514
+ const afterFuncPtr = typedefType.substring(funcPtrIndex + 3).trim();
125515
+ if (!afterFuncPtr.startsWith("(")) {
125516
+ return null;
125517
+ }
125518
+ const paramsStr = _TypedefParamParser.extractParenContent(afterFuncPtr);
125519
+ if (paramsStr === null) {
125520
+ return null;
125521
+ }
125522
+ if (!paramsStr || paramsStr === "void") {
125523
+ return { returnType, params: [] };
125524
+ }
125525
+ const paramStrings = _TypedefParamParser.splitParams(paramsStr);
125526
+ const params = paramStrings.map((p) => _TypedefParamParser.parseParam(p));
125527
+ return { returnType, params };
125528
+ }
125529
+ /**
125530
+ * Extract content between matching parentheses, handling arbitrary nesting.
125531
+ * @param str - String starting with '('
125532
+ * @returns Content between outer parens, or null if no match
125533
+ */
125534
+ static extractParenContent(str) {
125535
+ if (!str.startsWith("(")) {
125536
+ return null;
125537
+ }
125538
+ let depth = 0;
125539
+ for (let i = 0; i < str.length; i++) {
125540
+ if (str[i] === "(") {
125541
+ depth++;
125542
+ } else if (str[i] === ")") {
125543
+ depth--;
125544
+ if (depth === 0) {
125545
+ return str.substring(1, i);
125546
+ }
125547
+ }
125548
+ }
125549
+ return null;
125550
+ }
125551
+ /**
125552
+ * Split parameter string by commas, respecting nested parentheses.
125553
+ */
125554
+ static splitParams(paramsStr) {
125555
+ const params = [];
125556
+ let current = "";
125557
+ let depth = 0;
125558
+ for (const char of paramsStr) {
125559
+ if (char === "(") {
125560
+ depth++;
125561
+ current += char;
125562
+ } else if (char === ")") {
125563
+ depth--;
125564
+ current += char;
125565
+ } else if (char === "," && depth === 0) {
125566
+ params.push(current.trim());
125567
+ current = "";
125568
+ } else {
125569
+ current += char;
125570
+ }
125571
+ }
125572
+ if (current.trim()) {
125573
+ params.push(current.trim());
125574
+ }
125575
+ return params;
125576
+ }
125577
+ /**
125578
+ * Parse a single parameter type string.
125579
+ */
125580
+ static parseParam(paramStr) {
125581
+ const trimmed = paramStr.trim();
125582
+ const isPointer = trimmed.includes("*");
125583
+ const isConst = /\bconst\b/.test(trimmed) || trimmed.startsWith("const");
125584
+ let baseType = trimmed.replaceAll(/\bconst\b/g, "").replace(/^const/, "").replaceAll("*", "").replaceAll(/\s+/g, " ").trim();
125585
+ if (baseType.includes(" ")) {
125586
+ baseType = baseType.replace(/\s+\w+$/, "");
125587
+ }
125588
+ if (baseType.startsWith("struct ")) {
125589
+ baseType = baseType.substring(7);
125590
+ }
125591
+ return {
125592
+ type: trimmed,
125593
+ isPointer,
125594
+ isConst,
125595
+ baseType
125596
+ };
125597
+ }
125598
+ /**
125599
+ * Get the parameter info at a given index, or null if not found.
125600
+ */
125601
+ static getParamAt(typedefType, paramIndex) {
125602
+ const parsed = _TypedefParamParser.parse(typedefType);
125603
+ if (!parsed || paramIndex >= parsed.params.length) {
125604
+ return null;
125605
+ }
125606
+ return parsed.params[paramIndex];
125607
+ }
125608
+ /**
125609
+ * Check if a parameter at a given index should be a pointer based on the typedef.
125610
+ *
125611
+ * @param typedefType - The typedef type string
125612
+ * @param paramIndex - The parameter index (0-based)
125613
+ * @returns true if the param should be a pointer, false for value, null if unknown
125614
+ */
125615
+ static shouldBePointer(typedefType, paramIndex) {
125616
+ return _TypedefParamParser.getParamAt(typedefType, paramIndex)?.isPointer ?? null;
125617
+ }
125618
+ /**
125619
+ * Check if a parameter at a given index should be const based on the typedef.
125620
+ *
125621
+ * @param typedefType - The typedef type string
125622
+ * @param paramIndex - The parameter index (0-based)
125623
+ * @returns true if the param should be const, false otherwise, null if unknown
125624
+ */
125625
+ static shouldBeConst(typedefType, paramIndex) {
125626
+ return _TypedefParamParser.getParamAt(typedefType, paramIndex)?.isConst ?? null;
125627
+ }
125628
+ };
125629
+ var TypedefParamParser_default = TypedefParamParser;
125630
+
125458
125631
  // src/transpiler/output/codegen/helpers/FunctionContextManager.ts
125459
125632
  var FunctionContextManager = class _FunctionContextManager {
125460
125633
  /**
@@ -125515,14 +125688,15 @@ var FunctionContextManager = class _FunctionContextManager {
125515
125688
  static processParameterList(params, callbacks) {
125516
125689
  CodeGenState.currentParameters.clear();
125517
125690
  if (!params) return;
125518
- for (const param of params.parameter()) {
125519
- _FunctionContextManager.processParameter(param, callbacks);
125691
+ const paramList = params.parameter();
125692
+ for (let i = 0; i < paramList.length; i++) {
125693
+ _FunctionContextManager.processParameter(paramList[i], callbacks, i);
125520
125694
  }
125521
125695
  }
125522
125696
  /**
125523
125697
  * Process a single parameter declaration.
125524
125698
  */
125525
- static processParameter(param, callbacks) {
125699
+ static processParameter(param, callbacks, paramIndex) {
125526
125700
  const name = param.IDENTIFIER().getText();
125527
125701
  const isArray = param.arrayDimension().length > 0 || param.type().arrayType() !== null;
125528
125702
  const isConst = param.constModifier() !== null;
@@ -125531,17 +125705,21 @@ var FunctionContextManager = class _FunctionContextManager {
125531
125705
  typeCtx,
125532
125706
  callbacks
125533
125707
  );
125534
- const isCallbackCompat = CodeGenState.currentFunctionName !== null && CodeGenState.callbackCompatibleFunctions.has(
125535
- CodeGenState.currentFunctionName
125536
- );
125708
+ const callbackTypedefInfo = _FunctionContextManager.getCallbackTypedefParamInfo(paramIndex);
125709
+ const isCallbackPointerParam = callbackTypedefInfo?.shouldBePointer ?? false;
125710
+ const isStruct = callbackTypedefInfo ? isCallbackPointerParam && typeInfo.isStruct : typeInfo.isStruct;
125711
+ const isCallbackPointerPrimitive = isCallbackPointerParam && !typeInfo.isStruct && !isArray;
125537
125712
  const paramInfo = {
125538
125713
  name,
125539
125714
  baseType: typeInfo.typeName,
125540
125715
  isArray,
125541
- isStruct: isCallbackCompat ? false : typeInfo.isStruct,
125716
+ isStruct,
125542
125717
  isConst,
125543
125718
  isCallback: typeInfo.isCallback,
125544
- isString: typeInfo.isString
125719
+ isString: typeInfo.isString,
125720
+ isCallbackPointerPrimitive,
125721
+ // Issue #895: Callback-compatible params need pointer semantics even in C++ mode
125722
+ forcePointerSemantics: isCallbackPointerParam
125545
125723
  };
125546
125724
  CodeGenState.currentParameters.set(name, paramInfo);
125547
125725
  _FunctionContextManager.registerParameterType(
@@ -125704,6 +125882,32 @@ var FunctionContextManager = class _FunctionContextManager {
125704
125882
  }
125705
125883
  return dimensions;
125706
125884
  }
125885
+ /**
125886
+ * Issue #895: Get callback typedef parameter info from the C header.
125887
+ * Returns null if not callback-compatible or index is invalid.
125888
+ */
125889
+ static getCallbackTypedefParamInfo(paramIndex) {
125890
+ if (CodeGenState.currentFunctionName === null) return null;
125891
+ const typedefName = CodeGenState.callbackCompatibleFunctions.get(
125892
+ CodeGenState.currentFunctionName
125893
+ );
125894
+ if (!typedefName) return null;
125895
+ const typedefType = CodeGenState.getTypedefType(typedefName);
125896
+ if (!typedefType) return null;
125897
+ const shouldBePointer = TypedefParamParser_default.shouldBePointer(
125898
+ typedefType,
125899
+ paramIndex
125900
+ );
125901
+ const shouldBeConst = TypedefParamParser_default.shouldBeConst(
125902
+ typedefType,
125903
+ paramIndex
125904
+ );
125905
+ if (shouldBePointer === null) return null;
125906
+ return {
125907
+ shouldBePointer,
125908
+ shouldBeConst: shouldBeConst ?? false
125909
+ };
125910
+ }
125707
125911
  /**
125708
125912
  * Extract string capacity from a string type context.
125709
125913
  */
@@ -126496,7 +126700,8 @@ var ParameterInputAdapter = class {
126496
126700
  }
126497
126701
  const isKnownStruct = deps.isKnownStruct(typeName);
126498
126702
  const isKnownPrimitive = !!deps.typeMap[typeName];
126499
- const isAutoConst = !deps.isModified && !isConst;
126703
+ const isAutoConst = !deps.isCallbackCompatible && !deps.isModified && !isConst;
126704
+ const isPassByReference = deps.forcePassByReference || isKnownStruct || isKnownPrimitive;
126500
126705
  return {
126501
126706
  name,
126502
126707
  baseType: typeName,
@@ -126507,7 +126712,12 @@ var ParameterInputAdapter = class {
126507
126712
  isCallback: false,
126508
126713
  isString: false,
126509
126714
  isPassByValue: deps.isPassByValue,
126510
- isPassByReference: isKnownStruct || isKnownPrimitive
126715
+ isPassByReference,
126716
+ // Issue #895: Force pointer syntax in C++ mode for callback-compatible functions
126717
+ // because C callback typedefs expect pointers, not C++ references
126718
+ forcePointerSyntax: deps.forcePassByReference,
126719
+ // Issue #895: Preserve const from callback typedef signature
126720
+ forceConst: deps.forceConst
126511
126721
  };
126512
126722
  }
126513
126723
  /**
@@ -126721,11 +126931,15 @@ var ParameterSignatureBuilder = class {
126721
126931
  /**
126722
126932
  * Build pass-by-reference parameter signature.
126723
126933
  * C mode: const Point* p
126724
- * C++ mode: const Point& p
126934
+ * C++ mode: const Point& p (unless forcePointerSyntax)
126935
+ *
126936
+ * Issue #895: When forcePointerSyntax is set, always use pointer syntax
126937
+ * because C callback typedefs expect pointers, not C++ references.
126725
126938
  */
126726
126939
  static _buildRefParam(param, refSuffix) {
126727
126940
  const constPrefix = this._getConstPrefix(param);
126728
- return `${constPrefix}${param.mappedType}${refSuffix} ${param.name}`;
126941
+ const actualSuffix = param.forcePointerSyntax ? "*" : refSuffix;
126942
+ return `${constPrefix}${param.mappedType}${actualSuffix} ${param.name}`;
126729
126943
  }
126730
126944
  /**
126731
126945
  * Build unknown type parameter (pass by value, standard C semantics).
@@ -126735,13 +126949,15 @@ var ParameterSignatureBuilder = class {
126735
126949
  return `${constMod}${param.mappedType} ${param.name}`;
126736
126950
  }
126737
126951
  /**
126738
- * Get const prefix combining explicit const and auto-const.
126739
- * Auto-const is applied to unmodified parameters.
126952
+ * Get const prefix combining explicit const, auto-const, and forced const.
126953
+ * Priority: forceConst > isConst > isAutoConst
126954
+ * Issue #895: forceConst preserves const from callback typedef signature.
126740
126955
  */
126741
126956
  static _getConstPrefix(param) {
126742
- const autoConst = param.isAutoConst && !param.isConst ? "const " : "";
126743
- const explicitConst = param.isConst ? "const " : "";
126744
- return `${autoConst}${explicitConst}`;
126957
+ if (param.forceConst || param.isConst || param.isAutoConst) {
126958
+ return "const ";
126959
+ }
126960
+ return "";
126745
126961
  }
126746
126962
  };
126747
126963
  var ParameterSignatureBuilder_default = ParameterSignatureBuilder;
@@ -127717,7 +127933,7 @@ var CodeGenerator = class _CodeGenerator {
127717
127933
  }
127718
127934
  /** Generate parameter list for function signature */
127719
127935
  generateParameterList(ctx) {
127720
- return ctx.parameter().map((p) => this.generateParameter(p)).join(", ");
127936
+ return ctx.parameter().map((p, index) => this.generateParameter(p, index)).join(", ");
127721
127937
  }
127722
127938
  /** Get the raw type name without C conversion */
127723
127939
  getTypeName(ctx) {
@@ -129463,13 +129679,17 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129463
129679
  const typedef = this.generateCallbackTypedef(name);
129464
129680
  return typedef ? functionCode + typedef : functionCode;
129465
129681
  }
129466
- generateParameter(ctx) {
129682
+ generateParameter(ctx, paramIndex) {
129467
129683
  const typeName = this.getTypeName(ctx.type());
129468
129684
  const name = ctx.IDENTIFIER().getText();
129469
129685
  this._validateCStyleArrayParam(ctx, typeName, name);
129470
129686
  this._validateUnboundedArrayParam(ctx);
129471
129687
  const isModified = this._isCurrentParameterModified(name);
129472
- const isPassByValue = this._isPassByValueType(typeName, name);
129688
+ const callbackInfo = paramIndex === void 0 ? null : FunctionContextManager_default.getCallbackTypedefParamInfo(paramIndex);
129689
+ const isPassByValue = callbackInfo ? !callbackInfo.shouldBePointer : this._isPassByValueType(typeName, name);
129690
+ const isCallbackCompatible = callbackInfo !== null;
129691
+ const forcePassByReference = callbackInfo?.shouldBePointer ?? false;
129692
+ const forceConst = callbackInfo?.shouldBeConst ?? false;
129473
129693
  const input = ParameterInputAdapter_default.fromAST(ctx, {
129474
129694
  getTypeName: (t) => this.getTypeName(t),
129475
129695
  generateType: (t) => this.generateType(t),
@@ -129478,7 +129698,10 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129478
129698
  isKnownStruct: (t) => this.isKnownStruct(t),
129479
129699
  typeMap: TYPE_MAP_default,
129480
129700
  isModified,
129481
- isPassByValue
129701
+ isPassByValue,
129702
+ isCallbackCompatible,
129703
+ forcePassByReference,
129704
+ forceConst
129482
129705
  });
129483
129706
  return ParameterSignatureBuilder_default.build(input, CppModeHelper_default.refOrPtr());
129484
129707
  }
@@ -129816,14 +130039,18 @@ typedef ${callbackInfo.returnType} (*${callbackInfo.typedefName})(${paramList});
129816
130039
  const isStructParam = paramInfo?.isStruct ?? false;
129817
130040
  const isCppAccess = hasGlobal && this.isCppScopeSymbol(firstId);
129818
130041
  const separatorDeps = this._buildMemberSeparatorDeps();
130042
+ const forcePointerSemantics = paramInfo?.forcePointerSemantics ?? false;
129819
130043
  const separatorCtx = MemberSeparatorResolver_default.buildContext(
129820
- firstId,
129821
- hasGlobal,
129822
- hasThis,
129823
- CodeGenState.currentScope,
129824
- isStructParam,
129825
- separatorDeps,
129826
- isCppAccess
130044
+ {
130045
+ firstId,
130046
+ hasGlobal,
130047
+ hasThis,
130048
+ currentScope: CodeGenState.currentScope,
130049
+ isStructParam,
130050
+ isCppAccess,
130051
+ forcePointerSemantics
130052
+ },
130053
+ separatorDeps
129827
130054
  );
129828
130055
  return {
129829
130056
  generateExpression: (expr) => this.generateExpression(expr),
@@ -136890,6 +137117,11 @@ var FunctionCallAnalyzer = class {
136890
137117
  this.checkVarDeclForCallbackAssignment(varDecl);
136891
137118
  return;
136892
137119
  }
137120
+ const exprStmt = stmt.expressionStatement();
137121
+ if (exprStmt) {
137122
+ this.checkExpressionForCallbackArgs(exprStmt.expression());
137123
+ return;
137124
+ }
136893
137125
  const ifStmt = stmt.ifStatement();
136894
137126
  if (ifStmt) {
136895
137127
  for (const child of ifStmt.statement()) {
@@ -136945,7 +137177,7 @@ var FunctionCallAnalyzer = class {
136945
137177
  if (!funcRef) return;
136946
137178
  const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
136947
137179
  if (this.allLocalFunctions.has(lookupName)) {
136948
- CodeGenState.callbackCompatibleFunctions.add(lookupName);
137180
+ CodeGenState.callbackCompatibleFunctions.set(lookupName, typeName);
136949
137181
  }
136950
137182
  }
136951
137183
  /**
@@ -136961,6 +137193,68 @@ var FunctionCallAnalyzer = class {
136961
137193
  }
136962
137194
  return null;
136963
137195
  }
137196
+ /**
137197
+ * Issue #895: Check expression for function calls that pass C-Next functions
137198
+ * to C function pointer parameters.
137199
+ *
137200
+ * Pattern: `global.widget_set_flush_cb(w, my_flush)`
137201
+ * Where widget_set_flush_cb's 2nd param is a C function pointer typedef.
137202
+ */
137203
+ checkExpressionForCallbackArgs(expr) {
137204
+ const postfix = this.findPostfixExpression(expr);
137205
+ if (!postfix) return;
137206
+ const callInfo = this.extractCallInfo(postfix);
137207
+ if (!callInfo) return;
137208
+ const cFunc = this.symbolTable?.getCSymbol(callInfo.funcName);
137209
+ if (cFunc?.kind !== "function" || !cFunc.parameters) return;
137210
+ for (let i = 0; i < callInfo.args.length; i++) {
137211
+ const param = cFunc.parameters[i];
137212
+ if (!param) continue;
137213
+ if (!this.isCFunctionPointerTypedef(param.type)) continue;
137214
+ const funcRef = this.extractFunctionReference(callInfo.args[i]);
137215
+ if (!funcRef) continue;
137216
+ const lookupName = funcRef.includes(".") ? funcRef.replace(".", "_") : funcRef;
137217
+ if (this.allLocalFunctions.has(lookupName)) {
137218
+ CodeGenState.callbackCompatibleFunctions.set(lookupName, param.type);
137219
+ }
137220
+ }
137221
+ }
137222
+ /**
137223
+ * Find the postfix expression within an expression tree.
137224
+ * Uses ExpressionUnwrapper which validates that expression is "simple"
137225
+ * (single term at each level), returning null for complex expressions.
137226
+ */
137227
+ findPostfixExpression(expr) {
137228
+ return ExpressionUnwrapper_default.getPostfixExpression(expr);
137229
+ }
137230
+ /**
137231
+ * Extract function name and arguments from a postfix expression.
137232
+ * Returns null if not a function call.
137233
+ */
137234
+ extractCallInfo(postfix) {
137235
+ const primary = postfix.primaryExpression();
137236
+ const ops = postfix.postfixOp();
137237
+ const ident = primary.IDENTIFIER();
137238
+ const globalKw = primary.GLOBAL();
137239
+ if (!ident && !globalKw) {
137240
+ return null;
137241
+ }
137242
+ let funcName = ident ? ident.getText() : "";
137243
+ let argListOp = null;
137244
+ for (const op of ops) {
137245
+ if (op.IDENTIFIER()) {
137246
+ const member = op.IDENTIFIER().getText();
137247
+ funcName = funcName ? `${funcName}_${member}` : member;
137248
+ } else if (op.argumentList() || op.getText().startsWith("(")) {
137249
+ argListOp = op;
137250
+ break;
137251
+ }
137252
+ }
137253
+ if (!argListOp || !funcName) return null;
137254
+ const argList = argListOp.argumentList();
137255
+ const args = argList?.expression() ?? [];
137256
+ return { funcName, args };
137257
+ }
136964
137258
  /**
136965
137259
  * ADR-040: Register a variable that holds a callable (ISR or callback)
136966
137260
  */
@@ -138627,7 +138921,7 @@ var Transpiler = class {
138627
138921
  this.state.reset();
138628
138922
  CodeGenState.symbolTable.clear();
138629
138923
  SymbolRegistry_default.reset();
138630
- CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Set();
138924
+ CodeGenState.callbackCompatibleFunctions = /* @__PURE__ */ new Map();
138631
138925
  }
138632
138926
  /**
138633
138927
  * Ensure output directories exist