mirror of https://github.com/astral-sh/ruff
[ty] Add support for dict(...) calls in typed dict contexts (#22113)
## Summary fixes https://github.com/astral-sh/ty/issues/2127 - handle `dict(...)` calls in TypedDict context with bidirectional inference - validate keys/values using the existing TypedDict constructor implem - mdtest: add 1 positive test, 1 negative test for invalid coverage ## Test Plan ```sh cargo clippy --workspace --all-targets --all-features -- -D warnings # Rust linting cargo test # Rust testing uvx pre-commit run --all-files --show-diff-on-failure # Rust and Python formatting, Markdown and Python linting, etc. ``` fully green
This commit is contained in:
parent
f9a0e1e3f6
commit
3ec63b964c
|
|
@ -55,10 +55,13 @@ class TD(TypedDict):
|
|||
d1 = {"x": 1}
|
||||
d2: TD = {"x": 1}
|
||||
d3: dict[str, int] = {"x": 1}
|
||||
d4: TD = dict(x=1)
|
||||
d5: TD = dict(x="1") # error: [invalid-argument-type]
|
||||
|
||||
reveal_type(d1) # revealed: dict[Unknown | str, Unknown | int]
|
||||
reveal_type(d2) # revealed: TD
|
||||
reveal_type(d3) # revealed: dict[str, int]
|
||||
reveal_type(d4) # revealed: TD
|
||||
|
||||
def _() -> TD:
|
||||
return {"x": 1}
|
||||
|
|
|
|||
|
|
@ -8448,6 +8448,43 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
|
|||
arguments,
|
||||
} = call_expression;
|
||||
|
||||
// Fast-path dict(...) in TypedDict context: infer keyword values against fields,
|
||||
// then validate and return the TypedDict type.
|
||||
if let Some(tcx) = tcx.annotation
|
||||
&& let Some(typed_dict) = tcx
|
||||
.filter_union(self.db(), Type::is_typed_dict)
|
||||
.as_typed_dict()
|
||||
&& callable_type
|
||||
.as_class_literal()
|
||||
.is_some_and(|class_literal| class_literal.is_known(self.db(), KnownClass::Dict))
|
||||
&& arguments.args.is_empty()
|
||||
&& arguments
|
||||
.keywords
|
||||
.iter()
|
||||
.all(|keyword| keyword.arg.is_some())
|
||||
{
|
||||
let items = typed_dict.items(self.db());
|
||||
for keyword in &arguments.keywords {
|
||||
if let Some(arg_name) = &keyword.arg {
|
||||
let value_tcx = items
|
||||
.get(arg_name.id.as_str())
|
||||
.map(|field| TypeContext::new(Some(field.declared_ty)))
|
||||
.unwrap_or_default();
|
||||
self.infer_expression(&keyword.value, value_tcx);
|
||||
}
|
||||
}
|
||||
|
||||
validate_typed_dict_constructor(
|
||||
&self.context,
|
||||
typed_dict,
|
||||
arguments,
|
||||
func.as_ref().into(),
|
||||
|expr| self.expression_type(expr),
|
||||
);
|
||||
|
||||
return Type::TypedDict(typed_dict);
|
||||
}
|
||||
|
||||
// We don't call `Type::try_call`, because we want to perform type inference on the
|
||||
// arguments after matching them to parameters, but before checking that the argument types
|
||||
// are assignable to any parameter annotations.
|
||||
|
|
|
|||
Loading…
Reference in New Issue