[red-knot] Understand Annotated (#14950)

## Summary

Resolves #14922.

## Test Plan

Markdown tests.

---------

Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@astral.sh>
This commit is contained in:
InSync 2024-12-14 00:41:37 +07:00 committed by GitHub
parent 3533d7f5b4
commit aa1938f6ba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 199 additions and 90 deletions

View file

@ -1794,6 +1794,8 @@ impl<'db> Type<'db> {
}
Type::KnownInstance(KnownInstanceType::LiteralString) => Type::LiteralString,
Type::KnownInstance(KnownInstanceType::Any) => 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"),
}
@ -2163,6 +2165,8 @@ impl<'db> KnownClass {
/// Enumeration of specific runtime that are special enough to be considered their own type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, salsa::Update)]
pub enum KnownInstanceType<'db> {
/// The symbol `typing.Annotated` (which can also be found as `typing_extensions.Annotated`)
Annotated,
/// The symbol `typing.Literal` (which can also be found as `typing_extensions.Literal`)
Literal,
/// The symbol `typing.LiteralString` (which can also be found as `typing_extensions.LiteralString`)
@ -2215,6 +2219,7 @@ pub enum KnownInstanceType<'db> {
impl<'db> KnownInstanceType<'db> {
pub const fn as_str(self) -> &'static str {
match self {
Self::Annotated => "Annotated",
Self::Literal => "Literal",
Self::LiteralString => "LiteralString",
Self::Optional => "Optional",
@ -2253,7 +2258,8 @@ impl<'db> KnownInstanceType<'db> {
/// Evaluate the known instance in boolean context
pub const fn bool(self) -> Truthiness {
match self {
Self::Literal
Self::Annotated
| Self::Literal
| Self::LiteralString
| Self::Optional
| Self::TypeVar(_)
@ -2291,6 +2297,7 @@ impl<'db> KnownInstanceType<'db> {
/// Return the repr of the symbol at runtime
pub fn repr(self, db: &'db dyn Db) -> &'db str {
match self {
Self::Annotated => "typing.Annotated",
Self::Literal => "typing.Literal",
Self::LiteralString => "typing.LiteralString",
Self::Optional => "typing.Optional",
@ -2329,6 +2336,7 @@ impl<'db> KnownInstanceType<'db> {
/// Return the [`KnownClass`] which this symbol is an instance of
pub const fn class(self) -> KnownClass {
match self {
Self::Annotated => KnownClass::SpecialForm,
Self::Literal => KnownClass::SpecialForm,
Self::LiteralString => KnownClass::SpecialForm,
Self::Optional => KnownClass::SpecialForm,
@ -2395,6 +2403,7 @@ impl<'db> KnownInstanceType<'db> {
("typing", "Tuple") => Some(Self::Tuple),
("typing", "Type") => Some(Self::Type),
("typing", "Callable") => Some(Self::Callable),
("typing" | "typing_extensions", "Annotated") => Some(Self::Annotated),
("typing" | "typing_extensions", "Literal") => Some(Self::Literal),
("typing" | "typing_extensions", "LiteralString") => Some(Self::LiteralString),
("typing" | "typing_extensions", "Never") => Some(Self::Never),

View file

@ -74,6 +74,7 @@ impl<'db> ClassBase<'db> {
Type::KnownInstance(known_instance) => match known_instance {
KnownInstanceType::TypeVar(_)
| KnownInstanceType::TypeAliasType(_)
| KnownInstanceType::Annotated
| KnownInstanceType::Literal
| KnownInstanceType::LiteralString
| KnownInstanceType::Union

View file

@ -30,13 +30,11 @@ pub(crate) fn register_lints(registry: &mut LintRegistryBuilder) {
registry.register_lint(&CONFLICTING_DECLARATIONS);
registry.register_lint(&DIVISION_BY_ZERO);
registry.register_lint(&CALL_NON_CALLABLE);
registry.register_lint(&INVALID_TYPE_PARAMETER);
registry.register_lint(&INVALID_TYPE_VARIABLE_CONSTRAINTS);
registry.register_lint(&CYCLIC_CLASS_DEFINITION);
registry.register_lint(&DUPLICATE_BASE);
registry.register_lint(&INVALID_BASE);
registry.register_lint(&INCONSISTENT_MRO);
registry.register_lint(&INVALID_LITERAL_PARAMETER);
registry.register_lint(&CALL_POSSIBLY_UNBOUND_METHOD);
registry.register_lint(&POSSIBLY_UNBOUND_ATTRIBUTE);
registry.register_lint(&UNRESOLVED_ATTRIBUTE);
@ -249,16 +247,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// TODO #14889
pub(crate) static INVALID_TYPE_PARAMETER = {
summary: "detects invalid type parameters",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// TODO #14889
pub(crate) static INVALID_TYPE_VARIABLE_CONSTRAINTS = {
@ -308,18 +296,6 @@ declare_lint! {
}
}
declare_lint! {
/// ## What it does
/// Checks for invalid parameters to `typing.Literal`.
///
/// TODO #14889
pub(crate) static INVALID_LITERAL_PARAMETER = {
summary: "detects invalid literal parameters",
status: LintStatus::preview("1.0.0"),
default_level: Level::Error,
}
}
declare_lint! {
/// ## What it does
/// Checks for calls to possibly unbound methods.

View file

@ -54,8 +54,7 @@ use crate::types::diagnostic::{
TypeCheckDiagnostics, TypeCheckDiagnosticsBuilder, CALL_NON_CALLABLE,
CALL_POSSIBLY_UNBOUND_METHOD, CONFLICTING_DECLARATIONS, CONFLICTING_METACLASS,
CYCLIC_CLASS_DEFINITION, DIVISION_BY_ZERO, DUPLICATE_BASE, INCONSISTENT_MRO, INVALID_BASE,
INVALID_CONTEXT_MANAGER, INVALID_DECLARATION, INVALID_LITERAL_PARAMETER,
INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM, INVALID_TYPE_PARAMETER,
INVALID_CONTEXT_MANAGER, INVALID_DECLARATION, INVALID_PARAMETER_DEFAULT, INVALID_TYPE_FORM,
INVALID_TYPE_VARIABLE_CONSTRAINTS, POSSIBLY_UNBOUND_ATTRIBUTE, POSSIBLY_UNBOUND_IMPORT,
UNDEFINED_REVEAL, UNRESOLVED_ATTRIBUTE, UNRESOLVED_IMPORT, UNSUPPORTED_OPERATOR,
};
@ -4810,126 +4809,170 @@ impl<'db> TypeInferenceBuilder<'db> {
subscript: &ast::ExprSubscript,
known_instance: KnownInstanceType,
) -> Type<'db> {
let parameters = &*subscript.slice;
let arguments_slice = &*subscript.slice;
match known_instance {
KnownInstanceType::Literal => match self.infer_literal_parameter_type(parameters) {
Ok(ty) => ty,
Err(nodes) => {
for node in nodes {
self.diagnostics.add_lint(
&INVALID_LITERAL_PARAMETER,
node.into(),
format_args!(
"Type arguments for `Literal` must be `None`, \
a literal value (int, bool, str, or bytes), or an enum value"
),
);
}
Type::Unknown
KnownInstanceType::Annotated => {
let mut report_invalid_arguments = || {
self.diagnostics.add_lint(
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Special form `{}` expected at least 2 arguments (one type and at least one metadata element)",
known_instance.repr(self.db)
),
);
};
let ast::Expr::Tuple(ast::ExprTuple {
elts: arguments, ..
}) = arguments_slice
else {
report_invalid_arguments();
// `Annotated[]` with less than two arguments is an error at runtime.
// However, we still treat `Annotated[T]` as `T` here for the purpose of
// giving better diagnostics later on.
// Pyright also does this. Mypy doesn't; it falls back to `Any` instead.
return self.infer_type_expression(arguments_slice);
};
if arguments.len() < 2 {
report_invalid_arguments();
}
},
let [type_expr, metadata @ ..] = &arguments[..] else {
self.infer_type_expression(arguments_slice);
return Type::Unknown;
};
for element in metadata {
self.infer_expression(element);
}
let ty = self.infer_type_expression(type_expr);
self.store_expression_type(arguments_slice, ty);
ty
}
KnownInstanceType::Literal => {
match self.infer_literal_parameter_type(arguments_slice) {
Ok(ty) => ty,
Err(nodes) => {
for node in nodes {
self.diagnostics.add_lint(
&INVALID_TYPE_FORM,
node.into(),
format_args!(
"Type arguments for `Literal` must be `None`, \
a literal value (int, bool, str, or bytes), or an enum value"
),
);
}
Type::Unknown
}
}
}
KnownInstanceType::Optional => {
let param_type = self.infer_type_expression(parameters);
let param_type = self.infer_type_expression(arguments_slice);
UnionType::from_elements(self.db, [param_type, Type::none(self.db)])
}
KnownInstanceType::Union => match parameters {
KnownInstanceType::Union => match arguments_slice {
ast::Expr::Tuple(t) => {
let union_ty = UnionType::from_elements(
self.db,
t.iter().map(|elt| self.infer_type_expression(elt)),
);
self.store_expression_type(parameters, union_ty);
self.store_expression_type(arguments_slice, union_ty);
union_ty
}
_ => self.infer_type_expression(parameters),
_ => self.infer_type_expression(arguments_slice),
},
KnownInstanceType::TypeVar(_) => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("TypeVar annotations")
}
KnownInstanceType::TypeAliasType(_) => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Generic PEP-695 type alias")
}
KnownInstanceType::Callable => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Callable types")
}
KnownInstanceType::ChainMap => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.ChainMap alias")
}
KnownInstanceType::OrderedDict => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.OrderedDict alias")
}
KnownInstanceType::Dict => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.Dict alias")
}
KnownInstanceType::List => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.List alias")
}
KnownInstanceType::DefaultDict => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.DefaultDict[] alias")
}
KnownInstanceType::Counter => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.Counter[] alias")
}
KnownInstanceType::Set => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.Set alias")
}
KnownInstanceType::FrozenSet => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.FrozenSet alias")
}
KnownInstanceType::Deque => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("typing.Deque alias")
}
KnownInstanceType::ReadOnly => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Required[] type qualifier")
}
KnownInstanceType::NotRequired => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("NotRequired[] type qualifier")
}
KnownInstanceType::ClassVar => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("ClassVar[] type qualifier")
}
KnownInstanceType::Final => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Final[] type qualifier")
}
KnownInstanceType::Required => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Required[] type qualifier")
}
KnownInstanceType::TypeIs => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("TypeIs[] special form")
}
KnownInstanceType::TypeGuard => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("TypeGuard[] special form")
}
KnownInstanceType::Concatenate => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Concatenate[] special form")
}
KnownInstanceType::Unpack => {
self.infer_type_expression(parameters);
self.infer_type_expression(arguments_slice);
todo_type!("Unpack[] special form")
}
KnownInstanceType::NoReturn | KnownInstanceType::Never | KnownInstanceType::Any => {
self.diagnostics.add_lint(
&INVALID_TYPE_PARAMETER,
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Type `{}` expected no type parameter",
@ -4940,7 +4983,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
KnownInstanceType::TypingSelf | KnownInstanceType::TypeAlias => {
self.diagnostics.add_lint(
&INVALID_TYPE_PARAMETER,
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Special form `{}` expected no type parameter",
@ -4951,7 +4994,7 @@ impl<'db> TypeInferenceBuilder<'db> {
}
KnownInstanceType::LiteralString => {
self.diagnostics.add_lint(
&INVALID_TYPE_PARAMETER,
&INVALID_TYPE_FORM,
subscript.into(),
format_args!(
"Type `{}` expected no type parameter. Did you mean to use `Literal[...]` instead?",
@ -4960,8 +5003,8 @@ impl<'db> TypeInferenceBuilder<'db> {
);
Type::Unknown
}
KnownInstanceType::Type => self.infer_subclass_of_type_expression(parameters),
KnownInstanceType::Tuple => self.infer_tuple_type_expression(parameters),
KnownInstanceType::Type => self.infer_subclass_of_type_expression(arguments_slice),
KnownInstanceType::Tuple => self.infer_tuple_type_expression(arguments_slice),
}
}