Skip to content

Fix bug with unclear function signature #8668

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

Merged
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()