From 321a0e66e6d8ad49d19ac8e2ab1381a96d8dbe46 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 6 Feb 2025 23:26:31 +0300 Subject: [PATCH 1/6] Test and fix trailing commas in many multiline string options in `pyproject.toml` --- mypy/config_parser.py | 9 +- test-data/unit/cmdline.pyproject.test | 115 ++++++++++++++++++++++++++ test-data/unit/cmdline.test | 32 +++++++ 3 files changed, 153 insertions(+), 3 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 94427a347779..3d2d81ac9910 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -55,9 +55,12 @@ 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""" if isinstance(v, str): - return [p.strip() for p in re.split(split_regex, v)] - - return [p.strip() for p in v] + items = [p.strip() for p in re.split(split_regex, v)] + else: + items = [p.strip() for p in v] + if items and items[-1] == "": + items.pop(-1) + return items def validate_codes(codes: list[str]) -> list[str]: diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 57e6facad032..f8934999d343 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -133,3 +133,118 @@ Neither is this! description = "Factory ⸻ A code generator 🏭" \[tool.mypy] [file x.py] + +[case testPyprojectFilesTrailingComma] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +files = """ + a.py, + b.py, +""" +[file a.py] +x: str = 'x' # ok +[file b.py] +y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file c.py] +# This should not trigger any errors, because it is not included: +z: int = 'z' +[out] + +[case testPyprojectPluginsTrailingComma] +# cmd: mypy . +[file pyproject.toml] +\[tool.mypy] +plugins = """ + /test-data/unit/plugins/function_sig_hook.py, + /test-data/unit/plugins/method_in_decorator.py, +""" +[out] + +[case testPyprojectAlwaysTrueTrailingComma] +# cmd: mypy . +[file pyproject.toml] +\[tool.mypy] +always_true = """ + FLAG_A, + FLAG_B, +""" +[file a.py] +FLAG_A = False +FLAG_B = False +if not FLAG_A: # unreachable + x: int = 'x' +if not FLAG_B: # unreachable + y: int = 'y' + +[case testPyprojectAlwaysFalseTrailingComma] +# cmd: mypy . +[file pyproject.toml] +\[tool.mypy] +always_false = """ + FLAG_A, + FLAG_B, +""" +[file a.py] +FLAG_A = True +FLAG_B = True +if FLAG_A: # unreachable + x: int = 'x' +if FLAG_B: # unreachable + y: int = 'y' + +[case testPyprojectEnableErrorCodeTrailingComma] +# cmd: mypy . +[file pyproject.toml] +\[tool.mypy] +enable_error_code = """ + redundant-expr, + ignore-without-code, +""" +[file a.py] +1 + 'a' # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) + +[case testPyprojectDisableErrorCodeTrailingComma] +# cmd: mypy . +[file pyproject.toml] +\[tool.mypy] +disable_error_code = """ + operator, + import, +""" +[file a.py] +1 + 'a' + +[case testPyprojectModulesTrailingComma] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +modules = """ + a, + b, +""" +[file a.py] +x: str = 'x' # ok +[file b.py] +y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file c.py] +# This should not trigger any errors, because it is not included: +z: int = 'z' +[out] + +[case testPyprojectPackagesTrailingComma] +# cmd: mypy +[file pyproject.toml] +\[tool.mypy] +packages = """ + a, + b, +""" +[file a/__init__.py] +x: str = 'x' # ok +[file b/__init__.py] +y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file c/__init__.py] +# This should not trigger any errors, because it is not included: +z: int = 'z' +[out] diff --git a/test-data/unit/cmdline.test b/test-data/unit/cmdline.test index f298f6dbe2df..b9da5883c793 100644 --- a/test-data/unit/cmdline.test +++ b/test-data/unit/cmdline.test @@ -1342,6 +1342,38 @@ always_true = MY_VAR, [out] +[case testCmdlineCfgModulesTrailingComma] +# cmd: mypy +[file mypy.ini] +\[mypy] +modules = + a, + b, +[file a.py] +x: str = 'x' # ok +[file b.py] +y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file c.py] +# This should not trigger any errors, because it is not included: +z: int = 'z' +[out] + +[case testCmdlineCfgPackagesTrailingComma] +# cmd: mypy +[file mypy.ini] +\[mypy] +packages = + a, + b, +[file a/__init__.py] +x: str = 'x' # ok +[file b/__init__.py] +y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file c/__init__.py] +# This should not trigger any errors, because it is not included: +z: int = 'z' +[out] + [case testTypeVarTupleUnpackEnabled] # cmd: mypy --enable-incomplete-feature=TypeVarTuple --enable-incomplete-feature=Unpack a.py [file a.py] From 23236f5a6a7b3f1f99f8d780eca03c625f00c72c Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 7 Feb 2025 00:09:37 +0300 Subject: [PATCH 2/6] Fix CI --- test-data/unit/cmdline.pyproject.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index f8934999d343..2e36e47190c6 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -151,7 +151,8 @@ y: int = 'y' # E: Incompatible types in assignment (expression has type "str", z: int = 'z' [out] -[case testPyprojectPluginsTrailingComma] +# Fails on windows for some reason +[case testPyprojectPluginsTrailingComma-posix] # cmd: mypy . [file pyproject.toml] \[tool.mypy] From 1c488b09607f6f00353227362c10ac103fb2d7f9 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 7 Feb 2025 00:36:48 +0300 Subject: [PATCH 3/6] Fix CI --- 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 2e36e47190c6..cbc0546192e8 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -151,10 +151,10 @@ y: int = 'y' # E: Incompatible types in assignment (expression has type "str", z: int = 'z' [out] -# Fails on windows for some reason [case testPyprojectPluginsTrailingComma-posix] # cmd: mypy . [file pyproject.toml] +# This test fails on windows for some reason, skipping it there. \[tool.mypy] plugins = """ /test-data/unit/plugins/function_sig_hook.py, From 5a0f305d96ee5c8c2d40c52b9b33806025664b77 Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 7 Feb 2025 09:50:28 +0300 Subject: [PATCH 4/6] Address review --- mypy/config_parser.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mypy/config_parser.py b/mypy/config_parser.py index 3d2d81ac9910..0e033471d2e9 100644 --- a/mypy/config_parser.py +++ b/mypy/config_parser.py @@ -56,11 +56,10 @@ 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""" if isinstance(v, str): items = [p.strip() for p in re.split(split_regex, v)] - else: - items = [p.strip() for p in v] - if items and items[-1] == "": - items.pop(-1) - return items + if items and items[-1] == "": + items.pop(-1) + return items + return [p.strip() for p in v] def validate_codes(codes: list[str]) -> list[str]: From 168b4b847693dbee6ca2bf44872ae52e24a2670a Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 7 Feb 2025 09:52:28 +0300 Subject: [PATCH 5/6] Address review --- test-data/unit/check-custom-plugin.test | 12 ++++++++++++ test-data/unit/cmdline.pyproject.test | 11 ----------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/test-data/unit/check-custom-plugin.test b/test-data/unit/check-custom-plugin.test index db2ea2d5e659..feb135bee165 100644 --- a/test-data/unit/check-custom-plugin.test +++ b/test-data/unit/check-custom-plugin.test @@ -1098,3 +1098,15 @@ reveal_type(1) # N: Revealed type is "Literal[1]?" [file mypy.ini] \[mypy] plugins=/test-data/unit/plugins/custom_errorcode.py + + +[case testPyprojectPluginsTrailingComma] +# flags: --config-file tmp/pyproject.toml +[file pyproject.toml] +# This test checks that trailing commas in string-based `plugins` are allowed. +\[tool.mypy] +plugins = """ + /test-data/unit/plugins/function_sig_hook.py, + /test-data/unit/plugins/method_in_decorator.py, +""" +[out] diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index cbc0546192e8..6ae8605ce25d 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -151,17 +151,6 @@ y: int = 'y' # E: Incompatible types in assignment (expression has type "str", z: int = 'z' [out] -[case testPyprojectPluginsTrailingComma-posix] -# cmd: mypy . -[file pyproject.toml] -# This test fails on windows for some reason, skipping it there. -\[tool.mypy] -plugins = """ - /test-data/unit/plugins/function_sig_hook.py, - /test-data/unit/plugins/method_in_decorator.py, -""" -[out] - [case testPyprojectAlwaysTrueTrailingComma] # cmd: mypy . [file pyproject.toml] From dcea16678e234072dc3e8a965fc5cb446ae615da Mon Sep 17 00:00:00 2001 From: sobolevn Date: Fri, 7 Feb 2025 13:43:46 +0300 Subject: [PATCH 6/6] Speed up tests by combining them --- test-data/unit/cmdline.pyproject.test | 98 ++++++++++++--------------- 1 file changed, 43 insertions(+), 55 deletions(-) diff --git a/test-data/unit/cmdline.pyproject.test b/test-data/unit/cmdline.pyproject.test index 6ae8605ce25d..f9691ba245f9 100644 --- a/test-data/unit/cmdline.pyproject.test +++ b/test-data/unit/cmdline.pyproject.test @@ -138,89 +138,77 @@ description = "Factory ⸻ A code generator 🏭" # cmd: mypy [file pyproject.toml] \[tool.mypy] +# We combine multiple tests in a single one here, because these tests are slow. files = """ a.py, b.py, """ -[file a.py] -x: str = 'x' # ok -[file b.py] -y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") -[file c.py] -# This should not trigger any errors, because it is not included: -z: int = 'z' -[out] - -[case testPyprojectAlwaysTrueTrailingComma] -# cmd: mypy . -[file pyproject.toml] -\[tool.mypy] always_true = """ - FLAG_A, - FLAG_B, + FLAG_A1, + FLAG_B1, """ -[file a.py] -FLAG_A = False -FLAG_B = False -if not FLAG_A: # unreachable - x: int = 'x' -if not FLAG_B: # unreachable - y: int = 'y' - -[case testPyprojectAlwaysFalseTrailingComma] -# cmd: mypy . -[file pyproject.toml] -\[tool.mypy] always_false = """ - FLAG_A, - FLAG_B, + FLAG_A2, + FLAG_B2, """ [file a.py] -FLAG_A = True -FLAG_B = True -if FLAG_A: # unreachable +x: str = 'x' # ok' + +# --always-true +FLAG_A1 = False +FLAG_B1 = False +if not FLAG_A1: # unreachable x: int = 'x' -if FLAG_B: # unreachable +if not FLAG_B1: # unreachable y: int = 'y' -[case testPyprojectEnableErrorCodeTrailingComma] -# cmd: mypy . -[file pyproject.toml] -\[tool.mypy] -enable_error_code = """ - redundant-expr, - ignore-without-code, -""" -[file a.py] -1 + 'a' # type: ignore # E: "type: ignore" comment without error code (consider "type: ignore[operator]" instead) - -[case testPyprojectDisableErrorCodeTrailingComma] -# cmd: mypy . -[file pyproject.toml] -\[tool.mypy] -disable_error_code = """ - operator, - import, -""" -[file a.py] -1 + 'a' +# --always-false +FLAG_A2 = True +FLAG_B2 = True +if FLAG_A2: # unreachable + x: int = 'x' +if FLAG_B2: # unreachable + y: int = 'y' +[file b.py] +y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +[file c.py] +# This should not trigger any errors, because it is not included: +z: int = 'z' +[out] [case testPyprojectModulesTrailingComma] # cmd: mypy [file pyproject.toml] \[tool.mypy] +# We combine multiple tests in a single one here, because these tests are slow. modules = """ a, b, """ +disable_error_code = """ + operator, + import, +""" +enable_error_code = """ + redundant-expr, + ignore-without-code, +""" [file a.py] x: str = 'x' # ok + +# --enable-error-code +a: int = 'a' # type: ignore + +# --disable-error-code +'a' + 1 [file b.py] -y: int = 'y' # E: Incompatible types in assignment (expression has type "str", variable has type "int") +y: int = 'y' [file c.py] # This should not trigger any errors, because it is not included: z: int = 'z' [out] +b.py:1: error: Incompatible types in assignment (expression has type "str", variable has type "int") +a.py:4: error: "type: ignore" comment without error code (consider "type: ignore[assignment]" instead) [case testPyprojectPackagesTrailingComma] # cmd: mypy