rust-lang/rust · error

invalid level/lint_id combination

Error message

invalid level/lint_id combination

What it means

This panic is raised inside `LevelSpec::new` (rustc's lint-level machinery) when the supplied `Level` and `Option<lint_id>` pair is not one of the combinations the compiler treats as well-formed. The valid combinations are encoded directly in the match at lint.rs:83-88: `Allow`/`Warn`/`Deny`/`Forbid` require `lint_id == None`, `Expect` requires `lint_id == Some(_)`, and `ForceWarn` accepts either. Hitting this means rustc-internal (or plugin/lint-pass) code constructed a `LevelSpec` that violates that invariant; it is an internal compiler error, not user source-level lint configuration.

Source

Thrown at compiler/rustc_middle/src/lint.rs:87

    level: Level,

    // This field *must* be private. See the comment on `level`.
    lint_id: Option<Id>,

    pub src: LintLevelSource,
}

pub type UnstableLevelSpec = LevelSpec<UnstableLintExpectationId>;
pub type StableLevelSpec = LevelSpec<StableLintExpectationId>;

impl<Id: Copy> LevelSpec<Id> {
    // Panics if an invalid `level`/`lint_id` combination is given.
    pub fn new(level: Level, lint_id: Option<Id>, src: LintLevelSource) -> LevelSpec<Id> {
        match (level, lint_id) {
            (Level::Allow | Level::Warn | Level::Deny | Level::Forbid, None) => {}
            (Level::Expect, Some(_)) => {}
            (Level::ForceWarn, _) => {}
            _ => panic!("invalid level/lint_id combination"),
        }
        LevelSpec { level, lint_id, src }
    }

    pub fn level(self) -> Level {
        self.level
    }

    pub fn is_allow(self) -> bool {
        self.level == Level::Allow
    }

    pub fn is_expect(self) -> bool {
        self.level == Level::Expect
    }

    pub fn lint_id(self) -> Option<Id> {
        self.lint_id

View on GitHub (pinned to 22057b88b0)

Solutions

  1. Match the level/id pair to the contract in lint.rs:84-86: use `None` for Allow/Warn/Deny/Forbid and `Some(expectation_id)` for Expect.
  2. If you hold an externally-sourced level/id pair, validate it before calling `LevelSpec::new` and map invalid pairs to a default rather than panicking.
  3. When porting code across rustc versions, re-read the match arms in `LevelSpec::new` — the ForceWarn/Expect rules have shifted historically.
  4. Run with `RUST_BACKTRACE=1` to identify which caller constructed the bad spec.

Example fix

// before
let spec = LevelSpec::new(Level::Expect, None, src);

// after
let spec = LevelSpec::new(Level::Expect, Some(expectation_id), src);
Defensive patterns

Strategy: validation

Validate before calling

// rustc_middle::lint::LevelSpec::new panics on a bad (level, lint_id) pair.
// Rule: Allow/Warn/Deny/Forbid => lint_id must be None;
//       Expect                          => lint_id must be Some(_);
//       ForceWarn                        => either.
fn valid_level_lint_id_combo<Id>(level: Level, lint_id: Option<Id>) -> bool {
    matches!(
        (level, lint_id),
        (Level::Allow | Level::Warn | Level::Deny | Level::Forbid, None)
            | (Level::Expect, Some(_))
            | (Level::ForceWarn, _)
    )
}

// At the call site:
if valid_level_lint_id_combo(level, lint_id) {
    let spec = LevelSpec::new(level, lint_id, src);
} else {
    return Err("invalid level/lint_id combination");
}

Try / catch

// Panics are not Result-based; the only runtime catch is catch_unwind.
let spec = std::panic::catch_unwind(|| LevelSpec::new(level, lint_id, src));
match spec {
    Ok(s) => /* use s */,
    Err(_) => /* log + reject this lint registration */,
}

Prevention

When it happens

Trigger: Calling `LevelSpec::new(Level::Allow, Some(id), src)` (or any of Warn/Deny/Forbid with a Some id), `LevelSpec::new(Level::Expect, None, src)`, or constructing a `UnstableLevelSpec`/`StableLevelSpec` whose level/id pair is not in the allowed match arms. Typically introduced while wiring up a new lint expectation id, porting lint-level data across query boundaries, or by a tool that hand-builds `LevelSpec` values.

Common situations: Developing a rustc lint pass or clippy lint that emits expectations; refactoring the `LintLevelSource` plumbing; mismatches after a rustc upgrade where the `Level::Expect` semantics changed; buggy deserialization of lint level configs from rmeta/crater artifacts.

Related errors


AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03). Data as JSON: /data/errors/92b27b50de088939.json. Report an issue: GitHub.