Skip to content

Unify and fixLiteral/Final interaction - #2353

Open
srittau wants to merge 13 commits into
python:mainfrom
srittau:final-literal
Open

Unify and fixLiteral/Final interaction#2353
srittau wants to merge 13 commits into
python:mainfrom
srittau:final-literal

Conversation

@srittau

@srittau srittau commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator
  • Add an "Inference Rules" subsection to the Final section.
  • Move the inference rules for class vars there.
  • Remove contradictory paragraph about literal handling from Final section.
  • Merge the "Interactions with Final" section from literals into the new "Inference Rules" section, add a link, and trim slightly.

I've decided to move the interaction section to the "qualifiers" chapters,
since Literal seems much closer linked to Final than vice versa.

Closes: #2351

- Add an "Inference Rules" subsection to the `Final` section.
- Move the inference rules for class vars there.
- Remove contradictory paragraph about literal handling from `Final`
  section.
- Merge the "Interactions with Final" section from literals into the new
  "Inference Rules" section, add a link, and trim slightly.
@srittau srittau added the topic: typing spec For improving the typing spec label Aug 31, 2026
Comment thread docs/spec/qualifiers.rst
Comment on lines -162 to -168
Type checkers should infer a final attribute that is initialized in a class
body as being a class variable, except in the case of :doc:`dataclasses`, where
``x: Final[int] = 3`` creates a dataclass field and instance-level final
attribute ``x`` with default value ``3``; ``x: ClassVar[Final[int]] = 3`` is
necessary to create a final class variable with value ``3``. In
non-dataclasses, combining ``ClassVar`` and ``Final`` is redundant, and type
checkers may choose to warn or error on the redundancy.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got moved to the "Inference Rules" section.

Comment thread docs/spec/qualifiers.rst
Comment on lines -196 to -204
Type checkers should treat uses of a final name that was initialized
with a literal as if it was replaced by the literal. For example, the
following should be allowed::

from typing import NamedTuple, Final

X: Final = "x"
Y: Final = "y"
N = NamedTuple("N", [(X, int), (Y, int)])

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This got removed completely as it's redundant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is redundant or should be removed. The position of X and Y in this NamedTuple declaration is not an ordinary "call expects a literal type" position; it's syntax-sensitive type-factory metadata that is special-cased by type checkers; they must be able to understand as describing a specific field name. I don't think the rules below are sufficient to clarify that this example must work.

Also the conformance tests still quote this deleted paragraph.

Comment thread docs/spec/qualifiers.rst
Comment on lines +198 to +205
In the example below, we know that ``foo`` will always be equal to
exactly ``3``. A type checker can use this information to deduce that ``foo``
is valid to use in any context that expects a ``Literal[3]``::

def expects_three(x: Literal[3]) -> None: ...

foo: Final = 3
expects_three(foo) # Type checks, since 'foo' is Final and equal to 3

@srittau srittau Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The introductory part was reshuffled a bit from the original section to be a better fit here. The rest is identical.

@srittau

srittau commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

I just noticed that the introductory sentence about "normal inference" is actually important. I will change the PR when I get home.

@srittau

srittau commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

The spec used to say:

The typechecker should apply its usual type inference mechanisms to determine the type of ID (here, likely, int)"

before I replaced that sentence with a link to the new inference rules section. The problem is that this contradicts the explicit guidance in the section I copied over from the literals spec:

Type checkers are not obligated to understand any other uses of Final. For
example, whether or not the following program type checks is left unspecified: [...]

My suggestion: We leave this original sentence out for now, reverting back to "left unspecified" for now. But I did plan to open a discuss thread anyway to define a few more supported cases, like X: Final = cls(), Y: Final = 3.2, and remove the ambiguity surrounding Z: Final[int] = 3.

@carljm carljm left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think combining these sections makes sense. Left some inline comments.

I think this is a clarification, not a substantive spec change, so I don't know that we need the full process including DPO post here. But it's a hefty enough rewording / rearrangement that I do think we should try to get at least most of the typing council to approve it.

Comment thread docs/spec/qualifiers.rst

The typechecker should apply its usual type inference mechanisms to
determine the type of ``ID`` (here, likely, ``int``). Note that unlike for
The typechecker should apply the inference mechanisms

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rules below don't attempt to cover every possible right-hand-side of an assignment to a Final annotated attribute; they are specific to valid literal values. So I think we still need the "typechecker should apply its usual type inference mechanisms" default fallback language here, with the literal rules below as additional constraints. I don't think we can just refer to the rules below as though they fully specify inference of all Final assignments.

Currently this seems to drop any requirement for type checkers to support e.g. x: Final = C() or x: Final = ['a', 'b'], because those right-hand-sides are not valid Literal values.

expects_one(ID4) # E?: May or may not be accepted by type checkers

ID5: Final = range(1)
assert_type(ID5, range) # E?: May or may not be inferred by type checkers

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three of these tests are kind of useless as conformance tests, but the first two I don't mind, since I think they still provide useful clarification that this behavior is not specified either way. But this third one is a really strange test, since we assert that range(1) may or may not be inferred as range -- which is not even a literal type anyway. So it really feels like this test is saying nothing at all. I would just remove it.

Comment thread docs/spec/qualifiers.rst
Comment on lines -196 to -204
Type checkers should treat uses of a final name that was initialized
with a literal as if it was replaced by the literal. For example, the
following should be allowed::

from typing import NamedTuple, Final

X: Final = "x"
Y: Final = "y"
N = NamedTuple("N", [(X, int), (Y, int)])

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is redundant or should be removed. The position of X and Y in this NamedTuple declaration is not an ordinary "call expects a literal type" position; it's syntax-sensitive type-factory metadata that is special-cased by type checkers; they must be able to understand as describing a specific field name. I don't think the rules below are sufficient to clarify that this example must work.

Also the conformance tests still quote this deleted paragraph.

Comment thread docs/spec/qualifiers.rst
``Literal[...]``, type checkers should understand that ``var`` may be used in
any context that expects a ``Literal[value]``.

Type checkers are not obligated to understand any other uses of Final. For

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this sentence confusing, so if we're clarifying here, I think we should clarify it. Obviously type checkers are generally expected to understand any use of Final as requiring finality (i.e. not accepting later reassignments). So why would we say they are "not obligated to understand any other uses of Final"? It seems like what we really mean is more like this:

Suggested change
Type checkers are not obligated to understand any other uses of Final. For
Type checkers are not obligated to understand any other uses of Final as implying a Literal type. For

Comment thread docs/spec/qualifiers.rst
Type checkers are expected to
support this shortcut. Specifically, given a variable or attribute assignment
of the form ``var: Final = value`` where ``value`` is a valid parameter for
``Literal[...]``, type checkers should understand that ``var`` may be used in

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wording is a bit vague. Should we just say that type checkers must infer Literal[...]?


ID1: Final = 1
assert_type(ID1, Literal[1])
expects_one(ID1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems redundant with the assert_type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

topic: typing spec For improving the typing spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Finals and Literals: Spec contradiction

3 participants