Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer type analysis of integer Literal when builtins.int is slow to resolve #18046

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -3181,7 +3181,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.
Expand Down Expand Up @@ -3280,6 +3284,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?

Expand Down
6 changes: 6 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1701,6 +1701,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)
Expand Down
73 changes: 73 additions & 0 deletions test-data/unit/check-literal.test
Original file line number Diff line number Diff line change
Expand Up @@ -2984,3 +2984,76 @@ class C(Base):
reveal_type(sep) # N: Revealed type is "Union[Literal['a'], Literal['b']]"
return super().feed_data(sep)
[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: ...
Loading