Skip to content

Commit

Permalink
Extend safe_infer's sense of ambiguity to arg names and defaults (#8668)
Browse files Browse the repository at this point in the history
Co-authored-by: Mehdi Drissi <mdrissi@snapchat.com>
Co-authored-by: Jacob Walls <jacobtylerwalls@gmail.com>
  • Loading branch information
3 people committed Jun 19, 2023
1 parent 5bb1fd1 commit 49653f4
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 4 deletions.
6 changes: 6 additions & 0 deletions doc/whatsnew/fragments/3613.false_positive
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Extend concept of "function ambiguity" in ``safe_infer()`` from
differing number of function arguments to differing set of argument names.

Solves false positives in ``tensorflow``.

Closes #3613
33 changes: 29 additions & 4 deletions pylint/checkers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -1381,12 +1381,10 @@ def safe_infer(
return None
if (
isinstance(inferred, nodes.FunctionDef)
and inferred.args.args is not None
and isinstance(value, nodes.FunctionDef)
and value.args.args is not None
and len(inferred.args.args) != len(value.args.args)
and function_arguments_are_ambiguous(inferred, value)
):
return None # Different number of arguments indicates ambiguity
return None
except astroid.InferenceError:
return None # There is some kind of ambiguity
except StopIteration:
Expand All @@ -1408,6 +1406,33 @@ def infer_all(
raise AstroidError from e


def function_arguments_are_ambiguous(
func1: nodes.FunctionDef, func2: nodes.FunctionDef
) -> bool:
if func1.argnames() != func2.argnames():
return True
# Check ambiguity among function default values
pairs_of_defaults = [
(func1.args.defaults, func2.args.defaults),
(func1.args.kw_defaults, func2.args.kw_defaults),
]
for zippable_default in pairs_of_defaults:
if None in zippable_default:
continue
if len(zippable_default[0]) != len(zippable_default[1]):
return True
for default1, default2 in zip(*zippable_default):
if isinstance(default1, nodes.Const) and isinstance(default2, nodes.Const):
if default1.value != default2.value:
return True
elif isinstance(default1, nodes.Name) and isinstance(default2, nodes.Name):
if default1.name != default2.name:
return True
else:
return True
return False


def has_known_bases(
klass: nodes.ClassDef, context: InferenceContext | None = None
) -> bool:
Expand Down
47 changes: 47 additions & 0 deletions tests/functional/u/unexpected_keyword_arg.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,50 @@ def test_no_return():


test_no_return(internal_arg=2) # [unexpected-keyword-arg]


def ambiguous_func1(arg1):
print(arg1)


def ambiguous_func2(other_arg1):
print(other_arg1)


func1 = ambiguous_func1 if unknown else ambiguous_func2
func1(other_arg1=1)


def ambiguous_func3(arg1=None):
print(arg1)


func2 = ambiguous_func1 if unknown else ambiguous_func3
func2()


def ambiguous_func4(arg1=print):
print(arg1)


def ambiguous_func5(arg1=input):
print(arg1)


def ambiguous_func6(arg1=42):
print(arg1)


# Two functions with same keyword argument but different defaults (names)
func3 = ambiguous_func4 if unknown else ambiguous_func5
func3()


# Two functions with same keyword argument but different defaults (constants)
func4 = ambiguous_func3 if unknown else ambiguous_func6
func4()


# Two functions with same keyword argument but mixed defaults (names, constant)
func5 = ambiguous_func3 if unknown else ambiguous_func5
func5()

0 comments on commit 49653f4

Please sign in to comment.