Skip to content

Add a SYMBOL_FUNCBASE_TYPES constant to avoid multiple-inheritance bug #6481

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
merged 2 commits into from
Feb 28, 2019
Merged
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
14 changes: 6 additions & 8 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Type checking of attribute access"""

from typing import cast, Callable, List, Optional, TypeVar, Any
from typing import cast, Callable, List, Optional, TypeVar

from mypy.types import (
Type, Instance, AnyType, TupleType, TypedDictType, CallableType, FunctionLike, TypeVarDef,
Expand All @@ -10,7 +10,7 @@
from mypy.nodes import (
TypeInfo, FuncBase, Var, FuncDef, SymbolNode, Context, MypyFile, TypeVarExpr,
ARG_POS, ARG_STAR, ARG_STAR2, Decorator, OverloadedFuncDef, TypeAlias, TempNode,
is_final_node
is_final_node, SYMBOL_FUNCBASE_TYPES,
)
from mypy.messages import MessageBuilder
from mypy.maptype import map_instance_to_supertype
Expand Down Expand Up @@ -624,12 +624,10 @@ def analyze_class_attribute_access(itype: Instance,
return mx.chk.handle_partial_var_type(t, mx.is_lvalue, symnode, mx.context)

# Find the class where method/variable was defined.
# mypyc hack to workaround mypy misunderstanding multiple inheritance (#3603)
node_node = node.node # type: Any
if isinstance(node_node, Decorator):
super_info = node_node.var.info # type: Optional[TypeInfo]
elif isinstance(node_node, (Var, FuncBase)):
super_info = node_node.info
if isinstance(node.node, Decorator):
super_info = node.node.var.info # type: Optional[TypeInfo]
elif isinstance(node.node, (Var, SYMBOL_FUNCBASE_TYPES)):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the hack now is that this is a variable and the binder doesn't know whether it's true or false?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no hack, anymore, really. We're just explicitly enumerating the overlap between SymbolNode and FuncBase instead of using isinstance on FuncBase

super_info = node.node.info
else:
super_info = None

Expand Down
23 changes: 18 additions & 5 deletions mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,18 @@ def __str__(self) -> str:


class FuncBase(Node):
"""Abstract base class for function-like nodes"""
"""Abstract base class for function-like nodes.

N.B: Although this has SymbolNode subclasses (FuncDef,
OverloadedFuncDef), avoid calling isinstance(..., FuncBase) on
something that is typed as SymbolNode. This is to work around
mypy bug #3603, in which mypy doesn't understand multiple
inheritance very well, and will assume that a SymbolNode
cannot be a FuncBase.

Instead, test against SYMBOL_FUNCBASE_TYPES, which enumerates
SymbolNode subclasses that are also FuncBase subclasses.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds tedious. :-(

"""

__slots__ = ('type',
'unanalyzed_type',
Expand Down Expand Up @@ -668,6 +679,11 @@ def deserialize(cls, data: JsonDict) -> 'FuncDef':
return ret


# All types that are both SymbolNodes and FuncBases. See the FuncBase
# docstring for the rationale.
SYMBOL_FUNCBASE_TYPES = (OverloadedFuncDef, FuncDef)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we ensure this stays up to date? If a new class were to be added, would tests for that new class fail clearly?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They would fail, I think, though I'm not sure how cleanly.



class Decorator(SymbolNode, Statement):
"""A decorated function.

Expand Down Expand Up @@ -2857,10 +2873,7 @@ def fullname(self) -> Optional[str]:
@property
def type(self) -> 'Optional[mypy.types.Type]':
node = self.node
if (isinstance(node, Var) and node.type is not None):
return node.type
# mypy thinks this branch is unreachable but it is wrong (#3603)
elif (isinstance(node, FuncBase) and node.type is not None):
if isinstance(node, (Var, SYMBOL_FUNCBASE_TYPES)) and node.type is not None:
return node.type
elif isinstance(node, Decorator):
return node.var.type
Expand Down
7 changes: 3 additions & 4 deletions mypy/plugins/common.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from typing import List, Optional, Any

from mypy.nodes import (
ARG_POS, MDEF, Argument, Block, CallExpr, Expression, FuncBase,
ARG_POS, MDEF, Argument, Block, CallExpr, Expression, FuncBase, SYMBOL_FUNCBASE_TYPES,
FuncDef, PassStmt, RefExpr, SymbolTableNode, Var, StrExpr,
)
from mypy.plugin import ClassDefContext
Expand Down Expand Up @@ -51,9 +51,8 @@ def _get_argument(call: CallExpr, name: str) -> Optional[Expression]:
return None

callee_type = None
# mypyc hack to workaround mypy misunderstanding multiple inheritance (#3603)
callee_node = call.callee.node # type: Any
if (isinstance(callee_node, (Var, FuncBase))
callee_node = call.callee.node
if (isinstance(callee_node, (Var, SYMBOL_FUNCBASE_TYPES))
and callee_node.type):
callee_node_type = callee_node.type
if isinstance(callee_node_type, Overloaded):
Expand Down