Bug 1436329 - Parse WebIDL "partial dictionary" r=peterv
authorDiego Pino Garcia <dpino@igalia.com>
Thu, 29 Nov 2018 01:11:00 +0200
changeset 508860 7f32ccd251ef3ec15d0e916aeff6b9f7d70661af
parent 508808 e766954700c7e9999d199bfa522afa3617f03d21
child 508861 4fc3f968d0b627c5eb36a4f0f0ad5c821f3da608
push id1905
push userffxbld-merge
push dateMon, 21 Jan 2019 12:33:13 +0000
treeherdermozilla-release@c2fca1944d8c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspeterv
bugs1436329
milestone65.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 1436329 - Parse WebIDL "partial dictionary" r=peterv
dom/bindings/parser/WebIDL.py
dom/bindings/parser/tests/test_dictionary.py
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -604,16 +604,44 @@ class IDLExternalInterface(IDLObjectWith
 
     def isNavigatorProperty(self):
         return False
 
     def _getDependentObjects(self):
         return set()
 
 
+class IDLPartialDictionary(IDLObject):
+    def __init__(self, location, name, members, nonPartialDictionary):
+        assert isinstance(name, IDLUnresolvedIdentifier)
+
+        IDLObject.__init__(self, location)
+        self.identifier = name
+        self.members = members
+        self._nonPartialDictionary = nonPartialDictionary
+        self._finished = False
+        nonPartialDictionary.addPartialDictionary(self)
+
+    def addExtendedAttributes(self, attrs):
+        pass
+
+    def finish(self, scope):
+        if self._finished:
+            return
+        self._finished = True
+
+        # Need to make sure our non-partial dictionary gets
+        # finished so it can report cases when we only have partial
+        # dictionaries.
+        self._nonPartialDictionary.finish(scope)
+
+    def validate(self):
+        pass
+
+
 class IDLPartialInterfaceOrNamespace(IDLObject):
     def __init__(self, location, name, members, nonPartialInterfaceOrNamespace):
         assert isinstance(name, IDLUnresolvedIdentifier)
 
         IDLObject.__init__(self, location)
         self.identifier = name
         self.members = members
         # propagatedExtendedAttrs are the ones that should get
@@ -1837,16 +1865,17 @@ class IDLDictionary(IDLObjectWithScope):
     def __init__(self, location, parentScope, name, parent, members):
         assert isinstance(parentScope, IDLScope)
         assert isinstance(name, IDLUnresolvedIdentifier)
         assert not parent or isinstance(parent, IDLIdentifierPlaceholder)
 
         self.parent = parent
         self._finished = False
         self.members = list(members)
+        self._partialDictionaries = []
 
         IDLObjectWithScope.__init__(self, location, parentScope, name)
 
     def __str__(self):
         return "Dictionary '%s'" % self.identifier.name
 
     def isDictionary(self):
         return True
@@ -1873,16 +1902,21 @@ class IDLDictionary(IDLObjectWithScope):
                 raise WebIDLError("Dictionary %s has parent that is not a dictionary" %
                                   self.identifier.name,
                                   [oldParent.location, self.parent.location])
 
             # Make sure the parent resolves all its members before we start
             # looking at them.
             self.parent.finish(scope)
 
+        # Now go ahead and merge in our partial dictionaries.
+        for partial in self._partialDictionaries:
+            partial.finish(scope)
+            self.members.extend(partial.members)
+
         for member in self.members:
             member.resolve(self)
             if not member.isComplete():
                 member.complete(scope)
                 assert member.type.isComplete()
 
         # Members of a dictionary are sorted in lexicographic order
         self.members.sort(cmp=cmp, key=lambda x: x.identifier.name)
@@ -1977,16 +2011,19 @@ class IDLDictionary(IDLObjectWithScope):
                               [attrs[0].location, self.location])
 
     def _getDependentObjects(self):
         deps = set(self.members)
         if (self.parent):
             deps.add(self.parent)
         return deps
 
+    def addPartialDictionary(self, partial):
+        assert self.identifier.name == partial.identifier.name
+        self._partialDictionaries.append(partial)
 
 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,
@@ -5525,19 +5562,20 @@ class Parser(Tokenizer):
                                     | Interface
         """
         assert p[1]
         p[0] = p[1]
 
     def handleNonPartialObject(self, location, identifier, constructor,
                                constructorArgs, nonPartialArgs):
         """
-        This handles non-partial objects (interfaces and namespaces) by
-        checking for an existing partial object, and promoting it to
-        non-partial as needed.  The return value is the non-partial object.
+        This handles non-partial objects (interfaces, namespaces and
+        dictionaries) by checking for an existing partial object, and promoting
+        it to non-partial as needed.  The return value is the non-partial
+        object.
 
         constructorArgs are all the args for the constructor except the last
         one: isKnownNonPartial.
 
         nonPartialArgs are the args for the setNonPartial call.
         """
         # The name of the class starts with "IDL", so strip that off.
         # Also, starts with a capital letter after that, so nix that
@@ -5617,34 +5655,35 @@ class Parser(Tokenizer):
             Partial : PARTIAL PartialDefinition
         """
         p[0] = p[2]
 
     def p_PartialDefinition(self, p):
         """
             PartialDefinition : PartialInterface
                               | PartialNamespace
+                              | PartialDictionary
         """
         p[0] = p[1]
 
     def handlePartialObject(self, location, identifier, nonPartialConstructor,
                             nonPartialConstructorArgs,
                             partialConstructorArgs):
         """
-        This handles partial objects (interfaces and namespaces) by checking for
-        an existing non-partial object, and adding ourselves to it as needed.
-        The return value is our partial object.  For now we just use
-        IDLPartialInterfaceOrNamespace for partial objects.
+        This handles partial objects (interfaces, namespaces and dictionaries)
+        by checking for an existing non-partial object, and adding ourselves to
+        it as needed.  The return value is our partial object.  We use
+        IDLPartialInterfaceOrNamespace for partial interfaces or namespaces,
+        and IDLPartialDictionary for partial dictionaries.
 
         nonPartialConstructorArgs are all the args for the non-partial
         constructor except the last two: members and isKnownNonPartial.
 
-        partialConstructorArgs are the arguments for the
-        IDLPartialInterfaceOrNamespace constructor, except the last one (the
-        non-partial object).
+        partialConstructorArgs are the arguments for the partial object
+        constructor, except the last one (the non-partial object).
         """
         # The name of the class starts with "IDL", so strip that off.
         # Also, starts with a capital letter after that, so nix that
         # as well.
         prettyname = nonPartialConstructor.__name__[3:].lower()
 
         nonPartialObject = None
         try:
@@ -5659,19 +5698,29 @@ class Parser(Tokenizer):
             if isinstance(ex, WebIDLError):
                 raise ex
             pass
 
         if not nonPartialObject:
             nonPartialObject = nonPartialConstructor(
                 # No members, False for isKnownNonPartial
                 *(nonPartialConstructorArgs + [[], False]))
-        partialInterface = IDLPartialInterfaceOrNamespace(
-            *(partialConstructorArgs + [nonPartialObject]))
-        return partialInterface
+
+        partialObject = None
+        if isinstance(nonPartialObject, IDLDictionary):
+            partialObject = IDLPartialDictionary(
+                *(partialConstructorArgs + [nonPartialObject]))
+        elif isinstance(nonPartialObject, (IDLInterface, IDLNamespace)):
+            partialObject = IDLPartialInterfaceOrNamespace(
+                *(partialConstructorArgs + [nonPartialObject]))
+        else:
+            raise WebIDLError("Unknown partial object type %s" %
+                    type(partialObject))
+
+        return partialObject
 
     def p_PartialInterface(self, p):
         """
             PartialInterface : INTERFACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON
         """
         location = self.getLocation(p, 1)
         identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
         members = p[4]
@@ -5689,16 +5738,29 @@ class Parser(Tokenizer):
         identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
         members = p[4]
 
         p[0] = self.handlePartialObject(
             location, identifier, IDLNamespace,
             [location, self.globalScope(), identifier],
             [location, identifier, members])
 
+    def p_PartialDictionary(self, p):
+        """
+            PartialDictionary : DICTIONARY IDENTIFIER LBRACE DictionaryMembers RBRACE SEMICOLON
+        """
+        location = self.getLocation(p, 1)
+        identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2])
+        members = p[4]
+
+        p[0] = self.handlePartialObject(
+            location, identifier, IDLDictionary,
+            [location, self.globalScope(), identifier],
+            [location, identifier, members])
+
     def p_Inheritance(self, p):
         """
             Inheritance : COLON ScopedName
         """
         p[0] = IDLIdentifierPlaceholder(self.getLocation(p, 2), p[2])
 
     def p_InheritanceEmpty(self, p):
         """
--- a/dom/bindings/parser/tests/test_dictionary.py
+++ b/dom/bindings/parser/tests/test_dictionary.py
@@ -21,32 +21,75 @@ def WebIDLTest(parser, harness):
                   "'o' comes before 'p'")
     harness.check(dict1.members[1].identifier.name, "parent",
                   "'o' really comes before 'p'")
     harness.check(dict2.members[0].identifier.name, "aaandAnother",
                   "'a' comes before 'c'")
     harness.check(dict2.members[1].identifier.name, "child",
                   "'a' really comes before 'c'")
 
+    # Test partial dictionary.
+    parser = parser.reset();
+    parser.parse("""
+      dictionary A {
+        long c;
+        long g;
+      };
+      partial dictionary A {
+        long h;
+        long d;
+      };
+    """)
+    results = parser.finish()
+
+    dict1 = results[0];
+    harness.check(len(dict1.members), 4, "Dict1 has four members")
+    harness.check(dict1.members[0].identifier.name, "c",
+                  "c should be first")
+    harness.check(dict1.members[1].identifier.name, "d",
+                  "d should come after c")
+    harness.check(dict1.members[2].identifier.name, "g",
+                  "g should come after d")
+    harness.check(dict1.members[3].identifier.name, "h",
+                  "h should be last")
+
     # Now reset our parser
     parser = parser.reset()
     threw = False
     try:
         parser.parse("""
           dictionary Dict {
             long prop = 5;
             long prop;
           };
         """)
         results = parser.finish()
     except:
         threw = True
 
     harness.ok(threw, "Should not allow name duplication in a dictionary")
 
+    # Test no name duplication across normal and partial dictionary.
+    parser = parser.reset();
+    threw = False
+    try:
+        parser.parse("""
+          dictionary A {
+            long prop = 5;
+          };
+          partial dictionary A {
+            long prop;
+          };
+        """)
+        results = parser.finish()
+    except:
+        threw = True
+
+    harness.ok(threw, "Should not allow name duplication across normal and partial dictionary")
+
     # Now reset our parser again
     parser = parser.reset()
     threw = False
     try:
         parser.parse("""
           dictionary Dict1 : Dict2 {
             long prop = 5;
           };