From 10df7881adf4e564c461784655f0500bf765ee4a Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 23 Jul 2025 18:18:04 -0700 Subject: [PATCH 01/11] Fix #19491, crash when using enable_error_code value of wrong type in pyproject.toml --- mypy/config_parser.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index e5c0dc893c76..26c98db8128a 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -61,13 +61,19 @@ def parse_version(v: str | float) -> tuple[int, int]: def try_split(v: str | Sequence[str], split_regex: str = "[,]") -> list[str]: - """Split and trim a str or list of str into a list of str""" + """Split and trim a str or sequence (eg: list) of str into a list of str. + Non-str elements will simply be returned untouched. This is not documented + by the types but there was no type error before this bugfix, so we must be + type-ignoring elsewhere. Feel free to fix that.""" if isinstance(v, str): items = [p.strip() for p in re.split(split_regex, v)] if items and items[-1] == "": items.pop(-1) return items - return [p.strip() for p in v] + elif isinstance(v, Sequence): + return [p.strip() if isinstance(p, str) else p for p in v] + else: + return v def validate_codes(codes: list[str]) -> list[str]: From b32e01e89dc651d1eb6d6072b4f684c02ef17312 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 23 Jul 2025 18:41:12 -0700 Subject: [PATCH 02/11] add testPyProjectTOMLSettingOfWrongType --- test-data/unit/check-flags.test | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index bb64bb44d282..051d9dcf96cd 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -2482,3 +2482,10 @@ A = Union[C, List] # OK -- check_untyped_defs is False by default. def f(): x: int = "no" # N: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs + +[case testPyProjectTOMLSettingOfWrongType] +# E: Invalid error code(s): true +[file pyproject.toml] +\[tool.mypy] +enable_error_code = true + From a427377b4e88e257c6e9c269995bd1280f1fb1dc Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 23 Jul 2025 18:48:14 -0700 Subject: [PATCH 03/11] slip in a cheeky little Any, to match the other cheeky little Anys in the calling code --- mypy/config_parser.py | 7 +++---- test-data/unit/check-flags.test | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 26c98db8128a..379a98b370b7 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -60,11 +60,10 @@ def parse_version(v: str | float) -> tuple[int, int]: return major, minor -def try_split(v: str | Sequence[str], split_regex: str = "[,]") -> list[str]: +def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[str] | Any: """Split and trim a str or sequence (eg: list) of str into a list of str. - Non-str elements will simply be returned untouched. This is not documented - by the types but there was no type error before this bugfix, so we must be - type-ignoring elsewhere. Feel free to fix that.""" + Non-str elements will simply be returned untouched. Feel free to one day + fix the typing of the calling code, and remove this caveat and Any.""" if isinstance(v, str): items = [p.strip() for p in re.split(split_regex, v)] if items and items[-1] == "": diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 051d9dcf96cd..76ee515c6ea5 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -2488,4 +2488,3 @@ def f(): [file pyproject.toml] \[tool.mypy] enable_error_code = true - From 08558da31547327a25a169090b8755c2a471dddc Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Tue, 29 Jul 2025 02:59:40 -0400 Subject: [PATCH 04/11] Apply suggestion from @sterliakov Co-authored-by: Stanislav Terliakov <50529348+sterliakov@users.noreply.github.com> --- mypy/config_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 379a98b370b7..6199420013a6 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -72,7 +72,7 @@ def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[st elif isinstance(v, Sequence): return [p.strip() if isinstance(p, str) else p for p in v] else: - return v + raise argparse.ArgumentTypeError(f"Expected a list or a stringified version thereof, but got: '{v}'") def validate_codes(codes: list[str]) -> list[str]: From 96d1638c4235e18a05735bcfc57afa3a6fe2313c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 07:01:01 +0000 Subject: [PATCH 05/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/config_parser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 6199420013a6..c3771401c93b 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -72,7 +72,9 @@ def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[st elif isinstance(v, Sequence): return [p.strip() if isinstance(p, str) else p for p in v] else: - raise argparse.ArgumentTypeError(f"Expected a list or a stringified version thereof, but got: '{v}'") + raise argparse.ArgumentTypeError( + f"Expected a list or a stringified version thereof, but got: '{v}'" + ) def validate_codes(codes: list[str]) -> list[str]: From 4cb378a99719458910171a5e23024cae3fc994f7 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Tue, 29 Jul 2025 00:18:53 -0700 Subject: [PATCH 06/11] improve up-front typechecking --- mypy/config_parser.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index c3771401c93b..6174d37e89e3 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -17,15 +17,15 @@ from collections.abc import Mapping, MutableMapping, Sequence from typing import Any, Callable, Final, TextIO, Union -from typing_extensions import TypeAlias as _TypeAlias +from typing_extensions import TypeAlias, Never from mypy import defaults from mypy.options import PER_MODULE_OPTIONS, Options -_CONFIG_VALUE_TYPES: _TypeAlias = Union[ +_CONFIG_VALUE_TYPES: TypeAlias = Union[ str, bool, int, float, dict[str, str], list[str], tuple[int, int] ] -_INI_PARSER_CALLABLE: _TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES] +_INI_PARSER_CALLABLE: TypeAlias = Callable[[Any], _CONFIG_VALUE_TYPES] class VersionTypeError(argparse.ArgumentTypeError): @@ -60,21 +60,20 @@ def parse_version(v: str | float) -> tuple[int, int]: return major, minor -def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[str] | Any: +def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[str]: """Split and trim a str or sequence (eg: list) of str into a list of str. - Non-str elements will simply be returned untouched. Feel free to one day - fix the typing of the calling code, and remove this caveat and Any.""" + If an element of the input is not str, a type error will be raised.""" + def complain(x: object, additional_info: str = "") -> Never: + raise argparse.ArgumentTypeError(f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x)}.{additional_info}") if isinstance(v, str): items = [p.strip() for p in re.split(split_regex, v)] if items and items[-1] == "": items.pop(-1) return items elif isinstance(v, Sequence): - return [p.strip() if isinstance(p, str) else p for p in v] + return [p.strip() if isinstance(p, str) else complain(p, additional_info=" (As an element of the list.)") for p in v] else: - raise argparse.ArgumentTypeError( - f"Expected a list or a stringified version thereof, but got: '{v}'" - ) + complain(v) def validate_codes(codes: list[str]) -> list[str]: From 7f102209eaab90297b6bbac4066419accc281050 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Jul 2025 07:20:33 +0000 Subject: [PATCH 07/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- mypy/config_parser.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 6174d37e89e3..193af6b78208 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -17,7 +17,7 @@ from collections.abc import Mapping, MutableMapping, Sequence from typing import Any, Callable, Final, TextIO, Union -from typing_extensions import TypeAlias, Never +from typing_extensions import Never, TypeAlias from mypy import defaults from mypy.options import PER_MODULE_OPTIONS, Options @@ -63,15 +63,26 @@ def parse_version(v: str | float) -> tuple[int, int]: def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[str]: """Split and trim a str or sequence (eg: list) of str into a list of str. If an element of the input is not str, a type error will be raised.""" + def complain(x: object, additional_info: str = "") -> Never: - raise argparse.ArgumentTypeError(f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x)}.{additional_info}") + raise argparse.ArgumentTypeError( + f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x)}.{additional_info}" + ) + if isinstance(v, str): items = [p.strip() for p in re.split(split_regex, v)] if items and items[-1] == "": items.pop(-1) return items elif isinstance(v, Sequence): - return [p.strip() if isinstance(p, str) else complain(p, additional_info=" (As an element of the list.)") for p in v] + return [ + ( + p.strip() + if isinstance(p, str) + else complain(p, additional_info=" (As an element of the list.)") + ) + for p in v + ] else: complain(v) From f21dd93842a802826a63375eae31e4db2b367204 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Wed, 30 Jul 2025 17:23:09 -0700 Subject: [PATCH 08/11] accept sterliakov's suggestion as-is --- test-data/unit/check-flags.test | 6 ------ test-data/unit/cmdline.pyproject.test | 11 +++++++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/test-data/unit/check-flags.test b/test-data/unit/check-flags.test index 76ee515c6ea5..bb64bb44d282 100644 --- a/test-data/unit/check-flags.test +++ b/test-data/unit/check-flags.test @@ -2482,9 +2482,3 @@ A = Union[C, List] # OK -- check_untyped_defs is False by default. def f(): x: int = "no" # N: By default the bodies of untyped functions are not checked, consider using --check-untyped-defs - -[case testPyProjectTOMLSettingOfWrongType] -# E: Invalid error code(s): true -[file pyproject.toml] -\[tool.mypy] -enable_error_code = true diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index f9691ba245f9..629f75840743 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -226,3 +226,14 @@ y: int = 'y' # E: Incompatible types in assignment (expression has type "str", # This should not trigger any errors, because it is not included: z: int = 'z' [out] + +[case testPyprojectTOMLSettingOfWrongType] +# cmd: mypy a.py +[file pyproject.toml] +\[tool.mypy] +enable_error_code = true +[file a.py] +x: int = 1 +[out] +pyproject.toml: [mypy]: enable_error_code: Expected a list or a stringified version thereof, but got: 'True', of type . +== Return code: 0 From e8eaf36ff6b4639ebcd71f71188264a20920e7a7 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Fri, 1 Aug 2025 13:52:09 -0700 Subject: [PATCH 09/11] Update mypy/config_parser.py: object not Any Ah, this should be object, not Any, shouldn't it? And while I'm at it, I can simplify the split regex --- mypy/config_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 193af6b78208..7b6a062fc551 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -60,7 +60,7 @@ def parse_version(v: str | float) -> tuple[int, int]: return major, minor -def try_split(v: str | Sequence[str] | Any, split_regex: str = "[,]") -> list[str]: +def try_split(v: str | Sequence[str] | object, split_regex: str = ",") -> list[str]: """Split and trim a str or sequence (eg: list) of str into a list of str. If an element of the input is not str, a type error will be raised.""" From 6eb6ab9570f404bc78b7ee5342d517505edef161 Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sun, 3 Aug 2025 13:33:12 -0700 Subject: [PATCH 10/11] Update mypy/config_parser.py: slightly friendlier error message I'm committing this because it's very convenient through the web interface, but I will presumably have to make a second commit to change the test output that depends on this Co-authored-by: Shantanu <12621235+hauntsaninja@users.noreply.github.com> --- mypy/config_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 7b6a062fc551..db2c1bf0139e 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -66,7 +66,7 @@ def try_split(v: str | Sequence[str] | object, split_regex: str = ",") -> list[s def complain(x: object, additional_info: str = "") -> Never: raise argparse.ArgumentTypeError( - f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x)}.{additional_info}" + f"Expected a list or a stringified version thereof, but got: '{x}', of type {type(x).__name__}.{additional_info}" ) if isinstance(v, str): From 234a167c025eec7e11aca27821470305d29b21ad Mon Sep 17 00:00:00 2001 From: wyattscarpenter Date: Sun, 3 Aug 2025 13:34:34 -0700 Subject: [PATCH 11/11] Update test-data/unit/cmdline.pyproject.test: presumably necessary test change for friendlier error message --- test-data/unit/cmdline.pyproject.test | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 629f75840743..68dfacb372fb 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -235,5 +235,5 @@ enable_error_code = true [file a.py] x: int = 1 [out] -pyproject.toml: [mypy]: enable_error_code: Expected a list or a stringified version thereof, but got: 'True', of type . +pyproject.toml: [mypy]: enable_error_code: Expected a list or a stringified version thereof, but got: 'True', of type bool. == Return code: 0