diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index e8c64705a7747..ff0b5dce9af59 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -2529,12 +2529,22 @@ Movie2 = TypedDict("Movie2", name=str, year=int) ```py from typing_extensions import TypedDict -# error: [invalid-argument-type] "Invalid argument to parameter `typename` of `TypedDict()`" +# error: [invalid-argument-type] "TypedDict name must match the variable it is assigned to: Expected "Bad1", got variable of type `Literal[123]`" Bad1 = TypedDict(123, {"name": str}) -# error: [invalid-argument-type] "The name of a `TypedDict` (`WrongName`) must match the name of the variable it is assigned to (`BadTypedDict3`)" +# error: [invalid-argument-type] "TypedDict name must match the variable it is assigned to: Expected "BadTypedDict3", got "WrongName"" BadTypedDict3 = TypedDict("WrongName", {"name": str}) +def f(x: str) -> None: + # error: [invalid-argument-type] "TypedDict name must match the variable it is assigned to: Expected "Y", got variable of type `str`" + Y = TypedDict(x, {}) + +def g(x: str) -> None: + TypedDict(x, {}) # fine + +name = "GoodTypedDict" +GoodTypedDict = TypedDict(name, {"name": str}) + # error: [invalid-argument-type] "Expected a dict literal for parameter `fields` of `TypedDict()`" Bad2 = TypedDict("Bad2", "not a dict") diff --git a/crates/ty_python_semantic/src/types/infer/builder/typed_dict.rs b/crates/ty_python_semantic/src/types/infer/builder/typed_dict.rs index 2928633a22b35..21a261ccc1da5 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/typed_dict.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/typed_dict.rs @@ -158,34 +158,40 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ); } - let name = if let Some(literal) = name_type.as_string_literal() { - let name = literal.value(db); - - if let Some(assigned_name) = definition.and_then(|definition| definition.name(db)) - && name != assigned_name - && let Some(builder) = self.context.report_lint(&INVALID_ARGUMENT_TYPE, name_arg) - { - builder.into_diagnostic(format_args!( - "The name of a `TypedDict` (`{name}`) must match \ - the name of the variable it is assigned to (`{assigned_name}`)" - )); - } - - Name::new(name) - } else { - if !name_type.is_assignable_to(db, KnownClass::Str.to_instance(db)) - && let Some(builder) = self.context.report_lint(&INVALID_ARGUMENT_TYPE, name_arg) - { - let mut diagnostic = builder.into_diagnostic(format_args!( - "Invalid argument to parameter `typename` of `TypedDict()`" + let name = name_type + .as_string_literal() + .map(|literal| Name::new(literal.value(db))); + + if let Some(definition) = definition + && let Some(assigned_name) = definition.name(db) + && Some(assigned_name.as_str()) != name.as_deref() + && let Some(builder) = self.context.report_lint(&INVALID_ARGUMENT_TYPE, name_arg) + { + let mut diagnostic = + builder.into_diagnostic("TypedDict name must match the variable it is assigned to"); + if let Some(name) = name.as_deref() { + diagnostic.set_primary_message(format_args!( + "Expected \"{assigned_name}\", got \"{name}\"" )); + } else { diagnostic.set_primary_message(format_args!( - "Expected `str`, found `{}`", + "Expected \"{assigned_name}\", got variable of type `{}`", name_type.display(db) )); } - Name::new_static("") - }; + } else if !name_type.is_assignable_to(db, KnownClass::Str.to_instance(db)) + && let Some(builder) = self.context.report_lint(&INVALID_ARGUMENT_TYPE, name_arg) + { + let mut diagnostic = builder.into_diagnostic(format_args!( + "Invalid argument to parameter `typename` of `TypedDict()`" + )); + diagnostic.set_primary_message(format_args!( + "Expected `str`, found `{}`", + name_type.display(db) + )); + } + + let name = name.unwrap_or_else(|| Name::new_static("")); if let Some(definition) = definition { self.deferred.insert(definition);