diff --git a/mypy/semanal.py b/mypy/semanal.py index 77e6b0c005e2..150c5022ce9e 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -3218,7 +3218,11 @@ def visit_assignment_stmt(self, s: AssignmentStmt) -> None: else: s.rvalue.accept(self) - if self.found_incomplete_ref(tag) or self.should_wait_rhs(s.rvalue): + if ( + self.found_incomplete_ref(tag) + or self.should_wait_rhs(s.rvalue) + or self.should_wait_lhs(s) + ): # Initializer couldn't be fully analyzed. Defer the current node and give up. # Make sure that if we skip the definition of some local names, they can't be # added later in this scope, since an earlier definition should take precedence. @@ -3317,6 +3321,23 @@ def analyze_identity_global_assignment(self, s: AssignmentStmt) -> bool: node.fullname = sym.node.fullname return True + def should_wait_lhs(self, s: AssignmentStmt) -> bool: + """Is the l.h.s of an assignment ready? + + If the eventual l.h.s. type turns out to be a special form, we need to know that before + we can process the r.h.s. properly. + """ + if self.final_iteration: + # No chance, nothing has changed. + return False + if isinstance(s.type, UnboundType): + lookup = self.lookup_qualified(s.type.name, s, suppress_errors=True) + if lookup and isinstance(lookup.node, PlaceholderNode): + if isinstance(lookup.node.node, ImportFrom): + if lookup.node.node.id in ("typing", "typing_extensions"): + return True + return False + def should_wait_rhs(self, rv: Expression) -> bool: """Can we already classify this r.h.s. of an assignment or should we wait? diff --git a/mypy/typeanal.py b/mypy/typeanal.py index 204d3061c734..d22bbda7806b 100644 --- a/mypy/typeanal.py +++ b/mypy/typeanal.py @@ -1753,6 +1753,12 @@ def analyze_literal_param(self, idx: int, arg: Type, ctx: Context) -> list[Type] # helpful, but it generally won't make sense in the context of a Literal[...]. return None + # Make sure the literal's class is ready + sym = self.api.lookup_fully_qualified_or_none(arg.base_type_name) + if sym is None or isinstance(sym.node, PlaceholderNode): + self.api.record_incomplete_ref() + return [AnyType(TypeOfAny.special_form)] + # Remap bytes and unicode into the appropriate type for the correct Python version fallback = self.named_type(arg.base_type_name) assert isinstance(fallback, Instance) diff --git a/test-data/unit/check-literal.test b/test-data/unit/check-literal.test index 3c9290b8dbbb..b36119bc95bd 100644 --- a/test-data/unit/check-literal.test +++ b/test-data/unit/check-literal.test @@ -2995,3 +2995,75 @@ def check(obj: A[Literal[1]]) -> None: reveal_type(g('', obj)) # E: Cannot infer value of type parameter "T" of "g" \ # N: Revealed type is "Any" [builtins fixtures/tuple.pyi] + +-- This is constructed so that when mypy performs first pass +-- type analysis on _LiteralInteger, builtins.int is still a +-- placeholder, and the analysis must defer to avoid an error. +[case testDeferIntLiteral] +[file typing.py] +import abc + +[file abc.py] +import collections.abc + +[file collections/__init__.py] + +[file collections/abc.py] +from _collections_abc import * + +[file _collections_abc.py] + +[file typing_extensions.py] +Literal: object + +[file numbers.py] +class Number: ... + +[file builtins.py] +import numbers +import typing +from typing_extensions import Literal + +_LiteralInteger: Literal[0] + +class int(numbers.Number): ... +class str: ... +class list: ... +class dict: ... +class ellipsis: ... + + +-- In this version, looking up the node for builtins.int +-- during type analysis of _LiteralInteger returns None +[case testDeferIntLiteral2] +[file _collections_abc.pyi] +[file abc.pyi] +[file collections/__init__.pyi] +[file collections/abc.pyi] +[file types.pyi] +class UnionType: ... + +[file typing_extensions.pyi] +from typing import Any +TypeAlias: Any + +[file typing.pyi] +Any = object() +Literal: Any + +[file builtins.pyi] +import _collections_abc +import abc +import collections.abc +import types +from typing import Literal +from typing_extensions import TypeAlias + +_PositiveInteger: TypeAlias = Literal[1] +_LiteralInteger = _PositiveInteger | Literal[0] + +class int: ... +class ellipsis: ... +class str: ... +class list: ... +class dict: ...