diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/invalid.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/invalid.md new file mode 100644 index 0000000000..87c86ac360 --- /dev/null +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/invalid.md @@ -0,0 +1,45 @@ +# Tests for invalid types in type expressions + +## Invalid types are rejected + +Many types are illegal in the context of a type expression: + +```py +import typing +from knot_extensions import AlwaysTruthy, AlwaysFalsy +from typing_extensions import Literal, Never + +def _( + a: type[int], + b: AlwaysTruthy, + c: AlwaysFalsy, + d: Literal[True], + e: Literal["bar"], + f: Literal[b"foo"], + g: tuple[int, str], + h: Never, +): + def foo(): ... + def invalid( + i: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression" + j: b, # error: [invalid-type-form] + k: c, # error: [invalid-type-form] + l: d, # error: [invalid-type-form] + m: e, # error: [invalid-type-form] + n: f, # error: [invalid-type-form] + o: g, # error: [invalid-type-form] + p: h, # error: [invalid-type-form] + q: typing, # error: [invalid-type-form] + r: foo, # error: [invalid-type-form] + ): + reveal_type(i) # revealed: Unknown + reveal_type(j) # revealed: Unknown + reveal_type(k) # revealed: Unknown + reveal_type(l) # revealed: Unknown + reveal_type(m) # revealed: Unknown + reveal_type(n) # revealed: Unknown + reveal_type(o) # revealed: Unknown + reveal_type(p) # revealed: Unknown + reveal_type(q) # revealed: Unknown + reveal_type(r) # revealed: Unknown +``` diff --git a/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index 37d9dfb7d4..c061428aeb 100644 --- a/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/red_knot_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -18,7 +18,7 @@ def f(*args: Unpack[Ts]) -> tuple[Unpack[Ts]]: # TODO: should understand the annotation reveal_type(args) # revealed: tuple - reveal_type(Alias) # revealed: @Todo(Unsupported or invalid type in a type expression) + reveal_type(Alias) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`) def g() -> TypeGuard[int]: ... def h() -> TypeIs[int]: ... @@ -33,7 +33,7 @@ def i(callback: Callable[Concatenate[int, P], R_co], *args: P.args, **kwargs: P. class Foo: def method(self, x: Self): - reveal_type(x) # revealed: @Todo(Unsupported or invalid type in a type expression) + reveal_type(x) # revealed: @Todo(Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`) ``` ## Inheritance diff --git a/crates/red_knot_python_semantic/src/types.rs b/crates/red_knot_python_semantic/src/types.rs index b9d63afd5e..879ee52619 100644 --- a/crates/red_knot_python_semantic/src/types.rs +++ b/crates/red_knot_python_semantic/src/types.rs @@ -2470,32 +2470,45 @@ impl<'db> Type<'db> { match self { // Special cases for `float` and `complex` // https://typing.readthedocs.io/en/latest/spec/special-types.html#special-cases-for-float-and-complex - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Float) => - { - Ok(UnionType::from_elements( - db, - [ - KnownClass::Int.to_instance(db), - KnownClass::Float.to_instance(db), - ], - )) + Type::ClassLiteral(ClassLiteralType { class }) => { + let ty = match class.known(db) { + Some(KnownClass::Complex) => UnionType::from_elements( + db, + [ + KnownClass::Int.to_instance(db), + KnownClass::Float.to_instance(db), + KnownClass::Complex.to_instance(db), + ], + ), + Some(KnownClass::Float) => UnionType::from_elements( + db, + [ + KnownClass::Int.to_instance(db), + KnownClass::Float.to_instance(db), + ], + ), + _ => Type::instance(*class), + }; + Ok(ty) } - Type::ClassLiteral(ClassLiteralType { class }) - if class.is_known(db, KnownClass::Complex) => - { - Ok(UnionType::from_elements( - db, - [ - KnownClass::Int.to_instance(db), - KnownClass::Float.to_instance(db), - KnownClass::Complex.to_instance(db), - ], - )) - } - // In a type expression, a bare `type` is interpreted as "instance of `type`", which is - // equivalent to `type[object]`. - Type::ClassLiteral(_) | Type::SubclassOf(_) => Ok(self.to_instance(db)), + Type::SubclassOf(_) + | Type::BooleanLiteral(_) + | Type::BytesLiteral(_) + | Type::AlwaysTruthy + | Type::AlwaysFalsy + | Type::SliceLiteral(_) + | Type::IntLiteral(_) + | Type::LiteralString + | Type::ModuleLiteral(_) + | Type::StringLiteral(_) + | Type::Tuple(_) + | Type::Callable(_) + | Type::Never + | Type::FunctionLiteral(_) => Err(InvalidTypeExpressionError { + invalid_expressions: smallvec::smallvec![InvalidTypeExpression::InvalidType(*self)], + fallback_type: Type::unknown(), + }), + // We treat `typing.Type` exactly the same as `builtins.type`: Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)), Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)), @@ -2556,7 +2569,6 @@ impl<'db> Type<'db> { } Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString), Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::any()), - // TODO: Should emit a diagnostic Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError { invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated], fallback_type: Type::unknown(), @@ -2580,9 +2592,13 @@ impl<'db> Type<'db> { Type::KnownInstance(KnownInstanceType::Unknown) => Ok(Type::unknown()), Type::KnownInstance(KnownInstanceType::AlwaysTruthy) => Ok(Type::AlwaysTruthy), Type::KnownInstance(KnownInstanceType::AlwaysFalsy) => Ok(Type::AlwaysFalsy), - _ => Ok(todo_type!( - "Unsupported or invalid type in a type expression" + Type::KnownInstance(_) => Ok(todo_type!( + "Invalid or unsupported `KnownInstanceType` in `Type::to_type_expression`" )), + Type::Instance(_) => Ok(todo_type!( + "Invalid or unsupported `Instance` in `Type::to_type_expression`" + )), + Type::Intersection(_) => Ok(todo_type!("Type::Intersection.in_type_expression")), } } @@ -2815,7 +2831,7 @@ impl<'db> From> for TypeAndQualifiers<'db> { #[derive(Debug, PartialEq, Eq)] pub struct InvalidTypeExpressionError<'db> { fallback_type: Type<'db>, - invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression; 1]>, + invalid_expressions: smallvec::SmallVec<[InvalidTypeExpression<'db>; 1]>, } impl<'db> InvalidTypeExpressionError<'db> { @@ -2825,7 +2841,11 @@ impl<'db> InvalidTypeExpressionError<'db> { invalid_expressions, } = self; for error in invalid_expressions { - context.report_lint(&INVALID_TYPE_FORM, node, format_args!("{}", error.reason())); + context.report_lint( + &INVALID_TYPE_FORM, + node, + format_args!("{}", error.reason(context.db())), + ); } fallback_type } @@ -2833,7 +2853,7 @@ impl<'db> InvalidTypeExpressionError<'db> { /// Enumeration of various types that are invalid in type-expression contexts #[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum InvalidTypeExpression { +enum InvalidTypeExpression<'db> { /// `x: Annotated` is invalid as an annotation BareAnnotated, /// `x: Literal` is invalid as an annotation @@ -2842,16 +2862,42 @@ enum InvalidTypeExpression { ClassVarInTypeExpression, /// The `Final` type qualifier was used in a type expression FinalInTypeExpression, + /// Some types are always invalid in type expressions + InvalidType(Type<'db>), } -impl InvalidTypeExpression { - const fn reason(self) -> &'static str { - match self { - Self::BareAnnotated => "`Annotated` requires at least two arguments when used in an annotation or type expression", - Self::BareLiteral => "`Literal` requires at least one argument when used in a type expression", - Self::ClassVarInTypeExpression => "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)", - Self::FinalInTypeExpression => "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)", +impl<'db> InvalidTypeExpression<'db> { + const fn reason(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { + struct Display<'db> { + error: InvalidTypeExpression<'db>, + db: &'db dyn Db, } + + impl std::fmt::Display for Display<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self.error { + InvalidTypeExpression::BareAnnotated => f.write_str( + "`Annotated` requires at least two arguments when used in an annotation or type expression" + ), + InvalidTypeExpression::BareLiteral => f.write_str( + "`Literal` requires at least one argument when used in a type expression" + ), + InvalidTypeExpression::ClassVarInTypeExpression => f.write_str( + "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)" + ), + InvalidTypeExpression::FinalInTypeExpression => f.write_str( + "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)" + ), + InvalidTypeExpression::InvalidType(ty) => write!( + f, + "Variable of type `{ty}` is not allowed in a type expression", + ty = ty.display(self.db) + ), + } + } + } + + Display { error: self, db } } }