fix MRO for multimethod.__call__ using iterative algorithm.
Add tests MRO for inherited multimethods with multiple inheritance Add tests for inherited and overridden multimethods
This commit is contained in:
parent
43d94d4a30
commit
2621af41d1
@ -25,6 +25,7 @@
|
|||||||
so package authors should use their judgement.
|
so package authors should use their judgement.
|
||||||
"""
|
"""
|
||||||
import functools
|
import functools
|
||||||
|
import inspect
|
||||||
|
|
||||||
from llnl.util.lang import caller_locals
|
from llnl.util.lang import caller_locals
|
||||||
|
|
||||||
@ -107,14 +108,26 @@ def __call__(self, package_self, *args, **kwargs):
|
|||||||
return self.default(package_self, *args, **kwargs)
|
return self.default(package_self, *args, **kwargs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
superclass = super(package_self.__class__, package_self)
|
# Unwrap MRO by hand because super binds to the subclass
|
||||||
superclass_fn = getattr(superclass, self.__name__, None)
|
# and causes infinite recursion for inherited methods
|
||||||
if callable(superclass_fn):
|
for cls in inspect.getmro(package_self.__class__)[1:]:
|
||||||
return superclass_fn(*args, **kwargs)
|
superself = cls.__dict__.get(self.__name__, None)
|
||||||
else:
|
if isinstance(superself, self.__class__):
|
||||||
|
# Parent class method is a multimethod
|
||||||
|
# check it locally for methods, conditional or default
|
||||||
|
# Do not recurse, that will mess up MRO
|
||||||
|
for spec, method in superself.method_list:
|
||||||
|
if package_self.spec.satisfies(spec):
|
||||||
|
return method(package_self, *args, **kwargs)
|
||||||
|
if superself.default:
|
||||||
|
return superself.default(package_self, *args, **kwargs)
|
||||||
|
elif superself:
|
||||||
|
return superself(package_self, *args, **kwargs)
|
||||||
|
|
||||||
raise NoSuchMethodError(
|
raise NoSuchMethodError(
|
||||||
type(package_self), self.__name__, spec,
|
type(package_self), self.__name__, package_self.spec,
|
||||||
[m[0] for m in self.method_list])
|
[m[0] for m in self.method_list]
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "SpecMultiMethod {\n\tdefault: %s,\n\tspecs: %s\n}" % (
|
return "SpecMultiMethod {\n\tdefault: %s,\n\tspecs: %s\n}" % (
|
||||||
|
@ -117,7 +117,44 @@ def test_virtual_dep_match(pkg_name):
|
|||||||
|
|
||||||
def test_multimethod_with_base_class(pkg_name):
|
def test_multimethod_with_base_class(pkg_name):
|
||||||
pkg = spack.repo.get(pkg_name + '@3')
|
pkg = spack.repo.get(pkg_name + '@3')
|
||||||
assert pkg.base_method() == "subclass_method"
|
assert pkg.base_method() == pkg.spec.name
|
||||||
|
|
||||||
pkg = spack.repo.get(pkg_name + '@1')
|
pkg = spack.repo.get(pkg_name + '@1')
|
||||||
assert pkg.base_method() == "base_method"
|
assert pkg.base_method() == "base_method"
|
||||||
|
|
||||||
|
|
||||||
|
def test_multimethod_inherited_and_overridden():
|
||||||
|
pkg = spack.repo.get('multimethod-inheritor@1.0')
|
||||||
|
assert pkg.inherited_and_overridden() == 'inheritor@1.0'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-inheritor@2.0')
|
||||||
|
assert pkg.inherited_and_overridden() == 'base@2.0'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod@1.0')
|
||||||
|
assert pkg.inherited_and_overridden() == 'base@1.0'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod@2.0')
|
||||||
|
assert pkg.inherited_and_overridden() == 'base@2.0'
|
||||||
|
|
||||||
|
|
||||||
|
def test_multimethod_diamond_inheritance():
|
||||||
|
pkg = spack.repo.get('multimethod-diamond@1.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'base_package'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-base@1.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'base_package'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-diamond@2.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'first_parent'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-inheritor@2.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'first_parent'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-diamond@3.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'second_parent'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-diamond-parent@3.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'second_parent'
|
||||||
|
|
||||||
|
pkg = spack.repo.get('multimethod-diamond@4.0')
|
||||||
|
assert pkg.diamond_inheritance() == 'subclass'
|
||||||
|
@ -19,3 +19,6 @@ class when subclass multi-methods do not match.
|
|||||||
|
|
||||||
def base_method(self):
|
def base_method(self):
|
||||||
return "base_method"
|
return "base_method"
|
||||||
|
|
||||||
|
def diamond_inheritance(self):
|
||||||
|
return "base_package"
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
from spack.pkg.builtin.mock.multimethod_base import MultimethodBase
|
||||||
|
|
||||||
|
|
||||||
|
class MultimethodDiamondParent(MultimethodBase):
|
||||||
|
"""This package is designed for use with Spack's multimethod test.
|
||||||
|
It has a bunch of test cases for the @when decorator that the
|
||||||
|
test uses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@when('@3.0')
|
||||||
|
def diamond_inheritance(self):
|
||||||
|
return "second_parent"
|
||||||
|
|
||||||
|
@when('@4.0, 2.0')
|
||||||
|
def diamond_inheritance(self):
|
||||||
|
return "should never be reached"
|
@ -0,0 +1,18 @@
|
|||||||
|
# Copyright 2013-2018 Lawrence Livermore National Security, LLC and other
|
||||||
|
# Spack Project Developers. See the top-level COPYRIGHT file for details.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: (Apache-2.0 OR MIT)
|
||||||
|
|
||||||
|
import spack.pkg.builtin.mock.multimethod_inheritor as mi
|
||||||
|
import spack.pkg.builtin.mock.multimethod_diamond_parent as mp
|
||||||
|
|
||||||
|
|
||||||
|
class MultimethodDiamond(mi.MultimethodInheritor, mp.MultimethodDiamondParent):
|
||||||
|
"""This package is designed for use with Spack's multimethod test.
|
||||||
|
It has a bunch of test cases for the @when decorator that the
|
||||||
|
test uses.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@when('@4.0')
|
||||||
|
def diamond_inheritance(self):
|
||||||
|
return 'subclass'
|
@ -11,3 +11,14 @@ class MultimethodInheritor(Multimethod):
|
|||||||
It has a bunch of test cases for the @when decorator that the
|
It has a bunch of test cases for the @when decorator that the
|
||||||
test uses.
|
test uses.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@when('@1.0')
|
||||||
|
def inherited_and_overridden(self):
|
||||||
|
return "inheritor@1.0"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test multi-level inheritance
|
||||||
|
#
|
||||||
|
@when('@2:')
|
||||||
|
def base_method(self):
|
||||||
|
return 'multimethod-inheritor'
|
||||||
|
@ -124,4 +124,28 @@ def different_by_virtual_dep(self):
|
|||||||
#
|
#
|
||||||
@when("@2:")
|
@when("@2:")
|
||||||
def base_method(self):
|
def base_method(self):
|
||||||
return "subclass_method"
|
return 'multimethod'
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure methods with non-default implementations in a superclass
|
||||||
|
# will invoke those methods when none in the subclass match but one in
|
||||||
|
# the superclass does.
|
||||||
|
#
|
||||||
|
@when("@1.0")
|
||||||
|
def inherited_and_overridden(self):
|
||||||
|
return "base@1.0"
|
||||||
|
|
||||||
|
@when("@2.0")
|
||||||
|
def inherited_and_overridden(self):
|
||||||
|
return "base@2.0"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Make sure that multimethods follow MRO properly with diamond inheritance
|
||||||
|
#
|
||||||
|
@when('@2.0')
|
||||||
|
def diamond_inheritance(self):
|
||||||
|
return 'first_parent'
|
||||||
|
|
||||||
|
@when('@4.0')
|
||||||
|
def diamond_inheritance(self):
|
||||||
|
return "should_not_be_reached"
|
||||||
|
Loading…
Reference in New Issue
Block a user