astral-sh/ruff · critical
Expected dictionary argument to be kwarg
Error message
Expected dictionary argument to be kwarg
What it means
The fix for flake8-comprehensions C408 (unnecessary `dict()`/`tuple()` call) rewrites `dict(a=1)` into `{'a': 1}`. It assumes every argument in the call was already verified to be a keyword argument during diagnostic emission; if a positional or starred argument slips into the fix stage, `arg.keyword` is None and this expect panics.
Source
Thrown at crates/ruff_linter/src/rules/flake8_comprehensions/fixes.rs:255
// below.
let mut arena: Vec<String> = vec![];
let quote = checker
.interpolated_string_quote_style()
.unwrap_or(stylist.quote());
// Quote each argument.
//
// Python normalizes identifiers to NFKC, but string literals are not normalized. Emitting the
// raw source text of a keyword argument would change the dictionary key at runtime, so the
// name has to be normalized. See https://github.com/astral-sh/ruff/issues/16234.
for arg in &call.args {
let quoted = format!(
"{}{}{}",
quote,
arg.keyword
.as_ref()
.expect("Expected dictionary argument to be kwarg")
.value
.nfkc(),
quote,
);
arena.push(quoted);
}
let elements = call
.args
.iter()
.enumerate()
.map(|(i, arg)| DictElement::Simple {
key: Expression::SimpleString(Box::new(SimpleString {
value: &arena[i],
lpar: vec![],
rpar: vec![],
})),
value: arg.value.clone(),View on GitHub (pinned to 26f38c119c)
Solutions
- Report the file to Ruff — the fix must not be offered for calls with positional/starred args
- Upgrade Ruff to a version where the C408 fix/diagnostic mismatch is fixed
- Skip the fix for that file (`ruff check --fix --no-cache <file>` avoided; use `# noqa: C408` or exclude the rule)
- Manually rewrite `dict(...)` to a dict literal as a workaround
Example fix
// before
let key = arg.keyword.as_ref().expect("Expected dictionary argument to be kwarg");
// after
let Some(key) = arg.keyword.as_ref() else { return Err(anyhow!("positional arg in dict() fix")) }; Defensive patterns
Strategy: try-catch
Validate before calling
# avoid triggering the C408 fix on calls with positional/starred args
import re
if re.search(r'\bdict\((\*|[^)=,]+\s*,\s*)', src):
print('dict() call has positional/starred args; fix may crash — rewrite manually') Type guard
def is_safe_c408_target(call_src: str) -> bool:
args = call_src[call_src.index('(')+1:call_src.rindex(')')].strip()
return bool(args) and '*' not in args and all('=' in a for a in args.split(',')) Prevention
- Write dict literals directly instead of dict(k=v)
- Run `ruff check --fix` with --diff first to preview unsafe fixes
- Keep Ruff updated so fix/diagnostic mismatches are patched
When it happens
Trigger: Applying an unsafe fix for C408 (`ruff check --fix`) on a `dict(...)` call whose args include something without a keyword — e.g. `dict(**kwargs)`-style or a diagnostic emitted without re-checking positional/starred args (an internal fix-diagnostic mismatch).
Common situations: Users running `ruff --fix` on code like `dict(x, a=1)` or `dict(*args)` where detection and fix disagree, producing a crash during fixing.
Related errors
- Arguments should be non-empty
- `parsed_module` should have assigned a node index
- extra use-def data should have been retained
- should be set because `extract_if` only yields elements with
- Should only ever pass a positive integer to `from_nonnegativ
AI-assisted analysis of astral-sh/ruff@26f38c119c (2026-09-05).
Data as JSON: /api/errors/7049c5baaeeffab4.
Report an issue: GitHub.