Skip to content

Commit 9f2d7ce

Browse files
author
hauntsaninja
committed
Merge remote-tracking branch 'origin/master' into opplug
2 parents cbfc5a5 + e4b4959 commit 9f2d7ce

Some content is hidden

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

90 files changed

+2666
-2238
lines changed

docs/source/command_line.rst

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,36 @@ of the above sections.
553553
Note: the exact list of flags enabled by running :option:`--strict` may change
554554
over time.
555555

556+
.. option:: --disable-error-code
557+
558+
This flag allows disabling one or multiple error codes globally.
559+
560+
.. code-block:: python
561+
562+
# no flag
563+
x = 'a string'
564+
x.trim() # error: "str" has no attribute "trim" [attr-defined]
565+
566+
# --disable-error-code attr-defined
567+
x = 'a string'
568+
x.trim()
569+
570+
.. option:: --enable-error-code
571+
572+
This flag allows enabling one or multiple error codes globally.
573+
574+
Note: This flag will override disabled error codes from the --disable-error-code
575+
flag
576+
577+
.. code-block:: python
578+
579+
# --disable-error-code attr-defined
580+
x = 'a string'
581+
x.trim()
582+
583+
# --disable-error-code attr-defined --enable-error-code attr-defined
584+
x = 'a string'
585+
x.trim() # error: "str" has no attribute "trim" [attr-defined]
556586
557587
.. _configuring-error-messages:
558588

mypy/build.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ def _build(sources: List[BuildSource],
223223
options.show_error_codes,
224224
options.pretty,
225225
lambda path: read_py_file(path, cached_read, options.python_version),
226-
options.show_absolute_path)
226+
options.show_absolute_path,
227+
options.enabled_error_codes,
228+
options.disabled_error_codes)
227229
plugin, snapshot = load_plugins(options, errors, stdout, extra_plugins)
228230

229231
# Add catch-all .gitignore to cache dir if we created it
@@ -762,13 +764,14 @@ def is_module(self, id: str) -> bool:
762764
"""Is there a file in the file system corresponding to module id?"""
763765
return find_module_simple(id, self) is not None
764766

765-
def parse_file(self, id: str, path: str, source: str, ignore_errors: bool) -> MypyFile:
767+
def parse_file(self, id: str, path: str, source: str, ignore_errors: bool,
768+
options: Options) -> MypyFile:
766769
"""Parse the source of a file with the given name.
767770
768771
Raise CompileError if there is a parse error.
769772
"""
770773
t0 = time.time()
771-
tree = parse(source, path, id, self.errors, options=self.options)
774+
tree = parse(source, path, id, self.errors, options=options)
772775
tree._fullname = id
773776
self.add_stats(files_parsed=1,
774777
modules_parsed=int(not tree.is_stub),
@@ -2001,7 +2004,8 @@ def parse_file(self) -> None:
20012004

20022005
self.parse_inline_configuration(source)
20032006
self.tree = manager.parse_file(self.id, self.xpath, source,
2004-
self.ignore_all or self.options.ignore_errors)
2007+
self.ignore_all or self.options.ignore_errors,
2008+
self.options)
20052009

20062010
modules[self.id] = self.tree
20072011

mypy/checker.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3317,6 +3317,9 @@ def visit_raise_stmt(self, s: RaiseStmt) -> None:
33173317
def type_check_raise(self, e: Expression, s: RaiseStmt,
33183318
optional: bool = False) -> None:
33193319
typ = get_proper_type(self.expr_checker.accept(e))
3320+
if isinstance(typ, DeletedType):
3321+
self.msg.deleted_as_rvalue(typ, e)
3322+
return
33203323
exc_type = self.named_type('builtins.BaseException')
33213324
expected_type = UnionType([exc_type, TypeType(exc_type)])
33223325
if optional:

mypy/checkexpr.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3464,6 +3464,10 @@ def visit_super_expr(self, e: SuperExpr) -> Type:
34643464
self.chk.fail(message_registry.SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1, e)
34653465
return AnyType(TypeOfAny.from_error)
34663466

3467+
if len(mro) == index + 1:
3468+
self.chk.fail(message_registry.TARGET_CLASS_HAS_NO_BASE_CLASS, e)
3469+
return AnyType(TypeOfAny.from_error)
3470+
34673471
for base in mro[index+1:]:
34683472
if e.name in base.names or base == mro[-1]:
34693473
if e.info and e.info.fallback_to_any and base == mro[-1]:

mypy/checkstrformat.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -384,9 +384,10 @@ def perform_special_format_checks(self, spec: ConversionSpecifier, call: CallExp
384384
if self.chk.options.python_version >= (3, 0):
385385
if (has_type_component(actual_type, 'builtins.bytes') and
386386
not custom_special_method(actual_type, '__str__')):
387-
self.msg.fail("On Python 3 '{}'.format(b'abc') produces \"b'abc'\";"
388-
" use !r if this is a desired behavior", call,
389-
code=codes.STR_BYTES_PY3)
387+
self.msg.fail(
388+
"On Python 3 '{}'.format(b'abc') produces \"b'abc'\", not 'abc'; "
389+
"use '{!r}'.format(b'abc') if this is desired behavior",
390+
call, code=codes.STR_BYTES_PY3)
390391
if spec.flags:
391392
numeric_types = UnionType([self.named_type('builtins.int'),
392393
self.named_type('builtins.float')])
@@ -843,9 +844,10 @@ def check_s_special_cases(self, expr: FormatStringExpr, typ: Type, context: Cont
843844
# Couple special cases for string formatting.
844845
if self.chk.options.python_version >= (3, 0):
845846
if has_type_component(typ, 'builtins.bytes'):
846-
self.msg.fail("On Python 3 '%s' % b'abc' produces \"b'abc'\";"
847-
" use %r if this is a desired behavior", context,
848-
code=codes.STR_BYTES_PY3)
847+
self.msg.fail(
848+
"On Python 3 '%s' % b'abc' produces \"b'abc'\", not 'abc'; "
849+
"use '%r' % b'abc' if this is desired behavior",
850+
context, code=codes.STR_BYTES_PY3)
849851
if self.chk.options.python_version < (3, 0):
850852
if has_type_component(typ, 'builtins.unicode'):
851853
self.unicode_upcast = True

mypy/dmypy_server.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,10 @@ def fine_grained_increment_follow_imports(self, sources: List[BuildSource]) -> L
557557
if module[0] not in graph:
558558
continue
559559
sources2 = self.direct_imports(module, graph)
560+
# Filter anything already seen before. This prevents
561+
# infinite looping if there are any self edges. (Self
562+
# edges are maybe a bug, but...)
563+
sources2 = [source for source in sources2 if source.module not in seen]
560564
changed, new_files = self.find_reachable_changed_modules(
561565
sources2, graph, seen, changed_paths
562566
)

mypy/errorcodes.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,26 @@
33
These can be used for filtering specific errors.
44
"""
55

6-
from typing import List
6+
from typing import Dict, List
77
from typing_extensions import Final
88

99

1010
# All created error codes are implicitly stored in this list.
1111
all_error_codes = [] # type: List[ErrorCode]
1212

13+
error_codes = {} # type: Dict[str, ErrorCode]
14+
1315

1416
class ErrorCode:
15-
def __init__(self, code: str, description: str, category: str) -> None:
17+
def __init__(self, code: str,
18+
description: str,
19+
category: str,
20+
default_enabled: bool = True) -> None:
1621
self.code = code
1722
self.description = description
1823
self.category = category
24+
self.default_enabled = default_enabled
25+
error_codes[code] = self
1926

2027
def __str__(self) -> str:
2128
return '<ErrorCode {}>'.format(self.code)

mypy/errors.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,14 +164,18 @@ def __init__(self,
164164
show_error_codes: bool = False,
165165
pretty: bool = False,
166166
read_source: Optional[Callable[[str], Optional[List[str]]]] = None,
167-
show_absolute_path: bool = False) -> None:
167+
show_absolute_path: bool = False,
168+
enabled_error_codes: Optional[Set[ErrorCode]] = None,
169+
disabled_error_codes: Optional[Set[ErrorCode]] = None) -> None:
168170
self.show_error_context = show_error_context
169171
self.show_column_numbers = show_column_numbers
170172
self.show_error_codes = show_error_codes
171173
self.show_absolute_path = show_absolute_path
172174
self.pretty = pretty
173175
# We use fscache to read source code when showing snippets.
174176
self.read_source = read_source
177+
self.enabled_error_codes = enabled_error_codes or set()
178+
self.disabled_error_codes = disabled_error_codes or set()
175179
self.initialize()
176180

177181
def initialize(self) -> None:
@@ -195,7 +199,9 @@ def copy(self) -> 'Errors':
195199
self.show_error_codes,
196200
self.pretty,
197201
self.read_source,
198-
self.show_absolute_path)
202+
self.show_absolute_path,
203+
self.enabled_error_codes,
204+
self.disabled_error_codes)
199205
new.file = self.file
200206
new.import_ctx = self.import_ctx[:]
201207
new.function_or_member = self.function_or_member[:]
@@ -351,15 +357,25 @@ def add_error_info(self, info: ErrorInfo) -> None:
351357
self._add_error_info(file, info)
352358

353359
def is_ignored_error(self, line: int, info: ErrorInfo, ignores: Dict[int, List[str]]) -> bool:
354-
if line not in ignores:
360+
if info.code and self.is_error_code_enabled(info.code) is False:
361+
return True
362+
elif line not in ignores:
355363
return False
356364
elif not ignores[line]:
357365
# Empty list means that we ignore all errors
358366
return True
359-
elif info.code:
367+
elif info.code and self.is_error_code_enabled(info.code) is True:
360368
return info.code.code in ignores[line]
361369
return False
362370

371+
def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
372+
if error_code in self.disabled_error_codes:
373+
return False
374+
elif error_code in self.enabled_error_codes:
375+
return True
376+
else:
377+
return error_code.default_enabled
378+
363379
def clear_errors_in_targets(self, path: str, targets: Set[str]) -> None:
364380
"""Remove errors in specific fine-grained targets within a file."""
365381
if path in self.error_info_map:

mypy/join.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def visit_unbound_type(self, t: UnboundType) -> ProperType:
119119
return AnyType(TypeOfAny.special_form)
120120

121121
def visit_union_type(self, t: UnionType) -> ProperType:
122-
if is_subtype(self.s, t):
122+
if is_proper_subtype(self.s, t):
123123
return t
124124
else:
125125
return mypy.typeops.make_simplified_union([self.s, t])

mypy/main.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from mypy.find_sources import create_source_list, InvalidSourceList
1919
from mypy.fscache import FileSystemCache
2020
from mypy.errors import CompileError
21+
from mypy.errorcodes import error_codes
2122
from mypy.options import Options, BuildType
2223
from mypy.config_parser import parse_version, parse_config_file
2324
from mypy.split_namespace import SplitNamespace
@@ -612,6 +613,14 @@ def add_invertible_flag(flag: str,
612613
'--strict', action='store_true', dest='special-opts:strict',
613614
help=strict_help)
614615

616+
strictness_group.add_argument(
617+
'--disable-error-code', metavar='NAME', action='append', default=[],
618+
help="Disable a specific error code")
619+
strictness_group.add_argument(
620+
'--enable-error-code', metavar='NAME', action='append', default=[],
621+
help="Enable a specific error code"
622+
)
623+
615624
error_group = parser.add_argument_group(
616625
title='Configuring error messages',
617626
description="Adjust the amount of detail shown in error messages.")
@@ -860,6 +869,23 @@ def set_strict_flags() -> None:
860869
parser.error("You can't make a variable always true and always false (%s)" %
861870
', '.join(sorted(overlap)))
862871

872+
# Process `--enable-error-code` and `--disable-error-code` flags
873+
disabled_codes = set(options.disable_error_code)
874+
enabled_codes = set(options.enable_error_code)
875+
876+
valid_error_codes = set(error_codes.keys())
877+
878+
invalid_codes = (enabled_codes | disabled_codes) - valid_error_codes
879+
if invalid_codes:
880+
parser.error("Invalid error code(s): %s" %
881+
', '.join(sorted(invalid_codes)))
882+
883+
options.disabled_error_codes |= {error_codes[code] for code in disabled_codes}
884+
options.enabled_error_codes |= {error_codes[code] for code in enabled_codes}
885+
886+
# Enabling an error code always overrides disabling
887+
options.disabled_error_codes -= options.enabled_error_codes
888+
863889
# Set build flags.
864890
if options.strict_optional_whitelist is not None:
865891
# TODO: Deprecate, then kill this flag

mypy/message_registry.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@
109109
SUPER_POSITIONAL_ARGS_REQUIRED = '"super" only accepts positional arguments' # type: Final
110110
SUPER_ARG_2_NOT_INSTANCE_OF_ARG_1 = \
111111
'Argument 2 for "super" not an instance of argument 1' # type: Final
112+
TARGET_CLASS_HAS_NO_BASE_CLASS = 'Target class has no base class' # type: Final
112113
SUPER_OUTSIDE_OF_METHOD_NOT_SUPPORTED = \
113114
'super() outside of a method is not supported' # type: Final
114115
SUPER_ENCLOSING_POSITIONAL_ARGS_REQUIRED = \

mypy/modulefinder.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,18 @@ class ModuleNotFoundReason(Enum):
4949
# corresponding *-stubs package.
5050
FOUND_WITHOUT_TYPE_HINTS = 1
5151

52+
# The module was not found in the current working directory, but
53+
# was able to be found in the parent directory.
54+
WRONG_WORKING_DIRECTORY = 2
55+
5256
def error_message_templates(self) -> Tuple[str, str]:
5357
if self is ModuleNotFoundReason.NOT_FOUND:
5458
msg = "Cannot find implementation or library stub for module named '{}'"
5559
note = "See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports"
60+
elif self is ModuleNotFoundReason.WRONG_WORKING_DIRECTORY:
61+
msg = "Cannot find implementation or library stub for module named '{}'"
62+
note = ("You may be running mypy in a subpackage, "
63+
"mypy should be run on the package root")
5664
elif self is ModuleNotFoundReason.FOUND_WITHOUT_TYPE_HINTS:
5765
msg = "Skipping analyzing '{}': found module but no type hints or library stubs"
5866
note = "See https://mypy.readthedocs.io/en/latest/running_mypy.html#missing-imports"
@@ -166,6 +174,9 @@ def find_module(self, id: str) -> ModuleSearchResult:
166174
"""Return the path of the module source file or why it wasn't found."""
167175
if id not in self.results:
168176
self.results[id] = self._find_module(id)
177+
if (self.results[id] is ModuleNotFoundReason.NOT_FOUND
178+
and self._can_find_module_in_parent_dir(id)):
179+
self.results[id] = ModuleNotFoundReason.WRONG_WORKING_DIRECTORY
169180
return self.results[id]
170181

171182
def _find_module_non_stub_helper(self, components: List[str],
@@ -192,6 +203,20 @@ def _update_ns_ancestors(self, components: List[str], match: Tuple[str, bool]) -
192203
self.ns_ancestors[pkg_id] = path
193204
path = os.path.dirname(path)
194205

206+
def _can_find_module_in_parent_dir(self, id: str) -> bool:
207+
"""Test if a module can be found by checking the parent directories
208+
of the current working directory.
209+
"""
210+
working_dir = os.getcwd()
211+
parent_search = FindModuleCache(SearchPaths((), (), (), ()))
212+
while any(file.endswith(("__init__.py", "__init__.pyi"))
213+
for file in os.listdir(working_dir)):
214+
working_dir = os.path.dirname(working_dir)
215+
parent_search.search_paths = SearchPaths((working_dir,), (), (), ())
216+
if not isinstance(parent_search._find_module(id), ModuleNotFoundReason):
217+
return True
218+
return False
219+
195220
def _find_module(self, id: str) -> ModuleSearchResult:
196221
fscache = self.fscache
197222

mypy/options.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,15 @@
33
import pprint
44
import sys
55

6-
from typing_extensions import Final
6+
from typing_extensions import Final, TYPE_CHECKING
77
from typing import Dict, List, Mapping, Optional, Pattern, Set, Tuple, Callable, Any
88

99
from mypy import defaults
1010
from mypy.util import get_class_descriptors, replace_object_state
1111

12+
if TYPE_CHECKING:
13+
from mypy.errors import ErrorCode
14+
1215

1316
class BuildType:
1417
STANDARD = 0 # type: Final[int]
@@ -177,6 +180,14 @@ def __init__(self) -> None:
177180
# Variable names considered False
178181
self.always_false = [] # type: List[str]
179182

183+
# Error codes to disable
184+
self.disable_error_code = [] # type: List[str]
185+
self.disabled_error_codes = set() # type: Set[ErrorCode]
186+
187+
# Error codes to enable
188+
self.enable_error_code = [] # type: List[str]
189+
self.enabled_error_codes = set() # type: Set[ErrorCode]
190+
180191
# Use script name instead of __main__
181192
self.scripts_are_modules = False
182193

mypy/semanal.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4542,9 +4542,18 @@ def current_symbol_table(self, escape_comprehensions: bool = False) -> SymbolTab
45424542
if self.is_func_scope():
45434543
assert self.locals[-1] is not None
45444544
if escape_comprehensions:
4545+
assert len(self.locals) == len(self.is_comprehension_stack)
4546+
# Retrieve the symbol table from the enclosing non-comprehension scope.
45454547
for i, is_comprehension in enumerate(reversed(self.is_comprehension_stack)):
45464548
if not is_comprehension:
4547-
names = self.locals[-1 - i]
4549+
if i == len(self.locals) - 1: # The last iteration.
4550+
# The caller of the comprehension is in the global space.
4551+
names = self.globals
4552+
else:
4553+
names_candidate = self.locals[-1 - i]
4554+
assert names_candidate is not None, \
4555+
"Escaping comprehension from invalid scope"
4556+
names = names_candidate
45484557
break
45494558
else:
45504559
assert False, "Should have at least one non-comprehension scope"

0 commit comments

Comments
 (0)