Skip to content

Commit 9cfc081

Browse files
committed
Merge branch '2023-03-06-dataclasses-replace' of https://github.com/ikonst/mypy into 2023-03-06-dataclasses-replace
2 parents 0e84c4f + 367c0e9 commit 9cfc081

File tree

7 files changed

+432
-48
lines changed

7 files changed

+432
-48
lines changed

docs/source/error_code_list.rst

Lines changed: 29 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ Check that assignment target is not a method [method-assign]
344344

345345
In general, assigning to a method on class object or instance (a.k.a.
346346
monkey-patching) is ambiguous in terms of types, since Python's static type
347-
system cannot express difference between bound and unbound callable types.
347+
system cannot express the difference between bound and unbound callable types.
348348
Consider this example:
349349

350350
.. code-block:: python
@@ -355,18 +355,18 @@ Consider this example:
355355
356356
def h(self: A) -> None: pass
357357
358-
A.f = h # type of h is Callable[[A], None]
359-
A().f() # this works
360-
A.f = A().g # type of A().g is Callable[[], None]
361-
A().f() # but this also works at runtime
358+
A.f = h # Type of h is Callable[[A], None]
359+
A().f() # This works
360+
A.f = A().g # Type of A().g is Callable[[], None]
361+
A().f() # ...but this also works at runtime
362362
363363
To prevent the ambiguity, mypy will flag both assignments by default. If this
364-
error code is disabled, mypy will treat all method assignments r.h.s. as unbound,
365-
so the second assignment will still generate an error.
364+
error code is disabled, mypy will treat the assigned value in all method assignments as unbound,
365+
so only the second assignment will still generate an error.
366366

367367
.. note::
368368

369-
This error code is a sub-error code of a wider ``[assignment]`` code.
369+
This error code is a subcode of the more general ``[assignment]`` code.
370370

371371
Check type variable values [type-var]
372372
-------------------------------------
@@ -456,11 +456,11 @@ Example:
456456
Check TypedDict items [typeddict-item]
457457
--------------------------------------
458458

459-
When constructing a ``TypedDict`` object, mypy checks that each key and value is compatible
460-
with the ``TypedDict`` type that is inferred from the surrounding context.
459+
When constructing a TypedDict object, mypy checks that each key and value is compatible
460+
with the TypedDict type that is inferred from the surrounding context.
461461

462-
When getting a ``TypedDict`` item, mypy checks that the key
463-
exists. When assigning to a ``TypedDict``, mypy checks that both the
462+
When getting a TypedDict item, mypy checks that the key
463+
exists. When assigning to a TypedDict, mypy checks that both the
464464
key and the value are valid.
465465

466466
Example:
@@ -480,10 +480,13 @@ Example:
480480
Check TypedDict Keys [typeddict-unknown-key]
481481
--------------------------------------------
482482

483-
When constructing a ``TypedDict`` object, mypy checks whether the definition
484-
contains unknown keys. For convenience's sake, mypy will not generate an error
485-
when a ``TypedDict`` has extra keys if it's passed to a function as an argument.
486-
However, it will generate an error when these are created. Example:
483+
When constructing a TypedDict object, mypy checks whether the
484+
definition contains unknown keys, to catch invalid keys and
485+
misspellings. On the other hand, mypy will not generate an error when
486+
a previously constructed TypedDict value with extra keys is passed
487+
to a function as an argument, since TypedDict values support
488+
structural subtyping ("static duck typing") and the keys are assumed
489+
to have been validated at the point of construction. Example:
487490

488491
.. code-block:: python
489492
@@ -502,23 +505,23 @@ However, it will generate an error when these are created. Example:
502505
a: Point = {"x": 1, "y": 4}
503506
b: Point3D = {"x": 2, "y": 5, "z": 6}
504507
505-
# OK
506-
add_x_coordinates(a, b)
508+
add_x_coordinates(a, b) # OK
509+
507510
# Error: Extra key "z" for TypedDict "Point" [typeddict-unknown-key]
508511
add_x_coordinates(a, {"x": 1, "y": 4, "z": 5})
509512
510-
511-
Setting an unknown value on a ``TypedDict`` will also generate this error:
513+
Setting a TypedDict item using an unknown key will also generate this
514+
error, since it could be a misspelling:
512515

513516
.. code-block:: python
514517
515518
a: Point = {"x": 1, "y": 2}
516519
# Error: Extra key "z" for TypedDict "Point" [typeddict-unknown-key]
517520
a["z"] = 3
518521
519-
520-
Whereas reading an unknown value will generate the more generic/serious
521-
``typeddict-item``:
522+
Reading an unknown key will generate the more general (and serious)
523+
``typeddict-item`` error, which is likely to result in an exception at
524+
runtime:
522525

523526
.. code-block:: python
524527
@@ -528,7 +531,7 @@ Whereas reading an unknown value will generate the more generic/serious
528531
529532
.. note::
530533

531-
This error code is a sub-error code of a wider ``[typeddict-item]`` code.
534+
This error code is a subcode of the wider ``[typeddict-item]`` code.
532535

533536
Check that type of target is known [has-type]
534537
---------------------------------------------
@@ -810,8 +813,8 @@ Check that literal is used where expected [literal-required]
810813
There are some places where only a (string) literal value is expected for
811814
the purposes of static type checking, for example a ``TypedDict`` key, or
812815
a ``__match_args__`` item. Providing a ``str``-valued variable in such contexts
813-
will result in an error. Note however, in many cases you can use ``Final``,
814-
or ``Literal`` variables, for example:
816+
will result in an error. Note that in many cases you can also use ``Final``
817+
or ``Literal`` variables. Example:
815818

816819
.. code-block:: python
817820

docs/source/error_codes.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,13 @@ So one can e.g. enable some code globally, disable it for all tests in
114114
the corresponding config section, and then re-enable it with an inline
115115
comment in some specific test.
116116

117-
Sub-error codes of other error codes
118-
------------------------------------
117+
Subcodes of error codes
118+
-----------------------
119119

120-
In rare cases (mostly for backwards compatibility reasons), some error
121-
code may be covered by another, wider error code. For example, an error with
120+
In some cases, mostly for backwards compatibility reasons, an error
121+
code may be covered also by another, wider error code. For example, an error with
122122
code ``[method-assign]`` can be ignored by ``# type: ignore[assignment]``.
123123
Similar logic works for disabling error codes globally. If a given error code
124-
is a sub code of another one, it must mentioned in the docs for the narrower
125-
code. This hierarchy is not nested, there cannot be sub-error codes of other
126-
sub-error codes.
124+
is a subcode of another one, it will be mentioned in the documentation for the narrower
125+
code. This hierarchy is not nested: there cannot be subcodes of other
126+
subcodes.

mypy/plugins/dataclasses.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Optional
5+
from typing import Iterator, Optional
66
from typing_extensions import Final
77

88
from mypy import errorcodes, message_registry
@@ -18,12 +18,14 @@
1818
MDEF,
1919
Argument,
2020
AssignmentStmt,
21+
Block,
2122
CallExpr,
2223
ClassDef,
2324
Context,
2425
DataclassTransformSpec,
2526
Expression,
2627
FuncDef,
28+
IfStmt,
2729
JsonDict,
2830
NameExpr,
2931
Node,
@@ -413,6 +415,22 @@ def reset_init_only_vars(self, info: TypeInfo, attributes: list[DataclassAttribu
413415
# recreate a symbol node for this attribute.
414416
lvalue.node = None
415417

418+
def _get_assignment_statements_from_if_statement(
419+
self, stmt: IfStmt
420+
) -> Iterator[AssignmentStmt]:
421+
for body in stmt.body:
422+
if not body.is_unreachable:
423+
yield from self._get_assignment_statements_from_block(body)
424+
if stmt.else_body is not None and not stmt.else_body.is_unreachable:
425+
yield from self._get_assignment_statements_from_block(stmt.else_body)
426+
427+
def _get_assignment_statements_from_block(self, block: Block) -> Iterator[AssignmentStmt]:
428+
for stmt in block.body:
429+
if isinstance(stmt, AssignmentStmt):
430+
yield stmt
431+
elif isinstance(stmt, IfStmt):
432+
yield from self._get_assignment_statements_from_if_statement(stmt)
433+
416434
def collect_attributes(self) -> list[DataclassAttribute] | None:
417435
"""Collect all attributes declared in the dataclass and its parents.
418436
@@ -471,10 +489,10 @@ def collect_attributes(self) -> list[DataclassAttribute] | None:
471489
# Second, collect attributes belonging to the current class.
472490
current_attr_names: set[str] = set()
473491
kw_only = self._get_bool_arg("kw_only", self._spec.kw_only_default)
474-
for stmt in cls.defs.body:
492+
for stmt in self._get_assignment_statements_from_block(cls.defs):
475493
# Any assignment that doesn't use the new type declaration
476494
# syntax can be ignored out of hand.
477-
if not (isinstance(stmt, AssignmentStmt) and stmt.new_syntax):
495+
if not stmt.new_syntax:
478496
continue
479497

480498
# a: int, b: str = 1, 'foo' is not supported syntax so we

mypy/stubtest.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
from contextlib import redirect_stderr, redirect_stdout
2626
from functools import singledispatch
2727
from pathlib import Path
28-
from typing import Any, Generic, Iterator, TypeVar, Union, cast
28+
from typing import Any, Generic, Iterator, TypeVar, Union
2929
from typing_extensions import get_origin
3030

3131
import mypy.build
@@ -476,10 +476,7 @@ def verify_typeinfo(
476476
to_check = set(stub.names)
477477
# Check all public things on the runtime class
478478
to_check.update(
479-
# cast to workaround mypyc complaints
480-
m
481-
for m in cast(Any, vars)(runtime)
482-
if not is_probably_private(m) and m not in IGNORABLE_CLASS_DUNDERS
479+
m for m in vars(runtime) if not is_probably_private(m) and m not in IGNORABLE_CLASS_DUNDERS
483480
)
484481
# Special-case the __init__ method for Protocols
485482
#

mypy/types.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -950,7 +950,8 @@ def __init__(
950950

951951
def accept(self, visitor: TypeVisitor[T]) -> T:
952952
assert isinstance(visitor, SyntheticTypeVisitor)
953-
return cast(T, visitor.visit_callable_argument(self))
953+
ret: T = visitor.visit_callable_argument(self)
954+
return ret
954955

955956
def serialize(self) -> JsonDict:
956957
assert False, "Synthetic types don't serialize"
@@ -975,7 +976,8 @@ def __init__(self, items: list[Type], line: int = -1, column: int = -1) -> None:
975976

976977
def accept(self, visitor: TypeVisitor[T]) -> T:
977978
assert isinstance(visitor, SyntheticTypeVisitor)
978-
return cast(T, visitor.visit_type_list(self))
979+
ret: T = visitor.visit_type_list(self)
980+
return ret
979981

980982
def serialize(self) -> JsonDict:
981983
assert False, "Synthetic types don't serialize"
@@ -2489,7 +2491,8 @@ def simple_name(self) -> str:
24892491

24902492
def accept(self, visitor: TypeVisitor[T]) -> T:
24912493
assert isinstance(visitor, SyntheticTypeVisitor)
2492-
return cast(T, visitor.visit_raw_expression_type(self))
2494+
ret: T = visitor.visit_raw_expression_type(self)
2495+
return ret
24932496

24942497
def serialize(self) -> JsonDict:
24952498
assert False, "Synthetic types don't serialize"
@@ -2736,7 +2739,8 @@ class EllipsisType(ProperType):
27362739

27372740
def accept(self, visitor: TypeVisitor[T]) -> T:
27382741
assert isinstance(visitor, SyntheticTypeVisitor)
2739-
return cast(T, visitor.visit_ellipsis_type(self))
2742+
ret: T = visitor.visit_ellipsis_type(self)
2743+
return ret
27402744

27412745
def serialize(self) -> JsonDict:
27422746
assert False, "Synthetic types don't serialize"
@@ -2845,7 +2849,8 @@ def __init__(self, fullname: str | None, args: list[Type], line: int) -> None:
28452849

28462850
def accept(self, visitor: TypeVisitor[T]) -> T:
28472851
assert isinstance(visitor, SyntheticTypeVisitor)
2848-
return cast(T, visitor.visit_placeholder_type(self))
2852+
ret: T = visitor.visit_placeholder_type(self)
2853+
return ret
28492854

28502855
def __hash__(self) -> int:
28512856
return hash((self.fullname, tuple(self.args)))

0 commit comments

Comments
 (0)