Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions mypy/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def _find_config_file(

def parse_config_file(
options: Options,
set_strict_flags: Callable[[], None],
strict_flag_assignments: Sequence[tuple[str, object]],
filename: str | None,
stdout: TextIO | None = None,
stderr: TextIO | None = None,
Expand All @@ -327,6 +327,9 @@ def parse_config_file(
options.config_file = file_read
os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(file_read))

def set_strict_flags(updates: dict[str, object]) -> None:
updates.update(strict_flag_assignments)

if "mypy" not in parser:
if filename or os.path.basename(file_read) not in defaults.SHARED_CONFIG_NAMES:
print(f"{file_read}: No [mypy] section in config file", file=stderr)
Expand All @@ -340,11 +343,16 @@ def parse_config_file(
setattr(options, k, v)
options.report_dirs.update(report_dirs)

def set_strict_flags_section(updates: dict[str, object]) -> None:
for dest, value in strict_flag_assignments:
if dest in PER_MODULE_OPTIONS:
updates[dest] = value

for name, section in parser.items():
if name.startswith("mypy-"):
prefix = get_prefix(file_read, name)
updates, report_dirs = parse_section(
prefix, options, set_strict_flags, section, config_types, stderr
prefix, options, set_strict_flags_section, section, config_types, stderr
)
if report_dirs:
print(
Expand Down Expand Up @@ -483,7 +491,7 @@ def destructure_overrides(toml_data: dict[str, Any]) -> dict[str, Any]:
def parse_section(
prefix: str,
template: Options,
set_strict_flags: Callable[[], None],
set_strict_flags: Callable[[dict[str, object]], None],
section: Mapping[str, Any],
config_types: dict[str, Any],
stderr: TextIO = sys.stderr,
Expand All @@ -502,7 +510,7 @@ def parse_section(
"disabled_error_codes": "disable_error_code",
}

for key in section:
for key in sorted(section, key=lambda k: -1 if k in {"strict"} else 0):
invert = False
options_key = key
if key in config_types:
Expand Down Expand Up @@ -576,7 +584,7 @@ def parse_section(
continue
if key == "strict":
if v:
set_strict_flags()
set_strict_flags(results)
continue
results[options_key] = v

Expand Down Expand Up @@ -675,7 +683,7 @@ def parse_mypy_comments(
stderr = StringIO()
strict_found = False

def set_strict_flags() -> None:
def set_strict_flags(updates: dict[str, object]) -> None:
nonlocal strict_found
strict_found = True

Expand Down
15 changes: 3 additions & 12 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1397,21 +1397,15 @@ def process_options(
parser.error(f"Cannot find config file '{config_file}'")

options = Options()
strict_option_set = False

def set_strict_flags() -> None:
nonlocal strict_option_set
strict_option_set = True
for dest, value in strict_flag_assignments:
setattr(options, dest, value)

# Parse config file first, so command line can override.
parse_config_file(options, set_strict_flags, config_file, stdout, stderr)
parse_config_file(options, strict_flag_assignments, config_file, stdout, stderr)

# Set strict flags before parsing (if strict mode enabled), so other command
# line options can override.
if getattr(dummy, "special-opts:strict"):
set_strict_flags()
for dest, value in strict_flag_assignments:
setattr(options, dest, value)

# Override cache_dir if provided in the environment
environ_cache_dir = os.getenv("MYPY_CACHE_DIR", "")
Expand Down Expand Up @@ -1529,9 +1523,6 @@ def set_strict_flags() -> None:
if options.logical_deps:
options.cache_fine_grained = True

if options.strict_concatenate and not strict_option_set:
print("Warning: --strict-concatenate is deprecated; use --extra-checks instead")

# Set target.
if special_opts.modules + special_opts.packages:
options.build_type = BuildType.MODULE
Expand Down
2 changes: 1 addition & 1 deletion mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ def __init__(self) -> None:
# Disable treating bytearray and memoryview as subtypes of bytes
self.strict_bytes = False

# Deprecated, use extra_checks instead.
# Make arguments prepended via Concatenate be truly positional-only.
self.strict_concatenate = False

# Enable additional checks that are technically correct but impractical.
Expand Down
7 changes: 2 additions & 5 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -2279,15 +2279,12 @@ def test_stubs(args: _Arguments, use_builtins_fixtures: bool = False) -> int:
options.abs_custom_typeshed_dir = os.path.abspath(options.custom_typeshed_dir)
options.config_file = args.mypy_config_file
options.use_builtins_fixtures = use_builtins_fixtures
options.per_module_options = {}
options.show_traceback = args.show_traceback
options.pdb = args.pdb

if options.config_file:

def set_strict_flags() -> None: # not needed yet
return

parse_config_file(options, set_strict_flags, options.config_file, sys.stdout, sys.stderr)
parse_config_file(options, [], options.config_file, sys.stdout, sys.stderr)

def error_callback(msg: str) -> typing.NoReturn:
print(_style("error:", color="red", bold=True), msg)
Expand Down
2 changes: 1 addition & 1 deletion mypy/test/testfinegrained.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def get_options(self, source: str, testcase: DataDrivenTestCase, build_cache: bo

for name, _ in testcase.files:
if "mypy.ini" in name or "pyproject.toml" in name:
parse_config_file(options, lambda: None, name)
parse_config_file(options, [], name)
break

return options
Expand Down
50 changes: 50 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -2152,6 +2152,56 @@ disallow_subclassing_any = true
module = 'm'
disallow_subclassing_any = false

[case testStrictPerModule]
# flags: --config-file tmp/mypy.ini

import strictmodule
import loosemodule
a = 0 # type: ignore

[file strictmodule.py]
def foo(): # E: Function is missing a return type annotation \
# N: Use "-> None" if function does not return a value
1 + "asdf" # E: Unsupported operand types for + ("int" and "str")

a = 0 # type: ignore # E: Unused "type: ignore" comment


[file loosemodule.py]
def foo():
1 + "asdf"

a = 0 # type: ignore
1 + "asdf" # E: Unsupported operand types for + ("int" and "str")

[file mypy.ini]
\[mypy]
strict = False
\[mypy-strictmodule]
strict = True


[case testStrictPerModuleOverride]
# flags: --config-file tmp/mypy.ini

import strictmodule
import strictermodule

[file strictmodule.py]
x: list
0 # type: ignore

[file strictermodule.py]
x: list # E: Missing type arguments for generic type "list"
0 # type: ignore # E: Unused "type: ignore" comment

[file mypy.ini]
\[mypy]
disallow_any_generics = false
strict = true
warn_unused_ignores = false
\[mypy-strictermodule]
strict = true

[case testNoImplicitOptionalPerModule]
# flags: --config-file tmp/mypy.ini
Expand Down