Summary
Passing a named function (a function item, whose type is TyKind::FnDef) as the argument to a generic Fn-bounded parameter makes Thrust panic with not implemented: unrefined_ty: FnDef(..). The crash happens during type-template construction, before any solving, and aborts verification of the whole crate.
TemplateTypeBuilder::build (src/refine/template.rs:206) handles function pointers (TyKind::FnPtr, line 225) and closures (via closure_model, line 197), but has no arm for TyKind::FnDef, so the type of the generic parameter — once it has been instantiated with a concrete function item — falls through to the catch-all unimplemented!() at src/refine/template.rs:261.
This is ordinary, idiomatic Rust (e.g. apply(helper, x), opt.map(my_fn)), and the same shape works for closures and for fn(..) pointers, so the gap is specifically the FnDef → generic Fn path.
Reproduction (minimal, no annotations)
fn id(r: i32) -> i32 { r }
fn apply<F: Fn(i32) -> i32>(f: F, a: i32) -> i32 { f(a) }
fn main() { let _ = apply(id, 5); }
This is valid Rust and runs fine under plain rustc. Thrust panics:
$ cargo run --quiet -- -Adead_code -C debug-assertions=false repro.rs
thread 'rustc' panicked at src/refine/template.rs:261:21:
not implemented: unrefined_ty: FnDef(DefId(0:3 ~ repro[851a]::id), [])
No requires/ensures/param/ret annotation is involved; main need only construct the call.
What works vs. what panics
| case |
code |
result |
| direct call |
id(5) |
ok |
| bind item to local, call directly |
let f = id; f(5) |
ok |
named fn item → generic Fn param |
apply::<_>(id, 5) where F: Fn(i32)->i32 |
panic |
closure literal → generic Fn param |
apply(|y| y + 1, 5) |
ok (closures handled via closure_model) |
named fn item → fn(i32)->i32 pointer param |
fn apply(f: fn(i32)->i32, a: i32); apply(id, 5) |
ok (handled by the FnPtr arm) |
So the trigger is precisely: a FnDef type reaching TemplateTypeBuilder::build as (the instantiation of) a generic function-typed parameter.
Root cause
src/refine/template.rs:206-262, TemplateTypeBuilder::build:
pub fn build(&self, ty: mir_ty::Ty<'tcx>) -> rty::Type<rty::Closed> {
let ty = self.resolve_model_ty(ty);
match ty.kind() {
mir_ty::TyKind::Bool => rty::Type::bool(),
...
mir_ty::TyKind::FnPtr(sig_tys, hdr) => { // function POINTERS handled
let sig = sig_tys.with(*hdr).skip_binder();
let params = sig.inputs().iter()
.map(|ty| rty::RefinedType::unrefined(self.build(*ty)).vacuous())
.collect();
let ret = rty::RefinedType::unrefined(self.build(sig.output()));
rty::FunctionType::new(params, ret.vacuous()).into()
}
mir_ty::TyKind::Adt(def, params) => { ... } // closures handled via model_adt/closure_model
kind => unimplemented!("unrefined_ty: {:?}", kind), // <-- FnDef lands here (line 261)
}
}
When apply is analyzed/instantiated with F = FnDef(id), building the type of the parameter f: F calls build on the FnDef type. There is no TyKind::FnDef arm, so it hits the catch-all and panics.
A FnDef carries the same information needed to build a rty::FunctionType as the existing FnPtr arm: its signature is available via tcx.fn_sig(def_id).instantiate(tcx, args). The FnDef case can be handled analogously to FnPtr (a function item coerces to a function pointer of its signature), or — to track the callee precisely — resolved to that specific function's refined signature.
Suggested direction
Add a TyKind::FnDef(def_id, args) arm to TemplateTypeBuilder::build (and to the sibling builder referenced by the // TODO: consolidate two impls note at template.rs:205) that obtains the instantiated signature and constructs a rty::FunctionType, mirroring the FnPtr arm. At minimum, the unhandled case should produce a graceful "unsupported" diagnostic rather than an ICE-style panic.
Environment
- thrust @
8a269d8
- rustc
nightly-2025-09-08 (per rust-toolchain.toml)
- Z3 4.16.0, default solver configuration
Summary
Passing a named function (a function item, whose type is
TyKind::FnDef) as the argument to a genericFn-bounded parameter makes Thrust panic withnot implemented: unrefined_ty: FnDef(..). The crash happens during type-template construction, before any solving, and aborts verification of the whole crate.TemplateTypeBuilder::build(src/refine/template.rs:206) handles function pointers (TyKind::FnPtr, line 225) and closures (viaclosure_model, line 197), but has no arm forTyKind::FnDef, so the type of the generic parameter — once it has been instantiated with a concrete function item — falls through to the catch-allunimplemented!()atsrc/refine/template.rs:261.This is ordinary, idiomatic Rust (e.g.
apply(helper, x),opt.map(my_fn)), and the same shape works for closures and forfn(..)pointers, so the gap is specifically theFnDef→ genericFnpath.Reproduction (minimal, no annotations)
This is valid Rust and runs fine under plain rustc. Thrust panics:
No
requires/ensures/param/retannotation is involved;mainneed only construct the call.What works vs. what panics
id(5)let f = id; f(5)Fnparamapply::<_>(id, 5)whereF: Fn(i32)->i32Fnparamapply(|y| y + 1, 5)closure_model)fn(i32)->i32pointer paramfn apply(f: fn(i32)->i32, a: i32);apply(id, 5)FnPtrarm)So the trigger is precisely: a
FnDeftype reachingTemplateTypeBuilder::buildas (the instantiation of) a generic function-typed parameter.Root cause
src/refine/template.rs:206-262,TemplateTypeBuilder::build:When
applyis analyzed/instantiated withF = FnDef(id), building the type of the parameterf: Fcallsbuildon theFnDeftype. There is noTyKind::FnDefarm, so it hits the catch-all and panics.A
FnDefcarries the same information needed to build arty::FunctionTypeas the existingFnPtrarm: its signature is available viatcx.fn_sig(def_id).instantiate(tcx, args). TheFnDefcase can be handled analogously toFnPtr(a function item coerces to a function pointer of its signature), or — to track the callee precisely — resolved to that specific function's refined signature.Suggested direction
Add a
TyKind::FnDef(def_id, args)arm toTemplateTypeBuilder::build(and to the sibling builder referenced by the// TODO: consolidate two implsnote attemplate.rs:205) that obtains the instantiated signature and constructs arty::FunctionType, mirroring theFnPtrarm. At minimum, the unhandled case should produce a graceful "unsupported" diagnostic rather than an ICE-style panic.Environment
8a269d8nightly-2025-09-08(perrust-toolchain.toml)