Skip to content

Commit 0e04c96

Browse files
Merge branch 'master' into for-filter
2 parents 26ad0d5 + 5b7279b commit 0e04c96

40 files changed

+783
-189
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ repos:
4141
# actionlint has a shellcheck integration which extracts shell scripts in `run:` steps from GitHub Actions
4242
# and checks these with shellcheck. This is arguably its most useful feature,
4343
# but the integration only works if shellcheck is installed
44-
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.10.0"
44+
- "github.com/wasilibs/go-shellcheck/cmd/shellcheck@v0.11.0"
4545
- repo: https://github.com/woodruffw/zizmor-pre-commit
4646
rev: v1.5.2
4747
hooks:

docs/source/error_code_list2.rst

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -676,3 +676,26 @@ Example:
676676
print("red")
677677
case _:
678678
print("other")
679+
680+
.. _code-untyped-decorator:
681+
682+
Error if an untyped decorator makes a typed function effectively untyped [untyped-decorator]
683+
--------------------------------------------------------------------------------------------
684+
685+
If enabled with :option:`--disallow-untyped-decorators <mypy --disallow-untyped-decorators>`
686+
mypy generates an error if a typed function is wrapped by an untyped decorator
687+
(as this would effectively remove the benefits of typing the function).
688+
689+
Example:
690+
691+
.. code-block:: python
692+
693+
def printing_decorator(func):
694+
def wrapper(*args, **kwds):
695+
print("Calling", func)
696+
return func(*args, **kwds)
697+
return wrapper
698+
# A decorated function.
699+
@printing_decorator # E: Untyped decorator makes function "add_forty_two" untyped [untyped-decorator]
700+
def add_forty_two(value: int) -> int:
701+
return value + 42

docs/source/stubtest.rst

Lines changed: 74 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,75 @@ to mypy build errors". In this case, you will need to mitigate those errors
9999
before stubtest will run. Despite potential overlap in errors here, stubtest is
100100
not intended as a substitute for running mypy directly.
101101

102+
Allowlist
103+
*********
104+
102105
If you wish to ignore some of stubtest's complaints, stubtest supports a
103-
pretty handy allowlist system.
106+
pretty handy :option:`--allowlist` system.
107+
108+
Let's say that you have this python module called ``ex``:
109+
110+
.. code-block:: python
111+
112+
try:
113+
import optional_expensive_dep
114+
except ImportError:
115+
optional_expensive_dep = None
116+
117+
first = 1
118+
if optional_expensive_dep:
119+
second = 2
120+
121+
Let's say that you can't install ``optional_expensive_dep`` in CI for some reason,
122+
but you still want to include ``second: int`` in the stub file:
123+
124+
.. code-block:: python
125+
126+
first: int
127+
second: int
128+
129+
In this case stubtest will correctly complain:
130+
131+
.. code-block:: shell
132+
133+
error: ex.second is not present at runtime
134+
Stub: in file /.../ex.pyi:2
135+
builtins.int
136+
Runtime:
137+
MISSING
138+
139+
Found 1 error (checked 1 module)
140+
141+
To fix this, you can add an ``allowlist`` entry:
142+
143+
.. code-block:: ini
144+
145+
# Allowlist entries in `allowlist.txt` file:
146+
147+
# Does not exist if `optional_expensive_dep` is not installed:
148+
ex.second
149+
150+
And now when running stubtest with ``--allowlist=allowlist.txt``,
151+
no errors will be generated anymore.
152+
153+
Allowlists also support regular expressions,
154+
which can be useful to ignore many similar errors at once.
155+
They can also be useful for suppressing stubtest errors that occur sometimes,
156+
but not on every CI run. For example, if some CI workers have
157+
``optional_expensive_dep`` installed, stubtest might complain with this message
158+
on those workers if you had the ``ex.second`` allowlist entry:
159+
160+
.. code-block:: ini
161+
162+
note: unused allowlist entry ex.second
163+
Found 1 error (checked 1 module)
164+
165+
Changing ``ex.second`` to be ``(ex\.second)?`` will make this error optional,
166+
meaning that stubtest will pass whether or not a CI runner
167+
has``optional_expensive_dep`` installed.
168+
169+
CLI
170+
***
104171

105172
The rest of this section documents the command line interface of stubtest.
106173

@@ -119,15 +186,15 @@ The rest of this section documents the command line interface of stubtest.
119186
.. option:: --allowlist FILE
120187

121188
Use file as an allowlist. Can be passed multiple times to combine multiple
122-
allowlists. Allowlists can be created with --generate-allowlist. Allowlists
123-
support regular expressions.
189+
allowlists. Allowlists can be created with :option:`--generate-allowlist`.
190+
Allowlists support regular expressions.
124191

125192
The presence of an entry in the allowlist means stubtest will not generate
126193
any errors for the corresponding definition.
127194

128195
.. option:: --generate-allowlist
129196

130-
Print an allowlist (to stdout) to be used with --allowlist
197+
Print an allowlist (to stdout) to be used with :option:`--allowlist`.
131198

132199
When introducing stubtest to an existing project, this is an easy way to
133200
silence all existing errors.
@@ -141,17 +208,17 @@ The rest of this section documents the command line interface of stubtest.
141208

142209
Note if an allowlist entry is a regex that matches the empty string,
143210
stubtest will never consider it unused. For example, to get
144-
`--ignore-unused-allowlist` behaviour for a single allowlist entry like
211+
``--ignore-unused-allowlist`` behaviour for a single allowlist entry like
145212
``foo.bar`` you could add an allowlist entry ``(foo\.bar)?``.
146213
This can be useful when an error only occurs on a specific platform.
147214

148215
.. option:: --mypy-config-file FILE
149216

150-
Use specified mypy config file to determine mypy plugins and mypy path
217+
Use specified mypy config *file* to determine mypy plugins and mypy path
151218

152219
.. option:: --custom-typeshed-dir DIR
153220

154-
Use the custom typeshed in DIR
221+
Use the custom typeshed in *DIR*
155222

156223
.. option:: --check-typeshed
157224

mypy-requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ typing_extensions>=4.6.0
44
mypy_extensions>=1.0.0
55
pathspec>=0.9.0
66
tomli>=1.1.0; python_version<'3.11'
7-
librt>=0.1.0
7+
librt>=0.2.1

mypy/cache.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
from collections.abc import Sequence
44
from typing import Final
55

6-
from mypy_extensions import u8
7-
from native_internal import (
6+
from librt.internal import (
87
Buffer as Buffer,
98
read_bool as read_bool,
109
read_float as read_float,
@@ -17,6 +16,7 @@
1716
write_str as write_str,
1817
write_tag as write_tag,
1918
)
19+
from mypy_extensions import u8
2020

2121
# Always use this type alias to refer to type tags.
2222
Tag = u8

mypy/errorcodes.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ def __hash__(self) -> int:
302302
sub_code_of=MISC,
303303
)
304304

305+
UNTYPED_DECORATOR: Final = ErrorCode(
306+
"untyped-decorator", "Error if an untyped decorator makes a typed function untyped", "General"
307+
)
308+
305309
NARROWED_TYPE_NOT_SUBTYPE: Final = ErrorCode(
306310
"narrowed-type-not-subtype",
307311
"Warn if a TypeIs function's narrowed type is not a subtype of the original type",

mypy/errors.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -806,8 +806,8 @@ def generate_unused_ignore_errors(self, file: str) -> None:
806806
continue
807807
if codes.UNUSED_IGNORE.code in ignored_codes:
808808
continue
809-
used_ignored_codes = used_ignored_lines[line]
810-
unused_ignored_codes = set(ignored_codes) - set(used_ignored_codes)
809+
used_ignored_codes = set(used_ignored_lines[line])
810+
unused_ignored_codes = [c for c in ignored_codes if c not in used_ignored_codes]
811811
# `ignore` is used
812812
if not ignored_codes and used_ignored_codes:
813813
continue
@@ -817,7 +817,7 @@ def generate_unused_ignore_errors(self, file: str) -> None:
817817
# Display detail only when `ignore[...]` specifies more than one error code
818818
unused_codes_message = ""
819819
if len(ignored_codes) > 1 and unused_ignored_codes:
820-
unused_codes_message = f"[{', '.join(sorted(unused_ignored_codes))}]"
820+
unused_codes_message = f"[{', '.join(unused_ignored_codes)}]"
821821
message = f'Unused "type: ignore{unused_codes_message}" comment'
822822
for unused in unused_ignored_codes:
823823
narrower = set(used_ignored_codes) & codes.sub_code_map[unused]

mypy/fastparse.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,12 @@ def parse(
232232
assert options.python_version[0] >= 3
233233
feature_version = options.python_version[1]
234234
try:
235-
# Disable deprecation warnings about \u
235+
# Disable
236+
# - deprecation warnings for 'invalid escape sequence' (Python 3.11 and below)
237+
# - syntax warnings for 'invalid escape sequence' (3.12+) and 'return in finally' (3.14+)
236238
with warnings.catch_warnings():
237239
warnings.filterwarnings("ignore", category=DeprecationWarning)
240+
warnings.filterwarnings("ignore", category=SyntaxWarning)
238241
ast = ast3_parse(source, fnam, "exec", feature_version=feature_version)
239242

240243
tree = ASTConverter(

mypy/messages.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2008,7 +2008,11 @@ def untyped_decorated_function(self, typ: Type, context: Context) -> None:
20082008
)
20092009

20102010
def typed_function_untyped_decorator(self, func_name: str, context: Context) -> None:
2011-
self.fail(f'Untyped decorator makes function "{func_name}" untyped', context)
2011+
self.fail(
2012+
f'Untyped decorator makes function "{func_name}" untyped',
2013+
context,
2014+
code=codes.UNTYPED_DECORATOR,
2015+
)
20122016

20132017
def bad_proto_variance(
20142018
self, actual: int, tvar_name: str, expected: int, context: Context

mypy/semanal.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1791,11 +1791,10 @@ def push_type_args(
17911791
return None
17921792
tvs.append((p.name, tv))
17931793

1794-
for name, tv in tvs:
1795-
if self.is_defined_type_param(name):
1796-
self.fail(f'"{name}" already defined as a type parameter', context)
1794+
if self.is_defined_type_param(p.name):
1795+
self.fail(f'"{p.name}" already defined as a type parameter', context)
17971796
else:
1798-
self.add_symbol(name, tv, context, no_progress=True, type_param=True)
1797+
self.add_symbol(p.name, tv, context, no_progress=True, type_param=True)
17991798

18001799
return tvs
18011800

0 commit comments

Comments
 (0)