diff --git a/crates/ty_python_semantic/resources/mdtest/t_strings.md b/crates/ty_python_semantic/resources/mdtest/t_strings.md new file mode 100644 index 0000000000..97ebeed51a --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/t_strings.md @@ -0,0 +1,20 @@ +# Template strings + +(NB: `black` does not support Python 3.14 at the time of this writing) + + + +```toml +[environment] +python-version = "3.14" +``` + +Template strings, or t-strings, were added in Python 3.14. + +They may be specified as literals and are objects of type `string.templatelib.Template`. + +## Empty template string + +```py +reveal_type(t"") # revealed: Template +``` diff --git a/crates/ty_python_semantic/src/module_resolver/module.rs b/crates/ty_python_semantic/src/module_resolver/module.rs index a7d50829b0..3586f52933 100644 --- a/crates/ty_python_semantic/src/module_resolver/module.rs +++ b/crates/ty_python_semantic/src/module_resolver/module.rs @@ -270,6 +270,8 @@ pub enum KnownModule { Dataclasses, Collections, Inspect, + #[strum(serialize = "string.templatelib")] + Templatelib, #[strum(serialize = "_typeshed._type_checker_internals")] TypeCheckerInternals, TyExtensions, @@ -305,6 +307,7 @@ impl KnownModule { Self::UnittestMock => "unittest.mock", #[cfg(test)] Self::Uuid => "uuid", + Self::Templatelib => "string.templatelib", } } diff --git a/crates/ty_python_semantic/src/types/class.rs b/crates/ty_python_semantic/src/types/class.rs index ef706b18f9..d29a0afdee 100644 --- a/crates/ty_python_semantic/src/types/class.rs +++ b/crates/ty_python_semantic/src/types/class.rs @@ -3540,6 +3540,8 @@ pub enum KnownClass { NamedTupleFallback, NamedTupleLike, TypedDictFallback, + // string.templatelib + Template, } impl KnownClass { @@ -3582,7 +3584,8 @@ impl KnownClass { | Self::GeneratorType | Self::AsyncGeneratorType | Self::MethodWrapperType - | Self::CoroutineType => Some(Truthiness::AlwaysTrue), + | Self::CoroutineType + | Self::Template => Some(Truthiness::AlwaysTrue), Self::NoneType => Some(Truthiness::AlwaysFalse), @@ -3716,7 +3719,8 @@ impl KnownClass { | KnownClass::InitVar | KnownClass::NamedTupleFallback | KnownClass::NamedTupleLike - | KnownClass::TypedDictFallback => false, + | KnownClass::TypedDictFallback + | KnownClass::Template => false, } } @@ -3792,7 +3796,8 @@ impl KnownClass { | KnownClass::InitVar | KnownClass::NamedTupleFallback | KnownClass::NamedTupleLike - | KnownClass::TypedDictFallback => false, + | KnownClass::TypedDictFallback + | KnownClass::Template => false, } } @@ -3867,7 +3872,8 @@ impl KnownClass { | KnownClass::InitVar | KnownClass::TypedDictFallback | KnownClass::NamedTupleLike - | KnownClass::NamedTupleFallback => false, + | KnownClass::NamedTupleFallback + | KnownClass::Template => false, } } @@ -3955,7 +3961,8 @@ impl KnownClass { | Self::KwOnly | Self::InitVar | Self::NamedTupleFallback - | Self::TypedDictFallback => false, + | Self::TypedDictFallback + | Self::Template => false, } } @@ -4051,6 +4058,7 @@ impl KnownClass { Self::NamedTupleFallback => "NamedTupleFallback", Self::NamedTupleLike => "NamedTupleLike", Self::TypedDictFallback => "TypedDictFallback", + Self::Template => "Template", } } @@ -4315,6 +4323,7 @@ impl KnownClass { Self::Field | Self::KwOnly | Self::InitVar => KnownModule::Dataclasses, Self::NamedTupleFallback | Self::TypedDictFallback => KnownModule::TypeCheckerInternals, Self::NamedTupleLike => KnownModule::TyExtensions, + Self::Template => KnownModule::Templatelib, } } @@ -4392,7 +4401,8 @@ impl KnownClass { | Self::Iterator | Self::NamedTupleFallback | Self::NamedTupleLike - | Self::TypedDictFallback => Some(false), + | Self::TypedDictFallback + | Self::Template => Some(false), Self::Tuple => None, } @@ -4473,7 +4483,8 @@ impl KnownClass { | Self::Iterator | Self::NamedTupleFallback | Self::NamedTupleLike - | Self::TypedDictFallback => false, + | Self::TypedDictFallback + | Self::Template => false, } } @@ -4563,6 +4574,7 @@ impl KnownClass { "NamedTupleFallback" => Self::NamedTupleFallback, "NamedTupleLike" => Self::NamedTupleLike, "TypedDictFallback" => Self::TypedDictFallback, + "Template" => Self::Template, _ => return None, }; @@ -4629,7 +4641,8 @@ impl KnownClass { | Self::TypedDictFallback | Self::NamedTupleLike | Self::Awaitable - | Self::Generator => module == self.canonical_module(db), + | Self::Generator + | Self::Template => module == self.canonical_module(db), Self::NoneType => matches!(module, KnownModule::Typeshed | KnownModule::Types), Self::SpecialForm | Self::TypeVar @@ -5095,7 +5108,13 @@ mod tests { #[test] fn known_class_roundtrip_from_str() { - let db = setup_db(); + let mut db = setup_db(); + Program::get(&db) + .set_python_version_with_source(&mut db) + .to(PythonVersionWithSource { + version: PythonVersion::latest_preview(), + source: PythonVersionSource::default(), + }); for class in KnownClass::iter() { let class_name = class.name(&db); let class_module = resolve_module(&db, &class.canonical_module(&db).name()).unwrap(); @@ -5124,6 +5143,14 @@ mod tests { }); for class in KnownClass::iter() { + // Until the latest supported version is bumped to Python 3.14 + // we need to skip template strings here. + // The assertion below should remind the developer to + // remove this exception once we _do_ bump `latest_ty` + assert_ne!(PythonVersion::latest_ty(), PythonVersion::PY314); + if matches!(class, KnownClass::Template) { + continue; + } assert_ne!( class.to_instance(&db), Type::unknown(), @@ -5143,6 +5170,7 @@ mod tests { let mut classes: Vec<(KnownClass, PythonVersion)> = KnownClass::iter() .map(|class| { let version_added = match class { + KnownClass::Template => PythonVersion::PY314, KnownClass::UnionType => PythonVersion::PY310, KnownClass::BaseExceptionGroup | KnownClass::ExceptionGroup => { PythonVersion::PY311 diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 97392af811..abb032644e 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -5744,7 +5744,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } } } - todo_type!("Template") + KnownClass::Template.to_instance(self.db()) } fn infer_ellipsis_literal_expression(