diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md b/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md index e468d2538dbbce..01e64abc5716b7 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/annotated.md @@ -48,8 +48,10 @@ def _(x: Annotated | bool): reveal_type(x) # revealed: Unknown | bool # error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)" -def _(x: Annotated[()]): +# error: [invalid-type-form] "Special form `typing.Annotated` expected at least 2 arguments (one type and at least one metadata element)" +def _(x: Annotated[()], y: list[Annotated[()]]): reveal_type(x) # revealed: Unknown + reveal_type(y) # revealed: list[Unknown] # error: [invalid-type-form] def _(x: Annotated[int]): diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/aliases.md_-_Generic_type_aliases\342\200\246_-_Default_type_paramet\342\200\246_(cd50ade911a6afa4).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/aliases.md_-_Generic_type_aliases\342\200\246_-_Default_type_paramet\342\200\246_(cd50ade911a6afa4).snap" index 8075121bda409d..3113fd889b493c 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/aliases.md_-_Generic_type_aliases\342\200\246_-_Default_type_paramet\342\200\246_(cd50ade911a6afa4).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/aliases.md_-_Generic_type_aliases\342\200\246_-_Default_type_paramet\342\200\246_(cd50ade911a6afa4).snap" @@ -1,6 +1,5 @@ --- source: crates/ty_test/src/lib.rs -assertion_line: 621 expression: snapshot --- diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" index 62cc959db80b82..b7fdd244838952 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/liskov.md_-_The_Liskov_Substitut\342\200\246_-_Method_parameters_(d98059266bcc1e13).snap" @@ -2,6 +2,7 @@ source: crates/ty_test/src/lib.rs expression: snapshot --- + --- mdtest name: liskov.md - The Liskov Substitution Principle - Method parameters mdtest path: crates/ty_python_semantic/resources/mdtest/liskov.md diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Function_nested_in_c\342\200\246_(1a50b4ccb10b95dd).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Function_nested_in_c\342\200\246_(1a50b4ccb10b95dd).snap" index bf806c00c1af70..cb1792c33525ed 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Function_nested_in_c\342\200\246_(1a50b4ccb10b95dd).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Function_nested_in_c\342\200\246_(1a50b4ccb10b95dd).snap" @@ -1,6 +1,5 @@ --- source: crates/ty_test/src/lib.rs -assertion_line: 624 expression: snapshot --- diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Legacy_TypeVar_in_ne\342\200\246_(a1aca17ea750ffdd).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Legacy_TypeVar_in_ne\342\200\246_(a1aca17ea750ffdd).snap" index cf21e648fa8cd9..70fe7bcd59d313 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Legacy_TypeVar_in_ne\342\200\246_(a1aca17ea750ffdd).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Legacy_TypeVar_in_ne\342\200\246_(a1aca17ea750ffdd).snap" @@ -1,6 +1,5 @@ --- source: crates/ty_test/src/lib.rs -assertion_line: 624 expression: snapshot --- diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Type_alias_nested_in\342\200\246_(de027dcc5360f252).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Type_alias_nested_in\342\200\246_(de027dcc5360f252).snap" index 3ebc3bab85c36d..5fa52fb897e76d 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Type_alias_nested_in\342\200\246_(de027dcc5360f252).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/scoping.md_-_Scoping_rules_for_ty\342\200\246_-_Type_parameter_defau\342\200\246_-_Type_alias_nested_in\342\200\246_(de027dcc5360f252).snap" @@ -1,6 +1,5 @@ --- source: crates/ty_test/src/lib.rs -assertion_line: 624 expression: snapshot --- diff --git a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs index b200ac885d2918..9b563e575dc445 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/annotation_expression.rs @@ -2,10 +2,9 @@ use ruff_python_ast as ast; use super::{DeferredExpressionState, TypeInferenceBuilder}; use crate::place::TypeOrigin; -use crate::types::diagnostic::{ - INVALID_TYPE_FORM, REDUNDANT_FINAL_CLASSVAR, report_invalid_arguments_to_annotated, -}; +use crate::types::diagnostic::{INVALID_TYPE_FORM, REDUNDANT_FINAL_CLASSVAR}; use crate::types::infer::builder::InferenceFlags; +use crate::types::infer::builder::subscript::AnnotatedExprContext; use crate::types::infer::nearest_enclosing_class; use crate::types::string_annotation::{ BYTE_STRING_TYPE_ANNOTATION, FSTRING_TYPE_ANNOTATION, parse_string_annotation, @@ -15,7 +14,7 @@ use crate::types::{ }; #[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum PEP613Policy { +pub(super) enum PEP613Policy { Allowed, Disallowed, } @@ -86,7 +85,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { /// Implementation of [`infer_annotation_expression`]. /// /// [`infer_annotation_expression`]: TypeInferenceBuilder::infer_annotation_expression - fn infer_annotation_expression_impl( + pub(super) fn infer_annotation_expression_impl( &mut self, annotation: &ast::Expr, pep_613_policy: PEP613Policy, @@ -222,50 +221,23 @@ impl<'db> TypeInferenceBuilder<'db, '_> { match value_ty { Type::SpecialForm(special_form) => match special_form { SpecialFormType::Annotated => { - // This branch is similar to the corresponding branch in - // `infer_parameterized_special_form_type_expression`, but - // `Annotated[…]` can appear both in annotation expressions and in - // type expressions, and needs to be handled slightly - // differently in each case (calling either `infer_type_expression_*` - // or `infer_annotation_expression_*`). - if let ast::Expr::Tuple(ast::ExprTuple { - elts: arguments, .. - }) = slice - { - if arguments.len() < 2 { - report_invalid_arguments_to_annotated(&self.context, subscript); - } - - if let [inner_annotation, metadata @ ..] = &arguments[..] { - for element in metadata { - self.infer_expression(element, TypeContext::default()); - } - - let inner_annotation_ty = self - .infer_annotation_expression_impl( - inner_annotation, - PEP613Policy::Disallowed, - ); - - self.store_expression_type( - slice, - inner_annotation_ty.inner_type(), - ); - inner_annotation_ty - } else { - for argument in arguments { - self.infer_expression(argument, TypeContext::default()); - } - self.store_expression_type(slice, Type::unknown()); - TypeAndQualifiers::declared(Type::unknown()) - } - } else { - report_invalid_arguments_to_annotated(&self.context, subscript); - self.infer_annotation_expression_impl( - slice, - PEP613Policy::Disallowed, + let inferred = self.parse_subscription_of_annotated_special_form( + subscript, + AnnotatedExprContext::AnnotationExpression, + ); + let in_type_expression = inferred + .inner_type() + .in_type_expression( + self.db(), + self.scope(), + None, + self.inference_flags, ) - } + .unwrap_or_else(|err| { + err.into_fallback_type(&self.context, subscript) + }); + TypeAndQualifiers::declared(in_type_expression) + .with_qualifier(inferred.qualifiers()) } SpecialFormType::TypeQualifier(qualifier) => { let arguments = if let ast::Expr::Tuple(tuple) = slice { diff --git a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs index de0a50e5f483a8..f9c66a5b8eda61 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/subscript.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/subscript.rs @@ -23,6 +23,7 @@ use crate::types::diagnostic::{ }; use crate::types::generics::{GenericContext, InferableTypeVars, bind_typevar}; use crate::types::infer::InferenceFlags; +use crate::types::infer::builder::annotation_expression::PEP613Policy; use crate::types::infer::builder::{ArgExpr, ArgumentsIter, MultiInferenceGuard}; use crate::types::special_form::AliasSpec; use crate::types::subscript::{LegacyGenericOrigin, SubscriptError, SubscriptErrorKind}; @@ -31,8 +32,8 @@ use crate::types::typed_dict::{TypedDictAssignmentKind, TypedDictKeyAssignment}; use crate::types::{ BoundTypeVarInstance, CallArguments, CallDunderError, DynamicType, InternedType, KnownClass, KnownInstanceType, LintDiagnosticGuard, Parameter, Parameters, SpecialFormType, - StaticClassLiteral, Type, TypeAliasType, TypeContext, TypeVarBoundOrConstraints, UnionType, - UnionTypeInstance, any_over_type, todo_type, + StaticClassLiteral, Type, TypeAliasType, TypeAndQualifiers, TypeContext, + TypeVarBoundOrConstraints, UnionType, UnionTypeInstance, any_over_type, todo_type, }; use crate::{Db, FxOrderSet}; @@ -206,37 +207,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } }, SpecialFormType::Annotated => { - let ast::Expr::Tuple(ast::ExprTuple { - elts: ref arguments, - .. - }) = **slice - else { - report_invalid_arguments_to_annotated(&self.context, subscript); - - return self.infer_expression(slice, TypeContext::default()); - }; - - if arguments.len() < 2 { - report_invalid_arguments_to_annotated(&self.context, subscript); - } - - let [type_expr, metadata @ ..] = &arguments[..] else { - for argument in arguments { - self.infer_expression(argument, TypeContext::default()); - } - self.store_expression_type(slice, Type::unknown()); - return Type::unknown(); - }; - - for element in metadata { - self.infer_expression(element, TypeContext::default()); - } - - let ty = self.infer_type_expression(type_expr); - - return Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new( - db, ty, - ))); + return self + .parse_subscription_of_annotated_special_form( + subscript, + AnnotatedExprContext::TypeExpression, + ) + .inner_type(); } SpecialFormType::Optional => { if matches!(**slice, ast::Expr::Tuple(_)) @@ -1693,6 +1669,36 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ) .is_ok() } + + pub(super) fn parse_subscription_of_annotated_special_form( + &mut self, + subscript: &ast::ExprSubscript, + subscript_context: AnnotatedExprContext, + ) -> TypeAndQualifiers<'db> { + let slice = &*subscript.slice; + let ast::Expr::Tuple(ast::ExprTuple { + elts: arguments, .. + }) = slice + else { + report_invalid_arguments_to_annotated(&self.context, subscript); + return subscript_context.infer(self, slice); + }; + + if arguments.len() < 2 { + report_invalid_arguments_to_annotated(&self.context, subscript); + } + + let Some(first_argument) = arguments.first() else { + self.infer_expression(slice, TypeContext::default()); + return TypeAndQualifiers::declared(Type::unknown()); + }; + + for metadata_element in &arguments[1..] { + self.infer_expression(metadata_element, TypeContext::default()); + } + + subscript_context.infer(self, first_argument) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -1816,3 +1822,37 @@ fn legacy_generic_class_context<'db>( validated_typevars, )) } + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum AnnotatedExprContext { + TypeExpression, + AnnotationExpression, +} + +impl AnnotatedExprContext { + fn infer<'db>( + self, + builder: &mut TypeInferenceBuilder<'db, '_>, + argument: &ast::Expr, + ) -> TypeAndQualifiers<'db> { + match self { + AnnotatedExprContext::TypeExpression => { + let inner = builder.infer_type_expression(argument); + let outer = Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new( + builder.db(), + inner, + ))); + TypeAndQualifiers::declared(outer) + } + AnnotatedExprContext::AnnotationExpression => { + let inner = + builder.infer_annotation_expression_impl(argument, PEP613Policy::Disallowed); + let outer = Type::KnownInstance(KnownInstanceType::Annotated(InternedType::new( + builder.db(), + inner.inner_type(), + ))); + TypeAndQualifiers::declared(outer).with_qualifier(inner.qualifiers()) + } + } + } +} diff --git a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs index 27be5ee2ec0cbc..58318743533073 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/type_expression.rs @@ -9,6 +9,7 @@ use crate::types::diagnostic::{ report_invalid_arguments_to_callable, report_invalid_concatenate_last_arg, }; use crate::types::infer::InferenceFlags; +use crate::types::infer::builder::subscript::AnnotatedExprContext; use crate::types::signatures::{ConcatenateTail, Signature}; use crate::types::special_form::{AliasSpec, LegacyStdlibAlias}; use crate::types::string_annotation::parse_string_annotation; @@ -1563,21 +1564,14 @@ impl<'db> TypeInferenceBuilder<'db, '_> { let db = self.db(); let arguments_slice = &*subscript.slice; match special_form { - SpecialFormType::Annotated => { - let ty = self - .infer_subscript_load_impl( - Type::SpecialForm(SpecialFormType::Annotated), - subscript, - ) - .in_type_expression(db, self.scope(), None, self.inference_flags) - .unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript)); - // Only store on the tuple slice; non-tuple cases are handled by - // `infer_subscript_load_impl` via `infer_expression`. - if arguments_slice.is_tuple_expr() { - self.store_expression_type(arguments_slice, ty); - } - ty - } + SpecialFormType::Annotated => self + .parse_subscription_of_annotated_special_form( + subscript, + AnnotatedExprContext::TypeExpression, + ) + .inner_type() + .in_type_expression(self.db(), self.scope(), None, self.inference_flags) + .unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript)), SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) { Ok(ty) => ty, Err(nodes) => {