Bug 761772. Add support for 'implements' in WebIDL. r=khuey
authorBoris Zbarsky <bzbarsky@mit.edu>
Mon, 11 Jun 2012 18:21:35 -0400
changeset 96433 8c2a635f4a26f1e6266cbaa95ce10d85e1f5c284
parent 96432 da773d72a62073c108865123d003eede50a7d9ec
child 96434 f4ff27e63666ca4aabf37d10c6de0268d96af02b
push id22904
push useremorley@mozilla.com
push dateTue, 12 Jun 2012 09:45:09 +0000
treeherdermozilla-central@733994f12c53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskhuey
bugs761772
milestone16.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 761772. Add support for 'implements' in WebIDL. r=khuey
dom/bindings/Makefile.in
dom/bindings/parser/WebIDL.py
dom/bindings/parser/tests/test_implements.py
dom/bindings/test/Makefile.in
dom/bindings/test/TestBindingHeader.h
dom/bindings/test/TestCodeGen.webidl
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -78,16 +78,17 @@ include $(topsrcdir)/config/rules.mk
 
 # If you change bindinggen_dependencies here, change it in
 # dom/bindings/test/Makefile.in too.
 bindinggen_dependencies := \
   BindingGen.py \
   Bindings.conf \
   Configuration.py \
   Codegen.py \
+  parser/WebIDL.py \
   ParserResults.pkl \
   $(GLOBAL_DEPS) \
   $(NULL)
 
 $(webidl_files): %: $(webidl_base)/%
 	$(INSTALL) $(IFLAGS1) $(webidl_base)/$* .
 
 $(test_webidl_files): %: $(srcdir)/test/%
@@ -115,16 +116,17 @@ bindinggen_dependencies := \
 
 CACHE_DIR = _cache
 
 globalgen_dependencies := \
   GlobalGen.py \
   Bindings.conf \
   Configuration.py \
   Codegen.py \
+  parser/WebIDL.py \
   $(CACHE_DIR)/.done \
   $(GLOBAL_DEPS) \
   $(NULL)
 
 $(CACHE_DIR)/.done:
 	$(MKDIR) -p $(CACHE_DIR)
 	@$(TOUCH) $@
 
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -46,25 +46,29 @@ def M_add_class_attribs(attribs):
 def enum(*names):
     class Foo(object):
         __metaclass__ = M_add_class_attribs(names)
         def __setattr__(self, name, value):  # this makes it read-only
             raise NotImplementedError
     return Foo()
 
 class WebIDLError(Exception):
-    def __init__(self, message, location, warning=False):
+    def __init__(self, message, location, warning=False, extraLocation=""):
         self.message = message
         self.location = location
         self.warning = warning
+        self.extraLocation = extraLocation
 
     def __str__(self):
-        return "%s: %s%s%s" % (self.warning and 'warning' or 'error',
-                               self.message, ", " if self.location else "",
-                               self.location)
+        return "%s: %s%s%s%s%s" % (self.warning and 'warning' or 'error',
+                                   self.message,
+                                   ", " if self.location else "",
+                                   self.location,
+                                   "\n" if self.extraLocation else "",
+                                   self.extraLocation)
 
 class Location(object):
     def __init__(self, lexer, lineno, lexpos, filename):
         self._line = None
         self._lineno = lineno
         self._lexpos = lexpos
         self._lexdata = lexer.lexdata
         self._file = filename if filename else "<unknown>"
@@ -308,17 +312,17 @@ class IDLObjectWithIdentifier(IDLObject)
 
 class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope):
     def __init__(self, location, parentScope, identifier):
         assert isinstance(identifier, IDLUnresolvedIdentifier)
 
         IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
         IDLScope.__init__(self, location, parentScope, self.identifier)
 
-class IDLParentPlaceholder(IDLObjectWithIdentifier):
+class IDLInterfacePlaceholder(IDLObjectWithIdentifier):
     def __init__(self, location, identifier):
         assert isinstance(identifier, IDLUnresolvedIdentifier)
         IDLObjectWithIdentifier.__init__(self, location, None, identifier)
 
     def finish(self, scope):
         try:
             scope._lookupIdentifier(self.identifier)
         except:
@@ -349,22 +353,23 @@ class IDLExternalInterface(IDLObjectWith
 
     def resolve(self, parentScope):
         pass
 
 class IDLInterface(IDLObjectWithScope):
     def __init__(self, location, parentScope, name, parent, members):
         assert isinstance(parentScope, IDLScope)
         assert isinstance(name, IDLUnresolvedIdentifier)
-        assert not parent or isinstance(parent, IDLParentPlaceholder)
+        assert not parent or isinstance(parent, IDLInterfacePlaceholder)
 
         self.parent = parent
         self._callback = False
         self._finished = False
         self.members = list(members) # clone the list
+        self.implementedInterfaces = set()
 
         IDLObjectWithScope.__init__(self, location, parentScope, name)
 
     def __str__(self):
         return "Interface '%s'" % self.identifier.name
 
     def ctor(self):
         identifier = IDLUnresolvedIdentifier(self.location, "constructor",
@@ -393,51 +398,77 @@ class IDLInterface(IDLObjectWithScope):
         return retval
 
     def finish(self, scope):
         if self._finished:
             return
 
         self._finished = True
 
-        assert not self.parent or isinstance(self.parent, IDLParentPlaceholder)
+        assert not self.parent or isinstance(self.parent, IDLInterfacePlaceholder)
         parent = self.parent.finish(scope) if self.parent else None
         assert not parent or isinstance(parent, IDLInterface)
 
         self.parent = parent
 
         assert iter(self.members)
 
         if self.parent:
             self.parent.finish(scope)
-            assert iter(self.parent.members)
-
-            members = list(self.parent.members)
-            members.extend(self.members)
-        else:
-            members = list(self.members)
-
-        def memberNotOnParentChain(member, iface):
-            assert iface
-
-            if not iface.parent:
-                return True
-
-            assert isinstance(iface.parent, IDLInterface)
-            if member in iface.parent.members:
-                return False
-            return memberNotOnParentChain(member, iface.parent)
+
+            # Callbacks must not inherit from non-callbacks or inherit from
+            # anything that has consequential interfaces.
+            if self.isCallback():
+                assert(self.parent.isCallback())
+                assert(len(self.parent.getConsequentialInterfaces()) == 0)
+
+        for iface in self.implementedInterfaces:
+            iface.finish(scope)
+
+        # Now resolve() and finish() our members before importing the
+        # ones from our implemented interfaces.
+
+        # resolve() will modify self.members, so we need to iterate
+        # over a copy of the member list here.
+        for member in list(self.members):
+            member.resolve(self)
+
+        for member in self.members:
+            member.finish(scope)
+
+        ctor = self.ctor()
+        if ctor is not None:
+            ctor.finish(scope)
+
+        # Make a copy of our member list, so things tht implement us
+        # can get those without all the stuff we implement ourselves
+        # admixed.
+        self.originalMembers = list(self.members)
+
+        # Import everything from our consequential interfaces into
+        # self.members.  Sort our consequential interfaces by name
+        # just so we have a consistent order.
+        for iface in sorted(self.getConsequentialInterfaces(),
+                            cmp=cmp,
+                            key=lambda x: x.identifier.name):
+            additionalMembers = iface.originalMembers;
+            for additionalMember in additionalMembers:
+                for member in self.members:
+                    if additionalMember.identifier.name == member.identifier.name:
+                        raise WebIDLError(
+                            "Multiple definitions of %s on %s coming from 'implements' statements" %
+                            (member.identifier.name, self),
+                            additionalMember.location,
+                            extraLocation=member.location)
+            self.members.extend(additionalMembers)
 
         # Ensure that there's at most one of each {named,indexed}
         # {getter,setter,creator,deleter}.
         specialMembersSeen = set()
-        for member in members:
-            if memberNotOnParentChain(member, self):
-                member.resolve(self)
-
+        for member in self.members:
             if member.tag != IDLInterfaceMember.Tags.Method:
                 continue
 
             if member.isGetter():
                 memberType = "getters"
             elif member.isSetter():
                 memberType = "setters"
             elif member.isCreator():
@@ -455,23 +486,16 @@ class IDLInterface(IDLObjectWithScope):
                 continue
 
             if memberType in specialMembersSeen:
                 raise WebIDLError("Multiple " + memberType + " on %s" % (self),
                                    self.location)
 
             specialMembersSeen.add(memberType)
 
-        for member in self.members:
-            member.finish(scope)
-
-        ctor = self.ctor()
-        if ctor is not None:
-            ctor.finish(scope)
-
     def isInterface(self):
         return True
 
     def isExternal(self):
         return False
 
     def setCallback(self, value):
         self._callback = value
@@ -526,16 +550,49 @@ class IDLInterface(IDLObjectWithScope):
                 identifier = IDLUnresolvedIdentifier(self.location, "constructor",
                                                      allowForbidden=True)
 
                 method = IDLMethod(self.location, identifier, retType, args)
                 method.resolve(self)
 
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
 
+    def addImplementedInterface(self, implementedInterface):
+        assert(isinstance(implementedInterface, IDLInterface))
+        self.implementedInterfaces.add(implementedInterface)
+
+    def getInheritedInterfaces(self):
+        """
+        Returns a list of the interfaces this interface inherits from
+        (not including this interface itself).  The list is in order
+        from most derived to least derived.
+        """
+        assert(self._finished)
+        if not self.parent:
+            return []
+        parentInterfaces = self.parent.getInheritedInterfaces()
+        parentInterfaces.insert(0, self.parent)
+        return parentInterfaces
+
+    def getConsequentialInterfaces(self):
+        assert(self._finished)
+        # The interfaces we implement directly
+        consequentialInterfaces = set(self.implementedInterfaces)
+
+        # And their inherited interfaces
+        for iface in self.implementedInterfaces:
+            consequentialInterfaces |= set(iface.getInheritedInterfaces())
+
+        # And now collect up the consequential interfaces of all of those
+        temp = set()
+        for iface in consequentialInterfaces:
+            temp |= iface.getConsequentialInterfaces()
+
+        return consequentialInterfaces | temp
+
 class IDLEnum(IDLObjectWithIdentifier):
     def __init__(self, location, parentScope, name, values):
         assert isinstance(parentScope, IDLScope)
         assert isinstance(name, IDLUnresolvedIdentifier)
 
         if len(values) != len(set(values)):
             raise WebIDLError("Enum %s has multiple identical strings" % name.name, location)
 
@@ -1735,16 +1792,32 @@ class IDLMethod(IDLInterfaceMember, IDLS
                     continue
 
                 type = argument.type.complete(scope)
 
                 assert not isinstance(type, IDLUnresolvedType)
                 assert not isinstance(type.name, IDLUnresolvedIdentifier)
                 argument.type = type
 
+class IDLImplementsStatement(IDLObject):
+    def __init__(self, location, implementor, implementee):
+        IDLObject.__init__(self, location)
+        self.implementor = implementor;
+        self.implementee = implementee
+
+    def finish(self, scope):
+        assert(isinstance(self.implementor, IDLInterfacePlaceholder))
+        assert(isinstance(self.implementee, IDLInterfacePlaceholder))
+        implementor = self.implementor.finish(scope)
+        implementee = self.implementee.finish(scope)
+        implementor.addImplementedInterface(implementee)
+
+    def addExtendedAttributes(self, attrs):
+        assert len(attrs) == 0
+
 # Parser
 
 class Tokenizer(object):
     tokens = [
         "INTEGER",
         "FLOATLITERAL",
         "IDENTIFIER",
         "STRING",
@@ -1963,17 +2036,17 @@ class Parser(Tokenizer):
             PartialInterface : PARTIAL INTERFACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON
         """
         pass
 
     def p_Inheritance(self, p):
         """
             Inheritance : COLON ScopedName
         """
-        p[0] = IDLParentPlaceholder(self.getLocation(p, 2), p[2])
+        p[0] = IDLInterfacePlaceholder(self.getLocation(p, 2), p[2])
 
     def p_InheritanceEmpty(self, p):
         """
             Inheritance :
         """
         pass
 
     def p_InterfaceMembers(self, p):
@@ -2088,17 +2161,21 @@ class Parser(Tokenizer):
         typedef = IDLTypedefType(self.getLocation(p, 1), p[2], p[3])
         typedef.resolve(self.globalScope())
         p[0] = typedef
 
     def p_ImplementsStatement(self, p):
         """
             ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON
         """
-        pass
+        assert(p[2] == "implements")
+        implementor = IDLInterfacePlaceholder(self.getLocation(p, 1), p[1])
+        implementee = IDLInterfacePlaceholder(self.getLocation(p, 3), p[3])
+        p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor,
+                                      implementee)
 
     def p_Const(self, p):
         """
             Const : CONST ConstType IDENTIFIER EQUALS ConstValue SEMICOLON
         """
         location = self.getLocation(p, 1)
         type = p[2]
         identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3])
@@ -2945,17 +3022,26 @@ class Parser(Tokenizer):
         #for tok in iter(self.lexer.token, None):
         #    print tok
 
         self._filename = filename
         self._productions.extend(self.parser.parse(lexer=self.lexer,tracking=True))
         self._filename = None
 
     def finish(self):
-        for production in self._productions:
+        # First, finish all the IDLImplementsStatements.  In particular, we
+        # have to make sure we do those before we do the IDLInterfaces.
+        # XXX khuey hates this bit and wants to nuke it from orbit.
+        implementsStatements = [ p for p in self._productions if
+                                 isinstance(p, IDLImplementsStatement)]
+        otherStatements = [ p for p in self._productions if
+                            not isinstance(p, IDLImplementsStatement)]
+        for production in implementsStatements:
+            production.finish(self.globalScope())
+        for production in otherStatements:
             production.finish(self.globalScope())
 
         # De-duplicate self._productions, without modifying its order.
         seen = set()
         result = []
         for p in self._productions:
             if p not in seen:
                 seen.add(p)
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_implements.py
@@ -0,0 +1,143 @@
+# Import the WebIDL module, so we can do isinstance checks and whatnot
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    # Basic functionality
+    threw = False
+    try:
+        parser.parse("""
+            A implements B;
+            interface B {
+              attribute long x;
+            };
+            interface A {
+              attribute long y;
+            };
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+
+    harness.ok(not threw, "Should not have thrown on implements statement "
+               "before interfaces")
+    harness.check(len(results), 3, "We have three statements")
+    harness.ok(isinstance(results[1], WebIDL.IDLInterface), "B is an interface")
+    harness.check(len(results[1].members), 1, "B has one member")
+    A = results[2]
+    harness.ok(isinstance(A, WebIDL.IDLInterface), "A is an interface")
+    harness.check(len(A.members), 2, "A has two members")
+    harness.check(A.members[0].identifier.name, "y", "First member is 'y'")
+    harness.check(A.members[1].identifier.name, "x", "Second member is 'x'")
+
+    # Duplicated member names not allowed
+    threw = False
+    try:
+        parser.parse("""
+            C implements D;
+            interface D {
+              attribute long x;
+            };
+            interface C {
+              attribute long x;
+            };
+        """)
+        parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should have thrown on implemented interface duplicating "
+               "a name on base interface")
+
+    # Same, but duplicated across implemented interfaces
+    threw = False
+    try:
+        parser.parse("""
+            E implements F;
+            E implements G;
+            interface F {
+              attribute long x;
+            };
+            interface G {
+              attribute long x;
+            };
+            interface E {};
+        """)
+        parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should have thrown on implemented interfaces "
+               "duplicating each other's member names")
+
+    # Same, but duplicated across indirectly implemented interfaces
+    threw = False
+    try:
+        parser.parse("""
+            H implements I;
+            H implements J;
+            I implements K;
+            interface K {
+              attribute long x;
+            };
+            interface L {
+              attribute long x;
+            };
+            interface I {};
+            interface J : L {};
+            interface H {};
+        """)
+        parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should have thrown on indirectly implemented interfaces "
+               "duplicating each other's member names")
+
+    # Same, but duplicated across an implemented interface and its parent
+    threw = False
+    try:
+        parser.parse("""
+            M implements N;
+            interface O {
+              attribute long x;
+            };
+            interface N : O {
+              attribute long x;
+            };
+            interface M {};
+        """)
+        parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should have thrown on implemented interface and its "
+               "ancestor duplicating member names")
+
+    # Reset the parser so we can actually find things where we expect
+    # them in the list
+    parser = WebIDL.Parser()
+
+    # Diamonds should be allowed
+    threw = False
+    try:
+        parser.parse("""
+            P implements Q;
+            P implements R;
+            Q implements S;
+            R implements S;
+            interface Q {};
+            interface R {};
+            interface S {
+              attribute long x;
+            };
+            interface P {};
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+
+    harness.ok(not threw, "Diamond inheritance is fine")
+    harness.check(results[6].identifier.name, "S", "We should be looking at 'S'")
+    harness.check(len(results[6].members), 1, "S should have one member")
+    harness.check(results[6].members[0].identifier.name, "x",
+                  "S's member should be 'x'")
--- a/dom/bindings/test/Makefile.in
+++ b/dom/bindings/test/Makefile.in
@@ -38,16 +38,17 @@ include $(topsrcdir)/config/rules.mk
 # If you change bindinggen_dependencies here, change it in
 # dom/bindings/Makefile.in too.  But note that we include ../Makefile
 # here manually, since $(GLOBAL_DEPS) won't cover it.
 bindinggen_dependencies := \
   ../BindingGen.py \
   ../Bindings.conf \
   ../Configuration.py \
   ../Codegen.py \
+  ../parser/WebIDL.py \
   ../ParserResults.pkl \
   ../Makefile \
   $(GLOBAL_DEPS) \
   $(NULL)
 
 $(CPPSRCS): ../%Binding.cpp: $(bindinggen_dependencies) \
                              ../%.webidl \
                              $(NULL)
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -289,16 +289,28 @@ public:
 
   // binaryNames tests
   void MethodRenamedTo(ErrorResult&);
   void MethodRenamedTo(int8_t, ErrorResult&);
   int8_t GetAttributeGetterRenamedTo(ErrorResult&);
   int8_t GetAttributeRenamedTo(ErrorResult&);
   void SetAttributeRenamedTo(int8_t, ErrorResult&);
 
+  // Methods and properties imported via "implements"
+  bool GetImplementedProperty(ErrorResult&);
+  void SetImplementedProperty(bool, ErrorResult&);
+  void ImplementedMethod(ErrorResult&);
+  bool GetImplementedParentProperty(ErrorResult&);
+  void SetImplementedParentProperty(bool, ErrorResult&);
+  void ImplementedParentMethod(ErrorResult&);
+  bool GetIndirectlyImplementedProperty(ErrorResult&);
+  void SetIndirectlyImplementedProperty(bool, ErrorResult&);
+  void IndirectlyImplementedMethod(ErrorResult&);
+  uint32_t GetDiamondImplementedProperty(ErrorResult&);
+
 private:
   // We add signatures here that _could_ start matching if the codegen
   // got data types wrong.  That way if it ever does we'll have a call
   // to these private deleted methods and compilation will fail.
   void SetReadonlyByte(int8_t, ErrorResult&) MOZ_DELETE;
   template<typename T>
   void SetWritableByte(T, ErrorResult&) MOZ_DELETE;
   template<typename T>
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -11,16 +11,18 @@ interface TestNonCastableInterface {
 
 enum TestEnum {
   "a",
   "b"
 };
 
 callback TestCallback = void();
 
+TestInterface implements ImplementedInterface;
+
 [Constructor,
  Constructor(DOMString str),
  Constructor(unsigned long num, boolean? bool),
  Constructor(TestInterface? iface),
  Constructor(TestNonCastableInterface iface)]
 interface TestInterface {
   // Integer types
   // XXXbz add tests for infallible versions of all the integer stuff
@@ -218,8 +220,49 @@ interface TestInterface {
   object? receiveNullableObject();
 
   // binaryNames tests
   void methodRenamedFrom();
   void methodRenamedFrom(byte argument);
   readonly attribute byte attributeGetterRenamedFrom;
   attribute byte attributeRenamedFrom;
 };
+
+interface ImplementedInterfaceParent {
+  void implementedParentMethod();
+  attribute boolean implementedParentProperty;
+
+  const long implementedParentConstant = 8;
+};
+
+ImplementedInterfaceParent implements IndirectlyImplementedInterface;
+
+interface IndirectlyImplementedInterface {
+  void indirectlyImplementedMethod();
+  attribute boolean indirectlyImplementedProperty;
+
+  const long indirectlyImplementedConstant = 9;
+};
+
+interface ImplementedInterface : ImplementedInterfaceParent {
+  void implementedMethod();
+  attribute boolean implementedProperty;
+
+  const long implementedConstant = 5;
+};
+
+interface DiamondImplements {
+  readonly attribute long diamondImplementedProperty;
+};
+interface DiamondBranch1A {
+};
+interface DiamondBranch1B {
+};
+interface DiamondBranch2A : DiamondImplements {
+};
+interface DiamondBranch2B : DiamondImplements {
+};
+TestInterface implements DiamondBranch1A;
+TestInterface implements DiamondBranch1B;
+TestInterface implements DiamondBranch2A;
+TestInterface implements DiamondBranch2B;
+DiamondBranch1A implements DiamondImplements;
+DiamondBranch1B implements DiamondImplements;