Skip to content

Commit

Permalink
Make it work for attributes on modules as well
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood committed Oct 26, 2024
1 parent 7febf04 commit ae8ab30
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ reveal_type(__spec__) # revealed: @Todo
reveal_type(__path__) # revealed: @Todo

# TODO: this should probably be added to typeshed; not sure why it isn't?
reveal_type(__doc__) # revealed: Unbound
# error: [unresolved-reference]
# revealed: Unbound
reveal_type(__doc__)

class X:
reveal_type(__name__) # revealed: str
Expand All @@ -33,9 +35,28 @@ However, three attributes on `types.ModuleType` are not present as implicit
module globals; these are excluded:

```py path=unbound_dunders.py
reveal_type(__getattr__) # revealed: Unbound
reveal_type(__dict__) # revealed: Unbound
reveal_type(__init__) # revealed: Unbound
# error: [unresolved-reference]
# revealed: Unbound
reveal_type(__getattr__)

# error: [unresolved-reference]
# revealed: Unbound
reveal_type(__dict__)

# error: [unresolved-reference]
# revealed: Unbound
reveal_type(__init__)
```

## Accessed as attributes

`ModuleType` attributes can also be accessed as attributes
on module-literal inhabitants:

```py
import typing

reveal_type(typing.__name__) # revealed: str
```

## Conditionally global or `ModuleType` attribute
Expand Down
19 changes: 18 additions & 1 deletion crates/red_knot_python_semantic/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,24 @@ fn symbol_ty<'db>(db: &'db dyn Db, scope: ScopeId<'db>, name: &str) -> Type<'db>

/// Shorthand for `symbol_ty` that looks up a module-global symbol by name in a file.
pub(crate) fn global_symbol_ty<'db>(db: &'db dyn Db, file: File, name: &str) -> Type<'db> {
symbol_ty(db, global_scope(db, file), name)
let explicit_ty = symbol_ty(db, global_scope(db, file), name);

// Not defined explicitly in the global scope?
// All modules are instances of `types.ModuleType`;
// look it up there (with a few very special exceptions)
if explicit_ty.may_be_unbound(db) && !matches!(name, "__dict__" | "__init__" | "__getattr__") {
// TODO: this should be `KnownClass::ModuleType.to_instance()`,
// but we don't yet support looking up attributes on instances
let module_type = KnownClass::ModuleType.to_class(db);
let module_type_member_ty = module_type.member(db, name);
if module_type_member_ty.is_unbound() {
explicit_ty
} else {
explicit_ty.replace_unbound_with(db, module_type_member_ty)
}
} else {
explicit_ty
}
}

/// Infer the type of a binding.
Expand Down
14 changes: 0 additions & 14 deletions crates/red_knot_python_semantic/src/types/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2363,20 +2363,6 @@ impl<'db> TypeInferenceBuilder<'db> {
global_symbol_ty(self.db, self.file, name)
};

// Still possibly unbound? All modules are instances of `types.ModuleType`;
// look it up there (with a few very special exceptions)
if ty.may_be_unbound(self.db)
&& !matches!(&**name, "__dict__" | "__init__" | "__getattr__")
{
// TODO: this should be `KnownClass::ModuleType.to_instance()`,
// but we don't yet support looking up attributes on instances
let module_type = KnownClass::ModuleType.to_class(self.db);
let module_type_member_ty = module_type.member(self.db, name);
if !module_type_member_ty.is_unbound() {
return ty.replace_unbound_with(self.db, module_type_member_ty);
}
}

// Fallback to builtins (without infinite recursion if we're already in builtins.)
if ty.may_be_unbound(self.db) && Some(self.scope()) != builtins_module_scope(self.db) {
let mut builtin_ty = builtins_symbol_ty(self.db, name);
Expand Down

0 comments on commit ae8ab30

Please sign in to comment.