From 3b166ad7ec617d626070c757943eff93e5f23f4e Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 1 Apr 2026 18:20:06 +0100 Subject: [PATCH 1/3] Use `infer_type_expression` for parsing parameter annotations and return-type annotations --- .../mdtest/annotations/generic_alias.md | 2 +- .../resources/mdtest/annotations/invalid.md | 6 +- .../annotations/unsupported_special_forms.md | 4 +- .../unsupported_type_qualifiers.md | 6 +- .../resources/mdtest/implicit_type_aliases.md | 2 +- .../resources/mdtest/pep695_type_aliases.md | 18 ++-- .../resources/mdtest/protocols.md | 4 +- ...used_\342\200\246_(652fec4fd4a6c63a).snap" | 4 +- ...iagno\342\200\246_(a4b698196d337a3f).snap" | 4 +- ...d_var\342\200\246_(6ce5aa6d2a0ce029).snap" | 2 +- .../mdtest/type_qualifiers/classvar.md | 10 +- .../resources/mdtest/type_qualifiers/final.md | 8 +- .../mdtest/type_qualifiers/initvar.md | 6 +- .../resources/mdtest/typed_dict.md | 6 +- crates/ty_python_semantic/src/types.rs | 101 +++++++++++------- crates/ty_python_semantic/src/types/infer.rs | 25 ++++- .../src/types/infer/builder.rs | 2 + .../infer/builder/annotation_expression.rs | 26 +++-- .../src/types/infer/builder/function.rs | 94 +++++----------- .../types/infer/builder/type_expression.rs | 77 +++++++++---- .../src/types/special_form.rs | 19 ++-- 21 files changed, 231 insertions(+), 195 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/generic_alias.md b/crates/ty_python_semantic/resources/mdtest/annotations/generic_alias.md index 86a115d90e50b..4a9f1ac67b478 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/generic_alias.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/generic_alias.md @@ -28,7 +28,7 @@ reveal_type(Strings) # revealed: GenericAlias However, using such a `GenericAlias` instance in a type expression is currently not supported: ```py -# error: [invalid-type-form] "Variable of type `GenericAlias` is not allowed in a type expression" +# error: [invalid-type-form] "Variable of type `GenericAlias` is not allowed in a parameter annotation" def _(strings: Strings) -> None: reveal_type(strings) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md index 79df38114ebbb..5f1726b29cb7c 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md @@ -25,7 +25,7 @@ def _( ): def foo(): ... def invalid( - a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a type expression" + a_: a, # error: [invalid-type-form] "Variable of type `type[int]` is not allowed in a parameter annotation" b_: b, # error: [invalid-type-form] c_: c, # error: [invalid-type-form] d_: d, # error: [invalid-type-form] @@ -35,8 +35,8 @@ def _( h_: h, # error: [invalid-type-form] i_: typing, # error: [invalid-type-form] j_: foo, # error: [invalid-type-form] - k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a type expression" - l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a type expression" + k_: i, # error: [invalid-type-form] "Variable of type `int` is not allowed in a parameter annotation" + l_: j, # error: [invalid-type-form] "Variable of type `A` is not allowed in a parameter annotation" ): reveal_type(a_) # revealed: Unknown reveal_type(b_) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md index 13baa1a01f446..0b8e23102e6bc 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_special_forms.md @@ -62,14 +62,14 @@ def _( c: TypeIs, # error: [invalid-type-form] "`typing.TypeIs` requires exactly one argument when used in a type expression" d: Concatenate, # error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a type expression" e: ParamSpec, - f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in type expressions" + f: Generic, # error: [invalid-type-form] "`typing.Generic` is not allowed in parameter annotations" ) -> None: reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown reveal_type(c) # revealed: Unknown reveal_type(d) # revealed: Unknown - # error: [invalid-type-form] "Variable of type `ParamSpec` is not allowed in a type expression" + # error: [invalid-type-form] "Variable of type `ParamSpec` is not allowed in a parameter annotation" def foo(a_: e) -> None: reveal_type(a_) # revealed: Unknown ``` diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_type_qualifiers.md b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_type_qualifiers.md index 1c02eac9f0bf9..a110d48561f91 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_type_qualifiers.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/unsupported_type_qualifiers.md @@ -23,11 +23,11 @@ One thing that is supported is error messages for using type qualifiers in type from typing_extensions import Final, ClassVar, Required, NotRequired, ReadOnly def _( - # error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)" + # error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in parameter annotations" a: Final | int, - # error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)" + # error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in parameter annotations" b: ClassVar | int, - # error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)" + # error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in parameter annotations" c: ReadOnly | int, ) -> None: reveal_type(a) # revealed: Unknown | int diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index 625a63e913c58..71494a0628b0f 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -1582,7 +1582,7 @@ errors: ```py AliasForStr = "str" -# error: [invalid-type-form] "Variable of type `Literal["str"]` is not allowed in a type expression" +# error: [invalid-type-form] "Variable of type `Literal["str"]` is not allowed in a parameter annotation" def _(s: AliasForStr): reveal_type(s) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md index cdcdc7560a6a0..3596742438767 100644 --- a/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/pep695_type_aliases.md @@ -50,23 +50,23 @@ appear at the top level of a PEP 695 alias definition: from typing_extensions import ClassVar, Final, Required, NotRequired, ReadOnly from dataclasses import InitVar -# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type alias values" type Bad1 = ClassVar[str] -# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in type alias values" type Bad2 = ClassVar -# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type alias values" type Bad3 = Final[int] -# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in type alias values" type Bad4 = Final -# error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in type alias values" type Bad5 = Required[int] -# error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in type alias values" type Bad6 = NotRequired[int] -# error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in type alias values" type Bad7 = ReadOnly[int] -# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in type expressions (only in annotation expressions)" +# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in type alias values" type Bad8 = InitVar[int] -# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in type expressions (only in annotation expressions, and only with exactly one argument)" +# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in type alias values" type Bad9 = InitVar ``` diff --git a/crates/ty_python_semantic/resources/mdtest/protocols.md b/crates/ty_python_semantic/resources/mdtest/protocols.md index d71a7dd3df855..8ff1d495803e7 100644 --- a/crates/ty_python_semantic/resources/mdtest/protocols.md +++ b/crates/ty_python_semantic/resources/mdtest/protocols.md @@ -260,8 +260,8 @@ And it is also an error to use `Protocol` in type expressions: # fmt: off def f( - x: Protocol, # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions" - y: type[Protocol], # error: [invalid-type-form] "`typing.Protocol` is not allowed in type expressions" + x: Protocol, # error: [invalid-type-form] "`typing.Protocol` is not allowed in parameter annotations" + y: type[Protocol], # error: [invalid-type-form] "`typing.Protocol` is not allowed in parameter annotations" ): reveal_type(x) # revealed: Unknown reveal_type(y) # revealed: type[Unknown] diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Module-literal_used_\342\200\246_(652fec4fd4a6c63a).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Module-literal_used_\342\200\246_(652fec4fd4a6c63a).snap" index b28e8e246a8bf..8a11768ba0847 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Module-literal_used_\342\200\246_(652fec4fd4a6c63a).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Module-literal_used_\342\200\246_(652fec4fd4a6c63a).snap" @@ -35,7 +35,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md # Diagnostics ``` -error[invalid-type-form]: Module `datetime` is not valid in a type expression +error[invalid-type-form]: Module `datetime` is not valid in a parameter annotation --> src/foo.py:3:10 | 1 | import datetime @@ -53,7 +53,7 @@ note: This is an unsafe fix and may change runtime behavior ``` ``` -error[invalid-type-form]: Module `PIL.Image` is not valid in a type expression +error[invalid-type-form]: Module `PIL.Image` is not valid in a parameter annotation --> src/bar.py:3:10 | 1 | from PIL import Image diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Special-cased_diagno\342\200\246_(a4b698196d337a3f).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Special-cased_diagno\342\200\246_(a4b698196d337a3f).snap" index 1c56915789959..50b1305e9be4f 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Special-cased_diagno\342\200\246_(a4b698196d337a3f).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Special-cased_diagno\342\200\246_(a4b698196d337a3f).snap" @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md # Diagnostics ``` -error[invalid-type-form]: Function `callable` is not valid in a type expression +error[invalid-type-form]: Function `callable` is not valid in a parameter annotation --> src/mdtest_snippet.py:3:19 | 1 | # error: [invalid-type-form] @@ -36,7 +36,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Function `callable` is not valid in a type expression +error[invalid-type-form]: Function `callable` is not valid in a return type annotation --> src/mdtest_snippet.py:3:32 | 1 | # error: [invalid-type-form] diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var\342\200\246_(6ce5aa6d2a0ce029).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var\342\200\246_(6ce5aa6d2a0ce029).snap" index 4963a7411e561..e6085925a0b57 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var\342\200\246_(6ce5aa6d2a0ce029).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/unreachable.md_-_Unreachable_code_-_`Never`-inferred_var\342\200\246_(6ce5aa6d2a0ce029).snap" @@ -32,7 +32,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/unreachable.md # Diagnostics ``` -error[invalid-type-form]: Variable of type `Never` is not allowed in a type expression +error[invalid-type-form]: Variable of type `Never` is not allowed in a parameter annotation --> src/main.py:3:10 | 1 | import module diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md index 01138809deb43..9decd09b446a7 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/classvar.md @@ -347,21 +347,19 @@ class C: # error: [invalid-type-form] "`ClassVar` annotations are only allowed in class-body scopes" y: ClassVar[int] = 1 -# error: [invalid-type-form] "`ClassVar` is not allowed in function parameter annotations" +# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in parameter annotations" def f(x: ClassVar[int]) -> None: pass -# error: [invalid-type-form] "`ClassVar` is not allowed in function parameter annotations" -# error: [invalid-type-form] "`ClassVar` cannot contain type variables" +# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in parameter annotations" def f[T](x: ClassVar[T]) -> T: return x -# error: [invalid-type-form] "`ClassVar` is not allowed in function return type annotations" +# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in return type annotations" def f() -> ClassVar[int]: return 1 -# error: [invalid-type-form] "`ClassVar` is not allowed in function return type annotations" -# error: [invalid-type-form] "`ClassVar` cannot contain type variables" +# error: [invalid-type-form] "Type qualifier `typing.ClassVar` is not allowed in return type annotations" def f[T](x: T) -> ClassVar[T]: return x diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md index 8ad6a860d7338..8969e0f15e5a4 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md @@ -682,18 +682,18 @@ class C: self.LEGAL_H: Final[int] self.LEGAL_H = 1 -# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations" +# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in parameter annotations" def f(ILLEGAL: Final[int]) -> None: pass -# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations" +# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in parameter annotations" def f[T](ILLEGAL: Final[T]) -> T: return ILLEGAL -# error: [invalid-type-form] "`Final` is not allowed in function return type annotations" +# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in return type annotations" def f() -> Final[None]: ... -# error: [invalid-type-form] "`Final` is not allowed in function return type annotations" +# error: [invalid-type-form] "Type qualifier `typing.Final` is not allowed in return type annotations" def f[T](x: T) -> Final[T]: return x diff --git a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md index fcca32fb3320d..db32d2289ee46 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/initvar.md @@ -149,10 +149,12 @@ from dataclasses import InitVar, dataclass # error: [invalid-type-form] "`InitVar` annotations are only allowed in class-body scopes" x: InitVar[int] = 1 -def f(x: InitVar[int]) -> None: # error: [invalid-type-form] "`InitVar` is not allowed in function parameter annotations" +# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in parameter annotations" +def f(x: InitVar[int]) -> None: pass -def g() -> InitVar[int]: # error: [invalid-type-form] "`InitVar` is not allowed in function return type annotations" +# error: [invalid-type-form] "Type qualifier `dataclasses.InitVar` is not allowed in return type annotations" +def g() -> InitVar[int]: return 1 class C: diff --git a/crates/ty_python_semantic/resources/mdtest/typed_dict.md b/crates/ty_python_semantic/resources/mdtest/typed_dict.md index 91b3d8b0c51a1..77b0a4f4a1ea5 100644 --- a/crates/ty_python_semantic/resources/mdtest/typed_dict.md +++ b/crates/ty_python_semantic/resources/mdtest/typed_dict.md @@ -2778,11 +2778,11 @@ x: TypedDict = {"name": "Alice"} from typing_extensions import Required, NotRequired, ReadOnly def bad( - # error: [invalid-type-form] "`Required` is not allowed in function parameter annotations" + # error: [invalid-type-form] "Type qualifier `typing.Required` is not allowed in parameter annotations" a: Required[int], - # error: [invalid-type-form] "`NotRequired` is not allowed in function parameter annotations" + # error: [invalid-type-form] "Type qualifier `typing.NotRequired` is not allowed in parameter annotations" b: NotRequired[int], - # error: [invalid-type-form] "`ReadOnly` is not allowed in function parameter annotations" + # error: [invalid-type-form] "Type qualifier `typing.ReadOnly` is not allowed in parameter annotations" c: ReadOnly[int], ): ... ``` diff --git a/crates/ty_python_semantic/src/types.rs b/crates/ty_python_semantic/src/types.rs index ca3c071ffd187..a9f0771dcb715 100644 --- a/crates/ty_python_semantic/src/types.rs +++ b/crates/ty_python_semantic/src/types.rs @@ -6834,7 +6834,12 @@ pub struct InvalidTypeExpressionError<'db> { } impl<'db> InvalidTypeExpressionError<'db> { - fn into_fallback_type(self, context: &InferContext, node: &impl Ranged) -> Type<'db> { + fn into_fallback_type( + self, + context: &InferContext, + node: &impl Ranged, + flags: InferenceFlags, + ) -> Type<'db> { let InvalidTypeExpressionError { fallback_type, invalid_expressions, @@ -6843,7 +6848,7 @@ impl<'db> InvalidTypeExpressionError<'db> { let Some(builder) = context.report_lint(&INVALID_TYPE_FORM, node) else { continue; }; - let diagnostic = builder.into_diagnostic(error.reason(context.db())); + let diagnostic = builder.into_diagnostic(error.reason(context.db(), flags)); error.add_subdiagnostics(context.db(), diagnostic, node); } fallback_type @@ -6883,12 +6888,8 @@ enum InvalidTypeExpression<'db> { /// Same for `typing.Concatenate`, anywhere except for as the first parameter of a `Callable` /// type expression Concatenate, - /// Type qualifiers are always invalid in *type expressions*, - /// but these ones are okay with 0 arguments in *annotation expressions* + /// Type qualifiers are always invalid in type expressions TypeQualifier(TypeQualifier), - /// Type qualifiers that are invalid in type expressions, - /// and which would require exactly one argument even if they appeared in an annotation expression - TypeQualifierRequiresOneArgument(TypeQualifier), /// `typing.Self` cannot be used in `@staticmethod` definitions. TypingSelfInStaticMethod, /// `typing.Self` cannot be used in metaclass definitions. @@ -6899,14 +6900,17 @@ enum InvalidTypeExpression<'db> { } impl<'db> InvalidTypeExpression<'db> { - const fn reason(self, db: &'db dyn Db) -> impl std::fmt::Display + 'db { + const fn reason(self, db: &'db dyn Db, flags: InferenceFlags) -> impl std::fmt::Display + 'db { struct Display<'db> { error: InvalidTypeExpression<'db>, db: &'db dyn Db, + flags: InferenceFlags, } impl std::fmt::Display for Display<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let location = self.flags.type_expression_context(); + match self.error { InvalidTypeExpression::RequiresOneArgument(special_form) => write!( f, @@ -6921,47 +6925,68 @@ impl<'db> InvalidTypeExpression<'db> { "`{special_form}` requires at least two arguments when used in a type expression", ), InvalidTypeExpression::Protocol => { - f.write_str("`typing.Protocol` is not allowed in type expressions") + write!(f, "`typing.Protocol` is not allowed in {location}s") } InvalidTypeExpression::Generic => { - f.write_str("`typing.Generic` is not allowed in type expressions") + write!(f, "`typing.Generic` is not allowed in {location}s") } InvalidTypeExpression::Deprecated => { - f.write_str("`warnings.deprecated` is not allowed in type expressions") + write!(f, "`warnings.deprecated` is not allowed in {location}s") } InvalidTypeExpression::Field => { - f.write_str("`dataclasses.Field` is not allowed in type expressions") + write!(f, "`dataclasses.Field` is not allowed in {location}s") } - InvalidTypeExpression::ConstraintSet => f.write_str( - "`ty_extensions.ConstraintSet` is not allowed in type expressions", - ), - InvalidTypeExpression::GenericContext => f.write_str( - "`ty_extensions.GenericContext` is not allowed in type expressions", + InvalidTypeExpression::ConstraintSet => write!( + f, + "`ty_extensions.ConstraintSet` is not allowed in {location}s", ), - InvalidTypeExpression::Specialization => f.write_str( - "`ty_extensions.GenericContext` is not allowed in type expressions", + InvalidTypeExpression::GenericContext => { + write!( + f, + "`ty_extensions.GenericContext` is not allowed in {location}s" + ) + } + InvalidTypeExpression::Specialization => write!( + f, + "`ty_extensions.GenericContext` is not allowed in {location}s", ), InvalidTypeExpression::NamedTupleSpec => { - f.write_str("`NamedTupleSpec` is not allowed in type expressions") + write!(f, "`NamedTupleSpec` is not allowed in {location}s") } - InvalidTypeExpression::TypedDict => f.write_str( + InvalidTypeExpression::TypedDict => write!( + f, "The special form `typing.TypedDict` \ - is not allowed in type expressions", + is not allowed in {location}s", ), InvalidTypeExpression::TypeAlias => f.write_str( "`typing.TypeAlias` is only allowed \ as the sole annotation on an annotated assignment", ), - InvalidTypeExpression::TypeQualifier(qualifier) => write!( - f, - "Type qualifier `{qualifier}` is not allowed in type expressions \ - (only in annotation expressions)", - ), - InvalidTypeExpression::TypeQualifierRequiresOneArgument(qualifier) => write!( - f, - "Type qualifier `{qualifier}` is not allowed in type expressions \ - (only in annotation expressions, and only with exactly one argument)", - ), + InvalidTypeExpression::TypeQualifier(qualifier) => { + if self.flags.intersects( + InferenceFlags::IN_PARAMETER_ANNOTATION + | InferenceFlags::IN_RETURN_TYPE + | InferenceFlags::IN_TYPE_ALIAS, + ) { + write!( + f, + "Type qualifier `{qualifier}` is not allowed in {location}s", + ) + } else if qualifier.requires_one_argument() { + write!( + f, + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions, and only with \ + exactly one argument)", + ) + } else { + write!( + f, + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions)" + ) + } + } InvalidTypeExpression::TypingSelfInStaticMethod => { f.write_str("`Self` cannot be used in a static method") } @@ -6971,18 +6996,18 @@ impl<'db> InvalidTypeExpression<'db> { InvalidTypeExpression::InvalidType(Type::FunctionLiteral(function), _) => { write!( f, - "Function `{function}` is not valid in a type expression", + "Function `{function}` is not valid in a {location}", function = function.name(self.db) ) } InvalidTypeExpression::InvalidType(Type::ModuleLiteral(module), _) => write!( f, - "Module `{module}` is not valid in a type expression", + "Module `{module}` is not valid in a {location}", module = module.module(self.db).name(self.db) ), InvalidTypeExpression::InvalidType(ty, _) => write!( f, - "Variable of type `{ty}` is not allowed in a type expression", + "Variable of type `{ty}` is not allowed in a {location}", ty = ty.display(self.db) ), InvalidTypeExpression::InvalidBareParamSpec(paramspec) => write!( @@ -6997,7 +7022,11 @@ impl<'db> InvalidTypeExpression<'db> { } } - Display { error: self, db } + Display { + error: self, + db, + flags, + } } fn add_subdiagnostics( diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index fac6d20f0efdd..d987ab06bba7f 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -979,7 +979,7 @@ impl<'db> ExpressionInference<'db> { } bitflags::bitflags! { - #[derive(Debug, Clone, Copy, PartialEq, Eq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(crate) struct InferenceFlags: u8 { /// Whether to allow `ParamSpec` in type expressions. /// @@ -997,9 +997,20 @@ bitflags::bitflags! { /// Whether the visitor is currently visiting a vararg annotation /// (e.g., `*args: int` or `**kwargs: int` in a function definition). const IN_VARARG_ANNOTATION = 1 << 2; + + /// Whether the visitor is currently visiting a return-type annotation + const IN_RETURN_TYPE = 1 << 3; + + /// Whether the visitor is currently visiting a type alias value expression + const IN_TYPE_ALIAS = 1 << 4; + + /// Whether the visitor is currently visiting a parameter annotation + const IN_PARAMETER_ANNOTATION = 1 << 5; } } +impl get_size2::GetSize for InferenceFlags {} + impl InferenceFlags { #[must_use = "Inference flags should always be restored to the original value after being temporarily modified"] fn replace(&mut self, other: Self, set_to: bool) -> bool { @@ -1007,4 +1018,16 @@ impl InferenceFlags { self.set(other, set_to); previously_contained_flag } + + pub(super) const fn type_expression_context(self) -> &'static str { + if self.contains(InferenceFlags::IN_RETURN_TYPE) { + "return type annotation" + } else if self.contains(InferenceFlags::IN_PARAMETER_ANNOTATION) { + "parameter annotation" + } else if self.contains(InferenceFlags::IN_TYPE_ALIAS) { + "type alias value" + } else { + "type expression" + } + } } diff --git a/crates/ty_python_semantic/src/types/infer/builder.rs b/crates/ty_python_semantic/src/types/infer/builder.rs index a78fc100caa1e..b6dddb7c44e0d 100644 --- a/crates/ty_python_semantic/src/types/infer/builder.rs +++ b/crates/ty_python_semantic/src/types/infer/builder.rs @@ -1261,7 +1261,9 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let previous_check_unbound_typevars = self .inference_flags .replace(InferenceFlags::CHECK_UNBOUND_TYPEVARS, true); + self.inference_flags |= InferenceFlags::IN_TYPE_ALIAS; let value_ty = self.infer_type_expression(&type_alias.value); + self.inference_flags.remove(InferenceFlags::IN_TYPE_ALIAS); self.inference_flags.set( InferenceFlags::CHECK_UNBOUND_TYPEVARS, previous_check_unbound_typevars, 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 f2bbc379d6722..451f22922fa1a 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 @@ -39,18 +39,6 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_annotation_expression_inner(annotation, deferred_state, PEP613Policy::Allowed) } - /// Similar to [`infer_annotation_expression`], but accepts an optional annotation expression - /// and returns [`None`] if the annotation is [`None`]. - /// - /// [`infer_annotation_expression`]: TypeInferenceBuilder::infer_annotation_expression - pub(super) fn infer_optional_annotation_expression( - &mut self, - annotation: Option<&ast::Expr>, - deferred_state: DeferredExpressionState, - ) -> Option> { - annotation.map(|expr| self.infer_annotation_expression(expr, deferred_state)) - } - fn infer_annotation_expression_inner( &mut self, annotation: &ast::Expr, @@ -148,7 +136,13 @@ impl<'db> TypeInferenceBuilder<'db, '_> { builder.typevar_binding_context, builder.inference_flags, ) - .unwrap_or_else(|error| error.into_fallback_type(&builder.context, annotation)); + .unwrap_or_else(|error| { + error.into_fallback_type( + &builder.context, + annotation, + builder.inference_flags, + ) + }); let result_ty = builder.check_for_unbound_type_variable(annotation, result_ty); TypeAndQualifiers::declared(result_ty) }) @@ -223,7 +217,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.inference_flags, ) .unwrap_or_else(|err| { - err.into_fallback_type(&self.context, subscript) + err.into_fallback_type( + &self.context, + subscript, + self.inference_flags, + ) }); TypeAndQualifiers::declared(in_type_expression) .with_qualifier(inferred.qualifiers()) diff --git a/crates/ty_python_semantic/src/types/infer/builder/function.rs b/crates/ty_python_semantic/src/types/infer/builder/function.rs index fc1365568432c..0b22120b29793 100644 --- a/crates/ty_python_semantic/src/types/infer/builder/function.rs +++ b/crates/ty_python_semantic/src/types/infer/builder/function.rs @@ -1,5 +1,4 @@ use crate::{ - TypeQualifiers, semantic_index::{ definition::{Definition, DefinitionKind}, scope::NodeWithScopeRef, @@ -428,10 +427,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let previous_typevar_binding_context = self.typevar_binding_context.replace(definition); if !has_type_params { - self.infer_return_type_annotation( - function.returns.as_deref(), - self.defer_annotations().into(), - ); + self.infer_return_type_annotation(function.returns.as_deref()); self.infer_parameters(function.parameters.as_ref()); } @@ -488,32 +484,14 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { self.typevar_binding_context = previous_typevar_binding_context; } - fn infer_return_type_annotation( - &mut self, - returns: Option<&ast::Expr>, - deferred_expression_state: DeferredExpressionState, - ) { - let Some(returns) = returns else { - return; - }; - let annotated = self.infer_annotation_expression(returns, deferred_expression_state); - - if annotated.qualifiers.is_empty() { - return; - } - for qualifier in [ - TypeQualifiers::FINAL, - TypeQualifiers::CLASS_VAR, - TypeQualifiers::INIT_VAR, - ] { - if annotated.qualifiers.contains(qualifier) - && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, returns) - { - builder.into_diagnostic(format!( - "`{name}` is not allowed in function return type annotations", - name = qualifier.name() - )); - } + fn infer_return_type_annotation(&mut self, returns: Option<&ast::Expr>) { + if let Some(returns) = returns { + self.inference_flags |= InferenceFlags::IN_RETURN_TYPE; + self.infer_type_expression_with_state( + returns, + DeferredExpressionState::from(self.defer_annotations()), + ); + self.inference_flags.remove(InferenceFlags::IN_RETURN_TYPE); } } @@ -526,10 +504,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { let binding_context = self.index.expect_single_definition(function); let previous_typevar_binding_context = self.typevar_binding_context.replace(binding_context); - self.infer_return_type_annotation( - function.returns.as_deref(), - self.defer_annotations().into(), - ); + self.infer_return_type_annotation(function.returns.as_deref()); self.infer_type_parameters(type_params); self.infer_parameters(&function.parameters); self.typevar_binding_context = previous_typevar_binding_context; @@ -546,6 +521,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { kwarg, } = parameters; + self.inference_flags |= InferenceFlags::IN_PARAMETER_ANNOTATION; for param_with_default in parameters.iter_non_variadic_params() { self.infer_parameter_with_default(param_with_default); } @@ -558,6 +534,8 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if let Some(kwarg) = kwarg { self.infer_parameter(kwarg); } + self.inference_flags + .remove(InferenceFlags::IN_PARAMETER_ANNOTATION); } fn infer_parameter_with_default(&mut self, parameter_with_default: &ast::ParameterWithDefault) { @@ -568,37 +546,11 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { default: _, } = parameter_with_default; - let annotated = self.infer_optional_annotation_expression( - parameter.annotation.as_deref(), - self.defer_annotations().into(), - ); - - let Some(annotated) = annotated else { - return; - }; - - let qualifiers = annotated.qualifiers; - - if qualifiers.is_empty() { - return; - } - - for qualifier in [ - TypeQualifiers::FINAL, - TypeQualifiers::CLASS_VAR, - TypeQualifiers::INIT_VAR, - TypeQualifiers::REQUIRED, - TypeQualifiers::NOT_REQUIRED, - TypeQualifiers::READ_ONLY, - ] { - if qualifiers.contains(qualifier) - && let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameter) - { - builder.into_diagnostic(format!( - "`{name}` is not allowed in function parameter annotations", - name = qualifier.name() - )); - } + if let Some(annotation) = parameter.annotation.as_deref() { + self.infer_type_expression_with_state( + annotation, + DeferredExpressionState::from(self.defer_annotations()), + ); } } @@ -610,10 +562,12 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { annotation, } = parameter; - self.infer_optional_annotation_expression( - annotation.as_deref(), - self.defer_annotations().into(), - ); + if let Some(annotation) = annotation.as_deref() { + self.infer_type_expression_with_state( + annotation, + DeferredExpressionState::from(self.defer_annotations()), + ); + } } /// Set initial declared type (if annotated) and inferred type for a function-parameter symbol, 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 c8bc1513c161b..25de8d486c18f 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 @@ -1,6 +1,7 @@ use itertools::Either; use ruff_python_ast::helpers::is_dotted_name; use ruff_python_ast::{self as ast, PythonVersion}; +use ruff_text_size::Ranged; use super::{DeferredExpressionState, TypeInferenceBuilder}; use crate::semantic_index::scope::ScopeKind; @@ -51,7 +52,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { /// Similar to [`infer_type_expression`], but accepts a [`DeferredExpressionState`]. /// /// [`infer_type_expression`]: TypeInferenceBuilder::infer_type_expression - fn infer_type_expression_with_state( + pub(super) fn infer_type_expression_with_state( &mut self, expression: &ast::Expr, deferred_state: DeferredExpressionState, @@ -64,7 +65,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { fn report_invalid_type_expression( &self, - expression: &ast::Expr, + expression: impl Ranged, message: impl std::fmt::Display, ) -> Option> { self.context @@ -90,7 +91,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.inference_flags, ) .unwrap_or_else(|error| { - error.into_fallback_type(&self.context, expression) + error.into_fallback_type( + &self.context, + expression, + self.inference_flags, + ) }); self.check_for_unbound_type_variable(expression, ty) } @@ -103,18 +108,30 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::Attribute(attribute_expression) => { if is_dotted_name(expression) { match attribute_expression.ctx { - ast::ExprContext::Load => self - .infer_attribute_expression(attribute_expression) - .default_specialize(self.db()) - .in_type_expression( - self.db(), - self.scope(), - self.typevar_binding_context, - self.inference_flags, - ) - .unwrap_or_else(|error| { - error.into_fallback_type(&self.context, expression) - }), + ast::ExprContext::Load => { + let ty = self.infer_attribute_expression(attribute_expression); + + if let Type::TypeVar(tvar) = ty + && tvar.paramspec_attr(self.db()).is_some() + { + ty + } else { + ty.default_specialize(self.db()) + .in_type_expression( + self.db(), + self.scope(), + self.typevar_binding_context, + self.inference_flags, + ) + .unwrap_or_else(|error| { + error.into_fallback_type( + &self.context, + expression, + self.inference_flags, + ) + }) + } + } ast::ExprContext::Invalid => Type::unknown(), ast::ExprContext::Store | ast::ExprContext::Del => { todo_type!("Attribute expression annotation in Store/Del context") @@ -1682,7 +1699,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ) .inner_type() .in_type_expression(self.db(), self.scope(), None, self.inference_flags) - .unwrap_or_else(|err| err.into_fallback_type(&self.context, subscript)), + .unwrap_or_else(|err| { + err.into_fallback_type(&self.context, subscript, self.inference_flags) + }), SpecialFormType::Literal => match self.infer_literal_parameter_type(arguments_slice) { Ok(ty) => ty, Err(nodes) => { @@ -1912,12 +1931,26 @@ impl<'db> TypeInferenceBuilder<'db, '_> { self.infer_parameterized_legacy_typing_alias(subscript, alias) } SpecialFormType::TypeQualifier(qualifier) => { - if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { - let diag = builder.into_diagnostic(format_args!( - "Type qualifier `{qualifier}` is not allowed in type expressions \ - (only in annotation expressions)", - )); - diagnostic::add_type_expression_reference_link(diag); + if self.inference_flags.intersects( + InferenceFlags::IN_PARAMETER_ANNOTATION + | InferenceFlags::IN_RETURN_TYPE + | InferenceFlags::IN_TYPE_ALIAS, + ) { + self.report_invalid_type_expression( + subscript, + format_args!( + "Type qualifier `{qualifier}` is not allowed in {}s", + self.inference_flags.type_expression_context(), + ), + ); + } else { + self.report_invalid_type_expression( + subscript, + format_args!( + "Type qualifier `{qualifier}` is not allowed in type expressions \ + (only in annotation expressions)", + ), + ); } self.infer_type_expression(arguments_slice) } diff --git a/crates/ty_python_semantic/src/types/special_form.rs b/crates/ty_python_semantic/src/types/special_form.rs index b1e44f71d6a58..fd6dd7ba23627 100644 --- a/crates/ty_python_semantic/src/types/special_form.rs +++ b/crates/ty_python_semantic/src/types/special_form.rs @@ -745,7 +745,9 @@ impl SpecialFormType { SpecialFormType::Tuple => Ok(Type::homogeneous_tuple(db, Type::unknown())), SpecialFormType::Callable => Ok(Type::Callable(CallableType::unknown(db))), SpecialFormType::LegacyStdlibAlias(alias) => Ok(alias.aliased_class().to_instance(db)), - SpecialFormType::TypeQualifier(qualifier) => Err(qualifier.in_type_expression()), + SpecialFormType::TypeQualifier(qualifier) => { + Err(InvalidTypeExpression::TypeQualifier(qualifier)) + } } } } @@ -884,17 +886,12 @@ impl TypeQualifier { } } - const fn in_type_expression(self) -> InvalidTypeExpression<'static> { + /// Return `true` if this type qualifier requires exactly one argument + /// when used in a type expression. + pub(super) const fn requires_one_argument(self) -> bool { match self { - TypeQualifier::Final | TypeQualifier::ClassVar => { - InvalidTypeExpression::TypeQualifier(self) - } - TypeQualifier::ReadOnly - | TypeQualifier::NotRequired - | TypeQualifier::InitVar - | TypeQualifier::Required => { - InvalidTypeExpression::TypeQualifierRequiresOneArgument(self) - } + Self::Final | Self::ClassVar => false, + Self::Required | Self::NotRequired | Self::InitVar | Self::ReadOnly => true, } } } From 0bf4a84445eca082e6c9d9d1e6952177f78623aa Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 2 Apr 2026 16:13:30 +0100 Subject: [PATCH 2/3] more --- crates/ty/docs/rules.md | 8 +- .../resources/mdtest/annotations/callable.md | 18 +- .../resources/mdtest/annotations/invalid.md | 92 ++++----- .../resources/mdtest/annotations/literal.md | 2 +- .../resources/mdtest/annotations/string.md | 18 +- .../diagnostics/semantic_syntax_errors.md | 6 +- .../mdtest/generics/pep695/aliases.md | 4 +- .../mdtest/generics/pep695/concatenate.md | 6 +- .../resources/mdtest/implicit_type_aliases.md | 2 +- ...are_o\342\200\246_(58a3839a9bc7026d).snap" | 6 +- ..._set-\342\200\246_(15737b0beb194b0e).snap" | 4 +- ...ed_wh\342\200\246_(ba5cb09eaa3715d8).snap" | 8 +- ...sed_w\342\200\246_(f61204fc81905069).snap" | 12 +- ...n_3.1\342\200\246_(5e6477d05ddea33f).snap" | 16 +- .../infer/builder/annotation_expression.rs | 2 +- .../types/infer/builder/type_expression.rs | 192 +++++++++++++----- .../src/types/string_annotation.rs | 12 +- 17 files changed, 257 insertions(+), 151 deletions(-) diff --git a/crates/ty/docs/rules.md b/crates/ty/docs/rules.md index 1e62ff958d6b9..8d53b96ac0ebd 100644 --- a/crates/ty/docs/rules.md +++ b/crates/ty/docs/rules.md @@ -584,7 +584,7 @@ def bar() -> str: Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -705,7 +705,7 @@ a = 20 / 0 # ty: ignore[division-by-zero] Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -2138,7 +2138,7 @@ super(B, A) # error: `A` does not satisfy `issubclass(A, B)` Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source @@ -3160,7 +3160,7 @@ print(x) # NameError: name 'x' is not defined Default level: error · Added in 0.0.1-alpha.1 · Related issues · -View source +View source diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md index 76d42b11c4b0b..3fa30ed73afa4 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/callable.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/callable.md @@ -52,8 +52,8 @@ def _(c: Callable[42, str]): Or, when one of the parameter type is invalid in the list: ```py -# error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" -# error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression" +# error: [invalid-type-form] "Int literals are not allowed in this context in a parameter annotation" +# error: [invalid-type-form] "Boolean literals are not allowed in this context in a parameter annotation" def _(c: Callable[[int, 42, str, False], None]): # revealed: (int, Unknown, str, Unknown, /) -> None reveal_type(c) @@ -69,7 +69,7 @@ def _(c: Callable[[...], int]): ``` ```py -# error: [invalid-type-form] "`...` is not allowed in this context in a type expression" +# error: [invalid-type-form] "`...` is not allowed in this context in a parameter annotation" def _(c: Callable[[int, ...], int]): reveal_type(c) # revealed: (int, Unknown, /) -> int ``` @@ -114,7 +114,7 @@ from typing import Callable # fmt: off def _(c: Callable[ - # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" + # error: [invalid-type-form] "Int literals are not allowed in this context in a parameter annotation" {1, 2}, 2 # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" ] ): @@ -143,7 +143,7 @@ from typing import Callable def _(c: Callable[ int, # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" - [str] # error: [invalid-type-form] "List literals are not allowed in this context in a type expression" + [str] # error: [invalid-type-form] "List literals are not allowed in this context in a parameter annotation" ] ): reveal_type(c) # revealed: (...) -> Unknown @@ -158,7 +158,7 @@ from typing import Callable def _(c: Callable[ int, # error: [invalid-type-form] "The first argument to `Callable` must be either a list of types, ParamSpec, Concatenate, or `...`" - (str, ) # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression" + (str, ) # error: [invalid-type-form] "Tuple literals are not allowed in this context in a parameter annotation" ] ): reveal_type(c) # revealed: (...) -> Unknown @@ -169,7 +169,7 @@ def _(c: Callable[ ```py from typing import Callable -# error: [invalid-type-form] "List literals are not allowed in this context in a type expression" +# error: [invalid-type-form] "List literals are not allowed in this context in a parameter annotation" def _(c: Callable[[int], [str]]): reveal_type(c) # revealed: (int, /) -> Unknown ``` @@ -184,8 +184,8 @@ from typing import Callable def _(c: Callable[ # error: [invalid-type-form] "Special form `typing.Callable` expected exactly two arguments (parameter types and return type)" [int], - [str], # error: [invalid-type-form] "List literals are not allowed in this context in a type expression" - [bytes] # error: [invalid-type-form] "List literals are not allowed in this context in a type expression" + [str], # error: [invalid-type-form] "List literals are not allowed in this context in a parameter annotation" + [bytes] # error: [invalid-type-form] "List literals are not allowed in this context in a parameter annotation" ] ): reveal_type(c) # revealed: (...) -> Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md index 5f1726b29cb7c..5fc9348736bd0 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/invalid.md @@ -80,37 +80,37 @@ def bar() -> None: def outer_sync(): # `yield` from is only valid syntax inside a synchronous function def _( - a: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in type expressions" + a: (yield from [1]), # error: [invalid-type-form] "`yield from` expressions are not allowed in parameter annotations" ): ... async def baz(): ... async def outer_async(): # avoid unrelated syntax errors on `yield` and `await` def _( - a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" - b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in type expressions" - c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in type expressions" - d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression" + a: 1, # error: [invalid-type-form] "Int literals are not allowed in this context in a parameter annotation" + b: 2.3, # error: [invalid-type-form] "Float literals are not allowed in parameter annotations" + c: 4j, # error: [invalid-type-form] "Complex literals are not allowed in parameter annotations" + d: True, # error: [invalid-type-form] "Boolean literals are not allowed in this context in a parameter annotation" # error: [unsupported-operator] - # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression" + # error: [invalid-type-form] "Bytes literals are not allowed in this context in a parameter annotation" e: int | b"foo", - f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions" - g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in type expressions" - h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in type expressions" - i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in type expressions" - j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions" - k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in type expressions" - l: await baz(), # error: [invalid-type-form] "`await` expressions are not allowed in type expressions" - m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions" - n: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions" - o: bar(), # error: [invalid-type-form] "Function calls are not allowed in type expressions" + f: 1 and 2, # error: [invalid-type-form] "Boolean operations are not allowed in parameter annotations" + g: 1 or 2, # error: [invalid-type-form] "Boolean operations are not allowed in parameter annotations" + h: (foo := 1), # error: [invalid-type-form] "Named expressions are not allowed in parameter annotations" + i: not 1, # error: [invalid-type-form] "Unary operations are not allowed in parameter annotations" + j: lambda: 1, # error: [invalid-type-form] "`lambda` expressions are not allowed in parameter annotations" + k: 1 if True else 2, # error: [invalid-type-form] "`if` expressions are not allowed in parameter annotations" + l: await baz(), # error: [invalid-type-form] "`await` expressions are not allowed in parameter annotations" + m: (yield 1), # error: [invalid-type-form] "`yield` expressions are not allowed in parameter annotations" + n: 1 < 2, # error: [invalid-type-form] "Comparison expressions are not allowed in parameter annotations" + o: bar(), # error: [invalid-type-form] "Function calls are not allowed in parameter annotations" # error: [unsupported-operator] - # error: [invalid-type-form] "F-strings are not allowed in type expressions" + # error: [invalid-type-form] "F-strings are not allowed in parameter annotations" p: int | f"foo", - # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" + # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations" q: [1, 2, 3][1:2], - # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" + # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations" r: list[T][int], - # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" + # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations" s: list[list[T][int]], ): reveal_type(a) # revealed: Unknown @@ -270,25 +270,25 @@ def bar() -> None: async def baz(): ... async def outer_async(): # avoid unrelated syntax errors on `yield` and `await` def _( - a: "1", # error: [invalid-type-form] "Int literals are not allowed in this context in a type expression" - b: "2.3", # error: [invalid-type-form] "Float literals are not allowed in type expressions" - c: "4j", # error: [invalid-type-form] "Complex literals are not allowed in type expressions" - d: "True", # error: [invalid-type-form] "Boolean literals are not allowed in this context in a type expression" - e: "1 and 2", # error: [invalid-type-form] "Boolean operations are not allowed in type expressions" - f: "1 or 2", # error: [invalid-type-form] "Boolean operations are not allowed in type expressions" - g: "(foo := 1)", # error: [invalid-type-form] "Named expressions are not allowed in type expressions" - h: "not 1", # error: [invalid-type-form] "Unary operations are not allowed in type expressions" - i: "lambda: 1", # error: [invalid-type-form] "`lambda` expressions are not allowed in type expressions" - j: "1 if True else 2", # error: [invalid-type-form] "`if` expressions are not allowed in type expressions" - k: "await baz()", # error: [invalid-type-form] "`await` expressions are not allowed in type expressions" - l: "(yield 1)", # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions" - m: "1 < 2", # error: [invalid-type-form] "Comparison expressions are not allowed in type expressions" - n: "bar()", # error: [invalid-type-form] "Function calls are not allowed in type expressions" - # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" + a: "1", # error: [invalid-type-form] "Int literals are not allowed in this context in a parameter annotation" + b: "2.3", # error: [invalid-type-form] "Float literals are not allowed in parameter annotations" + c: "4j", # error: [invalid-type-form] "Complex literals are not allowed in parameter annotations" + d: "True", # error: [invalid-type-form] "Boolean literals are not allowed in this context in a parameter annotation" + e: "1 and 2", # error: [invalid-type-form] "Boolean operations are not allowed in parameter annotations" + f: "1 or 2", # error: [invalid-type-form] "Boolean operations are not allowed in parameter annotations" + g: "(foo := 1)", # error: [invalid-type-form] "Named expressions are not allowed in parameter annotations" + h: "not 1", # error: [invalid-type-form] "Unary operations are not allowed in parameter annotations" + i: "lambda: 1", # error: [invalid-type-form] "`lambda` expressions are not allowed in parameter annotations" + j: "1 if True else 2", # error: [invalid-type-form] "`if` expressions are not allowed in parameter annotations" + k: "await baz()", # error: [invalid-type-form] "`await` expressions are not allowed in parameter annotations" + l: "(yield 1)", # error: [invalid-type-form] "`yield` expressions are not allowed in parameter annotations" + m: "1 < 2", # error: [invalid-type-form] "Comparison expressions are not allowed in parameter annotations" + n: "bar()", # error: [invalid-type-form] "Function calls are not allowed in parameter annotations" + # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations" o: "[1, 2, 3][1:2]", - # error: [invalid-type-form] "Only simple names, dotted names and subscripts can be used in type expressions" + # error: [invalid-type-form] "Only simple names, dotted names and subscripts can be used in parameter annotations" p: list[int].append, - # error: [invalid-type-form] "Only simple names, dotted names and subscripts can be used in type expressions" + # error: [invalid-type-form] "Only simple names, dotted names and subscripts can be used in parameter annotations" q: list[list[int].append], ): reveal_type(a) # revealed: Unknown @@ -319,17 +319,17 @@ python-version = "3.12" ```py def _( - a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in type expressions" - b: {1, 2}, # error: [invalid-type-form] "Set literals are not allowed in type expressions" - c: {k: v for k, v in [(1, 2)]}, # error: [invalid-type-form] "Dict comprehensions are not allowed in type expressions" - d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in type expressions" - e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in type expressions" - f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in type expressions" - # error: [invalid-type-form] "List literals are not allowed in this context in a type expression" + a: {1: 2}, # error: [invalid-type-form] "Dict literals are not allowed in parameter annotations" + b: {1, 2}, # error: [invalid-type-form] "Set literals are not allowed in parameter annotations" + c: {k: v for k, v in [(1, 2)]}, # error: [invalid-type-form] "Dict comprehensions are not allowed in parameter annotations" + d: [k for k in [1, 2]], # error: [invalid-type-form] "List comprehensions are not allowed in parameter annotations" + e: {k for k in [1, 2]}, # error: [invalid-type-form] "Set comprehensions are not allowed in parameter annotations" + f: (k for k in [1, 2]), # error: [invalid-type-form] "Generator expressions are not allowed in parameter annotations" + # error: [invalid-type-form] "List literals are not allowed in this context in a parameter annotation" g: [int, str], - # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[int, str]`?" + # error: [invalid-type-form] "Tuple literals are not allowed in this context in a parameter annotation: Did you mean `tuple[int, str]`?" h: (int, str), - i: (), # error: [invalid-type-form] "Tuple literals are not allowed in this context in a type expression: Did you mean `tuple[()]`?" + i: (), # error: [invalid-type-form] "Tuple literals are not allowed in this context in a parameter annotation: Did you mean `tuple[()]`?" ): reveal_type(a) # revealed: Unknown reveal_type(b) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/literal.md b/crates/ty_python_semantic/resources/mdtest/annotations/literal.md index 119d8404783ed..1e36725340df0 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/literal.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/literal.md @@ -329,7 +329,7 @@ from other import Literal # # ? # -# error: [invalid-type-form] "Invalid subscript of object of type `_SpecialForm` in type expression" +# error: [invalid-type-form] "Invalid subscript of object of type `_SpecialForm` in a type expression" a1: Literal[26] def f(): diff --git a/crates/ty_python_semantic/resources/mdtest/annotations/string.md b/crates/ty_python_semantic/resources/mdtest/annotations/string.md index 1dc6a8ce18c78..5ad349fb4b20a 100644 --- a/crates/ty_python_semantic/resources/mdtest/annotations/string.md +++ b/crates/ty_python_semantic/resources/mdtest/annotations/string.md @@ -217,29 +217,29 @@ class Foo: ... ```py def f1( - # error: [raw-string-type-annotation] "Raw string literals are not allowed in type expressions" + # error: [raw-string-type-annotation] "Raw string literals are not allowed in parameter annotations" a: r"int", - # error: [raw-string-type-annotation] "Raw string literals are not allowed in type expressions" + # error: [raw-string-type-annotation] "Raw string literals are not allowed in parameter annotations" b: list[r"int"], - # error: [invalid-type-form] "F-strings are not allowed in type expressions" + # error: [invalid-type-form] "F-strings are not allowed in parameter annotations" c: f"int", - # error: [invalid-type-form] "F-strings are not allowed in type expressions" + # error: [invalid-type-form] "F-strings are not allowed in parameter annotations" d: list[f"int"], - # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression" + # error: [invalid-type-form] "Bytes literals are not allowed in this context in a parameter annotation" e: b"int", f: "int", # error: [implicit-concatenated-string-type-annotation] "Type expressions cannot span multiple string literals" g: "in" "t", # error: [implicit-concatenated-string-type-annotation] "Type expressions cannot span multiple string literals" h: list["in" "t"], - # error: [escape-character-in-forward-annotation] "Escape characters are not allowed in type expressions" + # error: [escape-character-in-forward-annotation] "Escape characters are not allowed in parameter annotations" i: "\N{LATIN SMALL LETTER I}nt", - # error: [escape-character-in-forward-annotation] "Escape characters are not allowed in type expressions" + # error: [escape-character-in-forward-annotation] "Escape characters are not allowed in parameter annotations" j: "\x69nt", k: """int""", - # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression" + # error: [invalid-type-form] "Bytes literals are not allowed in this context in a parameter annotation" l: "b'int'", - # error: [invalid-type-form] "Bytes literals are not allowed in this context in a type expression" + # error: [invalid-type-form] "Bytes literals are not allowed in this context in a parameter annotation" m: list[b"int"], ): # fmt:skip reveal_type(a) # revealed: Unknown diff --git a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md index 7212217e0ad86..1941be3e9bf33 100644 --- a/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md +++ b/crates/ty_python_semantic/resources/mdtest/diagnostics/semantic_syntax_errors.md @@ -97,7 +97,7 @@ python-version = "3.12" ```py from __future__ import annotations -# error: [invalid-type-form] "Named expressions are not allowed in type expressions" +# error: [invalid-type-form] "Named expressions are not allowed in return type annotations" # error: [invalid-syntax] "named expression cannot be used within a type annotation" def f() -> (y := 3): ... ``` @@ -326,11 +326,11 @@ def _(): type X[T: (yield 1)] = int def _(): - # error: [invalid-type-form] "`yield` expressions are not allowed in type expressions" + # error: [invalid-type-form] "`yield` expressions are not allowed in type alias values" # error: [invalid-syntax] "yield expression cannot be used within a type alias" type Y = (yield 1) -# error: [invalid-type-form] "Named expressions are not allowed in type expressions" +# error: [invalid-type-form] "Named expressions are not allowed in return type annotations" # error: [invalid-syntax] "named expression cannot be used within a generic definition" def f[T](x: int) -> (y := 3): return x diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md index f9588545249d9..396181bfc4db5 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/aliases.md @@ -105,11 +105,11 @@ def _(l: ListOfInts[int]): type List[T] = list[T] -# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" +# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations" def _(l: List[int][int]): reveal_type(l) # revealed: Unknown -# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" +# error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type alias values" type DoubleSpecialization[T] = list[T][T] def _(d: DoubleSpecialization[int]): diff --git a/crates/ty_python_semantic/resources/mdtest/generics/pep695/concatenate.md b/crates/ty_python_semantic/resources/mdtest/generics/pep695/concatenate.md index 6bacc0864e511..24264f09d6110 100644 --- a/crates/ty_python_semantic/resources/mdtest/generics/pep695/concatenate.md +++ b/crates/ty_python_semantic/resources/mdtest/generics/pep695/concatenate.md @@ -219,13 +219,13 @@ from typing import Concatenate # error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a type expression" def invalid0(x: Concatenate): ... -# error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a type expression" +# error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a parameter annotation" def invalid1(x: Concatenate[int]): ... -# error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a type expression" +# error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a parameter annotation" def invalid2(x: Concatenate[int, ...]) -> None: ... -# error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a type expression" +# error: [invalid-type-form] "`typing.Concatenate` is not allowed in this context in a return type annotation" def invalid3() -> Concatenate[int, ...]: ... ``` diff --git a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md index 71494a0628b0f..47596e34d9f9d 100644 --- a/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md +++ b/crates/ty_python_semantic/resources/mdtest/implicit_type_aliases.md @@ -783,7 +783,7 @@ def this_does_not_work() -> TypeOf[IntOrStr]: raise NotImplementedError() def _( - # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in type expressions" + # error: [invalid-type-form] "Only simple names and dotted names can be subscripted in parameter annotations" specialized: this_does_not_work()[int], ): reveal_type(specialized) # revealed: Unknown diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_AST_nodes_that_are_o\342\200\246_(58a3839a9bc7026d).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_AST_nodes_that_are_o\342\200\246_(58a3839a9bc7026d).snap" index 29dacab84a8ac..4652200f5793f 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_AST_nodes_that_are_o\342\200\246_(58a3839a9bc7026d).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_AST_nodes_that_are_o\342\200\246_(58a3839a9bc7026d).snap" @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md # Diagnostics ``` -error[invalid-type-form]: Int literals are not allowed in this context in a type expression +error[invalid-type-form]: Int literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:3:8 | 1 | def bad( @@ -45,7 +45,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Bytes literals are not allowed in this context in a type expression +error[invalid-type-form]: Bytes literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:5:8 | 3 | a: 42, @@ -62,7 +62,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Boolean literals are not allowed in this context in a type expression +error[invalid-type-form]: Boolean literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:7:8 | 5 | b: b"42", diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Dict-literal_or_set-\342\200\246_(15737b0beb194b0e).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Dict-literal_or_set-\342\200\246_(15737b0beb194b0e).snap" index 87da04f6651d4..273be276f8bb1 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Dict-literal_or_set-\342\200\246_(15737b0beb194b0e).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Dict-literal_or_set-\342\200\246_(15737b0beb194b0e).snap" @@ -22,7 +22,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md # Diagnostics ``` -error[invalid-type-form]: Dict literals are not allowed in type expressions +error[invalid-type-form]: Dict literals are not allowed in parameter annotations --> src/mdtest_snippet.py:2:8 | 1 | def _( @@ -38,7 +38,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Set literals are not allowed in type expressions +error[invalid-type-form]: Set literals are not allowed in parameter annotations --> src/mdtest_snippet.py:3:8 | 1 | def _( diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(ba5cb09eaa3715d8).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(ba5cb09eaa3715d8).snap" index 6dc642c163795..ab0e1caa4e5a2 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(ba5cb09eaa3715d8).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_List-literal_used_wh\342\200\246_(ba5cb09eaa3715d8).snap" @@ -28,7 +28,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md # Diagnostics ``` -error[invalid-type-form]: List literals are not allowed in this context in a type expression +error[invalid-type-form]: List literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:2:8 | 1 | def _( @@ -44,7 +44,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: List literals are not allowed in this context in a type expression +error[invalid-type-form]: List literals are not allowed in this context in a return type annotation --> src/mdtest_snippet.py:3:6 | 1 | def _( @@ -60,7 +60,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: List literals are not allowed in this context in a type expression +error[invalid-type-form]: List literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:8:8 | 6 | # No special hints for these: it's unclear what the user meant: @@ -77,7 +77,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: List literals are not allowed in this context in a type expression +error[invalid-type-form]: List literals are not allowed in this context in a return type annotation --> src/mdtest_snippet.py:9:6 | 7 | def _( diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" index 9da08843aeb5f..df4d0cf9ad8d8 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/invalid.md_-_Tests_for_invalid_ty\342\200\246_-_Diagnostics_for_comm\342\200\246_-_Tuple-literal_used_w\342\200\246_(f61204fc81905069).snap" @@ -30,7 +30,7 @@ mdtest path: crates/ty_python_semantic/resources/mdtest/annotations/invalid.md # Diagnostics ``` -error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression +error[invalid-type-form]: Tuple literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:2:8 | 1 | def _( @@ -46,7 +46,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression +error[invalid-type-form]: Tuple literals are not allowed in this context in a return type annotation --> src/mdtest_snippet.py:3:6 | 1 | def _( @@ -63,7 +63,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression +error[invalid-type-form]: Tuple literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:6:8 | 4 | return x @@ -80,7 +80,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression +error[invalid-type-form]: Tuple literals are not allowed in this context in a return type annotation --> src/mdtest_snippet.py:7:6 | 5 | def _( @@ -97,7 +97,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression +error[invalid-type-form]: Tuple literals are not allowed in this context in a parameter annotation --> src/mdtest_snippet.py:10:8 | 8 | return x @@ -114,7 +114,7 @@ info: rule `invalid-type-form` is enabled by default ``` ``` -error[invalid-type-form]: Tuple literals are not allowed in this context in a type expression +error[invalid-type-form]: Tuple literals are not allowed in this context in a return type annotation --> src/mdtest_snippet.py:11:6 | 9 | def _( diff --git "a/crates/ty_python_semantic/resources/mdtest/snapshots/string.md_-_String_annotations_-_Partially_deferred_a\342\200\246_-_Python_less_than_3.1\342\200\246_(5e6477d05ddea33f).snap" "b/crates/ty_python_semantic/resources/mdtest/snapshots/string.md_-_String_annotations_-_Partially_deferred_a\342\200\246_-_Python_less_than_3.1\342\200\246_(5e6477d05ddea33f).snap" index ca2eb92bea212..25131842ce216 100644 --- "a/crates/ty_python_semantic/resources/mdtest/snapshots/string.md_-_String_annotations_-_Partially_deferred_a\342\200\246_-_Python_less_than_3.1\342\200\246_(5e6477d05ddea33f).snap" +++ "b/crates/ty_python_semantic/resources/mdtest/snapshots/string.md_-_String_annotations_-_Partially_deferred_a\342\200\246_-_Python_less_than_3.1\342\200\246_(5e6477d05ddea33f).snap" @@ -102,7 +102,7 @@ error[unsupported-operator]: Unsupported `|` operation 20 | # error: [unsupported-operator] 21 | b: int | "memoryview" | bytes, | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default @@ -123,7 +123,7 @@ error[unsupported-operator]: Unsupported `|` operation 22 | # error: [unsupported-operator] 23 | c: "TD" | None, | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default @@ -144,7 +144,7 @@ error[unsupported-operator]: Unsupported `|` operation 24 | # error: [unsupported-operator] 25 | d: "P" | None, | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default @@ -165,7 +165,7 @@ error[unsupported-operator]: Unsupported `|` operation 26 | # fine: `TypeVar.__or__` accepts strings at runtime 27 | e: T | "Foo", | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default @@ -183,7 +183,7 @@ error[unsupported-operator]: Unsupported `|` operation 34 | # error: [unresolved-reference] "SomethingUndefined" 35 | # error: [unresolved-reference] "SomethingAlsoUndefined" | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line info: rule `unsupported-operator` is enabled by default @@ -233,7 +233,7 @@ error[unsupported-operator]: Unsupported `|` operation 40 | ): 41 | reveal_type(a) # revealed: int | Foo | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default @@ -254,7 +254,7 @@ error[unsupported-operator]: Unsupported `|` operation 40 | ): 41 | reveal_type(a) # revealed: int | Foo | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default @@ -316,7 +316,7 @@ error[unsupported-operator]: Unsupported `|` operation 67 | 68 | class Bar: | -info: All type expressions are evaluated at runtime by default on Python <3.14 +info: All parameter annotations are evaluated at runtime by default on Python <3.14 info: Python 3.13 was assumed when inferring types because it was specified on the command line help: Put quotes around the whole union rather than just certain elements info: rule `unsupported-operator` is enabled by default 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 451f22922fa1a..baa4588a7f097 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 @@ -342,7 +342,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { &mut self, string: &ast::ExprStringLiteral, ) -> TypeAndQualifiers<'db> { - match parse_string_annotation(&self.context, string) { + match parse_string_annotation(&self.context, self.inference_flags, string) { Some(parsed) => { self.string_annotations .insert(ruff_python_ast::ExprRef::StringLiteral(string).into()); 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 25de8d486c18f..c1fa5716cf840 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 @@ -27,6 +27,10 @@ use crate::{FxOrderSet, Program, add_inferred_python_version_hint_to_diagnostic} /// Type expressions impl<'db> TypeInferenceBuilder<'db, '_> { + pub(super) const fn type_expression_context(&self) -> &'static str { + self.inference_flags.type_expression_context() + } + /// Infer the type of a type expression. pub(super) fn infer_type_expression(&mut self, expression: &ast::Expr) -> Type<'db> { let previous_deferred_state = self.deferred_state; @@ -143,8 +147,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - "Only simple names, dotted names and subscripts \ - can be used in type expressions", + format_args!( + "Only simple names, dotted names and subscripts \ + can be used in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -174,7 +181,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - "Only simple names and dotted names can be subscripted in type expressions", + format_args!( + "Only simple names and dotted names can be subscripted in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -297,10 +307,11 @@ impl<'db> TypeInferenceBuilder<'db, '_> { &mut diagnostic, ); } else if python_version < PythonVersion::PY314 { - diagnostic.info( - "All type expressions are evaluated at \ + diagnostic.info(format_args!( + "All {}s are evaluated at \ runtime by default on Python <3.14", - ); + self.type_expression_context() + )); add_inferred_python_version_hint_to_diagnostic( self.db(), &mut diagnostic, @@ -350,7 +361,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::BytesLiteral(bytes) => { if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, - "Bytes literals are not allowed in this context in a type expression", + format_args!( + "Bytes literals are not allowed in this context in a {}", + self.type_expression_context() + ), ) { if let Some(single_element) = bytes.as_single_part_bytestring() && let Ok(valid_string) = String::from_utf8(single_element.value.to_vec()) @@ -370,7 +384,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, format_args!( - "Int literals are not allowed in this context in a type expression" + "Int literals are not allowed in this context in a {}", + self.type_expression_context() ), ) { if let Some(int) = int.as_i64() { @@ -389,7 +404,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }) => { self.report_invalid_type_expression( expression, - format_args!("Float literals are not allowed in type expressions"), + format_args!( + "Float literals are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -400,7 +418,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }) => { self.report_invalid_type_expression( expression, - format_args!("Complex literals are not allowed in type expressions"), + format_args!( + "Complex literals are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -409,7 +430,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, format_args!( - "Boolean literals are not allowed in this context in a type expression" + "Boolean literals are not allowed in this context in a {}", + self.type_expression_context() ), ) { diagnostic.set_primary_message(format_args!( @@ -430,7 +452,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, format_args!( - "List literals are not allowed in this context in a type expression" + "List literals are not allowed in this context in a {}", + self.type_expression_context() ), ) && let [single_element] = &*list.elts { @@ -461,7 +484,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, format_args!( - "Tuple literals are not allowed in this context in a type expression" + "Tuple literals are not allowed in this context in a {}", + self.type_expression_context() ), ) { let mut speculative = self.speculate(); @@ -494,7 +518,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Boolean operations are not allowed in type expressions"), + format_args!( + "Boolean operations are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -505,7 +532,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Named expressions are not allowed in type expressions"), + format_args!( + "Named expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -516,7 +546,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Unary operations are not allowed in type expressions"), + format_args!( + "Unary operations are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -527,7 +560,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("`lambda` expressions are not allowed in type expressions"), + format_args!( + "`lambda` expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -538,7 +574,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("`if` expressions are not allowed in type expressions"), + format_args!( + "`if` expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -549,7 +588,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, - format_args!("Dict literals are not allowed in type expressions"), + format_args!( + "Dict literals are not allowed in {}s", + self.type_expression_context() + ), ) && let [ ast::DictItem { key: Some(key), @@ -578,7 +620,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(mut diagnostic) = self.report_invalid_type_expression( expression, - format_args!("Set literals are not allowed in type expressions"), + format_args!( + "Set literals are not allowed in {}s", + self.type_expression_context() + ), ) && let [single_element] = &*set.elts { let mut speculative_builder = self.speculate(); @@ -603,7 +648,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Dict comprehensions are not allowed in type expressions"), + format_args!( + "Dict comprehensions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -614,7 +662,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("List comprehensions are not allowed in type expressions"), + format_args!( + "List comprehensions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -625,7 +676,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Set comprehensions are not allowed in type expressions"), + format_args!( + "Set comprehensions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -636,7 +690,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Generator expressions are not allowed in type expressions"), + format_args!( + "Generator expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -647,7 +704,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("`await` expressions are not allowed in type expressions"), + format_args!( + "`await` expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -658,7 +718,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("`yield` expressions are not allowed in type expressions"), + format_args!( + "`yield` expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -669,7 +732,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("`yield from` expressions are not allowed in type expressions"), + format_args!( + "`yield from` expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -680,7 +746,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Comparison expressions are not allowed in type expressions"), + format_args!( + "Comparison expressions are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -691,7 +760,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Function calls are not allowed in type expressions"), + format_args!( + "Function calls are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -702,7 +774,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - "F-strings are not allowed in type expressions", + format_args!( + "F-strings are not allowed in {}s", + self.type_expression_context(), + ), ); Type::unknown() } @@ -713,7 +788,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("T-strings are not allowed in type expressions"), + format_args!( + "T-strings are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -724,7 +802,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } self.report_invalid_type_expression( expression, - format_args!("Slices are not allowed in type expressions"), + format_args!( + "Slices are not allowed in {}s", + self.type_expression_context() + ), ); Type::unknown() } @@ -742,7 +823,10 @@ impl<'db> TypeInferenceBuilder<'db, '_> { ast::Expr::EllipsisLiteral(_) => { self.report_invalid_type_expression( expression, - "`...` is not allowed in this context in a type expression", + format_args!( + "`...` is not allowed in this context in a {}", + self.type_expression_context(), + ), ); Type::unknown() } @@ -788,7 +872,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { &mut self, string: &ast::ExprStringLiteral, ) -> Type<'db> { - match parse_string_annotation(&self.context, string) { + match parse_string_annotation(&self.context, self.inference_flags, string) { Some(parsed) => { self.string_annotations .insert(ruff_python_ast::ExprRef::StringLiteral(string).into()); @@ -1251,7 +1335,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`typing.Protocol` is not allowed in type expressions", + "`typing.Protocol` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1262,7 +1347,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`typing.Generic` is not allowed in type expressions", + "`typing.Generic` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1273,7 +1359,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`warnings.deprecated` is not allowed in type expressions", + "`warnings.deprecated` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1284,7 +1371,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`dataclasses.Field` is not allowed in type expressions", + "`dataclasses.Field` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1295,7 +1383,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`ty_extensions.ConstraintSet` is not allowed in type expressions", + "`ty_extensions.ConstraintSet` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1306,7 +1395,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`ty_extensions.GenericContext` is not allowed in type expressions", + "`ty_extensions.GenericContext` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1317,7 +1407,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`ty_extensions.Specialization` is not allowed in type expressions", + "`ty_extensions.Specialization` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -1531,8 +1622,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "Invalid subscript of object of type `{}` in type expression", - value_ty.display(self.db()) + "Invalid subscript of object of type `{}` in a {}", + value_ty.display(self.db()), + self.type_expression_context() )); } Type::unknown() @@ -2014,7 +2106,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { SpecialFormType::Concatenate => { if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { let mut diag = builder.into_diagnostic(format_args!( - "`typing.Concatenate` is not allowed in this context in a type expression", + "`typing.Concatenate` is not allowed in this context in a {}", + self.type_expression_context() )); diag.info("`typing.Concatenate` is only valid:"); diag.info(" - as the first argument to `typing.Callable`"); @@ -2158,7 +2251,8 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, subscript) { builder.into_diagnostic(format_args!( - "`{special_form}` is not allowed in type expressions", + "`{special_form}` is not allowed in {}s", + self.type_expression_context(), )); } Type::unknown() @@ -2364,7 +2458,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { } } ast::Expr::StringLiteral(string) => { - if let Some(parsed) = parse_string_annotation(&self.context, string) { + if let Some(parsed) = + parse_string_annotation(&self.context, self.inference_flags, string) + { self.string_annotations .insert(ruff_python_ast::ExprRef::StringLiteral(string).into()); let node_key = self.enclosing_node_key(string.into()); @@ -2481,7 +2577,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { Some(ConcatenateTail::ParamSpec(typevar)) } ast::Expr::StringLiteral(string) => { - let Some(parsed) = parse_string_annotation(&self.context, string) else { + let Some(parsed) = + parse_string_annotation(&self.context, self.inference_flags, string) + else { report_invalid_concatenate_last_arg(&self.context, expr, Type::unknown()); return None; }; diff --git a/crates/ty_python_semantic/src/types/string_annotation.rs b/crates/ty_python_semantic/src/types/string_annotation.rs index 4b730a55be7b3..0619e81845761 100644 --- a/crates/ty_python_semantic/src/types/string_annotation.rs +++ b/crates/ty_python_semantic/src/types/string_annotation.rs @@ -6,6 +6,7 @@ use ruff_text_size::Ranged; use crate::declare_lint; use crate::lint::{Level, LintStatus}; +use crate::types::infer::InferenceFlags; use super::context::InferContext; @@ -124,6 +125,7 @@ declare_lint! { /// Parses the given expression as a string annotation. pub(crate) fn parse_string_annotation( context: &InferContext, + inference_flags: InferenceFlags, string_expr: &ast::ExprStringLiteral, ) -> Option> { let file = context.file(); @@ -139,7 +141,10 @@ pub(crate) fn parse_string_annotation( if prefix.is_raw() { if let Some(builder) = context.report_lint(&RAW_STRING_TYPE_ANNOTATION, string_literal) { - builder.into_diagnostic("Raw string literals are not allowed in type expressions"); + builder.into_diagnostic(format_args!( + "Raw string literals are not allowed in {}s", + inference_flags.type_expression_context() + )); } // Compare the raw contents (without quotes) of the expression with the parsed contents // contained in the string literal. @@ -166,7 +171,10 @@ pub(crate) fn parse_string_annotation( { // The raw contents of the string doesn't match the parsed content. This could be the // case for annotations that contain escape sequences. - builder.into_diagnostic("Escape characters are not allowed in type expressions"); + builder.into_diagnostic(format_args!( + "Escape characters are not allowed in {}s", + inference_flags.type_expression_context() + )); } } else if let Some(builder) = context.report_lint(&IMPLICIT_CONCATENATED_STRING_TYPE_ANNOTATION, string_expr) From 8d2eec6d3fb6522db12d63f4f8d93342357e8ae8 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Thu, 2 Apr 2026 16:59:53 +0100 Subject: [PATCH 3/3] DRY --- .../infer/builder/annotation_expression.rs | 41 +++--------- .../types/infer/builder/type_expression.rs | 66 ++++++++----------- 2 files changed, 37 insertions(+), 70 deletions(-) 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 baa4588a7f097..0846b84ac051d 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 @@ -128,23 +128,9 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }; special_case.unwrap_or_else(|| { - let result_ty = ty - .default_specialize(builder.db()) - .in_type_expression( - builder.db(), - builder.scope(), - builder.typevar_binding_context, - builder.inference_flags, - ) - .unwrap_or_else(|error| { - error.into_fallback_type( - &builder.context, - annotation, - builder.inference_flags, - ) - }); - let result_ty = builder.check_for_unbound_type_variable(annotation, result_ty); - TypeAndQualifiers::declared(result_ty) + TypeAndQualifiers::declared( + builder.infer_name_or_attribute_type_expression(ty, annotation), + ) }) } @@ -158,21 +144,12 @@ impl<'db> TypeInferenceBuilder<'db, '_> { return TypeAndQualifiers::declared(self.infer_type_expression(annotation)); } match attribute.ctx { - ast::ExprContext::Load => { - let attribute_type = self.infer_attribute_expression(attribute); - if let Type::TypeVar(typevar) = attribute_type - && typevar.paramspec_attr(self.db()).is_some() - { - TypeAndQualifiers::declared(attribute_type) - } else { - infer_name_or_attribute( - attribute_type, - annotation, - self, - pep_613_policy, - ) - } - } + ast::ExprContext::Load => infer_name_or_attribute( + self.infer_attribute_expression(attribute), + annotation, + self, + pep_613_policy, + ), ast::ExprContext::Invalid => TypeAndQualifiers::declared(Type::unknown()), ast::ExprContext::Store | ast::ExprContext::Del => TypeAndQualifiers::declared( todo_type!("Attribute expression annotation in Store/Del context"), 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 c1fa5716cf840..cd70240a19b37 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 @@ -79,29 +79,39 @@ impl<'db> TypeInferenceBuilder<'db, '_> { }) } + pub(super) fn infer_name_or_attribute_type_expression( + &self, + ty: Type<'db>, + annotation: &ast::Expr, + ) -> Type<'db> { + if annotation.is_attribute_expr() + && let Type::TypeVar(tvar) = ty + && tvar.paramspec_attr(self.db()).is_some() + { + return ty; + } + let result_ty = ty + .default_specialize(self.db()) + .in_type_expression( + self.db(), + self.scope(), + self.typevar_binding_context, + self.inference_flags, + ) + .unwrap_or_else(|error| { + error.into_fallback_type(&self.context, annotation, self.inference_flags) + }); + self.check_for_unbound_type_variable(annotation, result_ty) + } + /// Infer the type of a type expression without storing the result. pub(super) fn infer_type_expression_no_store(&mut self, expression: &ast::Expr) -> Type<'db> { // https://typing.python.org/en/latest/spec/annotations.html#grammar-token-expression-grammar-type_expression match expression { ast::Expr::Name(name) => match name.ctx { ast::ExprContext::Load => { - let ty = self - .infer_name_expression(name) - .default_specialize(self.db()) - .in_type_expression( - self.db(), - self.scope(), - self.typevar_binding_context, - self.inference_flags, - ) - .unwrap_or_else(|error| { - error.into_fallback_type( - &self.context, - expression, - self.inference_flags, - ) - }); - self.check_for_unbound_type_variable(expression, ty) + let ty = self.infer_name_expression(name); + self.infer_name_or_attribute_type_expression(ty, expression) } ast::ExprContext::Invalid => Type::unknown(), ast::ExprContext::Store | ast::ExprContext::Del => { @@ -114,27 +124,7 @@ impl<'db> TypeInferenceBuilder<'db, '_> { match attribute_expression.ctx { ast::ExprContext::Load => { let ty = self.infer_attribute_expression(attribute_expression); - - if let Type::TypeVar(tvar) = ty - && tvar.paramspec_attr(self.db()).is_some() - { - ty - } else { - ty.default_specialize(self.db()) - .in_type_expression( - self.db(), - self.scope(), - self.typevar_binding_context, - self.inference_flags, - ) - .unwrap_or_else(|error| { - error.into_fallback_type( - &self.context, - expression, - self.inference_flags, - ) - }) - } + self.infer_name_or_attribute_type_expression(ty, expression) } ast::ExprContext::Invalid => Type::unknown(), ast::ExprContext::Store | ast::ExprContext::Del => {