From 01af039bad07f5eb1ca4d676cd4f033175dcf69e Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 22 Jul 2025 10:29:01 +0200 Subject: [PATCH 1/2] [ty] Disallow `Final` in function parameter annotations --- .../resources/mdtest/type_qualifiers/final.md | 2 +- crates/ty_python_semantic/src/types/infer.rs | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) 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 f52f1b45cde96..1e7e603de480f 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md @@ -275,7 +275,7 @@ class C: self.LEGAL_I: Final[int] self.LEGAL_I = 1 -# TODO: This should be an error +# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations" def f(ILLEGAL: Final[int]) -> None: pass diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index df1c814578ae8..372d22b4d1b7f 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -2668,10 +2668,16 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { default: _, } = parameter_with_default; - self.infer_optional_annotation_expression( + let annotated = self.infer_optional_annotation_expression( parameter.annotation.as_deref(), DeferredExpressionState::None, ); + + if annotated.is_some_and(|annotated| annotated.qualifiers.contains(TypeQualifiers::FINAL)) { + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, parameter) { + builder.into_diagnostic("`Final` is not allowed in function parameter annotations"); + } + } } fn infer_parameter(&mut self, parameter: &ast::Parameter) { From 5a96cfc7a099bc65217f2edf558ff1bbc4983fc0 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 22 Jul 2025 13:06:21 +0200 Subject: [PATCH 2/2] Emit errors for return type annotations as well --- .../resources/mdtest/type_qualifiers/final.md | 15 +++++++++++- crates/ty_python_semantic/src/types/infer.rs | 24 ++++++++++++++++--- 2 files changed, 35 insertions(+), 4 deletions(-) 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 1e7e603de480f..7db86e2f0f772 100644 --- a/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md +++ b/crates/ty_python_semantic/resources/mdtest/type_qualifiers/final.md @@ -255,6 +255,11 @@ class Derived(Base): Final may only be used in assignments or variable annotations. Using it in any other position is an error. +```toml +[environment] +python-version = "3.12" +``` + ```py from typing import Final, ClassVar, Annotated @@ -279,9 +284,17 @@ class C: def f(ILLEGAL: Final[int]) -> None: pass -# TODO: This should be an error +# error: [invalid-type-form] "`Final` is not allowed in function parameter annotations" +def f[T](ILLEGAL: Final[int]) -> None: + pass + +# error: [invalid-type-form] "`Final` is not allowed in function return type annotations" def f() -> Final[None]: ... +# error: [invalid-type-form] "`Final` is not allowed in function return type annotations" +def f[T](x: T) -> Final[T]: + return x + # TODO: This should be an error class Foo(Final[tuple[int]]): ... diff --git a/crates/ty_python_semantic/src/types/infer.rs b/crates/ty_python_semantic/src/types/infer.rs index 372d22b4d1b7f..89b374fbe3e6a 100644 --- a/crates/ty_python_semantic/src/types/infer.rs +++ b/crates/ty_python_semantic/src/types/infer.rs @@ -2219,7 +2219,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { .as_deref() .expect("function type params scope without type params"); - self.infer_optional_annotation_expression( + self.infer_return_type_annotation( function.returns.as_deref(), DeferredExpressionState::None, ); @@ -2581,7 +2581,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { if self.defer_annotations() { self.deferred.insert(definition); } else { - self.infer_optional_annotation_expression( + self.infer_return_type_annotation( returns.as_deref(), DeferredExpressionState::None, ); @@ -2638,6 +2638,24 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { ); } + fn infer_return_type_annotation( + &mut self, + returns: Option<&ast::Expr>, + deferred_expression_state: DeferredExpressionState, + ) { + if let Some(returns) = returns { + let annotated = self.infer_annotation_expression(returns, deferred_expression_state); + + if annotated.qualifiers.contains(TypeQualifiers::FINAL) { + if let Some(builder) = self.context.report_lint(&INVALID_TYPE_FORM, returns) { + builder.into_diagnostic( + "`Final` is not allowed in function return type annotations", + ); + } + } + } + } + fn infer_parameters(&mut self, parameters: &ast::Parameters) { let ast::Parameters { range: _, @@ -2962,7 +2980,7 @@ impl<'db, 'ast> TypeInferenceBuilder<'db, 'ast> { } fn infer_function_deferred(&mut self, function: &ast::StmtFunctionDef) { - self.infer_optional_annotation_expression( + self.infer_return_type_annotation( function.returns.as_deref(), DeferredExpressionState::Deferred, );