@@ -463,23 +463,149 @@ DECLARED_THEN_BOUND = 1
463463``` py
464464from typing import Final
465465
466- # TODO : This should be an error
467- NO_ASSIGNMENT_A : Final
468- # TODO : This should be an error
469- NO_ASSIGNMENT_B : Final[int ]
466+ NO_ASSIGNMENT_A : Final # error: [final-without-value] "`Final` symbol `NO_ASSIGNMENT_A` is not assigned a value"
467+ NO_ASSIGNMENT_B : Final[int ] # error: [final-without-value] "`Final` symbol `NO_ASSIGNMENT_B` is not assigned a value"
470468
471469class C :
472- # TODO : This should be an error
473- NO_ASSIGNMENT_A : Final
474- # TODO : This should be an error
475- NO_ASSIGNMENT_B : Final[int ]
470+ NO_ASSIGNMENT_A : Final # error: [final-without-value] "`Final` symbol `NO_ASSIGNMENT_A` is not assigned a value"
471+ NO_ASSIGNMENT_B : Final[int ] # error: [final-without-value] "`Final` symbol `NO_ASSIGNMENT_B` is not assigned a value"
476472
477473 DEFINED_IN_INIT : Final[int ]
478474
479475 def __init__ (self ):
480476 self .DEFINED_IN_INIT = 1
481477```
482478
479+ ### Function-local ` Final ` without value
480+
481+ ``` py
482+ from typing import Final
483+
484+ def f ():
485+ x: Final[int ] # error: [final-without-value] "`Final` symbol `x` is not assigned a value"
486+ ```
487+
488+ ### ` typing_extensions.Final ` without value
489+
490+ ``` py
491+ from typing_extensions import Final
492+
493+ TEXF_NO_VALUE : Final[str ] # error: [final-without-value] "`Final` symbol `TEXF_NO_VALUE` is not assigned a value"
494+ ```
495+
496+ ### ` Annotated[Final[...], ...] ` without value
497+
498+ ``` py
499+ from typing import Annotated, Final
500+
501+ ANNOTATED_FINAL : Annotated[ # error: [final-without-value] "`Final` symbol `ANNOTATED_FINAL` is not assigned a value"
502+ Final[int ], " metadata"
503+ ]
504+ ```
505+
506+ ### Imported ` Final ` symbol
507+
508+ Importing a symbol that is declared ` Final ` in its source module should not trigger
509+ ` final-without-value ` , because the import itself provides the binding.
510+
511+ ` module.py ` :
512+
513+ ``` py
514+ from typing import Final
515+
516+ MODULE_FINAL : Final[int ] = 1
517+ ```
518+
519+ ` test.py ` :
520+
521+ ``` py
522+ from module import MODULE_FINAL
523+ ```
524+
525+ Even if the imported symbol is later deleted (a common pattern to clean up module namespaces), it
526+ should not trigger the diagnostic.
527+
528+ ` test_del.py ` :
529+
530+ ``` py
531+ from module import MODULE_FINAL
532+
533+ _ = MODULE_FINAL
534+
535+ del MODULE_FINAL
536+ ```
537+
538+ ### Stub file ` Final ` without value
539+
540+ In stub files, ` Final ` declarations without a value are permitted, at both module and class scope.
541+
542+ ` stub.pyi ` :
543+
544+ ``` pyi
545+ from typing import Final
546+
547+ STUB_FINAL : Final[int ]
548+
549+ class StubClass :
550+ STUB_ATTR : Final[str ]
551+ ```
552+
553+ ### Conditional assignment in ` __init__ `
554+
555+ A ` Final ` attribute declared in the class body and conditionally assigned in ` __init__ ` should not
556+ trigger ` final-without-value ` , since at least one path provides a binding.
557+
558+ ``` py
559+ from typing import Final
560+
561+ class C :
562+ x: Final[int ]
563+
564+ def __init__ (self , flag : bool ):
565+ if flag:
566+ self .x = 1
567+ else :
568+ self .x = 2
569+
570+ class D :
571+ y: Final[int ]
572+
573+ def __init__ (self , flag : bool ):
574+ if flag:
575+ self .y = 1
576+ # No else: y may be unbound at runtime, but there is still an assignment path
577+ ```
578+
579+ ### Assignment in non-` __init__ ` method
580+
581+ Per the typing spec, a ` Final ` attribute declared in a class body without a value must be
582+ initialized in ` __init__ ` . Assignment in other methods does not satisfy the requirement.
583+
584+ ``` py
585+ from typing import Final
586+
587+ class E :
588+ x: Final[int ] # error: [final-without-value] "`Final` symbol `x` is not assigned a value"
589+
590+ def setup (self ):
591+ # error: [invalid-assignment] "Cannot assign to final attribute `x`"
592+ self .x = 1 # Too late: not __init__
593+ ```
594+
595+ ### Dataclass with ` Final ` field
596+
597+ Dataclass-like classes do not report ` final-without-value ` because the ` __init__ ` is synthesized by
598+ the framework.
599+
600+ ``` py
601+ from dataclasses import dataclass
602+ from typing import Final
603+
604+ @dataclass
605+ class D :
606+ x: Final[int ] # No error: dataclass generates __init__
607+ ```
608+
483609## Final attributes with Self annotation in ` __init__ `
484610
485611Issue #1409 : Final instance attributes should be assignable in ` __init__ ` even when using ` Self `
@@ -566,7 +692,7 @@ class DeclareAndAssignInInit:
566692
567693# Case 6: Assignment outside __init__ should still fail
568694class AssignmentOutsideInit :
569- attr6: Final[int ]
695+ attr6: Final[int ] # error: [final-without-value] "`Final` symbol `attr6` is not assigned a value"
570696
571697 def other_method (self ):
572698 # error: [invalid-assignment] "Cannot assign to final attribute `attr6`"
@@ -634,4 +760,12 @@ from _stat import ST_INO
634760ST_INO = 1 # error: [invalid-assignment]
635761```
636762
763+ ` Final ` declaration without value:
764+
765+ ``` py
766+ from typing import Final
767+
768+ UNINITIALIZED : Final[int ] # error: [final-without-value]
769+ ```
770+
637771[ `typing.final` ] : https://docs.python.org/3/library/typing.html#typing.Final
0 commit comments