diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md index d16f496cc5f7b..71b06a24f280b 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/annotated.md @@ -43,6 +43,10 @@ def _(flag: bool): def f(y: X): reveal_type(y) # revealed: Unknown | bool +# error: [invalid-type-form] "`Annotated` requires at least two arguments when used in an annotation or type expression" +def _(x: Annotated | bool): + reveal_type(x) # revealed: Unknown | bool + # error: [invalid-type-form] def _(x: Annotated[()]): reveal_type(x) # revealed: Unknown diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index 90de636acbfd1..580ebf5f3540d 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -28,7 +28,7 @@ use crate::stdlib::{ use crate::symbol::{Boundness, Symbol}; use crate::types::call::{CallDunderResult, CallOutcome}; use crate::types::class_base::ClassBase; -use crate::types::diagnostic::TypeCheckDiagnosticsBuilder; +use crate::types::diagnostic::{TypeCheckDiagnosticsBuilder, INVALID_TYPE_FORM}; use crate::types::mro::{Mro, MroError, MroIterator}; use crate::types::narrow::narrowing_constraint; use crate::{Db, FxOrderSet, Module, Program, PythonVersion}; @@ -1889,7 +1889,7 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)), Type::Union(union) => { let mut builder = UnionBuilder::new(db); - let mut invalid_expressions = vec![]; + let mut invalid_expressions = smallvec::SmallVec::default(); for element in union.elements(db) { match element.in_type_expression(db) { Ok(type_expr) => builder = builder.add(type_expr), @@ -1922,11 +1922,11 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::Any), // TODO: Should emit a diagnostic Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError { - invalid_expressions: vec![InvalidTypeExpression::BareAnnotated], + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated], fallback_type: Type::Unknown, }), Type::KnownInstance(KnownInstanceType::Literal) => Err(InvalidTypeExpressionError { - invalid_expressions: vec![InvalidTypeExpression::BareLiteral], + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareLiteral], fallback_type: Type::Unknown, }), Type::Todo(_) => Ok(*self), @@ -2067,7 +2067,28 @@ impl<'db> From> for Symbol<'db> { #[derive(Debug, PartialEq, Eq)] pub struct InvalidTypeExpressionError<'db> { fallback_type: Type<'db>, - invalid_expressions: Vec, + invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression; 1]>, +} + +impl<'db> InvalidTypeExpressionError<'db> { + fn into_fallback_type( + self, + diagnostics: &mut TypeCheckDiagnosticsBuilder, + node: &ast::Expr, + ) -> Type<'db> { + let InvalidTypeExpressionError { + fallback_type, + invalid_expressions, + } = self; + for error in invalid_expressions { + diagnostics.add_lint( + &INVALID_TYPE_FORM, + node.into(), + format_args!("{}", error.reason()), + ); + } + fallback_type + } } /// Enumeration of various types that are invalid in type-expression contexts diff --git a/crates/red_knot_python_semantic/src/types/infer.rs b/crates/red_knot_python_semantic/src/types/infer.rs index bed9247850129..d35b6c217366c 100644 --- a/crates/red_knot_python_semantic/src/types/infer.rs +++ b/crates/red_knot_python_semantic/src/types/infer.rs @@ -63,10 +63,10 @@ use crate::types::unpacker::{UnpackResult, Unpacker}; use crate::types::{ bindings_ty, builtins_symbol, declarations_ty, global_symbol, symbol, todo_type, typing_extensions_symbol, Boundness, Class, ClassLiteralType, FunctionType, InstanceType, - IntersectionBuilder, IntersectionType, InvalidTypeExpressionError, IterationOutcome, - KnownClass, KnownFunction, KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, - SliceLiteralType, Symbol, Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, - TypeVarBoundOrConstraints, TypeVarInstance, UnionBuilder, UnionType, + IntersectionBuilder, IntersectionType, IterationOutcome, KnownClass, KnownFunction, + KnownInstanceType, MetaclassCandidate, MetaclassErrorKind, SliceLiteralType, Symbol, + Truthiness, TupleType, Type, TypeAliasType, TypeArrayDisplay, TypeVarBoundOrConstraints, + TypeVarInstance, UnionBuilder, UnionType, }; use crate::unpack::Unpack; use crate::util::subscript::{PyIndex, PySlice}; @@ -4468,7 +4468,9 @@ impl<'db> TypeInferenceBuilder<'db> { ast::ExprContext::Load => self .infer_name_expression(name) .in_type_expression(self.db) - .unwrap_or_else(|error| self.infer_invalid_type_expression(expression, error)), + .unwrap_or_else(|error| { + error.into_fallback_type(&mut self.diagnostics, expression) + }), ast::ExprContext::Invalid => Type::Unknown, ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(), }, @@ -4477,7 +4479,9 @@ impl<'db> TypeInferenceBuilder<'db> { ast::ExprContext::Load => self .infer_attribute_expression(attribute_expression) .in_type_expression(self.db) - .unwrap_or_else(|error| self.infer_invalid_type_expression(expression, error)), + .unwrap_or_else(|error| { + error.into_fallback_type(&mut self.diagnostics, expression) + }), ast::ExprContext::Invalid => Type::Unknown, ast::ExprContext::Store | ast::ExprContext::Del => todo_type!(), }, @@ -4633,25 +4637,6 @@ impl<'db> TypeInferenceBuilder<'db> { } } - fn infer_invalid_type_expression( - &mut self, - expression: &ast::Expr, - error: InvalidTypeExpressionError<'db>, - ) -> Type<'db> { - let InvalidTypeExpressionError { - fallback_type, - invalid_expressions, - } = error; - for error in invalid_expressions { - self.diagnostics.add_lint( - &INVALID_TYPE_FORM, - expression.into(), - format_args!("{}", error.reason()), - ); - } - fallback_type - } - /// Infer the type of a string type expression. fn infer_string_type_expression(&mut self, string: &ast::ExprStringLiteral) -> Type<'db> { match parse_string_annotation(self.db, self.file, string) {