Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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]):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/ty_test/src/lib.rs
assertion_line: 621
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Not sure if intentional?)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was intentional... Somebody (or somebody's agent) has an old version of cargo-insta installed locally, which means that snapshots in "legacy formats" keep getting added, which then results in a bunch of annoying, noisy warnings being printed locally whenever you run cargo test -p ty_python_semantic --test=mdtest (or run uv run crates/ty_python_semantic/mdtest.py). The only way to fix the warnings is by running --force-update-snapshots locally, and this is the result. I could split it into its own PR, but that causes its own kind of churn...

expression: snapshot
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/ty_test/src/lib.rs
assertion_line: 624
expression: snapshot
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/ty_test/src/lib.rs
assertion_line: 624
expression: snapshot
---

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
---
source: crates/ty_test/src/lib.rs
assertion_line: 624
expression: snapshot
---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -15,7 +14,7 @@ use crate::types::{
};

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum PEP613Policy {
pub(super) enum PEP613Policy {
Allowed,
Disallowed,
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down
106 changes: 73 additions & 33 deletions crates/ty_python_semantic/src/types/infer/builder/subscript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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};

Expand Down Expand Up @@ -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(_))
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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())
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down
Loading