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_idView on GitHub (pinned to 22057b88b0)
Solutions
- 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.
- 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.
- When porting code across rustc versions, re-read the match arms in `LevelSpec::new` — the ForceWarn/Expect rules have shifted historically.
- 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
- Centralize every LevelSpec construction in one builder helper that enforces the (level, lint_id) invariant, instead of calling LevelSpec::new directly.
- Never pair Level::Expect with a missing lint_id; an expectation must name a lint.
- Treat ForceWarn as the only level that ignores lint_id; for all others lint_id participates in the contract.
- Add a unit test matrix over (Level x Option<Id>) so regressions in the panic condition surface in CI, not at runtime.
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
- this deprecation is always in effect; {since:?}
- there must be provenance somewhere here
- statics should not have generic parameters
- layout decided on a larger discriminant type ({min_ity:?}) t
- encountered a non-arbitrary layout during enum layout
AI-assisted analysis of rust-lang/rust@22057b88b0 (2026-08-03).
Data as JSON: /data/errors/92b27b50de088939.json.
Report an issue: GitHub.