Add __doc__

This commit is contained in:
Charlie Marsh 2025-12-13 17:04:13 -05:00
parent acb9e1563e
commit 5e87238f33
3 changed files with 24 additions and 16 deletions

View File

@ -2,14 +2,15 @@
## Class body implicit attributes ## Class body implicit attributes
Python makes certain names available implicitly inside class body scopes. These are `__qualname__` Python makes certain names available implicitly inside class body scopes. These are `__qualname__`,
and `__module__`, as documented at `__module__`, and `__doc__`, as documented at
<https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>. <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
```py ```py
class Foo: class Foo:
reveal_type(__qualname__) # revealed: str reveal_type(__qualname__) # revealed: str
reveal_type(__module__) # revealed: str reveal_type(__module__) # revealed: str
reveal_type(__doc__) # revealed: str | None
``` ```
## `__firstlineno__` (Python 3.13+) ## `__firstlineno__` (Python 3.13+)

View File

@ -1637,8 +1637,8 @@ mod implicit_globals {
/// Looks up the type of an "implicit class body symbol". Returns [`Place::Undefined`] if /// Looks up the type of an "implicit class body symbol". Returns [`Place::Undefined`] if
/// `name` is not present as an implicit symbol in class bodies. /// `name` is not present as an implicit symbol in class bodies.
/// ///
/// Implicit class body symbols are symbols such as `__qualname__`, `__module__`, and /// Implicit class body symbols are symbols such as `__qualname__`, `__module__`, `__doc__`,
/// `__firstlineno__` that Python implicitly makes available inside a class body during /// and `__firstlineno__` that Python implicitly makes available inside a class body during
/// class creation. /// class creation.
/// ///
/// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object> /// See <https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>
@ -1649,6 +1649,13 @@ pub(crate) fn class_body_implicit_symbol<'db>(
match name { match name {
"__qualname__" => Place::bound(KnownClass::Str.to_instance(db)).into(), "__qualname__" => Place::bound(KnownClass::Str.to_instance(db)).into(),
"__module__" => Place::bound(KnownClass::Str.to_instance(db)).into(), "__module__" => Place::bound(KnownClass::Str.to_instance(db)).into(),
// __doc__ is `str` if there's a docstring, `None` if there isn't
"__doc__" => Place::bound(UnionType::from_elements(
db,
[KnownClass::Str.to_instance(db), Type::none(db)],
))
.into(),
// __firstlineno__ was added in Python 3.13
"__firstlineno__" if Program::get(db).python_version(db) >= PythonVersion::PY313 => { "__firstlineno__" if Program::get(db).python_version(db) >= PythonVersion::PY313 => {
Place::bound(KnownClass::Int.to_instance(db)).into() Place::bound(KnownClass::Int.to_instance(db)).into()
} }

View File

@ -9173,18 +9173,18 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// If we're in a class body, check for implicit class body symbols first. // If we're in a class body, check for implicit class body symbols first.
// These take precedence over globals. // These take precedence over globals.
.or_fall_back_to(db, || { .or_fall_back_to(db, || {
if scope.node(db).scope_kind().is_class() { if scope.node(db).scope_kind().is_class()
if let Some(symbol) = place_expr.as_symbol() { && let Some(symbol) = place_expr.as_symbol()
let implicit = class_body_implicit_symbol(db, symbol.name()); {
if implicit.place.is_definitely_bound() { let implicit = class_body_implicit_symbol(db, symbol.name());
return implicit.map_type(|ty| { if implicit.place.is_definitely_bound() {
self.narrow_place_with_applicable_constraints( return implicit.map_type(|ty| {
place_expr, self.narrow_place_with_applicable_constraints(
ty, place_expr,
&constraint_keys, ty,
) &constraint_keys,
}); )
} });
} }
} }
Place::Undefined.into() Place::Undefined.into()