From 9180cd094d7a468ba2df3fc904b2f0549f6619c4 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 22 Jul 2025 13:15:19 +0200 Subject: [PATCH] [ty] Disallow `Final` in function parameter/return-type annotations (#19480) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Disallow `Final` in function parameter- and return-type annotations. [Typing spec](https://typing.python.org/en/latest/spec/qualifiers.html#uppercase-final): > `Final` may only be used in assignments or variable annotations. Using it in any other position is an error. In particular, `Final` can’t be used in annotations for function arguments ## Test Plan Updated MD test --- .../resources/mdtest/type_qualifiers/final.md | 17 ++++++++-- crates/ty_python_semantic/src/types/infer.rs | 32 ++++++++++++++++--- 2 files changed, 43 insertions(+), 6 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 f52f1b45cd..7db86e2f0f 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 @@ -275,13 +280,21 @@ 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 -# 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 df1c814578..89b374fbe3 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: _, @@ -2668,10 +2686,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) { @@ -2956,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, );