diff --git a/ChangeLog b/ChangeLog index a8a7323e94..01bd796c2e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -55,6 +55,11 @@ modules are added. Closes #3619 +* Fix raising false-positive ``no-member`` on instances' methods where class can be inferred + to be derived from Named Tuples + + Closes #4377 + What's New in Pylint 2.8.2? =========================== diff --git a/doc/whatsnew/2.9.rst b/doc/whatsnew/2.9.rst index 14669ca27c..588dad85e3 100644 --- a/doc/whatsnew/2.9.rst +++ b/doc/whatsnew/2.9.rst @@ -33,3 +33,6 @@ Other Changes of overridden functions. It aims to separate the functionality of ``arguments-differ``. * Fix incompatibility with Python 3.6.0 caused by ``typing.Counter`` and ``typing.NoReturn`` usage + +* Fix raising false-positive ``no-member`` on instances' methods where class can be inferred + to be derived from Named Tuples diff --git a/pylint/checkers/typecheck.py b/pylint/checkers/typecheck.py index f64960a218..a3574737ca 100644 --- a/pylint/checkers/typecheck.py +++ b/pylint/checkers/typecheck.py @@ -517,6 +517,24 @@ def _emit_no_member(node, owner, owner_name, ignored_mixins=True, ignored_none=T # See https://github.com/PyCQA/pylint/issues/4123 return False + if ( + isinstance(node.parent, astroid.Call) + and isinstance(owner, astroid.Instance) + and isinstance(owner.parent, astroid.Module) + and isinstance(getattr(owner, "_proxied", None), astroid.ClassDef) + and any( + ( + isinstance(b, astroid.Name) + and b.name == "tuple" + and utils.is_builtin_object(utils.safe_infer(b)) + ) + for b in owner._proxied.bases + ) + ): + # Avoid false positive on function calls on instances of named tuples + # See https://github.com/PyCQA/pylint/issues/4377 + return False + return True diff --git a/tests/functional/m/member/member_checks.py b/tests/functional/m/member/member_checks.py index 00fb35dcf8..924e180196 100644 --- a/tests/functional/m/member/member_checks.py +++ b/tests/functional/m/member/member_checks.py @@ -231,3 +231,17 @@ class Animal(Enum): print(keyy) for vall in Animal.__members__.values(): print(vall) + +# To test false positive no-member after _replace() described in issue #4377 +# pylint: disable=invalid-name +from urllib import parse +parsed_url = parse.urlparse("http://www.this-is-weird.com") +sorted_query = parse.urlencode( + sorted(parse.parse_qsl(parsed_url.query), + key=lambda param: param[0]) +) +new_parsed_url = parse.ParseResult._replace(parsed_url, query=sorted_query) +new_url = new_parsed_url.geturl() # This should not trigger a warning + +new_parsed_url = parsed_url._replace(query=sorted_query) +new_url = new_parsed_url.geturl()