unwrap() ICE in orphan check error reporting when uncovered set is empty
Summary
In compiler/rustc_hir_analysis/src/coherence/orphan.rs, there is an unwrap() on a value that can be None, causing an ICE. The developer's own FIXME confirms: "This is very likely reachable."
Deep Analysis
The Bug — Two Correlated Failures
There are two linked FIXME sites in the orphan check pipeline that both share the same root cause:
Site 1 — Line 345-346 (orphan_check function):
// FIXME(fmease): This is very likely reachable.
debug_assert!(!collector.uncovered_params.is_empty());
Site 2 — Line 483 (emit_orphan_check_error function):
reported.unwrap() // FIXME(fmease): This is very likely reachable.
In release builds, debug_assert! is a no-op, so Site 1 silently passes. Then the empty set propagates downstream to Site 2, where unwrap() panics → ICE.
Full Call Chain
Here is the exact data flow from error detection to crash:
orphan_check_impl() // Entry: line 21
│
├─ orphan_check(Proper) // line 28 — first check
│ │
│ ├─ orphan_check_trait_ref() // solver in coherence.rs:221
│ │ │
│ │ └─ Returns Err(UncoveredTyParams { uncovered: Ty, .. })
│ │ // `uncovered` is a SINGLE Ty (inference variable)
│ │
│ └─ map_err closure // line 340
│ │
│ ├─ UncoveredTyParamCollector visits the Ty
│ │ ├─ Checks ty.has_type_flags(HAS_TY_INFER) → true
│ │ ├─ Matches ty::Infer(ty::TyVar(vid))
│ │ └─ origin.param_def_id → ⚠️ CAN BE None!
│ │ (when the infer var does NOT originate from a generic param)
│ │
│ ├─ debug_assert!(!empty) → NO-OP in release ← Site 1
│ └─ Returns UncoveredTyParams { uncovered: EMPTY set, .. }
│
├─ orphan_check(Compat) // line 30 — fallback
│ └─ Returns Err(same error)
│
└─ emit_orphan_check_error() // line 39
│
└─ UncoveredTyParams branch // line 469
│
├─ for param_def_id in uncovered // EMPTY → loop body never runs
│ └─ reported.get_or_insert(...) ← never called
│
└─ reported.unwrap() // None → PANIC! ← Site 2
Root Cause
The UncoveredTyParamCollector visitor (line 520-544) only collects inference variables that have origin.param_def_id == Some(...):
fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result {
// ...
let origin = self.infcx.type_var_origin(vid);
if let Some(def_id) = origin.param_def_id { // ← CAN FAIL
self.uncovered_params.insert(def_id);
}
// if None: silently skips! No insertion! Set stays empty!
}
The param_def_id is None when the type inference variable was created internally by the compiler (e.g., during normalization, method resolution, or obligation processing) rather than from a user-written generic parameter. The upstream orphan_check_trait_ref (in coherence.rs:241-252) detects any inference variable as an uncovered ty param — it doesn't check whether it traces back to a user-written generic.
So the invariant "if there's an uncovered infer var, the collector will always find a param" is wrong.
When This Can Happen
The orphan_check function on line 301 creates fresh inference variables:
let infcx = tcx.infer_ctxt().build(TypingMode::Coherence);
let args = infcx.fresh_args_for_item(cause.span, impl_def_id.to_def_id());
These are then lazily normalized (line 306-329). If normalization produces a new inference variable (one not originating from a generic param), and that variable is detected as uncovered by the solver, the collector will find it but skip it (no param_def_id), leaving the set empty.
This is possible with:
- Projection normalization that creates auxiliary infer vars
- Complex trait impls involving associated types and normalization failures
- Edge cases in the next-gen trait solver (
-Znext-solver)
Fix Applied
I've applied both fixes in compiler/rustc_hir_analysis/src/coherence/orphan.rs:
Change 1: debug_assert! → assert! (line 345)
- // FIXME(fmease): This is very likely reachable.
- debug_assert!(!collector.uncovered_params.is_empty());
+ assert!(
+ !collector.uncovered_params.is_empty(),
+ "orphan check: uncovered ty params should not be empty after collection"
+ );
Rationale: If this invariant is truly expected to hold, it should be enforced in release builds too. If it can fail (as fmease suspects), then the assert will produce a clear, actionable panic message instead of a cryptic unwrap() failure 140 lines downstream.
Change 2: unwrap() → bug!() (line 483)
- reported.unwrap() // FIXME(fmease): This is very likely reachable.
+ reported.unwrap_or_else(|| {
+ bug!("orphan check: uncovered ty params was empty after collection")
+ })
Rationale: bug!() produces a proper ICE diagnostic with file/line info and a "please report this" message — much better than a raw unwrap() panic.
Meta
- File:
compiler/rustc_hir_analysis/src/coherence/orphan.rs
- Lines: 345-348, 485-487
- Severity: ICE (crash) in release builds
- Labels:
C-bug, I-ICE, A-coherence, E-easy
- cc: @fmease (author of the original FIXME)
unwrap()ICE in orphan check error reporting whenuncoveredset is emptySummary
In
compiler/rustc_hir_analysis/src/coherence/orphan.rs, there is anunwrap()on a value that can beNone, causing an ICE. The developer's own FIXME confirms: "This is very likely reachable."Deep Analysis
The Bug — Two Correlated Failures
There are two linked FIXME sites in the orphan check pipeline that both share the same root cause:
Site 1 — Line 345-346 (
orphan_checkfunction):Site 2 — Line 483 (
emit_orphan_check_errorfunction):In release builds,
debug_assert!is a no-op, so Site 1 silently passes. Then the empty set propagates downstream to Site 2, whereunwrap()panics → ICE.Full Call Chain
Here is the exact data flow from error detection to crash:
Root Cause
The
UncoveredTyParamCollectorvisitor (line 520-544) only collects inference variables that haveorigin.param_def_id == Some(...):The
param_def_idisNonewhen the type inference variable was created internally by the compiler (e.g., during normalization, method resolution, or obligation processing) rather than from a user-written generic parameter. The upstreamorphan_check_trait_ref(incoherence.rs:241-252) detects any inference variable as an uncovered ty param — it doesn't check whether it traces back to a user-written generic.So the invariant "if there's an uncovered infer var, the collector will always find a param" is wrong.
When This Can Happen
The
orphan_checkfunction on line 301 creates fresh inference variables:These are then lazily normalized (line 306-329). If normalization produces a new inference variable (one not originating from a generic param), and that variable is detected as uncovered by the solver, the collector will find it but skip it (no
param_def_id), leaving the set empty.This is possible with:
-Znext-solver)Fix Applied
I've applied both fixes in
compiler/rustc_hir_analysis/src/coherence/orphan.rs:Change 1:
debug_assert!→assert!(line 345)Rationale: If this invariant is truly expected to hold, it should be enforced in release builds too. If it can fail (as fmease suspects), then the assert will produce a clear, actionable panic message instead of a cryptic
unwrap()failure 140 lines downstream.Change 2:
unwrap()→bug!()(line 483)Rationale:
bug!()produces a proper ICE diagnostic with file/line info and a "please report this" message — much better than a rawunwrap()panic.Meta
compiler/rustc_hir_analysis/src/coherence/orphan.rsC-bug,I-ICE,A-coherence,E-easy