Skip to content

Commit bd40f7f

Browse files
committed
Merge remote-tracking branch 'upstream/master' into stubtover
2 parents 73e19e3 + 765a78f commit bd40f7f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+3253
-185
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ repos:
1111
- id: black
1212
exclude: '^(test-data/)'
1313
- repo: https://github.com/astral-sh/ruff-pre-commit
14-
rev: v0.8.6
14+
rev: v0.9.10
1515
hooks:
1616
- id: ruff
1717
args: [--exit-non-zero-on-fix]

docs/source/type_narrowing.rst

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,15 @@ techniques which are supported by mypy.
88

99
Type narrowing is when you convince a type checker that a broader type is actually more specific, for instance, that an object of type ``Shape`` is actually of the narrower type ``Square``.
1010

11+
The following type narrowing techniques are available:
12+
13+
- :ref:`type-narrowing-expressions`
14+
- :ref:`casts`
15+
- :ref:`type-guards`
16+
- :ref:`typeis`
17+
18+
19+
.. _type-narrowing-expressions:
1120

1221
Type narrowing expressions
1322
--------------------------
@@ -356,40 +365,6 @@ What happens here?
356365

357366
The same will work with ``isinstance(x := a, float)`` as well.
358367

359-
Limitations
360-
-----------
361-
362-
Mypy's analysis is limited to individual symbols and it will not track
363-
relationships between symbols. For example, in the following code
364-
it's easy to deduce that if :code:`a` is None then :code:`b` must not be,
365-
therefore :code:`a or b` will always be an instance of :code:`C`,
366-
but Mypy will not be able to tell that:
367-
368-
.. code-block:: python
369-
370-
class C:
371-
pass
372-
373-
def f(a: C | None, b: C | None) -> C:
374-
if a is not None or b is not None:
375-
return a or b # Incompatible return value type (got "C | None", expected "C")
376-
return C()
377-
378-
Tracking these sort of cross-variable conditions in a type checker would add significant complexity
379-
and performance overhead.
380-
381-
You can use an ``assert`` to convince the type checker, override it with a :ref:`cast <casts>`
382-
or rewrite the function to be slightly more verbose:
383-
384-
.. code-block:: python
385-
386-
def f(a: C | None, b: C | None) -> C:
387-
if a is not None:
388-
return a
389-
elif b is not None:
390-
return b
391-
return C()
392-
393368

394369
.. _typeis:
395370

@@ -555,3 +530,38 @@ You can use the assignment expression operator ``:=`` with ``TypeIs`` to create
555530
reveal_type(x) # Revealed type is 'float'
556531
# x is narrowed to float in this block
557532
print(x + 1.0)
533+
534+
535+
Limitations
536+
-----------
537+
538+
Mypy's analysis is limited to individual symbols and it will not track
539+
relationships between symbols. For example, in the following code
540+
it's easy to deduce that if :code:`a` is None then :code:`b` must not be,
541+
therefore :code:`a or b` will always be an instance of :code:`C`,
542+
but Mypy will not be able to tell that:
543+
544+
.. code-block:: python
545+
546+
class C:
547+
pass
548+
549+
def f(a: C | None, b: C | None) -> C:
550+
if a is not None or b is not None:
551+
return a or b # Incompatible return value type (got "C | None", expected "C")
552+
return C()
553+
554+
Tracking these sort of cross-variable conditions in a type checker would add significant complexity
555+
and performance overhead.
556+
557+
You can use an ``assert`` to convince the type checker, override it with a :ref:`cast <casts>`
558+
or rewrite the function to be slightly more verbose:
559+
560+
.. code-block:: python
561+
562+
def f(a: C | None, b: C | None) -> C:
563+
if a is not None:
564+
return a
565+
elif b is not None:
566+
return b
567+
return C()

mypy/binder.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,9 @@
77
from typing_extensions import TypeAlias as _TypeAlias
88

99
from mypy.erasetype import remove_instance_last_known_values
10-
from mypy.literals import Key, literal, literal_hash, subkeys
10+
from mypy.literals import Key, extract_var_from_literal_hash, literal, literal_hash, subkeys
1111
from mypy.nodes import Expression, IndexExpr, MemberExpr, NameExpr, RefExpr, TypeInfo, Var
12+
from mypy.options import Options
1213
from mypy.subtypes import is_same_type, is_subtype
1314
from mypy.typeops import make_simplified_union
1415
from mypy.types import (
@@ -39,6 +40,7 @@ class CurrentType(NamedTuple):
3940

4041
class Frame:
4142
"""A Frame represents a specific point in the execution of a program.
43+
4244
It carries information about the current types of expressions at
4345
that point, arising either from assignments to those expressions
4446
or the result of isinstance checks and other type narrowing
@@ -97,7 +99,7 @@ class A:
9799
# This maps an expression to a list of bound types for every item in the union type.
98100
type_assignments: Assigns | None = None
99101

100-
def __init__(self) -> None:
102+
def __init__(self, options: Options) -> None:
101103
# Each frame gets an increasing, distinct id.
102104
self.next_id = 1
103105

@@ -131,6 +133,11 @@ def __init__(self) -> None:
131133
self.break_frames: list[int] = []
132134
self.continue_frames: list[int] = []
133135

136+
# If True, initial assignment to a simple variable (e.g. "x", but not "x.y")
137+
# is added to the binder. This allows more precise narrowing and more
138+
# flexible inference of variable types (--allow-redefinition-new).
139+
self.bind_all = options.allow_redefinition_new
140+
134141
def _get_id(self) -> int:
135142
self.next_id += 1
136143
return self.next_id
@@ -226,12 +233,20 @@ def update_from_options(self, frames: list[Frame]) -> bool:
226233
for key in keys:
227234
current_value = self._get(key)
228235
resulting_values = [f.types.get(key, current_value) for f in frames]
229-
if any(x is None for x in resulting_values):
236+
# Keys can be narrowed using two different semantics. The new semantics
237+
# is enabled for plain variables when bind_all is true, and it allows
238+
# variable types to be widened using subsequent assignments. This is
239+
# tricky to support for instance attributes (primarily due to deferrals),
240+
# so we don't use it for them.
241+
old_semantics = not self.bind_all or extract_var_from_literal_hash(key) is None
242+
if old_semantics and any(x is None for x in resulting_values):
230243
# We didn't know anything about key before
231244
# (current_value must be None), and we still don't
232245
# know anything about key in at least one possible frame.
233246
continue
234247

248+
resulting_values = [x for x in resulting_values if x is not None]
249+
235250
if all_reachable and all(
236251
x is not None and not x.from_assignment for x in resulting_values
237252
):
@@ -278,7 +293,11 @@ def update_from_options(self, frames: list[Frame]) -> bool:
278293
# still equivalent to such type).
279294
if isinstance(type, UnionType):
280295
type = collapse_variadic_union(type)
281-
if isinstance(type, ProperType) and isinstance(type, UnionType):
296+
if (
297+
old_semantics
298+
and isinstance(type, ProperType)
299+
and isinstance(type, UnionType)
300+
):
282301
# Simplify away any extra Any's that were added to the declared
283302
# type when popping a frame.
284303
simplified = UnionType.make_union(

mypy/build.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2240,8 +2240,10 @@ def semantic_analysis_pass1(self) -> None:
22402240
# TODO: Do this while constructing the AST?
22412241
self.tree.names = SymbolTable()
22422242
if not self.tree.is_stub:
2243-
# Always perform some low-key variable renaming
2244-
self.tree.accept(LimitedVariableRenameVisitor())
2243+
if not self.options.allow_redefinition_new:
2244+
# Perform some low-key variable renaming when assignments can't
2245+
# widen inferred types
2246+
self.tree.accept(LimitedVariableRenameVisitor())
22452247
if options.allow_redefinition:
22462248
# Perform more renaming across the AST to allow variable redefinitions
22472249
self.tree.accept(VariableRenameVisitor())

0 commit comments

Comments
 (0)