mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
[red-knot] Reduce TODOs in Type::member()
(#15066)
This commit is contained in:
parent
c1eaf6ff72
commit
bb43085939
2 changed files with 110 additions and 20 deletions
|
@ -1,4 +1,6 @@
|
|||
# Class attributes
|
||||
# Attributes
|
||||
|
||||
Tests for attribute access on various kinds of types.
|
||||
|
||||
## Union of attributes
|
||||
|
||||
|
@ -155,3 +157,67 @@ class Foo: ...
|
|||
|
||||
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)
|
||||
```
|
||||
|
|
|
@ -1459,10 +1459,15 @@ impl<'db> Type<'db> {
|
|||
|
||||
match self {
|
||||
Type::Any | Type::Unknown | Type::Todo(_) => self.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) => {
|
||||
// `__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`,
|
||||
|
@ -1522,9 +1527,13 @@ impl<'db> Type<'db> {
|
|||
global_lookup
|
||||
}
|
||||
}
|
||||
|
||||
Type::ClassLiteral(class_ty) => class_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::Instance(InstanceType { class }) => {
|
||||
let ty = match (class.known(db), name) {
|
||||
(Some(KnownClass::VersionInfo), "major") => {
|
||||
|
@ -1538,6 +1547,7 @@ impl<'db> Type<'db> {
|
|||
};
|
||||
ty.into()
|
||||
}
|
||||
|
||||
Type::Union(union) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
|
||||
|
@ -1573,41 +1583,55 @@ impl<'db> Type<'db> {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
Type::Intersection(_) => {
|
||||
// TODO perform the get_member on each type in the intersection
|
||||
// TODO return the intersection of those results
|
||||
todo_type!("Attribute access on `Intersection` types").into()
|
||||
}
|
||||
Type::IntLiteral(_) => todo_type!("Attribute access on `IntLiteral` types").into(),
|
||||
Type::BooleanLiteral(_) => {
|
||||
todo_type!("Attribute access on `BooleanLiteral` types").into()
|
||||
}
|
||||
|
||||
Type::IntLiteral(_) => match name {
|
||||
"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(_) => {
|
||||
// TODO defer to `typing.LiteralString`/`builtins.str` methods
|
||||
// from typeshed's stubs
|
||||
todo_type!("Attribute access on `StringLiteral` types").into()
|
||||
}
|
||||
|
||||
Type::LiteralString => {
|
||||
// TODO defer to `typing.LiteralString`/`builtins.str` methods
|
||||
// from typeshed's stubs
|
||||
todo_type!("Attribute access on `LiteralString` types").into()
|
||||
}
|
||||
Type::BytesLiteral(_) => {
|
||||
// TODO defer to Type::Instance(<bytes from typeshed>).member
|
||||
todo_type!("Attribute access on `BytesLiteral` types").into()
|
||||
}
|
||||
Type::SliceLiteral(_) => {
|
||||
// TODO defer to `builtins.slice` methods
|
||||
todo_type!("Attribute access on `SliceLiteral` types").into()
|
||||
}
|
||||
|
||||
Type::BytesLiteral(_) => KnownClass::Bytes.to_instance(db).member(db, name),
|
||||
|
||||
// We could plausibly special-case `start`, `step`, and `stop` here,
|
||||
// but it doesn't seem worth the complexity given the very narrow range of places
|
||||
// where we infer `SliceLiteral` types.
|
||||
Type::SliceLiteral(_) => KnownClass::Slice.to_instance(db).member(db, name),
|
||||
|
||||
Type::Tuple(_) => {
|
||||
// TODO: implement tuple methods
|
||||
todo_type!("Attribute access on heterogeneous tuple types").into()
|
||||
}
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => {
|
||||
// TODO return `Callable[[], Literal[True/False]]` for `__bool__` access
|
||||
KnownClass::Object.to_instance(db).member(db, name)
|
||||
|
||||
Type::AlwaysTruthy | Type::AlwaysFalsy => match 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),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue