Skip to content

Commit 7cb5eac

Browse files
committed
Merge remote-tracking branch 'upstream/master' into conditional-overloads
2 parents d6cc690 + 14de8c6 commit 7cb5eac

Some content is hidden

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

77 files changed

+2112
-467
lines changed

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
with:
2424
python-version: '3.7'
2525
- name: Install tox
26-
run: pip install --upgrade 'setuptools!=50' 'virtualenv<16.7.11' tox==3.20.1
26+
run: pip install --upgrade 'setuptools!=50' 'virtualenv>=20.6.0' tox==3.24.5
2727
- name: Setup tox environment
2828
run: tox -e ${{ env.TOXENV }} --notest
2929
- name: Test

.github/workflows/mypy_primer_comment.yml

Lines changed: 10 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -42,42 +42,11 @@ jobs:
4242
- run: |
4343
cat diff_*.txt | head -c 30000 | tee fulldiff.txt
4444
45-
# Based on https://github.com/kanga333/comment-hider
46-
- name: Hide old comments
47-
uses: actions/github-script@v3
48-
with:
49-
github-token: ${{secrets.GITHUB_TOKEN}}
50-
script: |
51-
const fs = require('fs')
52-
53-
const response = await github.issues.listComments({
54-
issue_number: fs.readFileSync("pr_number.txt", { encoding: "utf8" }),
55-
owner: context.repo.owner,
56-
repo: context.repo.repo,
57-
})
58-
const botCommentIds = response.data
59-
.filter(comment => comment.user.login === 'github-actions[bot]')
60-
.map(comment => comment.node_id)
61-
62-
for (const id of botCommentIds) {
63-
const resp = await github.graphql(`
64-
mutation {
65-
minimizeComment(input: {classifier: OUTDATED, subjectId: "${id}"}) {
66-
minimizedComment {
67-
isMinimized
68-
}
69-
}
70-
}
71-
`)
72-
if (resp.errors) {
73-
throw new Error(resp.errors)
74-
}
75-
}
76-
7745
- name: Post comment
46+
id: post-comment
7847
uses: actions/github-script@v3
7948
with:
80-
github-token: ${{secrets.GITHUB_TOKEN}}
49+
github-token: ${{ secrets.GITHUB_TOKEN }}
8150
script: |
8251
const fs = require('fs')
8352
const data = fs.readFileSync('fulldiff.txt', { encoding: 'utf8' })
@@ -99,3 +68,11 @@ jobs:
9968
body
10069
})
10170
return prNumber
71+
72+
- name: Hide old comments
73+
# v0.3.0
74+
uses: kanga333/comment-hider@bbdf5b562fbec24e6f60572d8f712017428b92e0
75+
with:
76+
github_token: ${{ secrets.GITHUB_TOKEN }}
77+
leave_visible: 1
78+
issue_number: ${{ steps.post-comment.outputs.result }}

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ jobs:
107107
./misc/build-debug-python.sh $PYTHONVERSION $PYTHONDIR $VENV
108108
source $VENV/bin/activate
109109
- name: Install tox
110-
run: pip install --upgrade 'setuptools!=50' 'virtualenv>=20.6.0' tox==3.20.1
110+
run: pip install --upgrade 'setuptools!=50' 'virtualenv>=20.6.0' tox==3.24.5
111111
- name: Compiled with mypyc
112112
if: ${{ matrix.test_mypyc }}
113113
run: |

CONTRIBUTING.md

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ You can also use `tox` to run tests, for instance:
5454
tox -e py
5555
```
5656

57+
The easiest way to run a single test is:
58+
```
59+
pytest -n0 -k 'test_name'
60+
```
61+
There's more useful information on writing and running tests [here](test-data/unit/README.md)
62+
5763
## First time contributors
5864

5965
If you're looking for things to help with, browse our [issue tracker](https://github.com/python/mypy/issues)!
@@ -63,21 +69,24 @@ In particular, look for:
6369
- [good second issues](https://github.com/python/mypy/labels/good-second-issue)
6470
- [documentation issues](https://github.com/python/mypy/labels/documentation)
6571

66-
It's also extremely easy to get started contributing to our sister project
67-
[typeshed](https://github.com/python/typeshed/issues) that provides type stubs
68-
for libraries. This is a great way to become familiar with type syntax.
69-
70-
If you need help getting started, don't hesitate to ask on
71-
[gitter](https://gitter.im/python/typing).
72+
You do not need to ask for permission to work on any of these issues.
73+
Just fix the issue yourself, [try to add a unit test](#running-tests) and
74+
[open a pull request](#submitting-changes).
7275

7376
To get help fixing a specific issue, it's often best to comment on the issue
74-
itself. The more details you provide about what you've tried and where you've
75-
looked, the easier it will be for you to get help.
77+
itself. You're much more likely to get help if you provide details about what
78+
you've tried and where you've looked (maintainers tend to help those who help
79+
themselves). [gitter](https://gitter.im/python/typing) can also be a good place
80+
to ask for help.
7681

7782
Interactive debuggers like `pdb` and `ipdb` are really useful for getting
7883
started with the mypy codebase. This is a
7984
[useful tutorial](https://realpython.com/python-debugging-pdb/).
8085

86+
It's also extremely easy to get started contributing to our sister project
87+
[typeshed](https://github.com/python/typeshed/issues) that provides type stubs
88+
for libraries. This is a great way to become familiar with type syntax.
89+
8190
## Submitting changes
8291

8392
Even more excellent than a good bug report is a fix for a bug, or the

CREDITS

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@ Dropbox core team:
1111

1212
Jukka Lehtosalo <[email protected]>
1313
Ivan Levkivskyi <[email protected]>
14+
Jared Hance
1415

1516
Non-Dropbox core team members:
1617

1718
Ethan Smith
1819
Guido van Rossum <[email protected]>
19-
Jelle Zijlstra
20+
Jelle Zijlstra <[email protected]>
2021
Michael J. Sullivan <[email protected]>
2122
Shantanu Jain
22-
Xuanda Yang
23+
Xuanda Yang <[email protected]>
24+
Jingchen Ye <[email protected]>
25+
Nikita Sobolev <[email protected]>
2326

2427
Past Dropbox core team members:
2528

docs/source/error_code_list2.rst

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,3 +255,36 @@ no error in practice. In such case, it might be prudent to annotate ``items: Seq
255255
This is similar in concept to ensuring that an expression's type implements an expected interface (e.g. ``Sized``),
256256
except that attempting to invoke an undefined method (e.g. ``__len__``) results in an error,
257257
while attempting to evaluate an object in boolean context without a concrete implementation results in a truthy value.
258+
259+
260+
.. _ignore-without-code:
261+
262+
Check that ``# type: ignore`` include an error code [ignore-without-code]
263+
-------------------------------------------------------------------------
264+
265+
Warn when a ``# type: ignore`` comment does not specify any error codes.
266+
This clarifies the intent of the ignore and ensures that only the
267+
expected errors are silenced.
268+
269+
Example:
270+
271+
.. code-block:: python
272+
273+
# mypy: enable-error-code ignore-without-code
274+
275+
class Foo:
276+
def __init__(self, name: str) -> None:
277+
self.name = name
278+
279+
f = Foo('foo')
280+
281+
# This line has a typo that mypy can't help with as both:
282+
# - the expected error 'assignment', and
283+
# - the unexpected error 'attr-defined'
284+
# are silenced.
285+
# Error: "type: ignore" comment without error code (consider "type: ignore[attr-defined]" instead)
286+
f.nme = 42 # type: ignore
287+
288+
# This line warns correctly about the typo in the attribute name
289+
# Error: "Foo" has no attribute "nme"; maybe "name"?
290+
f.nme = 42 # type: ignore[assignment]

docs/source/error_codes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ or config `show_error_codes = True` to display error codes. Error codes are show
3131
$ mypy --show-error-codes prog.py
3232
prog.py:1: error: "str" has no attribute "trim" [attr-defined]
3333
34+
It's also possible to require error codes for ``type: ignore`` comments.
35+
See :ref:`ignore-without-code<ignore-without-code>` for more information.
36+
37+
3438
.. _silence-error-codes:
3539

3640
Silencing errors based on error codes

docs/source/literal_types.rst

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
Literal types and Enums
2+
=======================
3+
14
.. _literal_types:
25

36
Literal types
4-
=============
7+
-------------
58

69
Literal types let you indicate that an expression is equal to some specific
710
primitive value. For example, if we annotate a variable with type ``Literal["foo"]``,
@@ -369,3 +372,130 @@ whatever type the parameter has. For example, ``Literal[3]`` is treated as a
369372
subtype of ``int`` and so will inherit all of ``int``'s methods directly. This
370373
means that ``Literal[3].__add__`` accepts the same arguments and has the same
371374
return type as ``int.__add__``.
375+
376+
377+
Enums
378+
-----
379+
380+
Mypy has special support for :py:class:`enum.Enum` and its subclasses:
381+
:py:class:`enum.IntEnum`, :py:class:`enum.Flag`, :py:class:`enum.IntFlag`,
382+
and :py:class:`enum.StrEnum`.
383+
384+
.. code-block:: python
385+
386+
from enum import Enum
387+
388+
class Direction(Enum):
389+
up = 'up'
390+
down = 'down'
391+
392+
reveal_type(Direction.up) # Revealed type is "Literal[Direction.up]?"
393+
reveal_type(Direction.down) # Revealed type is "Literal[Direction.down]?"
394+
395+
You can use enums to annotate types as you would expect:
396+
397+
.. code-block:: python
398+
399+
class Movement:
400+
def __init__(self, direction: Direction, speed: float) -> None:
401+
self.direction = direction
402+
self.speed = speed
403+
404+
Movement(Direction.up, 5.0) # ok
405+
Movement('up', 5.0) # E: Argument 1 to "Movemement" has incompatible type "str"; expected "Direction"
406+
407+
Exhaustive checks
408+
*****************
409+
410+
Similiar to ``Literal`` types ``Enum`` supports exhaustive checks.
411+
Let's start with a definition:
412+
413+
.. code-block:: python
414+
415+
from enum import Enum
416+
from typing import NoReturn
417+
418+
def assert_never(value: NoReturn) -> NoReturn:
419+
# This also works in runtime as well:
420+
assert False, 'This code should never be reached, got: {0}'.format(value)
421+
422+
class Direction(Enum):
423+
up = 'up'
424+
down = 'down'
425+
426+
Now, let's define an exhaustive check:
427+
428+
.. code-block:: python
429+
430+
def choose_direction(direction: Direction) -> None:
431+
if direction is Direction.up:
432+
reveal_type(direction) # N: Revealed type is "Literal[ex.Direction.up]"
433+
print('Going up!')
434+
return
435+
elif direction is Direction.down:
436+
print('Down')
437+
return
438+
assert_never(direction)
439+
440+
And then test that it raises an error when some cases are not covered:
441+
442+
.. code-block:: python
443+
444+
def choose_direction(direction: Direction) -> None:
445+
if direction == Direction.up:
446+
print('Going up!')
447+
return
448+
assert_never(direction) # E: Argument 1 to "assert_never" has incompatible type "Direction"; expected "NoReturn"
449+
450+
Extra Enum checks
451+
*****************
452+
453+
Mypy also tries to support special features of ``Enum``
454+
the same way Python's runtime does.
455+
456+
Extra checks:
457+
458+
- Any ``Enum`` class with values is implicitly :ref:`final <final_attrs>`.
459+
This is what happens in CPython:
460+
461+
.. code-block:: python
462+
463+
>>> class AllDirection(Direction):
464+
... left = 'left'
465+
... right = 'right'
466+
Traceback (most recent call last):
467+
...
468+
TypeError: Other: cannot extend enumeration 'Some'
469+
470+
We do the same thing:
471+
472+
.. code-block:: python
473+
474+
class AllDirection(Direction): # E: Cannot inherit from final class "Some"
475+
left = 'left'
476+
right = 'right'
477+
478+
- All ``Enum`` fields are implictly ``final`` as well.
479+
480+
.. code-block:: python
481+
482+
Direction.up = '^' # E: Cannot assign to final attribute "up"
483+
484+
- All field names are checked to be unique.
485+
486+
.. code-block:: python
487+
488+
class Some(Enum):
489+
x = 1
490+
x = 2 # E: Attempted to reuse member name "x" in Enum definition "Some"
491+
492+
- Base classes have no conflicts and mixin types are correct.
493+
494+
.. code-block:: python
495+
496+
class WrongEnum(str, int, enum.Enum):
497+
# E: Only a single data type mixin is allowed for Enum subtypes, found extra "int"
498+
...
499+
500+
class MixinAfterEnum(enum.Enum, Mixin): # E: No base classes are allowed after "enum.Enum"
501+
...

mypy/build.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2186,8 +2186,10 @@ def type_checker(self) -> TypeChecker:
21862186
if not self._type_checker:
21872187
assert self.tree is not None, "Internal error: must be called on parsed file only"
21882188
manager = self.manager
2189-
self._type_checker = TypeChecker(manager.errors, manager.modules, self.options,
2190-
self.tree, self.xpath, manager.plugin)
2189+
self._type_checker = TypeChecker(
2190+
manager.errors, manager.modules, self.options,
2191+
self.tree, self.xpath, manager.plugin,
2192+
)
21912193
return self._type_checker
21922194

21932195
def type_map(self) -> Dict[Expression, Type]:
@@ -2369,6 +2371,13 @@ def generate_unused_ignore_notes(self) -> None:
23692371
self.verify_dependencies(suppressed_only=True)
23702372
self.manager.errors.generate_unused_ignore_errors(self.xpath)
23712373

2374+
def generate_ignore_without_code_notes(self) -> None:
2375+
if self.manager.errors.is_error_code_enabled(codes.IGNORE_WITHOUT_CODE):
2376+
self.manager.errors.generate_ignore_without_code_errors(
2377+
self.xpath,
2378+
self.options.warn_unused_ignores,
2379+
)
2380+
23722381

23732382
# Module import and diagnostic glue
23742383

@@ -2830,8 +2839,15 @@ def load_graph(sources: List[BuildSource], manager: BuildManager,
28302839
)
28312840
manager.errors.report(
28322841
-1, -1,
2833-
"Are you missing an __init__.py? Alternatively, consider using --exclude to "
2834-
"avoid checking one of them.",
2842+
"See https://mypy.readthedocs.io/en/stable/running_mypy.html#mapping-file-paths-to-modules " # noqa: E501
2843+
"for more info",
2844+
severity='note',
2845+
)
2846+
manager.errors.report(
2847+
-1, -1,
2848+
"Common resolutions include: a) using `--exclude` to avoid checking one of them, "
2849+
"b) adding `__init__.py` somewhere, c) using `--explicit-package-bases` or "
2850+
"adjusting MYPYPATH",
28352851
severity='note'
28362852
)
28372853

@@ -2905,6 +2921,12 @@ def load_graph(sources: List[BuildSource], manager: BuildManager,
29052921
"for more info",
29062922
severity='note',
29072923
)
2924+
manager.errors.report(
2925+
-1, 0,
2926+
"Common resolutions include: a) adding `__init__.py` somewhere, "
2927+
"b) using `--explicit-package-bases` or adjusting MYPYPATH",
2928+
severity='note',
2929+
)
29082930
manager.errors.raise_error()
29092931

29102932
seen_files[newst_path] = newst
@@ -3155,6 +3177,7 @@ def process_stale_scc(graph: Graph, scc: List[str], manager: BuildManager) -> No
31553177
graph[id].finish_passes()
31563178
for id in stale:
31573179
graph[id].generate_unused_ignore_notes()
3180+
graph[id].generate_ignore_without_code_notes()
31583181
if any(manager.errors.is_errors_for_file(graph[id].xpath) for id in stale):
31593182
for id in stale:
31603183
graph[id].transitive_error = True

0 commit comments

Comments
 (0)