[red-knot] Reduce TODOs in Type::member() (#15066)

This commit is contained in:
Alex Waygood 2024-12-19 15:54:01 +00:00 committed by GitHub
parent c1eaf6ff72
commit bb43085939
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 110 additions and 20 deletions

View file

@ -1,4 +1,6 @@
# Class attributes # Attributes
Tests for attribute access on various kinds of types.
## Union of attributes ## Union of attributes
@ -155,3 +157,67 @@ class Foo: ...
reveal_type(Foo.__class__) # revealed: Literal[type] reveal_type(Foo.__class__) # revealed: Literal[type]
``` ```
## Function-literal attributes
Most attribute accesses on function-literal types are delegated to `types.FunctionType`, since all
functions are instances of that class:
```py path=a.py
def f(): ...
reveal_type(f.__defaults__) # revealed: @Todo(instance attributes)
reveal_type(f.__kwdefaults__) # revealed: @Todo(instance attributes)
```
Some attributes are special-cased, however:
```py path=b.py
def f(): ...
reveal_type(f.__get__) # revealed: @Todo(`__get__` method on functions)
reveal_type(f.__call__) # revealed: @Todo(`__call__` method on functions)
```
## Int-literal attributes
Most attribute accesses on int-literal types are delegated to `builtins.int`, since all literal
integers are instances of that class:
```py path=a.py
reveal_type((2).bit_length) # revealed: @Todo(instance attributes)
reveal_type((2).denominator) # revealed: @Todo(instance attributes)
```
Some attributes are special-cased, however:
```py path=b.py
reveal_type((2).numerator) # revealed: Literal[2]
reveal_type((2).real) # revealed: Literal[2]
```
## Literal `bool` attributes
Most attribute accesses on bool-literal types are delegated to `builtins.bool`, since all literal
bols are instances of that class:
```py path=a.py
reveal_type(True.__and__) # revealed: @Todo(instance attributes)
reveal_type(False.__or__) # revealed: @Todo(instance attributes)
```
Some attributes are special-cased, however:
```py path=b.py
reveal_type(True.numerator) # revealed: Literal[1]
reveal_type(False.real) # revealed: Literal[0]
```
## Bytes-literal attributes
All attribute access on literal `bytes` types is currently delegated to `buitins.bytes`:
```py
reveal_type(b"foo".join) # revealed: @Todo(instance attributes)
reveal_type(b"foo".endswith) # revealed: @Todo(instance attributes)
```

View file

@ -1459,10 +1459,15 @@ impl<'db> Type<'db> {
match self { match self {
Type::Any | Type::Unknown | Type::Todo(_) => self.into(), Type::Any | Type::Unknown | Type::Todo(_) => self.into(),
Type::Never => todo_type!("attribute lookup on Never").into(), Type::Never => todo_type!("attribute lookup on Never").into(),
Type::FunctionLiteral(_) => {
todo_type!("Attribute access on `FunctionLiteral` types").into() Type::FunctionLiteral(_) => match name {
} "__get__" => todo_type!("`__get__` method on functions").into(),
"__call__" => todo_type!("`__call__` method on functions").into(),
_ => KnownClass::FunctionType.to_instance(db).member(db, name),
},
Type::ModuleLiteral(module_ref) => { Type::ModuleLiteral(module_ref) => {
// `__dict__` is a very special member that is never overridden by module globals; // `__dict__` is a very special member that is never overridden by module globals;
// we should always look it up directly as an attribute on `types.ModuleType`, // we should always look it up directly as an attribute on `types.ModuleType`,
@ -1522,9 +1527,13 @@ impl<'db> Type<'db> {
global_lookup global_lookup
} }
} }
Type::ClassLiteral(class_ty) => class_ty.member(db, name), Type::ClassLiteral(class_ty) => class_ty.member(db, name),
Type::SubclassOf(subclass_of_ty) => subclass_of_ty.member(db, name), Type::SubclassOf(subclass_of_ty) => subclass_of_ty.member(db, name),
Type::KnownInstance(known_instance) => known_instance.member(db, name), Type::KnownInstance(known_instance) => known_instance.member(db, name),
Type::Instance(InstanceType { class }) => { Type::Instance(InstanceType { class }) => {
let ty = match (class.known(db), name) { let ty = match (class.known(db), name) {
(Some(KnownClass::VersionInfo), "major") => { (Some(KnownClass::VersionInfo), "major") => {
@ -1538,6 +1547,7 @@ impl<'db> Type<'db> {
}; };
ty.into() ty.into()
} }
Type::Union(union) => { Type::Union(union) => {
let mut builder = UnionBuilder::new(db); let mut builder = UnionBuilder::new(db);
@ -1573,41 +1583,55 @@ impl<'db> Type<'db> {
) )
} }
} }
Type::Intersection(_) => { Type::Intersection(_) => {
// TODO perform the get_member on each type in the intersection // TODO perform the get_member on each type in the intersection
// TODO return the intersection of those results // TODO return the intersection of those results
todo_type!("Attribute access on `Intersection` types").into() todo_type!("Attribute access on `Intersection` types").into()
} }
Type::IntLiteral(_) => todo_type!("Attribute access on `IntLiteral` types").into(),
Type::BooleanLiteral(_) => { Type::IntLiteral(_) => match name {
todo_type!("Attribute access on `BooleanLiteral` types").into() "real" | "numerator" => self.into(),
} // TODO more attributes could probably be usefully special-cased
_ => KnownClass::Int.to_instance(db).member(db, name),
},
Type::BooleanLiteral(bool_value) => match name {
"real" | "numerator" => Type::IntLiteral(i64::from(*bool_value)).into(),
_ => KnownClass::Bool.to_instance(db).member(db, name),
},
Type::StringLiteral(_) => { Type::StringLiteral(_) => {
// TODO defer to `typing.LiteralString`/`builtins.str` methods // TODO defer to `typing.LiteralString`/`builtins.str` methods
// from typeshed's stubs // from typeshed's stubs
todo_type!("Attribute access on `StringLiteral` types").into() todo_type!("Attribute access on `StringLiteral` types").into()
} }
Type::LiteralString => { Type::LiteralString => {
// TODO defer to `typing.LiteralString`/`builtins.str` methods // TODO defer to `typing.LiteralString`/`builtins.str` methods
// from typeshed's stubs // from typeshed's stubs
todo_type!("Attribute access on `LiteralString` types").into() todo_type!("Attribute access on `LiteralString` types").into()
} }
Type::BytesLiteral(_) => {
// TODO defer to Type::Instance(<bytes from typeshed>).member Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).member(db, name),
todo_type!("Attribute access on `BytesLiteral` types").into()
} // We could plausibly special-case `start`, `step`, and `stop` here,
Type::SliceLiteral(_) => { // but it doesn't seem worth the complexity given the very narrow range of places
// TODO defer to `builtins.slice` methods // where we infer `SliceLiteral` types.
todo_type!("Attribute access on `SliceLiteral` types").into() Type::SliceLiteral(_) => KnownClass::Slice.to_instance(db).member(db, name),
}
Type::Tuple(_) => { Type::Tuple(_) => {
// TODO: implement tuple methods // TODO: implement tuple methods
todo_type!("Attribute access on heterogeneous tuple types").into() todo_type!("Attribute access on heterogeneous tuple types").into()
} }
Type::AlwaysTruthy | Type::AlwaysFalsy => {
// TODO return `Callable[[], Literal[True/False]]` for `__bool__` access Type::AlwaysTruthy | Type::AlwaysFalsy => match name {
KnownClass::Object.to_instance(db).member(db, name) "__bool__" => {
} // TODO should be `Callable[[], Literal[True/False]]`
todo_type!("`__bool__` for `AlwaysTruthy`/`AlwaysFalsy` Type variants").into()
}
_ => KnownClass::Object.to_instance(db).member(db, name),
},
} }
} }