Skip to content

Commit 2dc2550

Browse files
committed
Updated the class_checker.py file
1 parent d603785 commit 2dc2550

File tree

3 files changed

+123
-3
lines changed

3 files changed

+123
-3
lines changed

pylint/checkers/classes/class_checker.py

+31-3
Original file line numberDiff line numberDiff line change
@@ -1510,13 +1510,41 @@ def _check_invalid_overridden_method(
15101510
node=function_node,
15111511
)
15121512

1513-
# Handle return type compatibility check
1513+
# Handle return type compatibility check between the current function and the overridden parent function
1514+
if parent_function_node.returns is None:
1515+
# If the parent function has no return type, no further checks are needed
1516+
return
1517+
15141518
inferred_return = safe_infer(function_node.returns)
15151519
inferred_parent_return = safe_infer(parent_function_node.returns)
1516-
if inferred_return != inferred_parent_return:
1520+
1521+
# If both return types are None, there's no conflict, so no validation is required
1522+
if inferred_return is None and inferred_parent_return is None:
1523+
return
1524+
1525+
# If one return type is None and the other is not, this is an invalid override
1526+
if (inferred_return is None) != (inferred_parent_return is None):
15171527
self.add_message(
15181528
"invalid-overridden-method",
1519-
args=(function_node.name, inferred_parent_return, inferred_return),
1529+
args=(
1530+
function_node.name,
1531+
"None" if inferred_parent_return is None else inferred_parent_return,
1532+
"None" if inferred_return is None else inferred_return
1533+
),
1534+
node=function_node,
1535+
)
1536+
return
1537+
1538+
# If both return types are not None, compare them to check for compatibility
1539+
if inferred_return.name != inferred_parent_return.name:
1540+
# The return types are incompatible, so add an error message
1541+
self.add_message(
1542+
"invalid-overridden-method",
1543+
args=(
1544+
function_node.name,
1545+
inferred_parent_return, # Parent return type
1546+
inferred_return # Current return type
1547+
),
15201548
node=function_node,
15211549
)
15221550

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# pylint: disable=R0903, W0107
2+
3+
"""Test invalid return type overrides in method inheritance."""
4+
5+
from abc import ABC, abstractmethod
6+
from io import TextIOWrapper, BytesIO
7+
8+
# Case 1: Simple type mismatch
9+
class Parent:
10+
"""Parent class with a method returning int."""
11+
def method(self) -> int:
12+
"""Returns an integer value."""
13+
return 42
14+
15+
class Child(Parent):
16+
"""Child class overriding method to return a string."""
17+
def method(self) -> str: # [invalid-overridden-method]
18+
"""Overrides method to return a string."""
19+
return "hello"
20+
21+
# Case 2: None vs concrete type
22+
class ParentNone:
23+
"""Parent class with a method returning None."""
24+
def method(self) -> None:
25+
"""Method returns None."""
26+
raise NotImplementedError("This method should be overridden")
27+
28+
class ChildNone(ParentNone):
29+
"""Child class overriding method to return an int."""
30+
def method(self) -> int: # [invalid-overridden-method]
31+
"""Overrides method to return an integer."""
32+
return 42
33+
34+
# Case 5: Abstract base class with different return type
35+
class BaseClass(ABC):
36+
"""Abstract base class with an abstract method returning TextIOWrapper."""
37+
@abstractmethod
38+
def read_file(self, path: str) -> TextIOWrapper:
39+
"""Abstract method that should return a TextIOWrapper."""
40+
raise NotImplementedError("Method must be implemented by subclass")
41+
42+
class ChildClass(BaseClass):
43+
"""Child class overriding read_file method returning BytesIO."""
44+
def read_file(self, path: str) -> BytesIO: # [invalid-overridden-method]
45+
"""Implementation returns BytesIO instead of TextIOWrapper."""
46+
return BytesIO(b"content")
47+
48+
# Case 6: Method returns a subtype of the expected return type (valid override)
49+
class Animal:
50+
"""Base class with a method returning a generic Animal."""
51+
def make_sound(self) -> 'Animal':
52+
"""Returns an Animal instance."""
53+
return self
54+
55+
class Dog(Animal):
56+
"""Dog class overrides make_sound method returning a Dog."""
57+
def make_sound(self) -> 'Dog': # This is valid as Dog is a subtype of Animal
58+
"""Returns a Dog instance."""
59+
return self
60+
61+
# Case 8: Overriding method with more specific return type annotation
62+
class FileReader:
63+
"""Class for reading files."""
64+
def get_contents(self) -> 'Any':
65+
"""Returns contents of the file."""
66+
return "file contents"
67+
68+
class JSONReader(FileReader):
69+
"""Class for reading JSON files."""
70+
def get_contents(self) -> dict: # [invalid-overridden-method]
71+
"""Overrides to return a dictionary."""
72+
return {"key": "value"}
73+
74+
# Case 9: Abstract method with invalid return type in subclass
75+
class AbstractClass(ABC):
76+
"""Abstract class with an abstract method."""
77+
@abstractmethod
78+
def get_value(self) -> float:
79+
"""Returns a float value."""
80+
pass
81+
82+
class ConcreteClass(AbstractClass):
83+
"""Concrete class with an invalid override."""
84+
def get_value(self) -> str: # [invalid-overridden-method]
85+
"""Returns a string instead of a float."""
86+
return "not a float"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
************* Module functional.r.invalid_overridden_method
2+
invalid_overridden_method.py:17:4: W0236: Method 'method' was expected to be <ClassDef.int l.0 at 0x794d71667490>, found it instead as <ClassDef.str l.0 at 0x794d713ce250> (invalid-overridden-method)
3+
invalid_overridden_method.py:30:4: W0236: Method 'method' was expected to be <Const.NoneType l.24 at 0x794d711b7010>, found it instead as <ClassDef.int l.0 at 0x794d71667490> (invalid-overridden-method)
4+
invalid_overridden_method.py:44:4: W0236: Method 'read_file' was expected to be <ClassDef.TextIOWrapper l.0 at 0x794d70fa4b90>, found it instead as <ClassDef.BytesIO l.0 at 0x794d71102ad0> (invalid-overridden-method)
5+
invalid_overridden_method.py:70:4: W0236: Method 'get_contents' was expected to be <Const.str l.64 at 0x794d711beb90>, found it instead as <ClassDef.dict l.0 at 0x794d715b12d0> (invalid-overridden-method)
6+
invalid_overridden_method.py:84:4: W0236: Method 'get_value' was expected to be <ClassDef.float l.0 at 0x794d71603990>, found it instead as <ClassDef.str l.0 at 0x794d713ce250> (invalid-overridden-method)

0 commit comments

Comments
 (0)