Skip to content

Commit f3bc3be

Browse files
half-baked remaster of error code injestion
1 parent 61b3b5d commit f3bc3be

File tree

10 files changed

+62
-92
lines changed

10 files changed

+62
-92
lines changed

mypy/build.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2471,7 +2471,7 @@ def parse_inline_configuration(self, source: str) -> None:
24712471
flags = get_mypy_comments(source)
24722472
if flags:
24732473
changes, config_errors = parse_mypy_comments(flags, self.options)
2474-
self.options = self.options.apply_changes(changes)
2474+
self.options = self.options.copy_with_changes(changes)
24752475
self.manager.errors.set_file(self.xpath, self.id, self.options)
24762476
for lineno, error in config_errors:
24772477
self.manager.errors.report(lineno, 0, error)
@@ -2822,8 +2822,8 @@ def dependency_lines(self) -> list[int]:
28222822
def generate_unused_ignore_notes(self) -> None:
28232823
if (
28242824
self.options.warn_unused_ignores
2825-
or codes.UNUSED_IGNORE in self.options.enabled_error_codes
2826-
) and codes.UNUSED_IGNORE not in self.options.disabled_error_codes:
2825+
or codes.UNUSED_IGNORE in self.options.active_error_codes
2826+
):
28272827
# If this file was initially loaded from the cache, it may have suppressed
28282828
# dependencies due to imports with ignores on them. We need to generate
28292829
# those errors to avoid spuriously flagging them as unused ignores.

mypy/build_worker/worker.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def main(argv: list[str]) -> None:
7575
options_obj = Options()
7676
disable_error_code = options_dict.pop("disable_error_code", [])
7777
enable_error_code = options_dict.pop("enable_error_code", [])
78-
options = options_obj.apply_changes(options_dict)
78+
options = options_obj.copy_with_changes(options_dict)
7979

8080
status_file = args.status_file
8181
server = IPCServer(CONNECTION_NAME, WORKER_CONNECTION_TIMEOUT)

mypy/checker.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,7 +2401,7 @@ def check_method_override_for_base_with_name(
24012401
if (
24022402
ok
24032403
and original_node
2404-
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
2404+
and codes.MUTABLE_OVERRIDE in self.options.active_error_codes
24052405
and self.is_writable_attribute(original_node)
24062406
and not always_allow_covariant
24072407
and not is_subtype(original_type, typ, ignore_pos_arg_names=True)
@@ -2421,7 +2421,7 @@ def check_method_override_for_base_with_name(
24212421
# This method is a subtype of at least one union variant.
24222422
if (
24232423
original_node
2424-
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
2424+
and codes.MUTABLE_OVERRIDE in self.options.active_error_codes
24252425
and self.is_writable_attribute(original_node)
24262426
and not always_allow_covariant
24272427
):
@@ -3659,7 +3659,7 @@ def check_compatibility_super(
36593659
)
36603660
if (
36613661
ok
3662-
and codes.MUTABLE_OVERRIDE in self.options.enabled_error_codes
3662+
and codes.MUTABLE_OVERRIDE in self.options.active_error_codes
36633663
and self.is_writable_attribute(base_node)
36643664
and not always_allow_covariant
36653665
):

mypy/checkexpr.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4295,7 +4295,7 @@ def check_boolean_op(self, e: OpExpr) -> Type:
42954295
# If left_map is None then we know mypy considers the left expression
42964296
# to be redundant.
42974297
if (
4298-
codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes
4298+
codes.REDUNDANT_EXPR in self.chk.options.active_error_codes
42994299
and left_map is None
43004300
# don't report an error if it's intentional
43014301
and not e.right_always
@@ -4783,7 +4783,7 @@ def visit_reveal_expr(self, expr: RevealExpr) -> Type:
47834783
return NoneType()
47844784

47854785
def check_reveal_imported(self, expr: RevealExpr) -> None:
4786-
if codes.UNIMPORTED_REVEAL not in self.chk.options.enabled_error_codes:
4786+
if codes.UNIMPORTED_REVEAL not in self.chk.options.active_error_codes:
47874787
return
47884788

47894789
name = ""
@@ -5911,7 +5911,7 @@ def check_for_comp(self, e: GeneratorExpr | DictionaryComprehension) -> None:
59115911
if true_map:
59125912
self.chk.push_type_map(true_map)
59135913

5914-
if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes:
5914+
if codes.REDUNDANT_EXPR in self.chk.options.active_error_codes:
59155915
if true_map is None:
59165916
self.msg.redundant_condition_in_comprehension(False, condition)
59175917
elif false_map is None:
@@ -5924,7 +5924,7 @@ def visit_conditional_expr(self, e: ConditionalExpr, allow_none_return: bool = F
59245924
# Gain type information from isinstance if it is there
59255925
# but only for the current expression
59265926
if_map, else_map = self.chk.find_isinstance_check(e.cond)
5927-
if codes.REDUNDANT_EXPR in self.chk.options.enabled_error_codes:
5927+
if codes.REDUNDANT_EXPR in self.chk.options.active_error_codes:
59285928
if if_map is None:
59295929
self.msg.redundant_condition_in_if(False, e.cond)
59305930
elif else_map is None:

mypy/config_parser.py

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,8 @@ def parse_config_file(
336336
updates, report_dirs = parse_section(
337337
prefix, options, set_strict_flags, section, config_types, stderr
338338
)
339+
#TODO(Wyatt): should this be `options = options.copy_with_changes(updates)`,
340+
# or did trying that break everything?
339341
for k, v in updates.items():
340342
setattr(options, k, v)
341343
options.report_dirs.update(report_dirs)
@@ -495,9 +497,11 @@ def parse_section(
495497
results: dict[str, object] = {}
496498
report_dirs: dict[str, str] = {}
497499

498-
# Because these fields exist on Options, without proactive checking, we would accept them
499-
# and crash later
500500
invalid_options = {
501+
# Because this field exists on Options,
502+
# without this proactive checking we would accept it and crash later:
503+
"active_error_codes": "enable_error_code",
504+
# These used to also be fields on Options, but now are just helpful tips:
501505
"enabled_error_codes": "enable_error_code",
502506
"disabled_error_codes": "disable_error_code",
503507
}
@@ -579,13 +583,6 @@ def parse_section(
579583
set_strict_flags()
580584
continue
581585
results[options_key] = v
582-
583-
# These two flags act as per-module overrides, so store the empty defaults.
584-
if "disable_error_code" not in results:
585-
results["disable_error_code"] = []
586-
if "enable_error_code" not in results:
587-
results["enable_error_code"] = []
588-
589586
return results, report_dirs
590587

591588

@@ -694,24 +691,9 @@ def set_strict_flags() -> None:
694691
'(see "mypy -h" for the list of flags enabled in strict mode)',
695692
)
696693
)
697-
# Because this is currently special-cased
698-
# (the new_sections for an inline config *always* includes 'disable_error_code' and
699-
# 'enable_error_code' fields, usually empty, which overwrite the old ones),
700-
# we have to manipulate them specially.
701-
# This could use a refactor, but so could the whole subsystem.
702-
if (
703-
"enable_error_code" in new_sections
704-
and isinstance(neec := new_sections["enable_error_code"], list)
705-
and isinstance(eec := sections.get("enable_error_code", []), list)
706-
):
707-
new_sections["enable_error_code"] = sorted(set(neec + eec))
708-
if (
709-
"disable_error_code" in new_sections
710-
and isinstance(ndec := new_sections["disable_error_code"], list)
711-
and isinstance(dec := sections.get("disable_error_code", []), list)
712-
):
713-
new_sections["disable_error_code"] = sorted(set(ndec + dec))
694+
714695
sections.update(new_sections)
696+
715697
return sections, errors
716698

717699

mypy/dmypy/client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -622,7 +622,7 @@ def do_daemon(args: argparse.Namespace) -> None:
622622

623623
options_dict = pickle.loads(base64.b64decode(args.options_data))
624624
options_obj = Options()
625-
options = options_obj.apply_changes(options_dict)
625+
options = options_obj.copy_with_changes(options_dict)
626626
else:
627627
options = process_start_options(args.flags, allow_sources=False)
628628

mypy/errorcodes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,14 @@
1111
from mypy_extensions import mypyc_attr
1212

1313
error_codes: dict[str, ErrorCode] = {}
14+
error_codes_on_by_default: Final[set[ErrorCode]] = set()
1415
sub_code_map: dict[str, set[str]] = defaultdict(set)
1516

1617

18+
def print_code_set(error_codes: set[ErrorCode]) -> None:
19+
print(sorted([e.code for e in error_codes]))
20+
21+
1722
@mypyc_attr(allow_interpreted_subclasses=True)
1823
class ErrorCode:
1924
def __init__(
@@ -33,6 +38,8 @@ def __init__(
3338
assert sub_code_of.sub_code_of is None, "Nested subcategories are not supported"
3439
sub_code_map[sub_code_of.code].add(code)
3540
error_codes[code] = self
41+
if default_enabled:
42+
error_codes_on_by_default.add(self)
3643

3744
def __str__(self) -> str:
3845
return f"<ErrorCode {self.code}>"
@@ -336,3 +343,4 @@ def __hash__(self) -> int:
336343

337344
# This copy will not include any error codes defined later in the plugins.
338345
mypy_error_codes = error_codes.copy()
346+
mypy_error_codes_on_by_default = error_codes_on_by_default.copy()

mypy/errors.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -829,21 +829,7 @@ def is_ignored_error(self, line: int, info: ErrorInfo, ignores: dict[int, list[s
829829
return False
830830

831831
def is_error_code_enabled(self, error_code: ErrorCode) -> bool:
832-
if self.options:
833-
current_mod_disabled = self.options.disabled_error_codes
834-
current_mod_enabled = self.options.enabled_error_codes
835-
else:
836-
current_mod_disabled = set()
837-
current_mod_enabled = set()
838-
839-
if error_code in current_mod_disabled:
840-
return False
841-
elif error_code in current_mod_enabled:
842-
return True
843-
elif error_code.sub_code_of is not None and error_code.sub_code_of in current_mod_disabled:
844-
return False
845-
else:
846-
return error_code.default_enabled
832+
return error_code in self.options.active_error_codes
847833

848834
def clear_errors_in_targets(self, path: str, targets: set[str]) -> None:
849835
"""Remove errors in specific fine-grained targets within a file."""

mypy/options.py

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from typing import Any, Final
1010

1111
from mypy import defaults
12-
from mypy.errorcodes import ErrorCode, error_codes
12+
from mypy.errorcodes import ErrorCode, error_codes, error_codes_on_by_default
1313
from mypy.util import get_class_descriptors, replace_object_state
1414

1515

@@ -29,7 +29,6 @@ class BuildType:
2929
"check_untyped_defs",
3030
"debug_cache",
3131
"disable_error_code",
32-
"disabled_error_codes",
3332
"disallow_any_decorated",
3433
"disallow_any_explicit",
3534
"disallow_any_expr",
@@ -41,7 +40,6 @@ class BuildType:
4140
"disallow_untyped_decorators",
4241
"disallow_untyped_defs",
4342
"enable_error_code",
44-
"enabled_error_codes",
4543
"extra_checks",
4644
"follow_imports_for_stubs",
4745
"follow_imports",
@@ -254,13 +252,15 @@ def __init__(self) -> None:
254252
# Variable names considered False
255253
self.always_false: list[str] = []
256254

257-
# Error codes to disable
255+
# The set of error codes (as ErrorCode objects) we've computed as
256+
# being actually enabled or disabled after all is said and done.
257+
self.active_error_codes: set[ErrorCode] = error_codes_on_by_default.copy()
258+
259+
# List of error codes we are being instructed to disable
258260
self.disable_error_code: list[str] = []
259-
self.disabled_error_codes: set[ErrorCode] = set()
260261

261-
# Error codes to enable
262+
# List of error codes we are being instructed to enable
262263
self.enable_error_code: list[str] = []
263-
self.enabled_error_codes: set[ErrorCode] = set()
264264

265265
# Use script name instead of __main__
266266
self.scripts_are_modules = False
@@ -437,7 +437,11 @@ def __repr__(self) -> str:
437437
return f"Options({pprint.pformat(self.snapshot())})"
438438

439439
def process_error_codes(self, *, error_callback: Callable[[str], Any]) -> None:
440-
"""Process `--enable-error-code` and `--disable-error-code` flags."""
440+
# This function seems to be standalone so that it can be called after plugins, in various places.
441+
"""Process `enable-error-code` and `disable-error-code` flags.
442+
This clears those fields, which were temporary, and only used for option injestion.
443+
The persistant field to consult about error code enablement is active_error_code.
444+
"""
441445
disabled_code_names = set(self.disable_error_code)
442446
enabled_code_names = set(self.enable_error_code)
443447

@@ -448,12 +452,11 @@ def process_error_codes(self, *, error_callback: Callable[[str], Any]) -> None:
448452
) - valid_error_code_names
449453
if invalid_code_names_here:
450454
error_callback(f"Invalid error code(s): {', '.join(sorted(invalid_code_names_here))}")
451-
452-
self.disabled_error_codes |= {error_codes[code] for code in disabled_code_names}
453-
self.enabled_error_codes |= {error_codes[code] for code in enabled_code_names}
454-
455-
# Enabling an error code always overrides disabling
456-
self.disabled_error_codes -= self.enabled_error_codes
455+
#TODO(Wyatt): I don't think the new way accounts for subcodes
456+
self.active_error_codes -= {error_codes[code] for code in disabled_code_names}
457+
self.active_error_codes |= {error_codes[code] for code in enabled_code_names}
458+
self.disable_error_code.clear()
459+
self.enable_error_code.clear()
457460

458461
def process_incomplete_features(
459462
self, *, error_callback: Callable[[str], Any], warning_callback: Callable[[str], Any]
@@ -475,41 +478,32 @@ def process_strict_bytes(self) -> None:
475478
# forwards compatibility
476479
self.strict_bytes = True
477480

478-
def apply_changes(self, changes: dict[str, object]) -> Options:
479-
# Note: effects of this method *must* be idempotent.
481+
def copy_with_changes(self, changes: dict[str, object]) -> Options:
482+
"""Return a new Options object that has the changes applied over the old one.
483+
Note: effects of this method *must* be idempotent."""
480484
new_options = Options()
481485
# Under mypyc, we don't have a __dict__, so we need to do worse things.
482486
replace_object_state(new_options, self, copy_dict=True)
483487
for key, value in changes.items():
484488
setattr(new_options, key, value)
485489
if changes.get("ignore_missing_imports"):
486490
# This is the only option for which a per-module and a global
487-
# option sometimes beheave differently.
491+
# option sometimes behave differently.
488492
new_options.ignore_missing_imports_per_module = True
489-
490-
# These two act as overrides, so apply them when cloning.
491-
# Similar to global codes enabling overrides disabling, so we start from latter.
492-
new_options.disabled_error_codes = self.disabled_error_codes.copy()
493-
new_options.enabled_error_codes = self.enabled_error_codes.copy()
494-
for code_str in new_options.disable_error_code:
495-
code = error_codes[code_str]
496-
new_options.disabled_error_codes.add(code)
497-
new_options.enabled_error_codes.discard(code)
498-
for code_str in new_options.enable_error_code:
499-
code = error_codes[code_str]
500-
new_options.enabled_error_codes.add(code)
501-
new_options.disabled_error_codes.discard(code)
493+
new_options.process_error_codes(
494+
error_callback=lambda x: print(x, sys.stderr and exit(x)) if x else None
495+
)
502496
return new_options
503497

504498
def compare_stable(self, other_snapshot: dict[str, object]) -> bool:
505-
"""Compare options in a way that is stable for snapshot() -> apply_changes() roundtrip.
499+
"""Compare options in a way that is stable for snapshot() -> copy_with_changes() roundtrip.
506500
507-
This is needed because apply_changes() has non-trivial effects for some flags, so
508-
Options().apply_changes(options.snapshot()) may result in a (slightly) different object.
501+
This is needed because copy_with_changes() has non-trivial effects for some flags, so
502+
Options().copy_with_changes(options.snapshot()) may result in a (slightly) different object.
509503
"""
510504
return (
511-
Options().apply_changes(self.snapshot()).snapshot()
512-
== Options().apply_changes(other_snapshot).snapshot()
505+
Options().copy_with_changes(self.snapshot()).snapshot()
506+
== Options().copy_with_changes(other_snapshot).snapshot()
513507
)
514508

515509
def build_per_module_cache(self) -> None:
@@ -549,7 +543,7 @@ def build_per_module_cache(self) -> None:
549543
# on inheriting from parent configs.
550544
options = self.clone_for_module(key)
551545
# And then update it with its per-module options.
552-
self._per_module_cache[key] = options.apply_changes(self.per_module_options[key])
546+
self._per_module_cache[key] = options.copy_with_changes(self.per_module_options[key])
553547

554548
# Add the more structured sections into unused configs, since
555549
# they only count as used if actually used by a real module.
@@ -590,7 +584,7 @@ def clone_for_module(self, module: str) -> Options:
590584
for key, pattern in self._glob_options:
591585
if pattern.match(module):
592586
self.unused_configs.discard(key)
593-
options = options.apply_changes(self.per_module_options[key])
587+
options = options.copy_with_changes(self.per_module_options[key])
594588

595589
# We could update the cache to directly point to modules once
596590
# they have been looked up, but in testing this made things
@@ -612,7 +606,7 @@ def select_options_affecting_cache(self) -> dict[str, object]:
612606
result: dict[str, object] = {}
613607
for opt in OPTIONS_AFFECTING_CACHE:
614608
val = getattr(self, opt)
615-
if opt in ("disabled_error_codes", "enabled_error_codes"):
609+
if opt == "active_error_codes":
616610
val = sorted([code.code for code in val])
617611
result[opt] = val
618612
return result

mypy/test/testparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_parser(testcase: DataDrivenTestCase) -> None:
5656
# Apply mypy: comments to options.
5757
comments = get_mypy_comments(source)
5858
changes, _ = parse_mypy_comments(comments, options)
59-
options = options.apply_changes(changes)
59+
options = options.copy_with_changes(changes)
6060

6161
try:
6262
n = parse(

0 commit comments

Comments
 (0)