Skip to content

ICE: unwrap() on None in orphan check when UncoveredTyParamCollector finds no param_def_id #155102

@Md-Mushfiqur-Rahim123

Description

@Md-Mushfiqur-Rahim123

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)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions