From acb9e1563e177c78513433be9b6906e9ff4e0b33 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Sat, 13 Dec 2025 16:14:40 -0500 Subject: [PATCH] Add Python 3.13 guard --- .../mdtest/scopes/class_implicit_attrs.md | 51 +++++++++++++++++-- crates/ty_python_semantic/src/place.rs | 13 ++--- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/scopes/class_implicit_attrs.md b/crates/ty_python_semantic/resources/mdtest/scopes/class_implicit_attrs.md index 009b0a0a96..cf6ff80d71 100644 --- a/crates/ty_python_semantic/resources/mdtest/scopes/class_implicit_attrs.md +++ b/crates/ty_python_semantic/resources/mdtest/scopes/class_implicit_attrs.md @@ -2,17 +2,45 @@ ## Class body implicit attributes -Python makes certain names available implicitly inside class body scopes. These are `__qualname__`, -`__module__`, and `__firstlineno__`, as documented at +Python makes certain names available implicitly inside class body scopes. These are `__qualname__` +and `__module__`, as documented at . ```py class Foo: reveal_type(__qualname__) # revealed: str reveal_type(__module__) # revealed: str +``` + +## `__firstlineno__` (Python 3.13+) + +Python 3.13 added `__firstlineno__` to the class body namespace. + +### Available in Python 3.13+ + +```toml +[environment] +python-version = "3.13" +``` + +```py +class Foo: reveal_type(__firstlineno__) # revealed: int ``` +### Not available in Python 3.12 and earlier + +```toml +[environment] +python-version = "3.12" +``` + +```py +class Foo: + # error: [unresolved-reference] + __firstlineno__ +``` + ## Nested classes These implicit attributes are also available in nested classes, and refer to the nested class: @@ -32,17 +60,32 @@ within the class body: ```py __qualname__ = 42 __module__ = 42 -__firstlineno__ = "not an int" class Foo: # Inside the class body, these are the implicit class attributes reveal_type(__qualname__) # revealed: str reveal_type(__module__) # revealed: str - reveal_type(__firstlineno__) # revealed: int # Outside the class, the globals are visible reveal_type(__qualname__) # revealed: Literal[42] reveal_type(__module__) # revealed: Literal[42] +``` + +## `__firstlineno__` has priority over globals (Python 3.13+) + +The same applies to `__firstlineno__` on Python 3.13+: + +```toml +[environment] +python-version = "3.13" +``` + +```py +__firstlineno__ = "not an int" + +class Foo: + reveal_type(__firstlineno__) # revealed: int + reveal_type(__firstlineno__) # revealed: Literal["not an int"] ``` diff --git a/crates/ty_python_semantic/src/place.rs b/crates/ty_python_semantic/src/place.rs index cf3fe5660b..45bce10c18 100644 --- a/crates/ty_python_semantic/src/place.rs +++ b/crates/ty_python_semantic/src/place.rs @@ -1,4 +1,5 @@ use ruff_db::files::File; +use ruff_python_ast::PythonVersion; use crate::dunder_all::dunder_all_names; use crate::module_resolver::{KnownModule, file_to_module, resolve_module_confident}; @@ -1646,17 +1647,11 @@ pub(crate) fn class_body_implicit_symbol<'db>( name: &str, ) -> PlaceAndQualifiers<'db> { match name { - // __qualname__ is the fully-qualified name of the class "__qualname__" => Place::bound(KnownClass::Str.to_instance(db)).into(), - - // __module__ is the module name where the class is defined "__module__" => Place::bound(KnownClass::Str.to_instance(db)).into(), - - // __firstlineno__ was added in Python 3.13, but we don't version-check since - // it's always available in the class body namespace (just the __firstlineno__ - // attribute on the class itself may not be present) - "__firstlineno__" => Place::bound(KnownClass::Int.to_instance(db)).into(), - + "__firstlineno__" if Program::get(db).python_version(db) >= PythonVersion::PY313 => { + Place::bound(KnownClass::Int.to_instance(db)).into() + } _ => Place::Undefined.into(), } }