mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-29 16:03:50 +00:00
[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:
parent
9d8a4c0057
commit
6cdf996af6
4 changed files with 114 additions and 79 deletions
|
@ -233,31 +233,40 @@ fn declarations_ty<'db>(
|
|||
/// Unique ID for a type.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Type<'db> {
|
||||
/// the dynamic type: a statically-unknown set of values
|
||||
/// The dynamic type: a statically-unknown set of values
|
||||
Any,
|
||||
/// the empty set of values
|
||||
/// The empty set of values
|
||||
Never,
|
||||
/// unknown type (either no annotation, or some kind of type error)
|
||||
/// equivalent to Any, or possibly to object in strict mode
|
||||
/// Unknown type (either no annotation, or some kind of type error).
|
||||
/// Equivalent to Any, or possibly to object in strict mode
|
||||
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)
|
||||
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,
|
||||
/// 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>),
|
||||
/// The `typing.reveal_type` function, which has special `__call__` behavior.
|
||||
RevealTypeFunction(FunctionType<'db>),
|
||||
/// a specific module object
|
||||
/// A specific module object
|
||||
Module(File),
|
||||
/// a specific class object
|
||||
/// A specific class object
|
||||
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>),
|
||||
/// 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>),
|
||||
/// 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>),
|
||||
/// An integer literal
|
||||
IntLiteral(i64),
|
||||
|
@ -402,8 +411,8 @@ impl<'db> Type<'db> {
|
|||
return true;
|
||||
}
|
||||
match (self, target) {
|
||||
(Type::Unknown | Type::Any, _) => false,
|
||||
(_, Type::Unknown | Type::Any) => false,
|
||||
(Type::Unknown | Type::Any | Type::Todo, _) => false,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo) => false,
|
||||
(Type::Never, _) => true,
|
||||
(_, Type::Never) => false,
|
||||
(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
|
||||
pub(crate) fn is_assignable_to(self, db: &'db dyn Db, target: Type<'db>) -> bool {
|
||||
match (self, target) {
|
||||
(Type::Unknown | Type::Any, _) => true,
|
||||
(_, Type::Unknown | Type::Any) => true,
|
||||
(Type::Unknown | Type::Any | Type::Todo, _) => true,
|
||||
(_, Type::Unknown | Type::Any | Type::Todo) => true,
|
||||
(ty, Type::Union(union)) => union
|
||||
.elements(db)
|
||||
.iter()
|
||||
|
@ -475,53 +484,54 @@ impl<'db> Type<'db> {
|
|||
Type::Any => Type::Any,
|
||||
Type::Never => {
|
||||
// TODO: attribute lookup on Never type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::Unknown => Type::Unknown,
|
||||
Type::Unbound => Type::Unbound,
|
||||
Type::None => {
|
||||
// TODO: attribute lookup on None type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::Function(_) | Type::RevealTypeFunction(_) => {
|
||||
// TODO: attribute lookup on function type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::Module(file) => global_symbol_ty(db, *file, name),
|
||||
Type::Class(class) => class.class_member(db, name),
|
||||
Type::Instance(_) => {
|
||||
// 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::Intersection(_) => {
|
||||
// TODO perform the get_member on each type in the intersection
|
||||
// TODO return the intersection of those results
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::IntLiteral(_) => {
|
||||
// TODO raise error
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::BooleanLiteral(_) => Type::Unknown,
|
||||
Type::BooleanLiteral(_) => Type::Todo,
|
||||
Type::StringLiteral(_) => {
|
||||
// TODO defer to `typing.LiteralString`/`builtins.str` methods
|
||||
// from typeshed's stubs
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::LiteralString => {
|
||||
// TODO defer to `typing.LiteralString`/`builtins.str` methods
|
||||
// from typeshed's stubs
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::BytesLiteral(_) => {
|
||||
// TODO defer to Type::Instance(<bytes from typeshed>).member
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
Type::Tuple(_) => {
|
||||
// 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`.
|
||||
fn bool(&self, db: &'db dyn Db) -> Truthiness {
|
||||
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::Function(_) | Type::RevealTypeFunction(_) => Truthiness::AlwaysTrue,
|
||||
Type::Module(_) => Truthiness::AlwaysTrue,
|
||||
|
@ -602,11 +614,13 @@ impl<'db> Type<'db> {
|
|||
}
|
||||
|
||||
// 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`.
|
||||
Type::Any => CallOutcome::callable(Type::Any),
|
||||
|
||||
Type::Todo => CallOutcome::callable(Type::Todo),
|
||||
|
||||
Type::Unknown => CallOutcome::callable(Type::Unknown),
|
||||
|
||||
Type::Union(union) => CallOutcome::union(
|
||||
|
@ -619,7 +633,7 @@ impl<'db> Type<'db> {
|
|||
),
|
||||
|
||||
// TODO: intersection types
|
||||
Type::Intersection(_) => CallOutcome::callable(Type::Unknown),
|
||||
Type::Intersection(_) => CallOutcome::callable(Type::Todo),
|
||||
|
||||
_ => 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;
|
||||
// `__iter__` and `__next__` are both looked up on the class of the iterable:
|
||||
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> {
|
||||
match self {
|
||||
Type::Any => Type::Any,
|
||||
Type::Todo => Type::Todo,
|
||||
Type::Unknown => Type::Unknown,
|
||||
Type::Unbound => Type::Unknown,
|
||||
Type::Never => Type::Never,
|
||||
Type::Class(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::Unknown,
|
||||
Type::Intersection(_) => Type::Todo,
|
||||
// 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(_)
|
||||
|
@ -723,18 +744,19 @@ impl<'db> Type<'db> {
|
|||
Type::IntLiteral(_) => builtins_symbol_ty(db, "int"),
|
||||
Type::Function(_) | Type::RevealTypeFunction(_) => types_symbol_ty(db, "FunctionType"),
|
||||
Type::Module(_) => types_symbol_ty(db, "ModuleType"),
|
||||
Type::Tuple(_) => builtins_symbol_ty(db, "tuple"),
|
||||
Type::None => typeshed_symbol_ty(db, "NoneType"),
|
||||
// TODO not accurate if there's a custom metaclass...
|
||||
Type::Class(_) => builtins_symbol_ty(db, "type"),
|
||||
// TODO can we do better here? `type[LiteralString]`?
|
||||
Type::StringLiteral(_) | Type::LiteralString => builtins_symbol_ty(db, "str"),
|
||||
// TODO: `type[Any]`?
|
||||
Type::Any => Type::Any,
|
||||
Type::Any => Type::Todo,
|
||||
// TODO: `type[Unknown]`?
|
||||
Type::Unknown => Type::Unknown,
|
||||
Type::Unknown => Type::Todo,
|
||||
// TODO intersections
|
||||
Type::Intersection(_) => Type::Unknown,
|
||||
Type::Tuple(_) => builtins_symbol_ty(db, "tuple"),
|
||||
Type::Intersection(_) => Type::Todo,
|
||||
Type::Todo => Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1064,7 +1086,7 @@ impl<'db> FunctionType<'db> {
|
|||
// rather than from `bar`'s return annotation
|
||||
// in order to determine the type that `bar` returns
|
||||
if !function_stmt_node.decorator_list.is_empty() {
|
||||
return Type::Unknown;
|
||||
return Type::Todo;
|
||||
}
|
||||
|
||||
function_stmt_node
|
||||
|
@ -1073,7 +1095,7 @@ impl<'db> FunctionType<'db> {
|
|||
.map(|returns| {
|
||||
if function_stmt_node.is_async {
|
||||
// TODO: generic `types.CoroutineType`!
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
} else {
|
||||
definition_expression_ty(db, definition, returns.as_ref())
|
||||
}
|
||||
|
|
|
@ -215,6 +215,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
|
||||
/// Adds a positive type to this intersection.
|
||||
fn add_positive(&mut self, db: &'db dyn Db, ty: Type<'db>) {
|
||||
// TODO `Any`/`Unknown`/`Todo` actually should not self-cancel
|
||||
match ty {
|
||||
Type::Intersection(inter) => {
|
||||
let pos = inter.positive(db);
|
||||
|
@ -234,7 +235,7 @@ impl<'db> InnerIntersectionBuilder<'db> {
|
|||
|
||||
/// Adds a negative type to this intersection.
|
||||
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 {
|
||||
Type::Intersection(intersection) => {
|
||||
let pos = intersection.negative(db);
|
||||
|
|
|
@ -67,6 +67,9 @@ impl Display for DisplayRepresentation<'_> {
|
|||
Type::Unknown => f.write_str("Unknown"),
|
||||
Type::Unbound => f.write_str("Unbound"),
|
||||
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) => {
|
||||
write!(f, "<module '{:?}'>", file.path(self.db))
|
||||
}
|
||||
|
|
|
@ -782,7 +782,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::Unknown;
|
||||
let annotated_ty = Type::Todo;
|
||||
if parameter.annotation.is_some() {
|
||||
self.add_declaration_with_binding(
|
||||
parameter.into(),
|
||||
|
@ -968,6 +968,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
let node_ty = except_handler_definition
|
||||
.handled_exceptions()
|
||||
.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);
|
||||
|
||||
let symbol_ty = if except_handler_definition.is_star() {
|
||||
|
@ -983,7 +985,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
match node_ty {
|
||||
Type::Any | Type::Unknown => node_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`)
|
||||
// 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::Unknown);
|
||||
self.add_binding(pattern.into(), definition, Type::Todo);
|
||||
}
|
||||
|
||||
fn infer_match_pattern(&mut self, pattern: &ast::Pattern) {
|
||||
|
@ -1200,7 +1202,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.infer_expression(target);
|
||||
|
||||
// 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) {
|
||||
|
@ -1302,7 +1304,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
let loop_var_value_ty = if is_async {
|
||||
// TODO(Alex): async iterables/iterators!
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
|
@ -1816,7 +1818,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.infer_first_comprehension_iter(generators);
|
||||
|
||||
// TODO generator type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO list type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO dict type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO set type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_generator_expression_scope(&mut self, generator: &ast::ExprGenerator) {
|
||||
|
@ -1971,7 +1973,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
|
||||
let target_ty = if is_async {
|
||||
// TODO: async iterables/iterators! -- Alex
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
} else {
|
||||
iterable_ty
|
||||
.iterate(self.db)
|
||||
|
@ -2050,7 +2052,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
}
|
||||
|
||||
// TODO function type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
// TODO awaitable type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
// TODO get type from `ReturnType` of generator
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_await_expression(&mut self, await_expression: &ast::ExprAwait) -> Type<'db> {
|
||||
|
@ -2111,7 +2113,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.infer_expression(value);
|
||||
|
||||
// TODO awaitable type
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
/// 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),
|
||||
|
||||
_ => 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);
|
||||
|
||||
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::Unknown, _, _) | (_, Type::Unknown, _) => Type::Unknown,
|
||||
|
||||
|
@ -2306,8 +2310,8 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
(Type::IntLiteral(n), Type::IntLiteral(m), ast::Operator::Mod) => n
|
||||
.checked_rem(m)
|
||||
.map(Type::IntLiteral)
|
||||
// TODO: division by zero error
|
||||
.unwrap_or(Type::Unknown),
|
||||
// TODO division by zero error
|
||||
.unwrap_or(Type::Todo),
|
||||
|
||||
(Type::BytesLiteral(lhs), Type::BytesLiteral(rhs), ast::Operator::Add) => {
|
||||
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() {
|
||||
self.infer_expression(right);
|
||||
}
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
fn infer_subscript_expression(&mut self, subscript: &ast::ExprSubscript) -> Type<'db> {
|
||||
|
@ -2544,7 +2548,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
Type::Unknown
|
||||
})
|
||||
}
|
||||
_ => Type::Unknown,
|
||||
_ => Type::Todo,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2561,7 +2565,7 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
self.infer_optional_expression(step.as_deref());
|
||||
|
||||
// TODO slice
|
||||
Type::Unknown
|
||||
Type::Todo
|
||||
}
|
||||
|
||||
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
|
||||
// can be annotation expressions distinct from type expressions.
|
||||
// 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`.
|
||||
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.
|
||||
// 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
|
||||
// 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.
|
||||
// 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::Unknown,
|
||||
ast::Expr::NumberLiteral(_literal) => Type::Unknown,
|
||||
ast::Expr::BooleanLiteral(_literal) => Type::Unknown,
|
||||
ast::Expr::BytesLiteral(_literal) => Type::Todo,
|
||||
ast::Expr::NumberLiteral(_literal) => Type::Todo,
|
||||
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
|
||||
// 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);
|
||||
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) => {
|
||||
self.infer_starred_expression(starred);
|
||||
Type::Unknown
|
||||
|
@ -3911,7 +3916,7 @@ mod tests {
|
|||
)?;
|
||||
|
||||
// TODO: Generic `types.CoroutineType`!
|
||||
assert_public_ty(&db, "src/a.py", "x", "Unknown");
|
||||
assert_public_ty(&db, "src/a.py", "x", "@Todo");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -3940,7 +3945,7 @@ mod tests {
|
|||
)?;
|
||||
|
||||
// TODO: should be `int`!
|
||||
assert_public_ty(&db, "src/a.py", "x", "Unknown");
|
||||
assert_public_ty(&db, "src/a.py", "x", "@Todo");
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -5466,7 +5473,7 @@ mod tests {
|
|||
)?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
@ -5596,9 +5603,9 @@ mod tests {
|
|||
// For these TODOs we need support for `tuple` types:
|
||||
|
||||
// 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
|
||||
assert_public_ty(&db, "src/a.py", "e", "Unknown");
|
||||
assert_public_ty(&db, "src/a.py", "e", "@Todo");
|
||||
|
||||
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(())
|
||||
}
|
||||
|
@ -6025,7 +6034,7 @@ mod tests {
|
|||
)?;
|
||||
|
||||
// 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(())
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue