prio: 55 # auto
C functions returning function pointers: typedef'd return type + full declarator
- Type: bug (cparser declarators). Track C.
- Found: 2026-07-06 c-testsuite run.
Status 2026-07-08 (cfront-agent) — 00089 already GREEN; 00124 root-caused, parked
- 00089 now passes (compiles + runs exit 0; not in
pxx.skip). Landed by the earlier fnptr-typedef work. Only 00124 remains skipped. - 00124 isolated to a single missing link. Reductions from the test:
(*f1(0,2))(2,2)(call f1 directly, then call its returned fnptr) → 0 (correct). So f1's return type + calling-a-returned-fnptr already work.int (*g)(int,int) = (*p)(0,2); return g(2,2);(store the intermediate in a typed var) → 0 (correct). So the runtime VALUE(*p)(0,2)returns is right.(*(*p)(0,2))(2,2)and(*p(0,2))(2,2)(call the result inline) → 85 (wrong). Conclusion: only the inline chain-call through a fn-pointer variable whose pointee returns a fn-pointer is broken — the returned fnptr's call signature is lost.
- Precise cause.
CNodeProcSig(cparser.inc ~1650) resolves a chained callee's signature. It has anAN_CALLarm (go()()— direct call returning fnptr, usesProcRetProcSig[ASTIVal[inner]]) but noAN_CALL_INDarm, so(*p)(…)( … )falls through to sig -1 → the second call is emitted with the wrong ABI. Adding theAN_CALL_INDarm is necessary but not sufficient: instrumentedProcRetProcSig[<p's sig proc>] = -1. The variable declaratorint (* (*p)(int,int))(int,int)is parsed via thefnRetIsFuncpath (line 2399+, designed for the fn-definitionf1); it registersfpSig(line 2543) with return typeintand never records that p's pointee RETURNS a fn-pointer, soProcRetProcSig[fpSig]is never set. - Two-part fix (next session):
CNodeProcSig: addelse if ASTKind[inner] = AN_CALL_IND then Result := ProcRetProcSig[ASTIVal[inner]](mirror theAN_CALLarm;ASTIValof anAN_CALL_INDis the callee signature-proc index).- In the fn-pointer declarator (cparser.inc ~2399–2568), when the pointee's
RETURN type is itself a fn-pointer (nested
(* (*name)(…))(…)), thread the inner fnptr signature ontoProcRetProcSig[fpSig]. The inner sig is available from the return-type parse; thefnRetIsFuncreuse currently discards it for the variable case. Both edits are incparser.inc(Track C). Self-host-fragile — the switch-body attempt (sibling ticket) showed parse restructures in this file can desync the seed; verify with incremental self-host fixedpoint after each edit.
- Parked to
unfinished/(claimed, root-caused, not landed — no code change on master).
Failing tests
- 00089:
typedef struct S *(*fty)(); fty go() {...}— definition with typedef'd fnptr return type apparently not registered: caller gets "call to undeclared function: go". Call chaingo()()->zerofunc()(call result of call). - 00124: full declarator form
int (*f1(int a, int b))(int c, int d) {...}— COMPILES but exit 85: call(*(*p)(0, 2))(2, 2)returns garbage → returned fnptr or the double indirect call miscompiled.
Gate
Drop 00089.c/00124.c from test/c-conformance/pxx.skip; runner green.
Triage note (2026-07-06)
00089: fty go() where fty is a typedef for a fn-pointer — go is not registered as a function ("call to undeclared function: go"), so the fn-def detector mis-handles a typedef-fnptr RETURN type (likely treats go as a fn-pointer variable via the typedef proc-signature). 00124: full inline declarator int (*f1(int,int))(int,int) compiles but the double-indirect call returns garbage (85). Both are C declarator-grammar work (function returning function pointer), a focused session.
Pinned 2026-07-07
00089 core reduced: typedef int (*fty)(void); fty go(void){...} -> CERR
"expected C expression". A LOCAL fty f = &zero; works (ParseCLocalDeclAST has
the CTypeFnPtrName inline-fnptr branch). ParseCSubroutine's return-type path
(retType := ParseCDeclType at cparser.inc:5013) does NOT handle a fnptr-typedef
return type — after consuming fty (tyPointer + CTypeProcSig set), the name/param
reading derails. Fix: mirror the local fnptr-typedef handling for a function's
RETURN type (register go with retType tyPointer + the fnptr proc signature, read
name+params normally). 00124 separately needs the inline full declarator
int (*f(int,int))(int,int). Bounded-ish declarator work, focused session.
Further pinning 2026-07-07 — a fnptr-typedef declarator cluster
Probed the fnptr-typedef (typedef int (*fty)(void)) in several positions:
fty go(void);(prototype) — WORKS (pass-1 registration is fine).fty go(void){ return &z; }(definition) — CERR "expected C expression" in the BODY (the proto registers, but compiling the body of a fnptr-typedef-returning function derails).fty gp = &z;(global variable) — CERR "call to undeclared function: gp" (a fnptr-typedef-typed GLOBAL isn't registered either).myint go(void)(non-fnptr typedef return) — WORKS. So it's a cluster of fnptr-typedef declarator gaps (function-definition body, global-var decl) beyond the original 00089/00124, all rooted in how a tyPointer + CTypeProcSig "callable" type flows through the def/global paths. Focused session.
Progress 2026-07-07 — two sub-fixes landed; remaining gaps pinned
Resolved parts of the cluster (both gated, self-host green):
- fn-pointer TYPEDEF global
fty gp = &z;— commit a386edce (register callable + bind init). go()()— calling the result of a function that returns a function pointer — commit cfebdbbb (new ProcRetProcSig + CNodeProcSig AN_CALL case). Still open for 00089/00124:get()->f()— calling a fn-pointer FIELD on a call RESULT SIGSEGVs (the field fnptr call on a non-lvalue call result).- 00089's exact form
struct S *(*fty)()(empty params, struct-ptr return): go is reported "undeclared" at the call — its registration differs from the simpleint (*fty)(void)form that now works (likely the empty()param list or the struct-ptr return in ParseCSubroutine). - 00124: full inline declarator
int (*f1(int,int))(int,int)(compiles, runs 85). Each is a further ParseCSubroutine / postfix-call refinement — focused session.
00124 finding 2026-07-07
00124's inline declarator int (*f1(int,int))(int,int) PARSES (compiles) — the
remaining issue is the nested double-indirect call (*(*p)(0,2))(2,2) mis-lowers
(runs 85, want 0). So both remaining cases are fn-pointer CODEGEN, not parser:
00089 = fnptr-field call on a call result (SIGSEGV); 00124 = chained
deref+indirect-call value. Deep codegen, focused session.
Deeper layers pinned 2026-07-07 (3 sub-fixes now landed)
Landed this stretch: fnptr-typedef global (a386edce), go()() (cfebdbbb),
&func struct fn-ptr field init (81400021). Remaining for 00089 isolated to the
fnptr typedef whose return type is a STRUCT POINTER:
typedef int (*fty)(); ... go()()— WORKS (empty params + int return fine).typedef struct S*(*fty)(void); ... go()()->v— COMPILES but SIGSEGVs: the indirect call go()()'s RETURN TYPE (struct S*) isn't tracked, so->von the result can't resolve the record. Needs a ProcRetProcSig-analog for the return RECORD/type carried through the indirect call.typedef struct S*(*fty)()(empty params + struct-ptr return) — "go undeclared": go isn't even registered — the empty-()+ struct-ptr-return combination in the fn-def path fails registration. So the last of 00089 is return-type propagation through a fn-pointer indirect call (+ the empty-param struct-ptr-return registration edge). Deeper; focused.
Codegen wall 2026-07-07
go()()->v (fty = struct-ptr-returning fn pointer) SIGSEGVs in the DOUBLE-
INDIRECT CALL codegen — go()() itself returns a wrong pointer, so ->v derefs
garbage (record resolution is a red herring; adding AN_CALL_IND to ResolveNodeRec
did not help and was reverted). So the last of 00089/00124 is fn-pointer call
CODEGEN: (a) go()() (call-of-call-result) returning a struct pointer, (b) 00124's
(*(*p)(0,2))(2,2) chain. Deep backend work — focused session. The three parser-
level sub-fixes (typedef global, go()() parse, &func struct field) are landed.
Progress 2026-07-08 (a-agent) — 00089 FIXED, 00124 remains
Root cause of 00089 was three-fold, all around a fn-pointer typedef whose RETURN
type is a struct pointer (typedef struct S *(*fty)();):
- ParseCTypedef routing — the leading
structsent it down the aggregate fast-path (typedef struct Tag *Name;), which finds(instead of a plain name and registers NOTHING →ftystayed undefined ("stray token"). AddedCTypedefAggFnPtrDeclaratorlookahead: a struct/union typedef whose declarator is(*name)(...)now routes through the general ParseCDeclType path. - Signature lost its result record — the
$cfnptrsig registered at the fn-ptr declarator never set ProcRetPtrElemTk/Rec, so an indirect call's result had no pointed-at record. Now captured before the param recursion and set on the sig. p()->fieldIR gap —CNodeIsPointerhad no AN_CALL_IND case, so the arrow never wrapped the call result in AN_DEREF and builtAN_FIELD(AN_CALL_IND)→ IRLowerAddress hit IR_UNSUPPORTED. Added AN_CALL_IND to CNodeIsPointer and to both node-record resolvers (CNodePtrElemTk/Rec).
00089 green. Conformance 209 pass / 0 fail / 11 skip. Self-host byte-identical, quick tier + lua/core green. Dropped 00089 from pxx.skip.
Remaining (ticket stays open): 00124 — int (*f1(int,int))(int,int) (a
FUNCTION returning a fnptr, full inline declarator) still compiles but exits 85:
the double-indirect call (*(*p)(0,2))(2,2) returns garbage. Separate from the
typedef-fnptr-return path above — a fn-returning-fnptr codegen/declarator bug.
Log
- 2026-07-09 — resolved, commit PENDING.