Fixed bug that causes Final annotation not to be honored when wrapped in an Annotated annotation. This addresses #8097.

This commit is contained in:
Eric Traut 2024-06-08 10:10:38 -07:00
parent ec229aa925
commit 5a0ec2dda4
3 changed files with 44 additions and 22 deletions

View File

@ -3924,6 +3924,12 @@ export class Binder extends ParseTreeWalker {
if (this._isTypingAnnotation(typeAnnotation, 'Final')) {
isFinal = true;
} else if (
typeAnnotation.nodeType === ParseNodeType.Index &&
typeAnnotation.items.length > 0 &&
this._isTypingAnnotation(typeAnnotation.baseExpression, 'Annotated')
) {
return this._isAnnotationFinal(typeAnnotation.items[0].valueExpression);
} else if (typeAnnotation.nodeType === ParseNodeType.Index && typeAnnotation.items.length === 1) {
// Recursively call to see if the base expression is "Final".
const finalInfo = this._isAnnotationFinal(typeAnnotation.baseExpression);

View File

@ -2,37 +2,37 @@
# introduced in Python 3.8.
import typing
from typing import Any, Final, Protocol, TypeVar
from typing import Annotated, Any, Final, Protocol, TypeVar
T = TypeVar("T")
foo1: typing.Final = 3
v1: typing.Final = 3
must_be_int: int = foo1
must_be_int: int = v1
# This should generate an error because
# reassignment of a Final variable should
# not be allowed.
foo1 = 4
v1 = 4
# This should generate an error because there
# is a previous Final declaration.
foo1: Final[int]
v1: Final[int]
# This should generate an error because
# the type doesn't match.
foo2: Final[str] = 3
v2: Final[str] = 3
# This should generate an error because
# we expect only one type argument for Final.
foo3: Final[str, int] = "hello"
v3: Final[str, int] = "hello"
foo4: Final = 5
reveal_type(foo4, expected_text="Literal[5]")
v4: Final = 5
reveal_type(v4, expected_text="Literal[5]")
class Foo:
class ClassA:
member1: Final = 4
# This should generate an error because only
@ -85,11 +85,11 @@ class Foo:
self.member7: Final = 6
reveal_type(Foo.member1, expected_text="Literal[4]")
reveal_type(Foo(True).member1, expected_text="Literal[4]")
reveal_type(ClassA.member1, expected_text="Literal[4]")
reveal_type(ClassA(True).member1, expected_text="Literal[4]")
class Bar(Foo):
class ClassB(ClassA):
# This should generate an error because we are overriding
# a member that is marked Final in the parent class.
member1 = 5
@ -118,7 +118,7 @@ def func1(a: Final[int]):
b: list[Final[int]] = []
class ClassA:
class ClassC:
member1: Final = 3
member2: Final
member4: Final
@ -133,10 +133,10 @@ class ClassA:
self.member3: Final = "hi"
# This should generate an error.
ClassA.member4 = "hi"
ClassC.member4 = "hi"
# This should generate an error.
ClassA.member5 = 3
ClassC.member5 = 3
def other(self):
# This should generate an error.
@ -149,7 +149,7 @@ class ClassA:
self.member3 = "hi"
a = ClassA()
a = ClassC()
# This should generate an error.
a.member1 = 4
@ -185,7 +185,7 @@ def func2():
(a, x) = (1, 2)
class ClassB:
class ClassD:
def __init__(self):
self.x: Final = 1
@ -194,7 +194,7 @@ class ClassB:
self.x += 1
class ClassC(Protocol):
class ClassE(Protocol):
x: Final[int]
@ -206,11 +206,11 @@ def func3(x: type[T]) -> T:
func3(Final[int])
foo5: Final = lambda: None
v5: Final = lambda: None
# This should generate an error because foo5 is declared as Final.
def foo5() -> None:
def v5() -> None:
pass
@ -218,3 +218,19 @@ def foo5() -> None:
from typing import ClassVar
ClassVar: Final = 3
v6: Annotated[Final[int], "meta"] = 1
# This should generate an error
v6 = 2
v7: Annotated[Annotated[Final[int], "meta"], "meta"] = 1
# This should generate an error
v7 = 2
v8: Annotated[Final, "meta"] = 1
# This should generate an error
v8 = 2

View File

@ -32,7 +32,7 @@ test('Final2', () => {
test('Final3', () => {
const analysisResults = TestUtils.typeAnalyzeSampleFiles(['final3.py']);
TestUtils.validateResults(analysisResults, 35);
TestUtils.validateResults(analysisResults, 38);
});
test('Final4', () => {