Skip to content
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

detect unused nested functions #510

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
4 changes: 4 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## Unreleased

- Detect unused nested function and class definitions (#510)

## Version 0.8.0 (November 5, 2022)

Release highlights:
Expand Down
17 changes: 13 additions & 4 deletions pyanalyze/name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -2019,14 +2019,23 @@ def _check_function_unused_vars(
)
all_unused_nodes = all_def_nodes - all_used_def_nodes
for unused in all_unused_nodes:
# Ignore names not defined through a Name node (e.g., function arguments)
if not isinstance(unused, ast.Name) or not self._is_write_ctx(unused.ctx):
if isinstance(unused, ast.Name):
if not self._is_write_ctx(unused.ctx):
continue
name = unused.id
elif isinstance(
unused, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)
):
name = unused.name
else:
# Ignore function arguments and other unusual definition nodes
continue

# Ignore names that are meant to be ignored
if unused.id.startswith("_"):
if name.startswith("_"):
continue
# Ignore names involved in global and similar declarations
if unused.id in scope.accessed_from_special_nodes:
if name in scope.accessed_from_special_nodes:
continue
replacement = None
if self._name_node_to_statement is not None:
Expand Down
2 changes: 2 additions & 0 deletions pyanalyze/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ def nested(y):
class Nested(object):
xs = ys

return Nested

@assert_passes()
def test_asynq(self):
from asynq import asynq
Expand Down
16 changes: 13 additions & 3 deletions pyanalyze/test_name_check_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ def capybara():
class Capybara(metaclass=Hutia): # E: undefined_name
pass

return Capybara

@assert_passes()
def test_no_failure_on_builtin(self):
def run():
Expand Down Expand Up @@ -260,6 +262,8 @@ class Porcupine(object):
def coendou(self):
return 1

return Porcupine

@assert_passes()
def test_class_scope_is_not_searched(self):
class Porcupine(object):
Expand Down Expand Up @@ -415,6 +419,8 @@ class Capybaras(object):
if False:
print(neochoerus) # E: undefined_name

return Capybaras

@assert_passes()
def test_cant_del_tuple(self):
tpl = (1, 2, 3)
Expand Down Expand Up @@ -1005,6 +1011,8 @@ class Capybara(object):
incisors = [1, 2]
canines = {incisors[0] for _ in incisors} # E: undefined_name

return Capybara

@assert_passes()
def test_comprehension_within_class(self):
class Capybara(object):
Expand Down Expand Up @@ -1386,7 +1394,7 @@ def second_inner():
# this should not throw unused_variable
x = 5

return x
return x, inner_capybara, second_inner

@assert_passes()
def test_no_unused_var(self):
Expand All @@ -1397,7 +1405,7 @@ def handler():
nonlocal running
running = False

return running
return running, handler


class TestingCallSiteCollector(object):
Expand Down Expand Up @@ -1839,11 +1847,13 @@ def f(self, x) -> None: # E: missing_parameter_annotation

@assert_passes(settings=_AnnotSettings)
def test_dont_annotate_self():
def f() -> None:
def f() -> type:
class X:
def method(self) -> None:
pass

return X

class X:
def f(self) -> None:
pass
Expand Down
33 changes: 25 additions & 8 deletions pyanalyze/test_stacked_scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,22 +548,33 @@ def nested():
if condition:
print(x)

return nested

@assert_passes()
def test_unused(self):
def capybara():
y = 3 # E: unused_variable

def f(): # E: unused_variable
pass

async def g(): # E: unused_variable
pass

class Capybara: # E: unused_variable
pass

def test_replacement(self):
self.assert_is_changed(
"""
def capybara():
y = 3
return 3
""",
def capybara():
y = 3
return 3
""",
"""
def capybara():
return 3
""",
def capybara():
return 3
""",
)

@assert_passes()
Expand Down Expand Up @@ -1262,7 +1273,7 @@ def capybara(x: Optional[int], z):
def nested():
assert_is_value(x, TypedValue(int))

return [assert_is_value(x, TypedValue(int)) for _ in z]
return [assert_is_value(x, TypedValue(int)) for _ in z], nested

@assert_passes()
def test_repeated_constraints(self):
Expand Down Expand Up @@ -1354,6 +1365,8 @@ def nested():
else:
assert_is_value(x, KnownValue(False))

return nested

@assert_passes()
def test_nonlocal_known_with_write(self):
def capybara(y):
Expand All @@ -1374,6 +1387,8 @@ def nested():
x = True
assert_is_value(x, KnownValue(True))

return nested

@assert_passes()
def test_nonlocal_in_loop(self):
def capybara(x):
Expand All @@ -1382,6 +1397,8 @@ def nested(y):
if x:
pass

return nested

@assert_passes()
def test_nonlocal_not_unused(self):
def _get_call_point(x, y):
Expand Down
4 changes: 4 additions & 0 deletions pyanalyze/test_typeshed.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,8 @@ def nested(td1: TD1, td2: TD2, pep655: PEP655, inherited: Inherited):
assert_is_value(pep655, _EXPECTED_TYPED_DICTS["PEP655"])
assert_is_value(inherited, _EXPECTED_TYPED_DICTS["Inherited"])

return nested

def test_evaluated(self):
tsf = TypeshedFinder.make(Checker(), TEST_OPTIONS, verbose=True)
mod = "_pyanalyze_tests.evaluated"
Expand Down Expand Up @@ -304,6 +306,8 @@ def f(x: _ScandirIterator):
want_cm(x)
len(x) # E: incompatible_argument

return f

@assert_passes()
def test_args_kwargs(self):
def capybara():
Expand Down