[red-knot] feat: introduce a new [Type::Todo] variant (#13548)

This variant shows inference that is not yet implemented..

## Summary

PR #13500 reopened the idea of adding a new type variant to keep track
of not-implemented features in Red Knot.

It was based off of #12986 with a more generic approach of keeping track
of different kind of unknowns. Discussion in #13500 agreed that keeping
track of different `Unknown` is complicated for now, and this feature is
better achieved through a new variant of `Type`.

### Requirements

Requirements for this implementation can be summed up with some extracts
of comment from @carljm on the previous PR

> So at the moment we are leaning towards simplifying this PR to just
use a new top-level variant, which behaves like Any and Unknown but
represents inference that is not yet implemented in red-knot.

> I think the general rule should be that Todo should propagate only
when the presence of the input Todo caused the output to be unknown.
>
> To take a specific example, the inferred result of addition must be
Unknown if either operand is Unknown. That is, Unknown + X will always
be Unknown regardless of what X is. (Same for X + Unknown.) In this
case, I believe that Unknown + Todo (or Todo + Unknown) should result in
Unknown, not result in Todo. If we fix the upstream source of the Todo,
the result would still be Unknown, so it's not useful to propagate the
Todo in this case: it wrongly suggests that the output is unknown
because of a todo item.

## Test Plan

This PR does not introduce new tests, but it did required to edit some
tests with the display of `[Type::Todo]` (currently `@Todo`), which
suggests that those test are placeholders requirements for features we
don't support yet.
This commit is contained in:
Simon 2024-09-30 23:28:06 +02:00 committed by GitHub
parent 9d8a4c0057
commit 6cdf996af6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 114 additions and 79 deletions

View file

@ -233,31 +233,40 @@ fn declarations_ty<'db>(
/// Unique ID for a type. /// Unique ID for a type.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Type<'db> { pub enum Type<'db> {
/// the dynamic type: a statically-unknown set of values /// The dynamic type: a statically-unknown set of values
Any, Any,
/// the empty set of values /// The empty set of values
Never, Never,
/// unknown type (either no annotation, or some kind of type error) /// Unknown type (either no annotation, or some kind of type error).
/// equivalent to Any, or possibly to object in strict mode /// Equivalent to Any, or possibly to object in strict mode
Unknown, Unknown,
/// name does not exist or is not bound to any value (this represents an error, but with some /// Name does not exist or is not bound to any value (this represents an error, but with some
/// leniency options it could be silently resolved to Unknown in some cases) /// leniency options it could be silently resolved to Unknown in some cases)
Unbound, Unbound,
/// the None object -- TODO remove this in favor of Instance(types.NoneType) /// The None object -- TODO remove this in favor of Instance(types.NoneType)
None, None,
/// a specific function object /// Temporary type for symbols that can't be inferred yet because of missing implementations.
/// Behaves equivalently to `Any`.
///
/// This variant should eventually be removed once red-knot is spec-compliant.
///
/// 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,
/// A specific function object
Function(FunctionType<'db>), Function(FunctionType<'db>),
/// The `typing.reveal_type` function, which has special `__call__` behavior. /// The `typing.reveal_type` function, which has special `__call__` behavior.
RevealTypeFunction(FunctionType<'db>), RevealTypeFunction(FunctionType<'db>),
/// a specific module object /// A specific module object
Module(File), Module(File),
/// a specific class object /// A specific class object
Class(ClassType<'db>), Class(ClassType<'db>),
/// the set of Python objects with the given class in their __class__'s method resolution order /// The set of Python objects with the given class in their __class__'s method resolution order
Instance(ClassType<'db>), Instance(ClassType<'db>),
/// the set of objects in any of the types in the union /// The set of objects in any of the types in the union
Union(UnionType<'db>), Union(UnionType<'db>),
/// the set of objects in all of the types in the intersection /// The set of objects in all of the types in the intersection
Intersection(IntersectionType<'db>), Intersection(IntersectionType<'db>),
/// An integer literal /// An integer literal
IntLiteral(i64), IntLiteral(i64),
@ -402,8 +411,8 @@ impl<'db> Type<'db> {
return true; return true;
} }
match (self, target) { match (self, target) {
(Type::Unknown | Type::Any, _) => false, (Type::Unknown | Type::Any | Type::Todo, _) => false,
(_, Type::Unknown | Type::Any) => false, (_, Type::Unknown | Type::Any | Type::Todo) => false,
(Type::Never, _) => true, (Type::Never, _) => true,
(_, Type::Never) => false, (_, Type::Never) => false,
(Type::IntLiteral(_), Type::Instance(class)) (Type::IntLiteral(_), Type::Instance(class))
@ -438,8 +447,8 @@ impl<'db> Type<'db> {
/// [assignable to]: https://typing.readthedocs.io/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation /// [assignable to]: https://typing.readthedocs.io/en/latest/spec/concepts.html#the-assignable-to-or-consistent-subtyping-relation
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool { pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
match (self, target) { match (self, target) {
(Type::Unknown | Type::Any, _) => true, (Type::Unknown | Type::Any | Type::Todo, _) => true,
(_, Type::Unknown | Type::Any) => true, (_, Type::Unknown | Type::Any | Type::Todo) => true,
(ty, Type::Union(union)) => union (ty, Type::Union(union)) => union
.elements(db) .elements(db)
.iter() .iter()
@ -475,53 +484,54 @@ impl<'db> Type<'db> {
Type::Any => Type::Any, Type::Any => Type::Any,
Type::Never => { Type::Never => {
// TODO: attribute lookup on Never type // TODO: attribute lookup on Never type
Type::Unknown Type::Todo
} }
Type::Unknown => Type::Unknown, Type::Unknown => Type::Unknown,
Type::Unbound => Type::Unbound, Type::Unbound => Type::Unbound,
Type::None => { Type::None => {
// TODO: attribute lookup on None type // TODO: attribute lookup on None type
Type::Unknown Type::Todo
} }
Type::Function(_) | Type::RevealTypeFunction(_) => { Type::Function(_) | Type::RevealTypeFunction(_) => {
// TODO: attribute lookup on function type // TODO: attribute lookup on function type
Type::Unknown Type::Todo
} }
Type::Module(file) => global_symbol_ty(db, *file, name), Type::Module(file) => global_symbol_ty(db, *file, name),
Type::Class(class) => class.class_member(db, name), Type::Class(class) => class.class_member(db, name),
Type::Instance(_) => { Type::Instance(_) => {
// TODO MRO? get_own_instance_member, get_instance_member // TODO MRO? get_own_instance_member, get_instance_member
Type::Unknown Type::Todo
} }
Type::Union(union) => union.map(db, |element| element.member(db, name)), Type::Union(union) => union.map(db, |element| element.member(db, name)),
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
Type::Unknown Type::Todo
} }
Type::IntLiteral(_) => { Type::IntLiteral(_) => {
// TODO raise error // TODO raise error
Type::Unknown Type::Todo
} }
Type::BooleanLiteral(_) => Type::Unknown, Type::BooleanLiteral(_) => Type::Todo,
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
Type::Unknown Type::Todo
} }
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
Type::Unknown Type::Todo
} }
Type::BytesLiteral(_) => { Type::BytesLiteral(_) => {
// TODO defer to Type::Instance(<bytes from typeshed>).member // TODO defer to Type::Instance(<bytes from typeshed>).member
Type::Unknown Type::Todo
} }
Type::Tuple(_) => { Type::Tuple(_) => {
// TODO: implement tuple methods // TODO: implement tuple methods
Type::Unknown Type::Todo
} }
Type::Todo => Type::Todo,
} }
} }
@ -531,7 +541,9 @@ impl<'db> Type<'db> {
/// when `bool(x)` is called on an object `x`. /// when `bool(x)` is called on an object `x`.
fn bool(&self, db: &'db dyn Db) -> Truthiness { fn bool(&self, db: &'db dyn Db) -> Truthiness {
match self { match self {
Type::Any | Type::Never | Type::Unknown | Type::Unbound => Truthiness::Ambiguous, Type::Any | Type::Todo | Type::Never | Type::Unknown | Type::Unbound => {
Truthiness::Ambiguous
}
Type::None => Truthiness::AlwaysFalse, Type::None => Truthiness::AlwaysFalse,
Type::Function(_) | Type::RevealTypeFunction(_) => Truthiness::AlwaysTrue, Type::Function(_) | Type::RevealTypeFunction(_) => Truthiness::AlwaysTrue,
Type::Module(_) => Truthiness::AlwaysTrue, Type::Module(_) => Truthiness::AlwaysTrue,
@ -602,11 +614,13 @@ impl<'db> Type<'db> {
} }
// TODO: handle classes which implement the `__call__` protocol // TODO: handle classes which implement the `__call__` protocol
Type::Instance(_instance_ty) => CallOutcome::callable(Type::Unknown), Type::Instance(_instance_ty) => CallOutcome::callable(Type::Todo),
// `Any` is callable, and its return type is also `Any`. // `Any` is callable, and its return type is also `Any`.
Type::Any => CallOutcome::callable(Type::Any), Type::Any => CallOutcome::callable(Type::Any),
Type::Todo => CallOutcome::callable(Type::Todo),
Type::Unknown => CallOutcome::callable(Type::Unknown), Type::Unknown => CallOutcome::callable(Type::Unknown),
Type::Union(union) => CallOutcome::union( Type::Union(union) => CallOutcome::union(
@ -619,7 +633,7 @@ impl<'db> Type<'db> {
), ),
// TODO: intersection types // TODO: intersection types
Type::Intersection(_) => CallOutcome::callable(Type::Unknown), Type::Intersection(_) => CallOutcome::callable(Type::Todo),
_ => CallOutcome::not_callable(self), _ => CallOutcome::not_callable(self),
} }
@ -640,6 +654,12 @@ impl<'db> Type<'db> {
}; };
} }
if let Type::Unknown | Type::Any = self {
// 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 };
}
// `self` represents the type of the iterable; // `self` represents the type of the iterable;
// `__iter__` and `__next__` are both looked up on the class of the iterable: // `__iter__` and `__next__` are both looked up on the class of the iterable:
let iterable_meta_type = self.to_meta_type(db); let iterable_meta_type = self.to_meta_type(db);
@ -686,13 +706,14 @@ impl<'db> Type<'db> {
pub fn to_instance(&self, db: &'db dyn Db) -> Type<'db> { pub fn to_instance(&self, db: &'db dyn Db) -> Type<'db> {
match self { match self {
Type::Any => Type::Any, Type::Any => Type::Any,
Type::Todo => Type::Todo,
Type::Unknown => Type::Unknown, Type::Unknown => Type::Unknown,
Type::Unbound => Type::Unknown, Type::Unbound => Type::Unknown,
Type::Never => Type::Never, Type::Never => Type::Never,
Type::Class(class) => Type::Instance(*class), Type::Class(class) => Type::Instance(*class),
Type::Union(union) => union.map(db, |element| element.to_instance(db)), Type::Union(union) => union.map(db, |element| element.to_instance(db)),
// TODO: we can probably do better here: --Alex // TODO: we can probably do better here: --Alex
Type::Intersection(_) => Type::Unknown, Type::Intersection(_) => Type::Todo,
// TODO: calling `.to_instance()` on any of these should result in a diagnostic, // 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: // since they already indicate that the object is an instance of some kind:
Type::BooleanLiteral(_) Type::BooleanLiteral(_)
@ -723,18 +744,19 @@ impl<'db> Type<'db> {
Type::IntLiteral(_) => builtins_symbol_ty(db, "int"), Type::IntLiteral(_) => builtins_symbol_ty(db, "int"),
Type::Function(_) | Type::RevealTypeFunction(_) => types_symbol_ty(db, "FunctionType"), Type::Function(_) | Type::RevealTypeFunction(_) => types_symbol_ty(db, "FunctionType"),
Type::Module(_) => types_symbol_ty(db, "ModuleType"), Type::Module(_) => types_symbol_ty(db, "ModuleType"),
Type::Tuple(_) => builtins_symbol_ty(db, "tuple"),
Type::None => typeshed_symbol_ty(db, "NoneType"), Type::None => typeshed_symbol_ty(db, "NoneType"),
// TODO not accurate if there's a custom metaclass... // TODO not accurate if there's a custom metaclass...
Type::Class(_) => builtins_symbol_ty(db, "type"), Type::Class(_) => builtins_symbol_ty(db, "type"),
// TODO can we do better here? `type[LiteralString]`? // TODO can we do better here? `type[LiteralString]`?
Type::StringLiteral(_) | Type::LiteralString => builtins_symbol_ty(db, "str"), Type::StringLiteral(_) | Type::LiteralString => builtins_symbol_ty(db, "str"),
// TODO: `type[Any]`? // TODO: `type[Any]`?
Type::Any => Type::Any, Type::Any => Type::Todo,
// TODO: `type[Unknown]`? // TODO: `type[Unknown]`?
Type::Unknown => Type::Unknown, Type::Unknown => Type::Todo,
// TODO intersections // TODO intersections
Type::Intersection(_) => Type::Unknown, Type::Intersection(_) => Type::Todo,
Type::Tuple(_) => builtins_symbol_ty(db, "tuple"), Type::Todo => Type::Todo,
} }
} }
@ -1064,7 +1086,7 @@ impl<'db> FunctionType<'db> {
// rather than from `bar`'s return annotation // rather than from `bar`'s return annotation
// in order to determine the type that `bar` returns // in order to determine the type that `bar` returns
if !function_stmt_node.decorator_list.is_empty() { if !function_stmt_node.decorator_list.is_empty() {
return Type::Unknown; return Type::Todo;
} }
function_stmt_node function_stmt_node
@ -1073,7 +1095,7 @@ impl<'db> FunctionType<'db> {
.map(|returns| { .map(|returns| {
if function_stmt_node.is_async { if function_stmt_node.is_async {
// TODO: generic `types.CoroutineType`! // TODO: generic `types.CoroutineType`!
Type::Unknown Type::Todo
} else { } else {
definition_expression_ty(db, definition, returns.as_ref()) definition_expression_ty(db, definition, returns.as_ref())
} }

View file

@ -215,6 +215,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
/// Adds a positive type to this intersection. /// Adds a positive type to this intersection.
fn add_positive(&mut self, db: &'db dyn Db, ty: Type<'db>) { fn add_positive(&mut self, db: &'db dyn Db, ty: Type<'db>) {
// TODO `Any`/`Unknown`/`Todo` actually should not self-cancel
match ty { match ty {
Type::Intersection(inter) => { Type::Intersection(inter) => {
let pos = inter.positive(db); let pos = inter.positive(db);
@ -234,7 +235,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
/// Adds a negative type to this intersection. /// Adds a negative type to this intersection.
fn add_negative(&mut self, db: &'db dyn Db, ty: Type<'db>) { fn add_negative(&mut self, db: &'db dyn Db, ty: Type<'db>) {
// TODO Any/Unknown actually should not self-cancel // TODO `Any`/`Unknown`/`Todo` actually should not self-cancel
match ty { match ty {
Type::Intersection(intersection) => { Type::Intersection(intersection) => {
let pos = intersection.negative(db); let pos = intersection.negative(db);

View file

@ -67,6 +67,9 @@ impl Display for DisplayRepresentation<'_> {
Type::Unknown => f.write_str("Unknown"), Type::Unknown => f.write_str("Unknown"),
Type::Unbound => f.write_str("Unbound"), Type::Unbound => f.write_str("Unbound"),
Type::None => f.write_str("None"), Type::None => f.write_str("None"),
// `[Type::Todo]`'s display should be explicit that is not a valid display of
// any other type
Type::Todo => f.write_str("@Todo"),
Type::Module(file) => { Type::Module(file) => {
write!(f, "<module '{:?}'>", file.path(self.db)) write!(f, "<module '{:?}'>", file.path(self.db))
} }

View file

@ -782,7 +782,7 @@ impl<'db> TypeInferenceBuilder<'db> {
) { ) {
// TODO(dhruvmanila): Annotation expression is resolved at the enclosing scope, infer the // TODO(dhruvmanila): Annotation expression is resolved at the enclosing scope, infer the
// parameter type from there // parameter type from there
let annotated_ty = Type::Unknown; let annotated_ty = Type::Todo;
if parameter.annotation.is_some() { if parameter.annotation.is_some() {
self.add_declaration_with_binding( self.add_declaration_with_binding(
parameter.into(), parameter.into(),
@ -968,6 +968,8 @@ impl<'db> TypeInferenceBuilder<'db> {
let node_ty = except_handler_definition let node_ty = except_handler_definition
.handled_exceptions() .handled_exceptions()
.map(|ty| self.infer_expression(ty)) .map(|ty| self.infer_expression(ty))
// If there is no handled exception, it's invalid syntax;
// a diagnostic will have already been emitted
.unwrap_or(Type::Unknown); .unwrap_or(Type::Unknown);
let symbol_ty = if except_handler_definition.is_star() { let symbol_ty = if except_handler_definition.is_star() {
@ -983,7 +985,7 @@ impl<'db> TypeInferenceBuilder<'db> {
match node_ty { match node_ty {
Type::Any | Type::Unknown => node_ty, Type::Any | Type::Unknown => node_ty,
Type::Class(class_ty) => Type::Instance(class_ty), Type::Class(class_ty) => Type::Instance(class_ty),
_ => Type::Unknown, _ => Type::Todo,
} }
}; };
@ -1028,7 +1030,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// against the subject expression type (which we can query via `infer_expression_types`) // 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 // and extract the type at the `index` position if the pattern matches. This will be
// similar to the logic in `self.infer_assignment_definition`. // similar to the logic in `self.infer_assignment_definition`.
self.add_binding(pattern.into(), definition, Type::Unknown); self.add_binding(pattern.into(), definition, Type::Todo);
} }
fn infer_match_pattern(&mut self, pattern: &ast::Pattern) { fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
@ -1200,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_expression(target); self.infer_expression(target);
// TODO(dhruvmanila): Resolve the target type using the value type and the operator // TODO(dhruvmanila): Resolve the target type using the value type and the operator
Type::Unknown Type::Todo
} }
fn infer_type_alias_statement(&mut self, type_alias_statement: &ast::StmtTypeAlias) { fn infer_type_alias_statement(&mut self, type_alias_statement: &ast::StmtTypeAlias) {
@ -1302,7 +1304,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let loop_var_value_ty = if is_async { let loop_var_value_ty = if is_async {
// TODO(Alex): async iterables/iterators! // TODO(Alex): async iterables/iterators!
Type::Unknown Type::Todo
} else { } else {
iterable_ty iterable_ty
.iterate(self.db) .iterate(self.db)
@ -1816,7 +1818,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
// TODO generator type // TODO generator type
Type::Unknown Type::Todo
} }
fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> { fn infer_list_comprehension_expression(&mut self, listcomp: &ast::ExprListComp) -> Type<'db> {
@ -1829,7 +1831,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
// TODO list type // TODO list type
Type::Unknown Type::Todo
} }
fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> { fn infer_dict_comprehension_expression(&mut self, dictcomp: &ast::ExprDictComp) -> Type<'db> {
@ -1843,7 +1845,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
// TODO dict type // TODO dict type
Type::Unknown Type::Todo
} }
fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> { fn infer_set_comprehension_expression(&mut self, setcomp: &ast::ExprSetComp) -> Type<'db> {
@ -1856,7 +1858,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_first_comprehension_iter(generators); self.infer_first_comprehension_iter(generators);
// TODO set type // TODO set type
Type::Unknown Type::Todo
} }
fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) { fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) {
@ -1971,7 +1973,7 @@ impl<'db> TypeInferenceBuilder<'db> {
let target_ty = if is_async { let target_ty = if is_async {
// TODO: async iterables/iterators! -- Alex // TODO: async iterables/iterators! -- Alex
Type::Unknown Type::Todo
} else { } else {
iterable_ty iterable_ty
.iterate(self.db) .iterate(self.db)
@ -2050,7 +2052,7 @@ impl<'db> TypeInferenceBuilder<'db> {
} }
// TODO function type // TODO function type
Type::Unknown Type::Todo
} }
fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> { fn infer_call_expression(&mut self, call_expression: &ast::ExprCall) -> Type<'db> {
@ -2081,7 +2083,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_with_diagnostic(value.as_ref().into(), self); .unwrap_with_diagnostic(value.as_ref().into(), self);
// TODO // TODO
Type::Unknown Type::Todo
} }
fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> { fn infer_yield_expression(&mut self, yield_expression: &ast::ExprYield) -> Type<'db> {
@ -2090,7 +2092,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_optional_expression(value.as_deref()); self.infer_optional_expression(value.as_deref());
// TODO awaitable type // TODO awaitable type
Type::Unknown Type::Todo
} }
fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> { fn infer_yield_from_expression(&mut self, yield_from: &ast::ExprYieldFrom) -> Type<'db> {
@ -2102,7 +2104,7 @@ impl<'db> TypeInferenceBuilder<'db> {
.unwrap_with_diagnostic(value.as_ref().into(), self); .unwrap_with_diagnostic(value.as_ref().into(), self);
// TODO get type from `ReturnType` of generator // TODO get type from `ReturnType` of generator
Type::Unknown Type::Todo
} }
fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> { fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> {
@ -2111,7 +2113,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_expression(value); self.infer_expression(value);
// TODO awaitable type // TODO awaitable type
Type::Unknown Type::Todo
} }
/// Look up a name reference that isn't bound in the local scope. /// Look up a name reference that isn't bound in the local scope.
@ -2255,7 +2257,7 @@ impl<'db> TypeInferenceBuilder<'db> {
(UnaryOp::Not, ty) => ty.bool(self.db).negate().into_type(self.db), (UnaryOp::Not, ty) => ty.bool(self.db).negate().into_type(self.db),
_ => Type::Unknown, // TODO other unary op types _ => Type::Todo, // TODO other unary op types
} }
} }
@ -2271,6 +2273,8 @@ impl<'db> TypeInferenceBuilder<'db> {
let right_ty = self.infer_expression(right); let right_ty = self.infer_expression(right);
match (left_ty, right_ty, op) { match (left_ty, right_ty, op) {
// When interacting with Todo, Any and Unknown should propagate (as if we fix this
// `Todo` in the future, the result would then become Any or Unknown, respectively.)
(Type::Any, _, _) | (_, Type::Any, _) => Type::Any, (Type::Any, _, _) | (_, Type::Any, _) => Type::Any,
(Type::Unknown, _, _) | (_, Type::Unknown, _) => Type::Unknown, (Type::Unknown, _, _) | (_, Type::Unknown, _) => Type::Unknown,
@ -2306,8 +2310,8 @@ impl<'db> TypeInferenceBuilder<'db> {
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mod) => n (Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mod) => n
.checked_rem(m) .checked_rem(m)
.map(Type::IntLiteral) .map(Type::IntLiteral)
// TODO: division by zero error // TODO division by zero error
.unwrap_or(Type::Unknown), .unwrap_or(Type::Todo),
(Type::BytesLiteral(lhs), Type::BytesLiteral(rhs), ast::Operator::Add) => { (Type::BytesLiteral(lhs), Type::BytesLiteral(rhs), ast::Operator::Add) => {
Type::BytesLiteral(BytesLiteralType::new( Type::BytesLiteral(BytesLiteralType::new(
@ -2363,7 +2367,7 @@ impl<'db> TypeInferenceBuilder<'db> {
} }
} }
_ => Type::Unknown, // TODO _ => Type::Todo, // TODO
} }
} }
@ -2414,7 +2418,7 @@ impl<'db> TypeInferenceBuilder<'db> {
for right in comparators.as_ref() { for right in comparators.as_ref() {
self.infer_expression(right); self.infer_expression(right);
} }
Type::Unknown Type::Todo
} }
fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> { fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
@ -2544,7 +2548,7 @@ impl<'db> TypeInferenceBuilder<'db> {
Type::Unknown Type::Unknown
}) })
} }
_ => Type::Unknown, _ => Type::Todo,
} }
} }
@ -2561,7 +2565,7 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_optional_expression(step.as_deref()); self.infer_optional_expression(step.as_deref());
// TODO slice // TODO slice
Type::Unknown Type::Todo
} }
fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) { fn infer_type_parameters(&mut self, type_parameters: &ast::TypeParams) {
@ -2643,7 +2647,7 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: parse the expression and check whether it is a string annotation, since they // TODO: parse the expression and check whether it is a string annotation, since they
// can be annotation expressions distinct from type expressions. // can be annotation expressions distinct from type expressions.
// https://typing.readthedocs.io/en/latest/spec/annotations.html#string-annotations // https://typing.readthedocs.io/en/latest/spec/annotations.html#string-annotations
ast::Expr::StringLiteral(_literal) => Type::Unknown, ast::Expr::StringLiteral(_literal) => Type::Todo,
// Annotation expressions also get special handling for `*args` and `**kwargs`. // Annotation expressions also get special handling for `*args` and `**kwargs`.
ast::Expr::Starred(starred) => self.infer_starred_expression(starred), ast::Expr::Starred(starred) => self.infer_starred_expression(starred),
@ -2677,18 +2681,24 @@ impl<'db> TypeInferenceBuilder<'db> {
// TODO: parse the expression and check whether it is a string annotation. // TODO: parse the expression and check whether it is a string annotation.
// https://typing.readthedocs.io/en/latest/spec/annotations.html#string-annotations // https://typing.readthedocs.io/en/latest/spec/annotations.html#string-annotations
ast::Expr::StringLiteral(_literal) => Type::Unknown, ast::Expr::StringLiteral(_literal) => Type::Todo,
// TODO: an Ellipsis literal *on its own* does not have any meaning in annotation // 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. // expressions, but is meaningful in the context of a number of special forms.
ast::Expr::EllipsisLiteral(_literal) => Type::Unknown, ast::Expr::EllipsisLiteral(_literal) => Type::Todo,
// Other literals do not have meaningful values in the annotation expression context. // 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, // 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. // since (e.g.) `123` is not valid in an annotation expression but `Literal[123]` is.
ast::Expr::BytesLiteral(_literal) => Type::Unknown, ast::Expr::BytesLiteral(_literal) => Type::Todo,
ast::Expr::NumberLiteral(_literal) => Type::Unknown, ast::Expr::NumberLiteral(_literal) => Type::Todo,
ast::Expr::BooleanLiteral(_literal) => Type::Unknown, ast::Expr::BooleanLiteral(_literal) => Type::Todo,
// TODO: this may be a place we need to revisit with special forms.
ast::Expr::Subscript(subscript) => {
self.infer_subscript_expression(subscript);
Type::Todo
}
// Forms which are invalid in the context of annotation expressions: we infer their // Forms which are invalid in the context of annotation expressions: we infer their
// nested expressions as normal expressions, but the type of the top-level expression is // nested expressions as normal expressions, but the type of the top-level expression is
@ -2770,11 +2780,6 @@ impl<'db> TypeInferenceBuilder<'db> {
self.infer_attribute_expression(attribute); self.infer_attribute_expression(attribute);
Type::Unknown Type::Unknown
} }
// TODO: this may be a place we need to revisit with special forms.
ast::Expr::Subscript(subscript) => {
self.infer_subscript_expression(subscript);
Type::Unknown
}
ast::Expr::Starred(starred) => { ast::Expr::Starred(starred) => {
self.infer_starred_expression(starred); self.infer_starred_expression(starred);
Type::Unknown Type::Unknown
@ -3911,7 +3916,7 @@ mod tests {
)?; )?;
// TODO: Generic `types.CoroutineType`! // TODO: Generic `types.CoroutineType`!
assert_public_ty(&db, "src/a.py", "x", "Unknown"); assert_public_ty(&db, "src/a.py", "x", "@Todo");
Ok(()) Ok(())
} }
@ -3940,7 +3945,7 @@ mod tests {
)?; )?;
// TODO: should be `int`! // TODO: should be `int`!
assert_public_ty(&db, "src/a.py", "x", "Unknown"); assert_public_ty(&db, "src/a.py", "x", "@Todo");
Ok(()) Ok(())
} }
@ -5439,7 +5444,9 @@ mod tests {
", ",
)?; )?;
assert_scope_ty(&db, "src/a.py", &["foo"], "x", "Unbound | Unknown"); // We currently return `Todo` for all `async for` loops,
// including loops that have invalid syntax
assert_scope_ty(&db, "src/a.py", &["foo"], "x", "Unbound | @Todo");
Ok(()) Ok(())
} }
@ -5466,7 +5473,7 @@ mod tests {
)?; )?;
// TODO(Alex) async iterables/iterators! // TODO(Alex) async iterables/iterators!
assert_scope_ty(&db, "src/a.py", &["foo"], "x", "Unbound | Unknown"); assert_scope_ty(&db, "src/a.py", &["foo"], "x", "Unbound | @Todo");
Ok(()) Ok(())
} }
@ -5596,9 +5603,9 @@ mod tests {
// For these TODOs we need support for `tuple` types: // For these TODOs we need support for `tuple` types:
// TODO: Should be `RuntimeError | OSError` --Alex // TODO: Should be `RuntimeError | OSError` --Alex
assert_public_ty(&db, "src/a.py", "e", "Unknown"); assert_public_ty(&db, "src/a.py", "e", "@Todo");
// TODO: Should be `AttributeError | TypeError` --Alex // TODO: Should be `AttributeError | TypeError` --Alex
assert_public_ty(&db, "src/a.py", "e", "Unknown"); assert_public_ty(&db, "src/a.py", "e", "@Todo");
Ok(()) Ok(())
} }
@ -6001,7 +6008,9 @@ mod tests {
", ",
)?; )?;
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "Unknown"); // We currently return `Todo` for all async comprehensions,
// including comprehensions that have invalid syntax
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "@Todo");
Ok(()) Ok(())
} }
@ -6025,7 +6034,7 @@ mod tests {
)?; )?;
// TODO async iterables/iterators! --Alex // TODO async iterables/iterators! --Alex
assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "Unknown"); assert_scope_ty(&db, "src/a.py", &["foo", "<listcomp>"], "x", "@Todo");
Ok(()) Ok(())
} }