From 5d3ff98f300a771c376b5d98664e6922066a1d1b Mon Sep 17 00:00:00 2001 From: Carl Meyer Date: Fri, 31 Oct 2025 11:46:09 -0400 Subject: [PATCH] [ty] rollback preferring declared type on invalid TypedDict creation --- .../resources/mdtest/typed_dict.md | 9 ++++++--- .../ty_python_semantic/src/types/infer/builder.rs | 10 ++++++---- crates/ty_python_semantic/src/types/typed_dict.rs | 15 +++++++++++---- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index 8be6de4ef3f27..14142020a2385 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -99,7 +99,8 @@ eve1a: Person = {"name": b"Eve", "age": None} # error: [invalid-argument-type] "Invalid argument to key "name" with declared type `str` on TypedDict `Person`" eve1b = Person(name=b"Eve", age=None) -reveal_type(eve1a) # revealed: Person +# TODO should reveal Person (should be fixed by implementing assignability for TypedDicts) +reveal_type(eve1a) # revealed: dict[Unknown | str, Unknown | bytes | None] reveal_type(eve1b) # revealed: Person # error: [missing-typed-dict-key] "Missing required key 'name' in TypedDict `Person` constructor" @@ -107,7 +108,8 @@ eve2a: Person = {"age": 22} # error: [missing-typed-dict-key] "Missing required key 'name' in TypedDict `Person` constructor" eve2b = Person(age=22) -reveal_type(eve2a) # revealed: Person +# TODO should reveal Person (should be fixed by implementing assignability for TypedDicts) +reveal_type(eve2a) # revealed: dict[Unknown | str, Unknown | int] reveal_type(eve2b) # revealed: Person # error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra"" @@ -115,7 +117,8 @@ eve3a: Person = {"name": "Eve", "age": 25, "extra": True} # error: [invalid-key] "Invalid key for TypedDict `Person`: Unknown key "extra"" eve3b = Person(name="Eve", age=25, extra=True) -reveal_type(eve3a) # revealed: Person +# TODO should reveal Person (should be fixed by implementing assignability for TypedDicts) +reveal_type(eve3a) # revealed: dict[Unknown | str, Unknown | str | int] reveal_type(eve3b) # revealed: Person ``` diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index ea3f739f222cb..edf8581bcd184 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -6103,9 +6103,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { && let Some(typed_dict) = tcx .filter_union(self.db(), Type::is_typed_dict) .as_typed_dict() + && let Some(ty) = self.infer_typed_dict_expression(dict, typed_dict) { - self.infer_typed_dict_expression(dict, typed_dict); - return Type::TypedDict(typed_dict); + return ty; } // Avoid false positives for the functional `TypedDict` form, which is currently @@ -6130,7 +6130,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { &mut self, dict: &ast::ExprDict, typed_dict: TypedDictType<'db>, - ) { + ) -> Option> { let ast::ExprDict { range: _, node_index: _, @@ -6153,7 +6153,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { validate_typed_dict_dict_literal(&self.context, typed_dict, dict, dict.into(), |expr| { self.expression_type(expr) - }); + }) + .ok() + .map(|_| Type::TypedDict(typed_dict)) } // Infer the type of a collection literal expression. diff --git a/crates/ty_python_semantic/src/types/typed_dict.rs b/crates/ty_python_semantic/src/types/typed_dict.rs index 632d2a29332db..e29b836d8a8fd 100644 --- a/crates/ty_python_semantic/src/types/typed_dict.rs +++ b/crates/ty_python_semantic/src/types/typed_dict.rs @@ -389,7 +389,7 @@ fn validate_from_keywords<'db, 'ast>( provided_keys } -/// Validates a `TypedDict` dictionary literal assignment, emitting any needed diagnostics. +/// Validates a `TypedDict` dictionary literal assignment, /// e.g. `person: Person = {"name": "Alice", "age": 30}` pub(super) fn validate_typed_dict_dict_literal<'db>( context: &InferContext<'db, '_>, @@ -397,7 +397,8 @@ pub(super) fn validate_typed_dict_dict_literal<'db>( dict_expr: &ast::ExprDict, error_node: AnyNodeRef, expression_type_fn: impl Fn(&ast::Expr) -> Type<'db>, -) { +) -> Result, OrderSet<&'db str>> { + let mut valid = true; let mut provided_keys = OrderSet::new(); // Validate each key-value pair in the dictionary literal @@ -410,7 +411,7 @@ pub(super) fn validate_typed_dict_dict_literal<'db>( let value_type = expression_type_fn(&item.value); - validate_typed_dict_key_assignment( + valid &= validate_typed_dict_key_assignment( context, typed_dict, key_str, @@ -423,5 +424,11 @@ pub(super) fn validate_typed_dict_dict_literal<'db>( } } - validate_typed_dict_required_keys(context, typed_dict, &provided_keys, error_node); + valid &= validate_typed_dict_required_keys(context, typed_dict, &provided_keys, error_node); + + if valid { + Ok(provided_keys) + } else { + Err(provided_keys) + } }