mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 21:35:58 +00:00
[red-knot] Emit an error if a bare Annotated
or Literal
is used in a type expression (#14973)
This commit is contained in:
parent
fa46ba2306
commit
1389cb8e59
4 changed files with 135 additions and 23 deletions
|
@ -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};
|
||||
|
@ -1881,29 +1881,63 @@ impl<'db> Type<'db> {
|
|||
/// `Type::ClassLiteral(builtins.int)`, that is, it is the `int` class itself. As a type
|
||||
/// expression, it names the type `Type::Instance(builtins.int)`, that is, all objects whose
|
||||
/// `__class__` is `int`.
|
||||
#[must_use]
|
||||
pub fn in_type_expression(&self, db: &'db dyn Db) -> Type<'db> {
|
||||
pub fn in_type_expression(
|
||||
&self,
|
||||
db: &'db dyn Db,
|
||||
) -> Result<Type<'db>, InvalidTypeExpressionError<'db>> {
|
||||
match self {
|
||||
// In a type expression, a bare `type` is interpreted as "instance of `type`", which is
|
||||
// equivalent to `type[object]`.
|
||||
Type::ClassLiteral(_) | Type::SubclassOf(_) => self.to_instance(db),
|
||||
Type::ClassLiteral(_) | Type::SubclassOf(_) => Ok(self.to_instance(db)),
|
||||
// We treat `typing.Type` exactly the same as `builtins.type`:
|
||||
Type::KnownInstance(KnownInstanceType::Type) => KnownClass::Type.to_instance(db),
|
||||
Type::KnownInstance(KnownInstanceType::Tuple) => KnownClass::Tuple.to_instance(db),
|
||||
Type::Union(union) => union.map(db, |element| element.in_type_expression(db)),
|
||||
Type::Unknown => Type::Unknown,
|
||||
// TODO map this to a new `Type::TypeVar` variant
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => *self,
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => alias.value_ty(db),
|
||||
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
|
||||
Type::Never
|
||||
Type::KnownInstance(KnownInstanceType::Type) => Ok(KnownClass::Type.to_instance(db)),
|
||||
Type::KnownInstance(KnownInstanceType::Tuple) => Ok(KnownClass::Tuple.to_instance(db)),
|
||||
Type::Union(union) => {
|
||||
let mut builder = UnionBuilder::new(db);
|
||||
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),
|
||||
Err(InvalidTypeExpressionError {
|
||||
fallback_type,
|
||||
invalid_expressions: new_invalid_expressions,
|
||||
}) => {
|
||||
invalid_expressions.extend(new_invalid_expressions);
|
||||
builder = builder.add(fallback_type);
|
||||
}
|
||||
}
|
||||
}
|
||||
if invalid_expressions.is_empty() {
|
||||
Ok(builder.build())
|
||||
} else {
|
||||
Err(InvalidTypeExpressionError {
|
||||
fallback_type: builder.build(),
|
||||
invalid_expressions,
|
||||
})
|
||||
}
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::LiteralString) => Type::LiteralString,
|
||||
Type::KnownInstance(KnownInstanceType::Any) => Type::Any,
|
||||
Type::Unknown => Ok(Type::Unknown),
|
||||
// TODO map this to a new `Type::TypeVar` variant
|
||||
Type::KnownInstance(KnownInstanceType::TypeVar(_)) => Ok(*self),
|
||||
Type::KnownInstance(KnownInstanceType::TypeAliasType(alias)) => Ok(alias.value_ty(db)),
|
||||
Type::KnownInstance(KnownInstanceType::Never | KnownInstanceType::NoReturn) => {
|
||||
Ok(Type::Never)
|
||||
}
|
||||
Type::KnownInstance(KnownInstanceType::LiteralString) => Ok(Type::LiteralString),
|
||||
Type::KnownInstance(KnownInstanceType::Any) => Ok(Type::Any),
|
||||
// TODO: Should emit a diagnostic
|
||||
Type::KnownInstance(KnownInstanceType::Annotated) => Type::Unknown,
|
||||
Type::Todo(_) => *self,
|
||||
_ => todo_type!("Unsupported or invalid type in a type expression"),
|
||||
Type::KnownInstance(KnownInstanceType::Annotated) => Err(InvalidTypeExpressionError {
|
||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareAnnotated],
|
||||
fallback_type: Type::Unknown,
|
||||
}),
|
||||
Type::KnownInstance(KnownInstanceType::Literal) => Err(InvalidTypeExpressionError {
|
||||
invalid_expressions: smallvec::smallvec![InvalidTypeExpression::BareLiteral],
|
||||
fallback_type: Type::Unknown,
|
||||
}),
|
||||
Type::Todo(_) => Ok(*self),
|
||||
_ => Ok(todo_type!(
|
||||
"Unsupported or invalid type in a type expression"
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2032,6 +2066,54 @@ impl<'db> From<Type<'db>> for Symbol<'db> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Error struct providing information on type(s) that were deemed to be invalid
|
||||
/// in a type expression context, and the type we should therefore fallback to
|
||||
/// for the problematic type expression.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct InvalidTypeExpressionError<'db> {
|
||||
fallback_type: Type<'db>,
|
||||
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
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
enum InvalidTypeExpression {
|
||||
/// `x: Annotated` is invalid as an annotation
|
||||
BareAnnotated,
|
||||
/// `x: Literal` is invalid as an annotation
|
||||
BareLiteral,
|
||||
}
|
||||
|
||||
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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Non-exhaustive enumeration of known classes (e.g. `builtins.int`, `typing.Any`, ...) to allow
|
||||
/// for easier syntax when interacting with very common classes.
|
||||
///
|
||||
|
|
|
@ -4465,9 +4465,12 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
// https://typing.readthedocs.io/en/latest/spec/annotations.html#grammar-token-expression-grammar-type_expression
|
||||
match expression {
|
||||
ast::Expr::Name(name) => match name.ctx {
|
||||
ast::ExprContext::Load => {
|
||||
self.infer_name_expression(name).in_type_expression(self.db)
|
||||
}
|
||||
ast::ExprContext::Load => self
|
||||
.infer_name_expression(name)
|
||||
.in_type_expression(self.db)
|
||||
.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!(),
|
||||
},
|
||||
|
@ -4475,7 +4478,10 @@ impl<'db> TypeInferenceBuilder<'db> {
|
|||
ast::Expr::Attribute(attribute_expression) => match attribute_expression.ctx {
|
||||
ast::ExprContext::Load => self
|
||||
.infer_attribute_expression(attribute_expression)
|
||||
.in_type_expression(self.db),
|
||||
.in_type_expression(self.db)
|
||||
.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!(),
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue