summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/zope/interface/declarations.py35
-rw-r--r--src/zope/interface/tests/test_declarations.py50
2 files changed, 75 insertions, 10 deletions
diff --git a/src/zope/interface/declarations.py b/src/zope/interface/declarations.py
index 935b026..de90074 100644
--- a/src/zope/interface/declarations.py
+++ b/src/zope/interface/declarations.py
@@ -115,20 +115,35 @@ class Declaration(Specification):
])
def __add__(self, other):
- """Add two specifications or a specification and an interface
"""
- seen = {}
- result = []
- for i in self.interfaces():
- seen[i] = 1
- result.append(i)
+ Add two specifications or a specification and an interface
+ and produce a new declaration.
+
+ .. versionchanged:: 5.4.0
+ Now tries to preserve a consistent resolution order. Interfaces
+ being added to this object are added to the front of the resulting resolution
+ order if they already extend an interface in this object. Previously,
+ they were always added to the end of the order, which easily resulted in
+ invalid orders.
+ """
+ before = []
+ result = list(self.interfaces())
+ seen = set(result)
for i in other.interfaces():
- if i not in seen:
- seen[i] = 1
+ if i in seen:
+ continue
+ seen.add(i)
+ if any(i.extends(x) for x in result):
+ # It already extends us, e.g., is a subclass,
+ # so it needs to go at the front of the RO.
+ before.append(i)
+ else:
result.append(i)
+ return Declaration(*(before + result))
- return Declaration(*result)
-
+ # XXX: Is __radd__ needed? No tests break if it's removed.
+ # If it is needed, does it need to handle the C3 ordering differently?
+ # I (JAM) don't *think* it does.
__radd__ = __add__
@staticmethod
diff --git a/src/zope/interface/tests/test_declarations.py b/src/zope/interface/tests/test_declarations.py
index 8efe2d8..04a0dca 100644
--- a/src/zope/interface/tests/test_declarations.py
+++ b/src/zope/interface/tests/test_declarations.py
@@ -289,6 +289,56 @@ class DeclarationTests(EmptyDeclarationTests):
after = before + other
self.assertEqual(list(after), [IFoo, IBar, IBaz])
+ def test___add___overlapping_interface(self):
+ # The derived interfaces end up with higher priority, and
+ # don't produce a C3 resolution order violation. This
+ # example produced a C3 error, and the resulting legacy order
+ # used to be wrong ([IBase, IDerived] instead of
+ # the other way).
+ from zope.interface import Interface
+ from zope.interface.interface import InterfaceClass
+ from zope.interface.tests.test_ro import C3Setting
+ from zope.interface import ro
+
+ IBase = InterfaceClass('IBase')
+ IDerived = InterfaceClass('IDerived', (IBase,))
+
+ with C3Setting(ro.C3.STRICT_IRO, True):
+ base = self._makeOne(IBase)
+ after = base + IDerived
+
+ self.assertEqual(after.__iro__, (IDerived, IBase, Interface))
+ self.assertEqual(after.__bases__, (IDerived, IBase))
+ self.assertEqual(list(after), [IDerived, IBase])
+
+ def test___add___overlapping_interface_implementedBy(self):
+ # Like test___add___overlapping_interface, but pulling
+ # in a realistic example. This one previously produced a
+ # C3 error, but the resulting legacy order was (somehow)
+ # correct.
+ from zope.interface import Interface
+ from zope.interface import implementedBy
+ from zope.interface import implementer
+ from zope.interface.tests.test_ro import C3Setting
+ from zope.interface import ro
+
+ class IBase(Interface):
+ pass
+
+ class IDerived(IBase):
+ pass
+
+ @implementer(IBase)
+ class Base(object):
+ pass
+
+ with C3Setting(ro.C3.STRICT_IRO, True):
+ after = implementedBy(Base) + IDerived
+
+ self.assertEqual(after.__sro__, (after, IDerived, IBase, Interface))
+ self.assertEqual(after.__bases__, (IDerived, IBase))
+ self.assertEqual(list(after), [IDerived, IBase])
+
class TestImmutableDeclaration(EmptyDeclarationTests):