diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 7baf170c91..f3ebb696a6 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -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().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()) } diff --git a/crates/red_knot_python_semantic/src/types/builder.rs b/crates/red_knot_python_semantic/src/types/builder.rs index 8dcea30ca9..f264dc4f8f 100644 --- a/crates/red_knot_python_semantic/src/types/builder.rs +++ b/crates/red_knot_python_semantic/src/types/builder.rs @@ -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); diff --git a/crates/red_knot_python_semantic/src/types/display.rs b/crates/red_knot_python_semantic/src/types/display.rs index 3d037fe658..e1677a8d71 100644 --- a/crates/red_knot_python_semantic/src/types/display.rs +++ b/crates/red_knot_python_semantic/src/types/display.rs @@ -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, "", 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 cbd57540fe..f02eb15758 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -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", ""], "x", "Unknown"); + // We currently return `Todo` for all async comprehensions, + // including comprehensions that have invalid syntax + assert_scope_ty(&db, "src/a.py", &["foo", ""], "x", "@Todo"); Ok(()) } @@ -6025,7 +6034,7 @@ mod tests { )?; // TODO async iterables/iterators! --Alex - assert_scope_ty(&db, "src/a.py", &["foo", ""], "x", "Unknown"); + assert_scope_ty(&db, "src/a.py", &["foo", ""], "x", "@Todo"); Ok(()) }