Skip to content

Support module-level strict config flag #12174

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
18 changes: 13 additions & 5 deletions mypy/config_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ def split_commas(value: str) -> list[str]:

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 Down Expand Up @@ -284,6 +284,9 @@ def parse_config_file(

os.environ["MYPY_CONFIG_FILE_DIR"] = os.path.dirname(os.path.abspath(config_file))

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

if "mypy" not in parser:
if filename or file_read not in defaults.SHARED_CONFIG_FILES:
print(f"{file_read}: No [mypy] section in config file", file=stderr)
Expand All @@ -297,11 +300,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 @@ -440,7 +448,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 Down Expand Up @@ -535,7 +543,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 @@ -631,7 +639,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 @@ -1265,21 +1265,15 @@ def add_invertible_flag(
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 @@ -1400,9 +1394,6 @@ def set_strict_flags() -> None:
" new type inference algorithm is already enabled by default"
)

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 @@ -212,7 +212,7 @@ def __init__(self) -> None:
# This makes 1 == '1', 1 in ['1'], and 1 is '1' errors.
self.strict_equality = 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 @@ -1940,15 +1940,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 @@ -162,7 +162,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
25 changes: 25 additions & 0 deletions test-data/unit/check-flags.test
Original file line number Diff line number Diff line change
Expand Up @@ -2125,6 +2125,31 @@ 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

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

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