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
Python makes certain names available implicitly inside class body scopes. These are `__qualname__`
and `__module__`, as documented at
Python makes certain names available implicitly inside class body scopes. These are `__qualname__`,
`__module__`, and `__doc__`, as documented at
<https://docs.python.org/3/reference/datamodel.html#creating-the-class-object>.
```py
class Foo:
reveal_type(__qualname__) # revealed: str
reveal_type(__module__) # revealed: str
reveal_type(__doc__) # revealed: str | None
```
## `__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
/// `name` is not present as an implicit symbol in class bodies.
///
/// Implicit class body symbols are symbols such as `__qualname__`, `__module__`, and
/// `__firstlineno__` that Python implicitly makes available inside a class body during
/// Implicit class body symbols are symbols such as `__qualname__`, `__module__`, `__doc__`,
/// and `__firstlineno__` that Python implicitly makes available inside a class body during
/// class creation.
///
/// 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 {
"__qualname__" => 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 => {
Place::bound(KnownClass::Int.to_instance(db)).into()
}

View File

@ -9173,8 +9173,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
// If we're in a class body, check for implicit class body symbols first.
// These take precedence over globals.
.or_fall_back_to(db, || {
if scope.node(db).scope_kind().is_class() {
if let Some(symbol) = place_expr.as_symbol() {
if scope.node(db).scope_kind().is_class()
&& let Some(symbol) = place_expr.as_symbol()
{
let implicit = class_body_implicit_symbol(db, symbol.name());
if implicit.place.is_definitely_bound() {
return implicit.map_type(|ty| {
@ -9186,7 +9187,6 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> {
});
}
}
}
Place::Undefined.into()
})
// No nonlocal binding? Check the module's explicit globals.