← board

C: calling an inline function-pointer struct member mis-lowers

Symptom

After inline fn-ptr members are registered (layout fix landed), calling one still produces wrong results — every call form, different wrong value:

struct cfg { int (*fp)(int); };
static int add1(int x){ return x + 1; }
int main(void){ struct cfg c; c.fp = add1; return c.fp(41); }    /* want 42, got 36 */
/* p->fp(41)        -> 36                                                        */
/* (*c.fp)(41)      -> 85                                                        */

And sqlite3.c:19490 0==sqlite3Config.xAltLocaltime((const void*)t,(void*)pTm) still fails (unexpected token) — the call form in a comparison/cast context.

Key isolating fact

A typedef'd fn-ptr field calls correctly:

typedef int (*fn)(int);
struct cfg { fn fp; };
...
int main(void){ struct cfg c; c.fp = add1; return c.fp(41); }    /* 42 — works */

A typedef member goes through ParseCStructInto's normal declarator path; the inline member goes through the new fn-ptr branch. Both set UFldProcSig, and the call path (RecFieldProcSig -> AN_CALL_IND, cparser.inc ~1148) is shared — so the difference is in how the inline branch sets up the field vs the normal path. Prime suspect: the field's element-type tag (bfElemTk = tyUnknown in the inline branch vs the typedef's carried element type) or a sig-linkage detail the call lowering reads. lua's fn-ptr calls worked because they are all typedef-based ((*g->frealloc)(...)).

Next step

Diff the registered field (and the emitted AN_CALL_IND IR) between the typedef path and the inline path for the identical signature — find what the inline branch must additionally set (likely bfElemTk/elem-rec or the proc-sig wiring) so the indirect call reads the correct callee. Then handle the call form in a cast/comparison context for the sqlite line.

Acceptance

Log

DONE 2026-06-27

Root: the inline-member name's token off/len was captured into the CTypeFnPtrName* globals during the (*name) scan, but ParseCDeclType recurses to parse the parameter list and that recursion resets those globals — so the field registered with off/len 0, FindUField(name) missed, and the access fell back to offset-0 / tyInteger / no proc sig (8-byte address truncated to 4, call never lowered). Fix: keep the off/len in locals and assign the globals after the param recursion (mirroring fpName). Same root as the layout ticket — the registration was right, the name reference was clobbered.

c.fp(41) / p->fp(41) / (*c.fp)(41) all return 42; typedef path unchanged. Regression test test/cfnptr_struct_member.c. Full gate green, byte-identical. sqlite advances past the xAltLocaltime call to its next wall (pascal26:20679: expected C expression).