[ty] Add support for PEP 750 t-strings (#20085)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

This PR attempts to adds support for inferring`string.templatelib.Template` for t-string literals.
This commit is contained in:
Dylan 2025-08-25 13:49:49 -05:00 committed by GitHub
parent ecf3c4ca11
commit ef4897f9f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 61 additions and 10 deletions

View file

@ -0,0 +1,20 @@
# Template strings
(NB: `black` does not support Python 3.14 at the time of this writing)
<!-- blacken-docs:off -->
```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
```

View file

@ -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",
}
}

View file

@ -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

View file

@ -5744,7 +5744,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
}
}
}
todo_type!("Template")
KnownClass::Template.to_instance(self.db())
}
fn infer_ellipsis_literal_expression(