diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/starred.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/starred.md index a125afce48..d3fcae5ce2 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/starred.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/starred.md @@ -9,10 +9,10 @@ Ts = TypeVarTuple("Ts") def append_int(*args: *Ts) -> tuple[*Ts, int]: # TODO: should show some representation of the variadic generic type - reveal_type(args) # revealed: @Todo + reveal_type(args) # revealed: @Todo(function parameter type) return (*args, 1) # TODO should be tuple[Literal[True], Literal["a"], int] -reveal_type(append_int(True, "a")) # revealed: @Todo +reveal_type(append_int(True, "a")) # revealed: @Todo(full tuple[...] support) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md index 1dc8156d8b..ef401d4bf4 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md +++ b/crates/red_knot_python_semantic/resources/mdtest/assignment/annotations.md @@ -51,12 +51,12 @@ reveal_type(c) # revealed: tuple[str, int] reveal_type(d) # revealed: tuple[tuple[str, str], tuple[int, int]] # TODO: homogenous tuples, PEP-646 tuples -reveal_type(e) # revealed: @Todo -reveal_type(f) # revealed: @Todo -reveal_type(g) # revealed: @Todo +reveal_type(e) # revealed: @Todo(full tuple[...] support) +reveal_type(f) # revealed: @Todo(full tuple[...] support) +reveal_type(g) # revealed: @Todo(full tuple[...] support) # TODO: support more kinds of type expressions in annotations -reveal_type(h) # revealed: @Todo +reveal_type(h) # revealed: @Todo(full tuple[...] support) reveal_type(i) # revealed: tuple[str | int, str | int] reveal_type(j) # revealed: tuple[str | int] diff --git a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md index efc619132c..15029fead9 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md +++ b/crates/red_knot_python_semantic/resources/mdtest/binary/instances.md @@ -317,7 +317,7 @@ reveal_type(1 + A()) # revealed: int reveal_type(A() + "foo") # revealed: A # TODO should be `A` since `str.__add__` doesn't support `A` instances # TODO overloads -reveal_type("foo" + A()) # revealed: @Todo +reveal_type("foo" + A()) # revealed: @Todo(return type) reveal_type(A() + b"foo") # revealed: A # TODO should be `A` since `bytes.__add__` doesn't support `A` instances @@ -325,7 +325,7 @@ reveal_type(b"foo" + A()) # revealed: bytes reveal_type(A() + ()) # revealed: A # TODO this should be `A`, since `tuple.__add__` doesn't support `A` instances -reveal_type(() + A()) # revealed: @Todo +reveal_type(() + A()) # revealed: @Todo(return type) literal_string_instance = "foo" * 1_000_000_000 # the test is not testing what it's meant to be testing if this isn't a `LiteralString`: @@ -334,7 +334,7 @@ reveal_type(literal_string_instance) # revealed: LiteralString reveal_type(A() + literal_string_instance) # revealed: A # TODO should be `A` since `str.__add__` doesn't support `A` instances # TODO overloads -reveal_type(literal_string_instance + A()) # revealed: @Todo +reveal_type(literal_string_instance + A()) # revealed: @Todo(return type) ``` ## Operations involving instances of classes inheriting from `Any` diff --git a/crates/red_knot_python_semantic/resources/mdtest/call/function.md b/crates/red_knot_python_semantic/resources/mdtest/call/function.md index 2cafcbe5cc..b67b14d558 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/call/function.md +++ b/crates/red_knot_python_semantic/resources/mdtest/call/function.md @@ -16,7 +16,7 @@ async def get_int_async() -> int: return 42 # TODO: we don't yet support `types.CoroutineType`, should be generic `Coroutine[Any, Any, int]` -reveal_type(get_int_async()) # revealed: @Todo +reveal_type(get_int_async()) # revealed: @Todo(generic types.CoroutineType) ``` ## Generic @@ -44,7 +44,7 @@ def bar() -> str: return "bar" # TODO: should reveal `int`, as the decorator replaces `bar` with `foo` -reveal_type(bar()) # revealed: @Todo +reveal_type(bar()) # revealed: @Todo(return type) ``` ## Invalid callable diff --git a/crates/red_knot_python_semantic/resources/mdtest/exception/basic.md b/crates/red_knot_python_semantic/resources/mdtest/exception/basic.md index e03e51dcc6..c0d2d1e2f0 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/exception/basic.md +++ b/crates/red_knot_python_semantic/resources/mdtest/exception/basic.md @@ -50,11 +50,11 @@ def foo( help() except x as e: # TODO: should be `AttributeError` - reveal_type(e) # revealed: @Todo + reveal_type(e) # revealed: @Todo(exception type) except y as f: # TODO: should be `OSError | RuntimeError` - reveal_type(f) # revealed: @Todo + reveal_type(f) # revealed: @Todo(exception type) except z as g: # TODO: should be `BaseException` - reveal_type(g) # revealed: @Todo + reveal_type(g) # revealed: @Todo(exception type) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/generics.md b/crates/red_knot_python_semantic/resources/mdtest/generics.md index efb850b305..cc1eb392a9 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/generics.md +++ b/crates/red_knot_python_semantic/resources/mdtest/generics.md @@ -18,7 +18,7 @@ box: MyBox[int] = MyBox(5) wrong_innards: MyBox[int] = MyBox("five") # TODO reveal int -reveal_type(box.data) # revealed: @Todo +reveal_type(box.data) # revealed: @Todo(instance attributes) reveal_type(MyBox.box_model_number) # revealed: Literal[695] ``` @@ -39,7 +39,7 @@ class MySecureBox[T](MyBox[T]): ... secure_box: MySecureBox[int] = MySecureBox(5) reveal_type(secure_box) # revealed: MySecureBox # TODO reveal int -reveal_type(secure_box.data) # revealed: @Todo +reveal_type(secure_box.data) # revealed: @Todo(instance attributes) ``` ## Cyclical class definition diff --git a/crates/red_knot_python_semantic/resources/mdtest/literal/literal.md b/crates/red_knot_python_semantic/resources/mdtest/literal/literal.md index 92e76b45fd..0fb01df9a7 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/literal/literal.md +++ b/crates/red_knot_python_semantic/resources/mdtest/literal/literal.md @@ -78,7 +78,7 @@ from other import Literal a1: Literal[26] def f(): - reveal_type(a1) # revealed: @Todo + reveal_type(a1) # revealed: @Todo(generics) ``` ## Detecting typing_extensions.Literal diff --git a/crates/red_knot_python_semantic/resources/mdtest/loops/async_for.md b/crates/red_knot_python_semantic/resources/mdtest/loops/async_for.md index b9a6d083ec..c073576876 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/loops/async_for.md +++ b/crates/red_knot_python_semantic/resources/mdtest/loops/async_for.md @@ -18,7 +18,7 @@ async def foo(): pass # TODO: should reveal `Unknown` because `__aiter__` is not defined - # revealed: @Todo + # revealed: @Todo(async iterables/iterators) # error: [possibly-unresolved-reference] reveal_type(x) ``` @@ -40,6 +40,6 @@ async def foo(): pass # error: [possibly-unresolved-reference] - # revealed: @Todo + # revealed: @Todo(async iterables/iterators) reveal_type(x) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/metaclass.md b/crates/red_knot_python_semantic/resources/mdtest/metaclass.md index 1e9d726622..e6bcd6e43c 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/metaclass.md +++ b/crates/red_knot_python_semantic/resources/mdtest/metaclass.md @@ -171,7 +171,7 @@ def f(*args, **kwargs) -> int: ... class A(metaclass=f): ... # TODO should be `type[int]` -reveal_type(A.__class__) # revealed: @Todo +reveal_type(A.__class__) # revealed: @Todo(metaclass not a class) ``` ## Cyclic diff --git a/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md b/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md index 652d796825..df04fd71fb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md +++ b/crates/red_knot_python_semantic/resources/mdtest/scopes/moduletype_attrs.md @@ -17,8 +17,7 @@ reveal_type(__doc__) # revealed: str | None # (needs support for `*` imports) reveal_type(__spec__) # revealed: Unknown | None -# TODO: generics -reveal_type(__path__) # revealed: @Todo +reveal_type(__path__) # revealed: @Todo(generics) class X: reveal_type(__name__) # revealed: str @@ -64,7 +63,7 @@ reveal_type(typing.__class__) # revealed: Literal[type] # TODO: needs support for attribute access on instances, properties and generics; # should be `dict[str, Any]` -reveal_type(typing.__dict__) # revealed: @Todo +reveal_type(typing.__dict__) # revealed: @Todo(instance attributes) ``` Typeshed includes a fake `__getattr__` method in the stub for `types.ModuleType` to help out with @@ -96,8 +95,8 @@ from foo import __dict__ as foo_dict # TODO: needs support for attribute access on instances, properties, and generics; # should be `dict[str, Any]` for both of these: -reveal_type(foo.__dict__) # revealed: @Todo -reveal_type(foo_dict) # revealed: @Todo +reveal_type(foo.__dict__) # revealed: @Todo(instance attributes) +reveal_type(foo_dict) # revealed: @Todo(instance attributes) ``` ## Conditionally global or `ModuleType` attribute diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md index acaa115035..71c7775b76 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/bytes.md @@ -27,7 +27,7 @@ def int_instance() -> int: a = b"abcde"[int_instance()] # TODO: Support overloads... Should be `bytes` -reveal_type(a) # revealed: @Todo +reveal_type(a) # revealed: @Todo(return type) ``` ## Slices @@ -47,11 +47,11 @@ def int_instance() -> int: ... byte_slice1 = b[int_instance() : int_instance()] # TODO: Support overloads... Should be `bytes` -reveal_type(byte_slice1) # revealed: @Todo +reveal_type(byte_slice1) # revealed: @Todo(return type) def bytes_instance() -> bytes: ... byte_slice2 = bytes_instance()[0:5] # TODO: Support overloads... Should be `bytes` -reveal_type(byte_slice2) # revealed: @Todo +reveal_type(byte_slice2) # revealed: @Todo(return type) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md index 8e5fe60788..0ebd8c83af 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/lists.md @@ -12,13 +12,13 @@ x = [1, 2, 3] reveal_type(x) # revealed: list # TODO reveal int -reveal_type(x[0]) # revealed: @Todo +reveal_type(x[0]) # revealed: @Todo(return type) # TODO reveal list -reveal_type(x[0:1]) # revealed: @Todo +reveal_type(x[0:1]) # revealed: @Todo(return type) # TODO error -reveal_type(x["a"]) # revealed: @Todo +reveal_type(x["a"]) # revealed: @Todo(return type) ``` ## Assignments within list assignment diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md index ae8099196d..4627407459 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/string.md @@ -23,7 +23,7 @@ def int_instance() -> int: ... a = "abcde"[int_instance()] # TODO: Support overloads... Should be `str` -reveal_type(a) # revealed: @Todo +reveal_type(a) # revealed: @Todo(return type) ``` ## Slices @@ -78,13 +78,13 @@ def int_instance() -> int: ... substring1 = s[int_instance() : int_instance()] # TODO: Support overloads... Should be `LiteralString` -reveal_type(substring1) # revealed: @Todo +reveal_type(substring1) # revealed: @Todo(return type) def str_instance() -> str: ... substring2 = str_instance()[0:5] # TODO: Support overloads... Should be `str` -reveal_type(substring2) # revealed: @Todo +reveal_type(substring2) # revealed: @Todo(return type) ``` ## Unsupported slice types diff --git a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md index 38dea938eb..cb088f009f 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md +++ b/crates/red_knot_python_semantic/resources/mdtest/subscript/tuple.md @@ -71,5 +71,5 @@ def int_instance() -> int: ... tuple_slice = t[int_instance() : int_instance()] # TODO: Support overloads... Should be `tuple[Literal[1, 'a', b"b"] | None, ...]` -reveal_type(tuple_slice) # revealed: @Todo +reveal_type(tuple_slice) # revealed: @Todo(return type) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/sys_version_info.md b/crates/red_knot_python_semantic/resources/mdtest/sys_version_info.md index a9c175a0e7..1622720220 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/sys_version_info.md +++ b/crates/red_knot_python_semantic/resources/mdtest/sys_version_info.md @@ -112,9 +112,9 @@ properties on instance types: ```py path=b.py import sys -reveal_type(sys.version_info.micro) # revealed: @Todo -reveal_type(sys.version_info.releaselevel) # revealed: @Todo -reveal_type(sys.version_info.serial) # revealed: @Todo +reveal_type(sys.version_info.micro) # revealed: @Todo(instance attributes) +reveal_type(sys.version_info.releaselevel) # revealed: @Todo(instance attributes) +reveal_type(sys.version_info.serial) # revealed: @Todo(instance attributes) ``` ## Accessing fields by index/slice diff --git a/crates/red_knot_python_semantic/resources/mdtest/unpacking.md b/crates/red_knot_python_semantic/resources/mdtest/unpacking.md index 6781cbb438..0e9c3943a9 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/unpacking.md +++ b/crates/red_knot_python_semantic/resources/mdtest/unpacking.md @@ -84,7 +84,7 @@ reveal_type(b) # revealed: Literal[2] [a, *b, c, d] = (1, 2) reveal_type(a) # revealed: Literal[1] # TODO: Should be list[Any] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: Literal[2] reveal_type(d) # revealed: Unknown ``` @@ -95,7 +95,7 @@ reveal_type(d) # revealed: Unknown [a, *b, c] = (1, 2) reveal_type(a) # revealed: Literal[1] # TODO: Should be list[Any] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: Literal[2] ``` @@ -105,7 +105,7 @@ reveal_type(c) # revealed: Literal[2] [a, *b, c] = (1, 2, 3) reveal_type(a) # revealed: Literal[1] # TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: Literal[3] ``` @@ -115,7 +115,7 @@ reveal_type(c) # revealed: Literal[3] [a, *b, c, d] = (1, 2, 3, 4, 5, 6) reveal_type(a) # revealed: Literal[1] # TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: Literal[5] reveal_type(d) # revealed: Literal[6] ``` @@ -127,7 +127,7 @@ reveal_type(d) # revealed: Literal[6] reveal_type(a) # revealed: Literal[1] reveal_type(b) # revealed: Literal[2] # TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(c) # revealed: @Todo +reveal_type(c) # revealed: @Todo(starred unpacking) ``` ### Starred expression (6) @@ -138,7 +138,7 @@ reveal_type(c) # revealed: @Todo reveal_type(a) # revealed: Literal[1] reveal_type(b) # revealed: Unknown reveal_type(c) # revealed: Unknown -reveal_type(d) # revealed: @Todo +reveal_type(d) # revealed: @Todo(starred unpacking) reveal_type(e) # revealed: Unknown reveal_type(f) # revealed: Unknown ``` @@ -222,7 +222,7 @@ reveal_type(b) # revealed: LiteralString (a, *b, c, d) = "ab" reveal_type(a) # revealed: LiteralString # TODO: Should be list[LiteralString] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: LiteralString reveal_type(d) # revealed: Unknown ``` @@ -233,7 +233,7 @@ reveal_type(d) # revealed: Unknown (a, *b, c) = "ab" reveal_type(a) # revealed: LiteralString # TODO: Should be list[Any] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: LiteralString ``` @@ -243,7 +243,7 @@ reveal_type(c) # revealed: LiteralString (a, *b, c) = "abc" reveal_type(a) # revealed: LiteralString # TODO: Should be list[LiteralString] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: LiteralString ``` @@ -253,7 +253,7 @@ reveal_type(c) # revealed: LiteralString (a, *b, c, d) = "abcdef" reveal_type(a) # revealed: LiteralString # TODO: Should be list[LiteralString] once support for assigning to starred expression is added -reveal_type(b) # revealed: @Todo +reveal_type(b) # revealed: @Todo(starred unpacking) reveal_type(c) # revealed: LiteralString reveal_type(d) # revealed: LiteralString ``` @@ -265,5 +265,5 @@ reveal_type(d) # revealed: LiteralString reveal_type(a) # revealed: LiteralString reveal_type(b) # revealed: LiteralString # TODO: Should be list[int] once support for assigning to starred expression is added -reveal_type(c) # revealed: @Todo +reveal_type(c) # revealed: @Todo(starred unpacking) ``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/with/async.md b/crates/red_knot_python_semantic/resources/mdtest/with/async.md index 72aa4cad13..9b2b1439fb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/with/async.md +++ b/crates/red_knot_python_semantic/resources/mdtest/with/async.md @@ -17,5 +17,5 @@ class Manager: async def test(): async with Manager() as f: - reveal_type(f) # revealed: @Todo + reveal_type(f) # revealed: @Todo(async with statement) ``` diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index f4ffa4e0a4..50362c34cc 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -324,6 +324,61 @@ fn declarations_ty<'db>( } } +/// Meta data for `Type::Todo`, which represents a known limitation in red-knot. +#[cfg(debug_assertions)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub enum TodoType { + FileAndLine(&'static str, u32), + Message(&'static str), +} + +#[cfg(debug_assertions)] +impl std::fmt::Display for TodoType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TodoType::FileAndLine(file, line) => write!(f, "[{file}:{line}]"), + TodoType::Message(msg) => write!(f, "({msg})"), + } + } +} + +#[cfg(not(debug_assertions))] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct TodoType; + +#[cfg(not(debug_assertions))] +impl std::fmt::Display for TodoType { + fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +/// Create a `Type::Todo` variant to represent a known limitation in the type system. +/// +/// It can be used with a custom message (preferred): `todo_type!("PEP 604 not supported")`, +/// or simply using `todo_type!()`, which will include information about the file and line. +#[cfg(debug_assertions)] +macro_rules! todo_type { + () => { + Type::Todo(crate::types::TodoType::FileAndLine(file!(), line!())) + }; + ($message:literal) => { + Type::Todo(crate::types::TodoType::Message($message)) + }; +} + +#[cfg(not(debug_assertions))] +macro_rules! todo_type { + () => { + Type::Todo(crate::types::TodoType) + }; + ($message:literal) => { + Type::Todo(crate::types::TodoType) + }; +} + +pub(crate) use todo_type; + /// Representation of a type: a set of possible values at runtime. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, salsa::Update)] pub enum Type<'db> { @@ -340,7 +395,9 @@ pub enum Type<'db> { /// General rule: `Todo` should only propagate when the presence of the input `Todo` caused the /// output to be unknown. An output should only be `Todo` if fixing all `Todo` inputs to be not /// `Todo` would change the output type. - Todo, + /// + /// This variant should be created with the `todo_type!` macro. + Todo(TodoType), /// The empty set of values Never, /// A specific function object @@ -384,7 +441,7 @@ impl<'db> Type<'db> { } pub const fn is_todo(&self) -> bool { - matches!(self, Type::Todo) + matches!(self, Type::Todo(_)) } pub const fn class_literal(class: Class<'db>) -> Self { @@ -530,8 +587,8 @@ impl<'db> Type<'db> { return true; } match (self, target) { - (Type::Unknown | Type::Any | Type::Todo, _) => false, - (_, Type::Unknown | Type::Any | Type::Todo) => false, + (Type::Unknown | Type::Any | Type::Todo(_), _) => false, + (_, Type::Unknown | Type::Any | Type::Todo(_)) => false, (Type::Never, _) => true, (_, Type::Never) => false, (_, Type::Instance(InstanceType { class })) @@ -666,8 +723,8 @@ impl<'db> Type<'db> { return true; } match (self, target) { - (Type::Unknown | Type::Any | Type::Todo, _) => true, - (_, Type::Unknown | Type::Any | Type::Todo) => true, + (Type::Unknown | Type::Any | Type::Todo(_), _) => true, + (_, Type::Unknown | Type::Any | Type::Todo(_)) => true, (Type::Union(union), ty) => union .elements(db) .iter() @@ -703,6 +760,7 @@ impl<'db> Type<'db> { // of `NoneType` and `NoDefaultType` in typeshed. This should not be required anymore once // we understand `sys.version_info` branches. self == other + || matches!((self, other), (Type::Todo(_), Type::Todo(_))) || matches!((self, other), ( Type::Instance(InstanceType { class: self_class }), @@ -726,7 +784,7 @@ impl<'db> Type<'db> { (Type::Any, _) | (_, Type::Any) => false, (Type::Unknown, _) | (_, Type::Unknown) => false, - (Type::Todo, _) | (_, Type::Todo) => false, + (Type::Todo(_), _) | (_, Type::Todo(_)) => false, (Type::Union(union), other) | (other, Type::Union(union)) => union .elements(db) @@ -931,7 +989,7 @@ impl<'db> Type<'db> { Type::Any | Type::Never | Type::Unknown - | Type::Todo + | Type::Todo(_) | Type::IntLiteral(..) | Type::StringLiteral(..) | Type::BytesLiteral(..) @@ -1034,7 +1092,7 @@ impl<'db> Type<'db> { Type::Any | Type::Never | Type::Unknown - | Type::Todo + | Type::Todo(_) | Type::Union(..) | Type::Intersection(..) | Type::LiteralString => false, @@ -1052,12 +1110,12 @@ impl<'db> Type<'db> { Type::Any => Type::Any.into(), Type::Never => { // TODO: attribute lookup on Never type - Type::Todo.into() + todo_type!().into() } Type::Unknown => Type::Unknown.into(), Type::FunctionLiteral(_) => { // TODO: attribute lookup on function type - Type::Todo.into() + todo_type!().into() } Type::ModuleLiteral(file) => { // `__dict__` is a very special member that is never overridden by module globals; @@ -1107,7 +1165,7 @@ impl<'db> Type<'db> { Type::IntLiteral(Program::get(db).target_version(db).minor.into()) } // TODO MRO? get_own_instance_member, get_instance_member - _ => Type::Todo, + _ => todo_type!("instance attributes"), }; ty.into() } @@ -1149,36 +1207,36 @@ impl<'db> Type<'db> { Type::Intersection(_) => { // TODO perform the get_member on each type in the intersection // TODO return the intersection of those results - Type::Todo.into() + todo_type!().into() } Type::IntLiteral(_) => { // TODO raise error - Type::Todo.into() + todo_type!().into() } - Type::BooleanLiteral(_) => Type::Todo.into(), + Type::BooleanLiteral(_) => todo_type!().into(), Type::StringLiteral(_) => { // TODO defer to `typing.LiteralString`/`builtins.str` methods // from typeshed's stubs - Type::Todo.into() + todo_type!().into() } Type::LiteralString => { // TODO defer to `typing.LiteralString`/`builtins.str` methods // from typeshed's stubs - Type::Todo.into() + todo_type!().into() } Type::BytesLiteral(_) => { // TODO defer to Type::Instance().member - Type::Todo.into() + todo_type!().into() } Type::SliceLiteral(_) => { // TODO defer to `builtins.slice` methods - Type::Todo.into() + todo_type!().into() } Type::Tuple(_) => { // TODO: implement tuple methods - Type::Todo.into() + todo_type!().into() } - Type::Todo => Type::Todo.into(), + &todo @ Type::Todo(_) => todo.into(), } } @@ -1188,7 +1246,7 @@ impl<'db> Type<'db> { /// when `bool(x)` is called on an object `x`. fn bool(&self, db: &'db dyn Db) -> Truthiness { match self { - Type::Any | Type::Todo | Type::Never | Type::Unknown => Truthiness::Ambiguous, + Type::Any | Type::Todo(_) | Type::Never | Type::Unknown => Truthiness::Ambiguous, Type::FunctionLiteral(_) => Truthiness::AlwaysTrue, Type::ModuleLiteral(_) => Truthiness::AlwaysTrue, Type::ClassLiteral(_) => { @@ -1329,7 +1387,7 @@ impl<'db> Type<'db> { // `Any` is callable, and its return type is also `Any`. Type::Any => CallOutcome::callable(Type::Any), - Type::Todo => CallOutcome::callable(Type::Todo), + Type::Todo(_) => CallOutcome::callable(todo_type!()), Type::Unknown => CallOutcome::callable(Type::Unknown), @@ -1342,7 +1400,7 @@ impl<'db> Type<'db> { ), // TODO: intersection types - Type::Intersection(_) => CallOutcome::callable(Type::Todo), + Type::Intersection(_) => CallOutcome::callable(todo_type!()), _ => CallOutcome::not_callable(self), } @@ -1381,7 +1439,7 @@ impl<'db> Type<'db> { }; } - if matches!(self, Type::Unknown | Type::Any | Type::Todo) { + if matches!(self, Type::Unknown | Type::Any | Type::Todo(_)) { // Explicit handling of `Unknown` and `Any` necessary until `type[Unknown]` and // `type[Any]` are not defined as `Todo` anymore. return IterationOutcome::Iterable { element_ty: self }; @@ -1440,14 +1498,14 @@ impl<'db> Type<'db> { pub fn to_instance(&self, db: &'db dyn Db) -> Type<'db> { match self { Type::Any => Type::Any, - Type::Todo => Type::Todo, + todo @ Type::Todo(_) => *todo, Type::Unknown => Type::Unknown, Type::Never => Type::Never, Type::ClassLiteral(ClassLiteralType { class }) => Type::instance(*class), Type::SubclassOf(SubclassOfType { class }) => Type::instance(*class), Type::Union(union) => union.map(db, |element| element.to_instance(db)), // TODO: we can probably do better here: --Alex - Type::Intersection(_) => Type::Todo, + Type::Intersection(_) => todo_type!(), // TODO: calling `.to_instance()` on any of these should result in a diagnostic, // since they already indicate that the object is an instance of some kind: Type::BooleanLiteral(_) @@ -1478,7 +1536,7 @@ impl<'db> Type<'db> { Type::Unknown => Type::Unknown, // TODO map this to a new `Type::TypeVar` variant Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self, - _ => Type::Todo, + _ => todo_type!(), } } @@ -1553,8 +1611,8 @@ impl<'db> Type<'db> { // TODO: `type[Unknown]`? Type::Unknown => Type::Unknown, // TODO intersections - Type::Intersection(_) => Type::Todo, - Type::Todo => Type::Todo, + Type::Intersection(_) => todo_type!(), + todo @ Type::Todo(_) => *todo, } } @@ -2607,7 +2665,7 @@ impl<'db> Class<'db> { // TODO: If the metaclass is not a class, we should verify that it's a callable // which accepts the same arguments as `type.__new__` (otherwise error), and return // the meta-type of its return type. (And validate that is a class type?) - return Ok(Type::Todo); + return Ok(todo_type!("metaclass not a class")); }; // Reconcile all base classes' metaclasses with the candidate metaclass. @@ -2907,6 +2965,10 @@ impl<'db> TupleType<'db> { } } +// Make sure that the `Type` enum does not grow unexpectedly. +#[cfg(not(debug_assertions))] +static_assertions::assert_eq_size!(Type, [u8; 16]); + #[cfg(test)] pub(crate) mod tests { use super::*; @@ -2922,13 +2984,6 @@ pub(crate) mod tests { use ruff_python_ast as ast; use test_case::test_case; - #[cfg(target_pointer_width = "64")] - #[test] - fn no_bloat_enum_sizes() { - use std::mem::size_of; - assert_eq!(size_of::(), 16); - } - pub(crate) fn setup_db() -> TestDb { let db = TestDb::new(); @@ -2982,7 +3037,7 @@ pub(crate) mod tests { Ty::Unknown => Type::Unknown, Ty::None => Type::none(db), Ty::Any => Type::Any, - Ty::Todo => Type::Todo, + Ty::Todo => todo_type!("Ty::Todo"), Ty::IntLiteral(n) => Type::IntLiteral(n), Ty::StringLiteral(s) => Type::string_literal(db, s), Ty::BooleanLiteral(b) => Type::BooleanLiteral(b), @@ -3573,4 +3628,66 @@ pub(crate) mod tests { Ok(()) } + + /// All other tests also make sure that `Type::Todo` works as expected. This particular + /// test makes sure that we handle `Todo` types correctly, even if they originate from + /// different sources. + #[test] + fn todo_types() { + let db = setup_db(); + + let todo1 = todo_type!("1"); + let todo2 = todo_type!("2"); + let todo3 = todo_type!(); + let todo4 = todo_type!(); + + assert!(todo1.is_equivalent_to(&db, todo2)); + assert!(todo3.is_equivalent_to(&db, todo4)); + assert!(todo1.is_equivalent_to(&db, todo3)); + + assert!(todo1.is_subtype_of(&db, todo2)); + assert!(todo2.is_subtype_of(&db, todo1)); + + assert!(todo3.is_subtype_of(&db, todo4)); + assert!(todo4.is_subtype_of(&db, todo3)); + + assert!(todo1.is_subtype_of(&db, todo3)); + assert!(todo3.is_subtype_of(&db, todo1)); + + let int = KnownClass::Int.to_instance(&db); + + assert!(int.is_assignable_to(&db, todo1)); + assert!(int.is_assignable_to(&db, todo3)); + + assert!(todo1.is_assignable_to(&db, int)); + assert!(todo3.is_assignable_to(&db, int)); + + // We lose information when combining several `Todo` types. This is an + // acknowledged limitation of the current implementation. We can not + // easily store the meta information of several `Todo`s in a single + // variant, as `TodoType` needs to implement `Copy`, meaning it can't + // contain `Vec`/`Box`/etc., and can't be boxed itself. + // + // Lifting this restriction would require us to intern `TodoType` in + // salsa, but that would mean we would have to pass in `db` everywhere. + + // A union of several `Todo` types collapses to a single `Todo` type: + assert!(UnionType::from_elements(&db, vec![todo1, todo2, todo3, todo4]).is_todo()); + + // And similar for intersection types: + assert!(IntersectionBuilder::new(&db) + .add_positive(todo1) + .add_positive(todo2) + .add_positive(todo3) + .add_positive(todo4) + .build() + .is_todo()); + assert!(IntersectionBuilder::new(&db) + .add_positive(todo1) + .add_negative(todo2) + .add_positive(todo3) + .add_negative(todo4) + .build() + .is_todo()); + } } diff --git a/crates/red_knot_python_semantic/src/types/builder.rs b/crates/red_knot_python_semantic/src/types/builder.rs index d14716b537..067fccdcf2 100644 --- a/crates/red_knot_python_semantic/src/types/builder.rs +++ b/crates/red_knot_python_semantic/src/types/builder.rs @@ -309,7 +309,7 @@ impl<'db> InnerIntersectionBuilder<'db> { self.add_positive(db, *neg); } } - ty @ (Type::Any | Type::Unknown | Type::Todo) => { + ty @ (Type::Any | Type::Unknown | Type::Todo(_)) => { // Adding any of these types to the negative side of an intersection // is equivalent to adding it to the positive side. We do this to // simplify the representation. @@ -379,7 +379,7 @@ mod tests { use crate::program::{Program, SearchPathSettings}; use crate::python_version::PythonVersion; use crate::stdlib::typing_symbol; - use crate::types::{global_symbol, KnownClass, UnionBuilder}; + use crate::types::{global_symbol, todo_type, KnownClass, UnionBuilder}; use crate::ProgramSettings; use ruff_db::files::system_path_to_file; use ruff_db::system::{DbWithTestSystem, SystemPathBuf}; @@ -987,7 +987,7 @@ mod tests { #[test_case(Type::Any)] #[test_case(Type::Unknown)] - #[test_case(Type::Todo)] + #[test_case(todo_type!())] fn build_intersection_t_and_negative_t_does_not_simplify(ty: Type) { let db = setup_db(); diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index fcfb5a0cdf..cf86187dbb 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -77,7 +77,7 @@ impl Display for DisplayRepresentation<'_> { } // `[Type::Todo]`'s display should be explicit that is not a valid display of // any other type - Type::Todo => f.write_str("@Todo"), + Type::Todo(todo) => write!(f, "@Todo{todo}"), Type::ModuleLiteral(file) => { write!(f, "", file.path(self.db)) } diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index 3f6cc5cfa7..a00f9d4e8b 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -52,11 +52,12 @@ use crate::types::diagnostic::{TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder use crate::types::mro::MroErrorKind; use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ - bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, typing_extensions_symbol, - Boundness, Class, ClassLiteralType, FunctionType, InstanceType, IntersectionBuilder, - IntersectionType, IterationOutcome, KnownClass, KnownFunction, KnownInstanceType, - MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, Truthiness, TupleType, Type, - TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, + bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type, + typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType, + IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction, + KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, + Truthiness, TupleType, Type, TypeArrayDisplay, TypeVarBoundOrConstraints, TypeVarInstance, + UnionBuilder, UnionType, }; use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; @@ -1027,7 +1028,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) { // TODO(dhruvmanila): Annotation expression is resolved at the enclosing scope, infer the // parameter type from there - let annotated_ty = Type::Todo; + let annotated_ty = todo_type!("function parameter type"); if parameter.annotation.is_some() { self.add_declaration_with_binding( parameter.into(), @@ -1236,7 +1237,7 @@ impl<'db> TypeInferenceBuilder<'db> { ) -> Type<'db> { // TODO: Handle async with statements (they use `aenter` and `aexit`) if is_async { - return Type::Todo; + return todo_type!("async with statement"); } let context_manager_ty = context_expression_ty.to_meta_type(self.db); @@ -1386,12 +1387,12 @@ impl<'db> TypeInferenceBuilder<'db> { self.db, tuple.elements(self.db).iter().map(|ty| { ty.into_class_literal() - .map_or(Type::Todo, |ClassLiteralType { class }| { + .map_or(todo_type!(), |ClassLiteralType { class }| { Type::instance(class) }) }), ), - _ => Type::Todo, + _ => todo_type!("exception type"), } }; @@ -1461,7 +1462,7 @@ impl<'db> TypeInferenceBuilder<'db> { default, } = node; self.infer_optional_expression(default.as_deref()); - self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo); + self.add_declaration_with_binding(node.into(), definition, todo_type!(), todo_type!()); } fn infer_typevartuple_definition( @@ -1475,7 +1476,7 @@ impl<'db> TypeInferenceBuilder<'db> { default, } = node; self.infer_optional_expression(default.as_deref()); - self.add_declaration_with_binding(node.into(), definition, Type::Todo, Type::Todo); + self.add_declaration_with_binding(node.into(), definition, todo_type!(), todo_type!()); } fn infer_match_statement(&mut self, match_statement: &ast::StmtMatch) { @@ -1510,7 +1511,7 @@ impl<'db> TypeInferenceBuilder<'db> { // against the subject expression type (which we can query via `infer_expression_types`) // and extract the type at the `index` position if the pattern matches. This will be // similar to the logic in `self.infer_assignment_definition`. - self.add_binding(pattern.into(), definition, Type::Todo); + self.add_binding(pattern.into(), definition, todo_type!()); } fn infer_match_pattern(&mut self, pattern: &ast::Pattern) { @@ -1874,8 +1875,7 @@ impl<'db> TypeInferenceBuilder<'db> { let iterable_ty = self.infer_standalone_expression(iterable); let loop_var_value_ty = if is_async { - // TODO(Alex): async iterables/iterators! - Type::Todo + todo_type!("async iterables/iterators") } else { iterable_ty .iterate(self.db) @@ -2203,7 +2203,7 @@ impl<'db> TypeInferenceBuilder<'db> { ast::Expr::Await(await_expression) => self.infer_await_expression(await_expression), ast::Expr::IpyEscapeCommand(_) => { // TODO Implement Ipy escape command support - Type::Todo + todo_type!() } }; @@ -2397,7 +2397,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_first_comprehension_iter(generators); // TODO generator type - Type::Todo + todo_type!() } fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> { @@ -2410,7 +2410,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_first_comprehension_iter(generators); // TODO list type - Type::Todo + todo_type!() } fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> { @@ -2424,7 +2424,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_first_comprehension_iter(generators); // TODO dict type - Type::Todo + todo_type!() } fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> { @@ -2437,7 +2437,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_first_comprehension_iter(generators); // TODO set type - Type::Todo + todo_type!() } fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) { @@ -2552,7 +2552,7 @@ impl<'db> TypeInferenceBuilder<'db> { let target_ty = if is_async { // TODO: async iterables/iterators! -- Alex - Type::Todo + todo_type!("async iterables/iterators") } else { iterable_ty .iterate(self.db) @@ -2642,7 +2642,7 @@ impl<'db> TypeInferenceBuilder<'db> { } // TODO function type - Type::Todo + todo_type!() } fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> { @@ -2673,7 +2673,7 @@ impl<'db> TypeInferenceBuilder<'db> { .unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics); // TODO - Type::Todo + todo_type!() } fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> { @@ -2682,7 +2682,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_optional_expression(value.as_deref()); // TODO awaitable type - Type::Todo + todo_type!() } fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> { @@ -2694,7 +2694,7 @@ impl<'db> TypeInferenceBuilder<'db> { .unwrap_with_diagnostic(value.as_ref().into(), &mut self.diagnostics); // TODO get type from `ReturnType` of generator - Type::Todo + todo_type!() } fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> { @@ -2703,7 +2703,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_expression(value); // TODO awaitable type - Type::Todo + todo_type!() } /// Look up a name reference that isn't bound in the local scope. @@ -2979,7 +2979,7 @@ impl<'db> TypeInferenceBuilder<'db> { Type::Unknown } } - _ => Type::Todo, // TODO other unary op types + _ => todo_type!(), // TODO other unary op types } } @@ -3227,7 +3227,7 @@ impl<'db> TypeInferenceBuilder<'db> { (left, Type::BooleanLiteral(bool_value), op) => { self.infer_binary_expression_type(left, Type::IntLiteral(i64::from(bool_value)), op) } - _ => Some(Type::Todo), // TODO + _ => Some(todo_type!()), // TODO } } @@ -3665,7 +3665,7 @@ impl<'db> TypeInferenceBuilder<'db> { ).expect("infer_binary_type_comparison should never return None for `CmpOp::Eq`"); match eq_result { - Type::Todo => return Ok(Type::Todo), + todo @ Type::Todo(_) => return Ok(todo), ty => match ty.bool(self.db) { Truthiness::AlwaysTrue => eq_count += 1, Truthiness::AlwaysFalse => not_eq_count += 1, @@ -3690,7 +3690,7 @@ impl<'db> TypeInferenceBuilder<'db> { ); Ok(match eq_result { - Type::Todo => Type::Todo, + todo @ Type::Todo(_) => todo, ty => match ty.bool(self.db) { Truthiness::AlwaysFalse => Type::BooleanLiteral(op.is_is_not()), _ => KnownClass::Bool.to_instance(self.db), @@ -3745,7 +3745,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO: handle more types _ => match op { ast::CmpOp::Is | ast::CmpOp::IsNot => Ok(KnownClass::Bool.to_instance(self.db)), - _ => Ok(Type::Todo), + _ => Ok(todo_type!()), }, } } @@ -3773,7 +3773,7 @@ impl<'db> TypeInferenceBuilder<'db> { match pairwise_eq_result { // If propagation is required, return the result as is - Type::Todo => return Ok(Type::Todo), + todo @ Type::Todo(_) => return Ok(todo), ty => match ty.bool(self.db) { // - AlwaysTrue : Continue to the next pair for lexicographic comparison Truthiness::AlwaysTrue => continue, @@ -4263,7 +4263,7 @@ impl<'db> TypeInferenceBuilder<'db> { self.infer_name_expression(name).in_type_expression(self.db) } ast::ExprContext::Invalid => Type::Unknown, - ast::ExprContext::Store | ast::ExprContext::Del => Type::Todo, + ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(), }, ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx { @@ -4271,7 +4271,7 @@ impl<'db> TypeInferenceBuilder<'db> { .infer_attribute_expression(attribute_expression) .in_type_expression(self.db), ast::ExprContext::Invalid => Type::Unknown, - ast::ExprContext::Store | ast::ExprContext::Del => Type::Todo, + ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(), }, ast::Expr::NoneLiteral(_literal) => Type::none(self.db), @@ -4281,14 +4281,14 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO: an Ellipsis literal *on its own* does not have any meaning in annotation // expressions, but is meaningful in the context of a number of special forms. - ast::Expr::EllipsisLiteral(_literal) => Type::Todo, + ast::Expr::EllipsisLiteral(_literal) => todo_type!(), // Other literals do not have meaningful values in the annotation expression context. // However, we will we want to handle these differently when working with special forms, // since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is. - ast::Expr::BytesLiteral(_literal) => Type::Todo, - ast::Expr::NumberLiteral(_literal) => Type::Todo, - ast::Expr::BooleanLiteral(_literal) => Type::Todo, + ast::Expr::BytesLiteral(_literal) => todo_type!(), + ast::Expr::NumberLiteral(_literal) => todo_type!(), + ast::Expr::BooleanLiteral(_literal) => todo_type!(), ast::Expr::Subscript(subscript) => { let ast::ExprSubscript { @@ -4331,7 +4331,7 @@ impl<'db> TypeInferenceBuilder<'db> { // TODO PEP 646 ast::Expr::Starred(starred) => { self.infer_starred_expression(starred); - Type::Todo + todo_type!() } // Forms which are invalid in the context of annotation expressions: we infer their @@ -4476,7 +4476,7 @@ impl<'db> TypeInferenceBuilder<'db> { } let ty = if return_todo { - Type::Todo + todo_type!("full tuple[...] support") } else { Type::tuple(self.db, &element_types) }; @@ -4491,7 +4491,7 @@ impl<'db> TypeInferenceBuilder<'db> { single_element => { let single_element_ty = self.infer_type_expression(single_element); if element_could_alter_type_of_whole_tuple(single_element, single_element_ty) { - Type::Todo + todo_type!() } else { Type::tuple(self.db, &[single_element_ty]) } @@ -4507,13 +4507,13 @@ impl<'db> TypeInferenceBuilder<'db> { if let Some(ClassLiteralType { class }) = name_ty.into_class_literal() { Type::subclass_of(class) } else { - Type::Todo + todo_type!() } } // TODO: attributes, unions, subscripts, etc. _ => { self.infer_type_expression(slice); - Type::Todo + todo_type!() } } } @@ -4536,7 +4536,7 @@ impl<'db> TypeInferenceBuilder<'db> { } _ => { self.infer_type_expression(slice); - Type::Todo // TODO: generics + todo_type!("generics") } } } @@ -4574,7 +4574,7 @@ impl<'db> TypeInferenceBuilder<'db> { ), _ => self.infer_type_expression(parameters), }, - KnownInstanceType::TypeVar(_) => Type::Todo, + KnownInstanceType::TypeVar(_) => todo_type!(), } } @@ -4942,8 +4942,8 @@ fn perform_membership_test_comparison<'db>( compare_result_opt .map(|ty| { - if matches!(ty, Type::Todo) { - return Type::Todo; + if matches!(ty, Type::Todo(_)) { + return ty; } match op { @@ -5914,7 +5914,13 @@ mod tests { // We currently return `Todo` for all async comprehensions, // including comprehensions that have invalid syntax - assert_scope_ty(&db, "src/a.py", &["foo", ""], "x", "@Todo"); + assert_scope_ty( + &db, + "src/a.py", + &["foo", ""], + "x", + "@Todo(async iterables/iterators)", + ); Ok(()) } @@ -5938,7 +5944,13 @@ mod tests { )?; // TODO async iterables/iterators! --Alex - assert_scope_ty(&db, "src/a.py", &["foo", ""], "x", "@Todo"); + assert_scope_ty( + &db, + "src/a.py", + &["foo", ""], + "x", + "@Todo(async iterables/iterators)", + ); Ok(()) } diff --git a/crates/red_knot_python_semantic/src/types/mro.rs b/crates/red_knot_python_semantic/src/types/mro.rs index 6883b396c6..5d37617fa8 100644 --- a/crates/red_knot_python_semantic/src/types/mro.rs +++ b/crates/red_knot_python_semantic/src/types/mro.rs @@ -5,7 +5,7 @@ use itertools::Either; use rustc_hash::FxHashSet; use super::{Class, ClassLiteralType, KnownClass, KnownInstanceType, Type}; -use crate::Db; +use crate::{types::todo_type, Db}; /// The inferred method resolution order of a given class. /// @@ -354,7 +354,7 @@ impl<'db> ClassBase<'db> { match ty { Type::Any => Some(Self::Any), Type::Unknown => Some(Self::Unknown), - Type::Todo => Some(Self::Todo), + Type::Todo(_) => Some(Self::Todo), Type::ClassLiteral(ClassLiteralType { class }) => Some(Self::Class(class)), Type::Union(_) => None, // TODO -- forces consideration of multiple possible MROs? Type::Intersection(_) => None, // TODO -- probably incorrect? @@ -406,7 +406,7 @@ impl<'db> From> for Type<'db> { fn from(value: ClassBase<'db>) -> Self { match value { ClassBase::Any => Type::Any, - ClassBase::Todo => Type::Todo, + ClassBase::Todo => todo_type!(), ClassBase::Unknown => Type::Unknown, ClassBase::Class(class) => Type::class_literal(class), } diff --git a/crates/red_knot_python_semantic/src/types/signatures.rs b/crates/red_knot_python_semantic/src/types/signatures.rs index 976c472da4..406da42c25 100644 --- a/crates/red_knot_python_semantic/src/types/signatures.rs +++ b/crates/red_knot_python_semantic/src/types/signatures.rs @@ -1,7 +1,7 @@ #![allow(dead_code)] use super::{definition_expression_ty, Type}; -use crate::semantic_index::definition::Definition; use crate::Db; +use crate::{semantic_index::definition::Definition, types::todo_type}; use ruff_python_ast::{self as ast, name::Name}; /// A typed callable signature. @@ -18,7 +18,7 @@ impl<'db> Signature<'db> { pub(crate) fn todo() -> Self { Self { parameters: Parameters::todo(), - return_ty: Type::Todo, + return_ty: todo_type!("return type"), } } @@ -33,8 +33,7 @@ impl<'db> Signature<'db> { .as_ref() .map(|returns| { if function_node.is_async { - // TODO: generic `types.CoroutineType`! - Type::Todo + todo_type!("generic types.CoroutineType") } else { definition_expression_ty(db, definition, returns.as_ref()) } @@ -81,11 +80,11 @@ impl<'db> Parameters<'db> { Self { variadic: Some(Parameter { name: Some(Name::new_static("args")), - annotated_ty: Type::Todo, + annotated_ty: todo_type!(), }), keywords: Some(Parameter { name: Some(Name::new_static("kwargs")), - annotated_ty: Type::Todo, + annotated_ty: todo_type!(), }), ..Default::default() } diff --git a/crates/red_knot_python_semantic/src/types/unpacker.rs b/crates/red_knot_python_semantic/src/types/unpacker.rs index efbc0df6ff..2e8c39a9b2 100644 --- a/crates/red_knot_python_semantic/src/types/unpacker.rs +++ b/crates/red_knot_python_semantic/src/types/unpacker.rs @@ -6,7 +6,7 @@ use rustc_hash::FxHashMap; use crate::semantic_index::ast_ids::{HasScopedExpressionId, ScopedExpressionId}; use crate::semantic_index::symbol::ScopeId; -use crate::types::{Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder}; +use crate::types::{todo_type, Type, TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder}; use crate::Db; /// Unpacks the value expression type to their respective targets. @@ -59,7 +59,7 @@ impl<'db> Unpacker<'db> { // TODO: Combine the types into a list type. If the // starred_element_types is empty, then it should be `List[Any]`. // combine_types(starred_element_types); - element_types.push(Type::Todo); + element_types.push(todo_type!("starred unpacking")); element_types.extend_from_slice( // SAFETY: Safe because of the length check above. @@ -72,7 +72,7 @@ impl<'db> Unpacker<'db> { // index. element_types.resize(elts.len() - 1, Type::Unknown); // TODO: This should be `list[Unknown]` - element_types.insert(starred_index, Type::Todo); + element_types.insert(starred_index, todo_type!("starred unpacking")); Cow::Owned(element_types) } } else {