Skip to content

Commit 3db4a75

Browse files
authored
Invalid use of @overload v2 (#9127)
* Version 2 Checker Duplicated * Test file missed * should have been commited before * Async removed as requested * final tidy * Extra File Removed
1 parent 68c10a4 commit 3db4a75

5 files changed

Lines changed: 166 additions & 3 deletions

File tree

tools/pylint-extensions/azure-pylint-guidelines-checker/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ In the case of a false positive, use the disable command to remove the pylint er
9696
| do-not-log-raised-errors | Do not log errors at `error` or `warning` level when error is raised in an exception block. | pylint:disable=do-not-log-raised-errors | No Link. |
9797
| do-not-use-legacy-typing | Do not use legacy (<Python 3.8) type hinting comments | pylint:disable=do-not-use-legacy-typing | No Link. |
9898
| do-not-import-asyncio | Do not import asyncio directly. | pylint:disable=do-not-import-asyncio | No Link. |
99+
| invalid-use-of-overload | Do not mix async and synchronous overloads | pylint:disable=invalid-use-of-overload | No Link. | | Add a check for connection_verify hardcoded settings #35355 | | |
99100
| do-not-hardcode-connection-verify | Do not hardcode a boolean value to connection_verify | pylint:disable=do-not-hardcode-connection-verify | No LInk. |
100101
| TODO | custom linter check for invalid use of @overload #3229 | | |
101102
| do-not-log-exceptions | Do not log exceptions in levels other than debug, otherwise it can reveal sensitive information | pylint:disable=do-not-log-exceptions | [link](https://azure.github.io/azure-sdk/python_implementation.html#python-logging-sensitive-info) |

tools/pylint-extensions/azure-pylint-guidelines-checker/pylint_guidelines_checker.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2884,7 +2884,63 @@ def visit_import(self, node):
28842884
)
28852885

28862886

2887-
# [Pylint] custom linter check for invalid use of @overload #3229
2887+
2888+
class InvalidUseOfOverload(BaseChecker):
2889+
"""Rule to check that use of the @overload decorator matches the async/sync nature of the underlying function"""
2890+
2891+
name = "invalid-use-of-overload"
2892+
priority = -1
2893+
msgs = {
2894+
"C4765": (
2895+
"Do not mix async and synchronous overloads",
2896+
"invalid-use-of-overload",
2897+
"Functions and their overloads must be either all async or all synchronous.",
2898+
),
2899+
}
2900+
2901+
def visit_classdef(self, node):
2902+
"""Check that use of the @overload decorator matches the async/sync nature of the underlying function"""
2903+
2904+
# Obtain a list of all functions and function names
2905+
functions = []
2906+
node.body
2907+
for item in node.body:
2908+
if hasattr(item, 'name'):
2909+
functions.append(item)
2910+
2911+
# Dictionary of lists of all functions by name
2912+
overloadedfunctions = {}
2913+
for item in functions:
2914+
if item.name in overloadedfunctions:
2915+
overloadedfunctions[item.name].append(item)
2916+
else:
2917+
overloadedfunctions[item.name] = [item]
2918+
2919+
2920+
# Loop through the overloaded functions and check they are the same type
2921+
for funct in overloadedfunctions.values():
2922+
if len(funct) > 1: # only need to check if there is more than 1 function with the same name
2923+
function_is_async = None
2924+
2925+
for item in funct:
2926+
if function_is_async is None:
2927+
function_is_async = self.is_function_async(item)
2928+
2929+
else:
2930+
if function_is_async != self.is_function_async(item):
2931+
self.add_message(
2932+
msgid=f"invalid-use-of-overload",
2933+
node=item,
2934+
confidence=None,
2935+
)
2936+
2937+
2938+
def is_function_async(self, node):
2939+
try:
2940+
str(node.__class__).index("Async")
2941+
return True
2942+
except:
2943+
return False
28882944

28892945

28902946
class DoNotLogExceptions(BaseChecker):
@@ -3071,9 +3127,9 @@ def register(linter):
30713127
linter.register_checker(NoImportTypingFromTypeCheck(linter))
30723128
linter.register_checker(DoNotUseLegacyTyping(linter))
30733129
linter.register_checker(DoNotLogErrorsEndUpRaising(linter))
3130+
linter.register_checker(InvalidUseOfOverload(linter))
30743131
linter.register_checker(DoNotLogExceptions(linter))
30753132

3076-
# [Pylint] custom linter check for invalid use of @overload #3229
30773133
# [Pylint] Address Commented out Pylint Custom Plugin Checkers #3228
30783134
linter.register_checker(DoNotHardcodeConnectionVerify(linter))
30793135
# [Pylint] Investigate pylint rule around missing dependency #3231
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Test file for InvalidUseOfOverload checker
2+
3+
from typing import overload, Union
4+
5+
class testingOverload:
6+
@overload
7+
async def double(a: str):
8+
...
9+
10+
@overload
11+
async def double(a: int):
12+
...
13+
14+
async def double(a: Union[str, int]) -> int:
15+
if isinstance(a, str):
16+
return len(a)*2
17+
return a * 2
18+
19+
20+
@overload
21+
def single(a: str):
22+
...
23+
24+
@overload
25+
def single(a: int):
26+
...
27+
28+
def single(a: Union[str, int]) -> int:
29+
if isinstance(a, str):
30+
return len(a)
31+
return a
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Test file for InvalidUseOfOverload checker - testing what mypy doesn't pick up
2+
3+
from typing import overload, Union
4+
5+
class testingOverload:
6+
@overload
7+
def double(a: str):
8+
...
9+
10+
@overload
11+
def double(a: int):
12+
...
13+
14+
async def double(a: Union[str, int]):
15+
if isinstance(a, str):
16+
return len(a)*2
17+
return a * 2
18+
19+
20+
@overload
21+
async def doubleAgain(a: str) -> int:
22+
...
23+
24+
@overload
25+
def doubleAgain(a: int) -> int:
26+
...
27+
28+
async def doubleAgain(a: Union[str, int]) -> int:
29+
if isinstance(a, str):
30+
return len(a)*2
31+
return a * 2

tools/pylint-extensions/azure-pylint-guidelines-checker/tests/test_pylint_custom_plugins.py

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3532,8 +3532,52 @@ def test_extra_nested_branches_exception_logged(self, setup):
35323532
):
35333533
self.checker.visit_try(try_node)
35343534

3535+
3536+
class TestInvalidUseOfOverload(pylint.testutils.CheckerTestCase):
3537+
"""Test that use of the @overload decorator matches the async/sync nature of the underlying function"""
35353538

3536-
# [Pylint] custom linter check for invalid use of @overload #3229
3539+
CHECKER_CLASS = checker.InvalidUseOfOverload
3540+
3541+
def test_valid_use_overload(self):
3542+
file = open(
3543+
os.path.join(
3544+
TEST_FOLDER, "test_files", "invalid_use_of_overload_acceptable.py"
3545+
)
3546+
)
3547+
node = astroid.parse(file.read())
3548+
file.close()
3549+
with self.assertNoMessages():
3550+
self.checker.visit_classdef(node.body[1])
3551+
3552+
3553+
def test_invalid_use_overload(self):
3554+
file = open(
3555+
os.path.join(
3556+
TEST_FOLDER, "test_files", "invalid_use_of_overload_violation.py"
3557+
)
3558+
)
3559+
node = astroid.extract_node(file.read())
3560+
file.close()
3561+
3562+
with self.assertAddsMessages(
3563+
pylint.testutils.MessageTest(
3564+
msg_id="invalid-use-of-overload",
3565+
line=14,
3566+
node=node.body[2],
3567+
col_offset=4,
3568+
end_line=14,
3569+
end_col_offset=20,
3570+
),
3571+
pylint.testutils.MessageTest(
3572+
msg_id="invalid-use-of-overload",
3573+
line=25,
3574+
node=node.body[4],
3575+
col_offset=4,
3576+
end_line=25,
3577+
end_col_offset=19,
3578+
),
3579+
):
3580+
self.checker.visit_classdef(node)
35373581

35383582

35393583
class TestDoNotLogExceptions(pylint.testutils.CheckerTestCase):

0 commit comments

Comments
 (0)