`field_projections` incorrectly handles recursion limits
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.1k
- PR merge metrics
- PR metrics pending
Description
I am filing this issue on behalf of @carlini, who discovered this bug with an LLM (labelled as "bug 3" at https://rust-lang.zulipchat.com/#narrow/channel/364551-t-types.2Ftrait-system-refactor/topic/Reporting.20a.20collection.20of.20soundness.20bugs.20in.20next-solver/near/617737394)
The following code is unsound. It prints garbage data when compiled with -Znext-solver with debuginfo turned off.
It appears that the compiler doesn't realize that the two Tr implementations overlap, defining two different types for <S<Deep> as Trait>::Out. This appears to be caused by the compiler incorrectly thinking that field_of!(S<Deep>, f): Field doesn't hold, after reaching a recursion limit.
Note that this code (and comments in it) are generated by an LLM. I did not verify the correctness of the comments.
cc @BennoLossin, who worked on field_projections
// -Znext-solver soundness bug: `consider_builtin_field_candidate` turns an ambiguous
// `Sized` premise (Certainty::Maybe) into `Err(NoSolution)`, so `field_of!(S<Y>, f): Field`
// holds at generic `Y` but is "refuted" at `Y = Deep`; two impls overlap, `Out` gets two answers.
//
// rustc --edition 2021 bug.rs -> error (E0275, overflow)
// rustc --edition 2021 -Znext-solver bug.rs -> accepts; the binary prints 16 bytes of
// stack garbage read from a 1-byte return value
#![forbid(unsafe_code)]
#![feature(field_projections)] // gates `Field` / `field_of!` (the buggy builtin candidate)
#![allow(incomplete_features)] // only silences the feature warning; optional
use core::field::{Field, field_of};
// A projection chain that costs ~1 solver step per link. Needed because the Sized premise
// must be *ambiguous* (overflow) at `Y = Deep`: a direct cycle / infinite descent is a hard
// E0275 in both solvers, and a chain that is too short (<= 112) or too long (>= 256) makes
// the new solver report E0284 / E0275 instead of accepting. Window with the default
// recursion_limit (128): 128..=240 links.
trait Chain { type A; }
impl Chain for () { type A = u8; }
struct Link<T>(T);
impl<T: Chain> Chain for Link<T> { type A = <T as Chain>::A; }
type L4<T> = Link<Link<Link<Link<T>>>>;
type L16<T> = L4<L4<L4<L4<T>>>>;
type L64<T> = L16<L16<L16<L16<T>>>>;
type Deep = L64<L64<L64<()>>>; // 192 links
// The field type depends on `Y`, so `field_of!(S<Y>, f): Field` needs `<Y as Chain>::A: Sized`.
struct S<Y: Chain> { f: <Y as Chain>::A }
trait Tr { type Out; }
// Proven at generic `Y`; falsely refuted at `Y = Deep`, so coherence misses the overlap
// (remove the where-clause and both solvers report E0119).
impl<Y: Chain> Tr for S<Y> where field_of!(S<Y>, f): Field { type Out = u8; }
impl Tr for S<Deep> { type Out = [u8; 16]; }
// Typechecked at generic `Y`: `Out = u8`, so the body returns one byte.
fn poly<Y: Chain>() -> <S<Y> as Tr>::Out { 0 }
fn main() {
// Instantiated at `Y = Deep`: `Out = [u8; 16]`, so 15 never-written bytes are read.
let v = poly::<Deep>();
println!("{:?}", v);
}
Meta
Reproducible with 1.100.0-nightly (2026-08-31 0dfb098f3aeecbe38c25)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by compiling the provided bug.rs with and without -Znext-solver using the commands in the issue, then trace the next-solver field projection handling described in consider_builtin_field_candidate. Investigate how recursion-limit ambiguity is treated for field_of!(S, f): Field. Done means the reproducer is rejected or otherwise no longer permits the overlapping implementations to produce unsound output, with a regression test covering the case.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100