Bug 1359269 - Part 1: Add parser support for attributes on types in WebIDL; r=bzbarsky
☠☠ backed out by 0c70617150d9 ☠ ☠
authorManish Goregaokar <manishearth@gmail.com>
Sat, 02 Mar 2019 01:23:07 +0000
changeset 519941 bb694b612b1ba466b71f22aa1793f7d3149ec945
parent 519940 0b61149893f94fcc65fe6535c55f6ef6240741db
child 519942 c3f37539cb314566aef560602fecf68fe2de1602
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs1359269
milestone67.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 1359269 - Part 1: Add parser support for attributes on types in WebIDL; r=bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D19733
dom/bindings/parser/WebIDL.py
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -2089,19 +2089,25 @@ class IDLType(IDLObject):
         'record',
         'promise',
         )
 
     def __init__(self, location, name):
         IDLObject.__init__(self, location)
         self.name = name
         self.builtin = False
+        self.clamp = False
+        self.treatNullAsEmpty = False
+        self.enforceRange = False
+        self._extendedAttrDict = {}
 
     def __eq__(self, other):
-        return other and self.builtin == other.builtin and self.name == other.name
+        return (other and self.builtin == other.builtin and self.name == other.name and
+                          self.clamp == other.clamp and self.enforceRange == other.enforceRange and
+                          self.treatNullAsEmpty == other.treatNullAsEmpty)
 
     def __ne__(self, other):
         return not self == other
 
     def __str__(self):
         return str(self.name)
 
     def isType(self):
@@ -2217,22 +2223,24 @@ class IDLType(IDLObject):
     def treatNonCallableAsNull(self):
         assert self.tag() == IDLType.Tags.callback
         return self.nullable() and self.inner.callback._treatNonCallableAsNull
 
     def treatNonObjectAsNull(self):
         assert self.tag() == IDLType.Tags.callback
         return self.nullable() and self.inner.callback._treatNonObjectAsNull
 
-    def addExtendedAttributes(self, attrs):
-        if len(attrs) != 0:
-            raise WebIDLError("There are no extended attributes that are "
-                              "allowed on types, for now (but this is "
-                              "changing; see bug 1359269)",
+    def withExtendedAttributes(self, attrs):
+        if len(attrs) > 0:
+            raise WebIDLError("Extended attributes on types only supported for builtins",
                               [attrs[0].location, self.location])
+        return self
+
+    def getExtendedAttribute(self, name):
+        return self._extendedAttrDict.get(name, None)
 
     def resolveType(self, parentScope):
         pass
 
     def unroll(self):
         return self
 
     def isDistinguishableFrom(self, other):
@@ -3100,20 +3108,69 @@ class IDLBuiltinType(IDLType):
         Types.Uint16Array: IDLType.Tags.interface,
         Types.Int32Array: IDLType.Tags.interface,
         Types.Uint32Array: IDLType.Tags.interface,
         Types.Float32Array: IDLType.Tags.interface,
         Types.Float64Array: IDLType.Tags.interface,
         Types.ReadableStream: IDLType.Tags.interface,
     }
 
-    def __init__(self, location, name, type):
+    def __init__(self, location, name, type, clamp=False, enforceRange=False, treatNullAsEmpty=False,
+                 attrLocation=[]):
+        """
+        The mutually exclusive clamp/enforceRange/treatNullAsEmpty arguments are used to create instances
+        of this type with the appropriate attributes attached. Use .clamped(), .rangeEnforced(), and .treatNullAs().
+
+        attrLocation is an array of source locations of these attributes for error reporting.
+        """
         IDLType.__init__(self, location, name)
         self.builtin = True
         self._typeTag = type
+        self._clamped = None
+        self._rangeEnforced = None
+        self._withTreatNullAs = None
+        if self.isNumeric():
+            if clamp:
+                self.clamp = True
+                self.name = "Clamped" + self.name
+                self._extendedAttrDict["Clamp"] = True
+            elif enforceRange:
+                self.enforceRange = True
+                self.name = "RangeEnforced" + self.name
+                self._extendedAttrDict["EnforceRange"] = True
+        elif clamp or enforceRange:
+            raise WebIDLError("Non-numeric types cannot be [Clamp] or [EnforceRange]", attrLocation)
+        if self.isDOMString():
+            if treatNullAsEmpty:
+                self.treatNullAsEmpty = True
+                self.name = "NullIsEmpty" + self.name
+                self._extendedAttrDict["TreatNullAs"] = ["EmptyString"]
+        elif treatNullAsEmpty:
+            raise WebIDLError("Non-string types cannot be [TreatNullAs]", attrLocation)
+
+    def clamped(self, attrLocation):
+        if not self._clamped:
+            self._clamped = IDLBuiltinType(self.location, self.name,
+                                           self._typeTag, clamp=True,
+                                           attrLocation=attrLocation)
+        return self._clamped
+
+    def rangeEnforced(self, attrLocation):
+        if not self._rangeEnforced:
+            self._rangeEnforced = IDLBuiltinType(self.location, self.name,
+                                           self._typeTag, enforceRange=True,
+                                           attrLocation=attrLocation)
+        return self._rangeEnforced
+
+    def withTreatNullAs(self, attrLocation):
+        if not self._withTreatNullAs:
+            self._withTreatNullAs = IDLBuiltinType(self.location, self.name,
+                                           self._typeTag, treatNullAsEmpty=True,
+                                           attrLocation=attrLocation)
+        return self._withTreatNullAs
 
     def isPrimitive(self):
         return self._typeTag <= IDLBuiltinType.Types.double
 
     def isBoolean(self):
         return self._typeTag == IDLBuiltinType.Types.boolean
 
     def isNumeric(self):
@@ -3239,16 +3296,55 @@ class IDLBuiltinType(IDLType):
                  # except ArrayBufferView and the same type of typed
                  # array
                  (self.isTypedArray() and not other.isArrayBufferView() and not
                   (other.isTypedArray() and other.name == self.name)))))
 
     def _getDependentObjects(self):
         return set()
 
+    def withExtendedAttributes(self, attrs):
+        ret = self
+        for attribute in attrs:
+            identifier = attribute.identifier()
+            if identifier == "Clamp":
+                if not attribute.noArguments():
+                    raise WebIDLError("[Clamp] must take no arguments",
+                                      [attribute.location])
+                if ret.enforceRange or self.enforceRange:
+                    raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
+                                      [self.location, attribute.location])
+                ret = self.clamped([self.location, attribute.location])
+            elif identifier == "EnforceRange":
+                if not attribute.noArguments():
+                    raise WebIDLError("[EnforceRange] must take no arguments",
+                                      [attribute.location])
+                if ret.clamp or self.clamp:
+                    raise WebIDLError("[EnforceRange] and [Clamp] are mutually exclusive",
+                                      [self.location, attribute.location])
+                ret = self.rangeEnforced([self.location, attribute.location])
+            elif identifier == "TreatNullAs":
+                if not self.isDOMString():
+                    raise WebIDLError("[TreatNullAs] only allowed on DOMStrings",
+                                      [self.location, attribute.location])
+                assert not self.nullable()
+                if not attribute.hasValue():
+                    raise WebIDLError("[TreatNullAs] must take an identifier argument"
+                                      [attribute.location])
+                value = attribute.value()
+                if value != 'EmptyString':
+                    raise WebIDLError("[TreatNullAs] must take the identifier "
+                                      "'EmptyString', not '%s'" % value,
+                                      [attribute.location])
+                ret = self.withTreatNullAs([self.location, attribute.location])
+            else:
+                raise WebIDLError("Unhandled extended attribute on type",
+                                  [self.location, attribute.location])
+        return ret
+
 BuiltinTypes = {
     IDLBuiltinType.Types.byte:
         IDLBuiltinType(BuiltinLocation("<builtin type>"), "Byte",
                        IDLBuiltinType.Types.byte),
     IDLBuiltinType.Types.octet:
         IDLBuiltinType(BuiltinLocation("<builtin type>"), "Octet",
                        IDLBuiltinType.Types.octet),
     IDLBuiltinType.Types.short:
@@ -5810,17 +5906,16 @@ class Parser(Tokenizer):
         """
             DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers
                              |
         """
         if len(p) == 1:
             # We're at the end of the list
             p[0] = []
             return
-        # Add our extended attributes
         p[2].addExtendedAttributes(p[1])
         p[0] = [p[2]]
         p[0].extend(p[3])
 
     def p_DictionaryMember(self, p):
         """
             DictionaryMember : Required Type IDENTIFIER Default SEMICOLON
         """
@@ -5922,17 +6017,17 @@ class Parser(Tokenizer):
         """
             ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers
                              |
         """
         pass
 
     def p_Typedef(self, p):
         """
-            Typedef : TYPEDEF Type IDENTIFIER SEMICOLON
+            Typedef : TYPEDEF TypeWithExtendedAttributes IDENTIFIER SEMICOLON
         """
         typedef = IDLTypedef(self.getLocation(p, 1), self.globalScope(),
                              p[2], p[3])
         p[0] = typedef
 
     def p_ImplementsStatement(self, p):
         """
             ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON
@@ -6015,48 +6110,48 @@ class Parser(Tokenizer):
                                                              | Setlike
                                                              | Iterable
                                                              | Operation
         """
         p[0] = p[1]
 
     def p_Iterable(self, p):
         """
-            Iterable : ITERABLE LT Type GT SEMICOLON
-                     | ITERABLE LT Type COMMA Type GT SEMICOLON
+            Iterable : ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON
+                     | ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON
         """
         location = self.getLocation(p, 2)
         identifier = IDLUnresolvedIdentifier(location, "__iterable",
                                              allowDoubleUnderscore=True)
         if (len(p) > 6):
             keyType = p[3]
             valueType = p[5]
         else:
             keyType = None
             valueType = p[3]
 
         p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope())
 
     def p_Setlike(self, p):
         """
-            Setlike : ReadOnly SETLIKE LT Type GT SEMICOLON
+            Setlike : ReadOnly SETLIKE LT TypeWithExtendedAttributes GT SEMICOLON
         """
         readonly = p[1]
         maplikeOrSetlikeType = p[2]
         location = self.getLocation(p, 2)
         identifier = IDLUnresolvedIdentifier(location, "__setlike",
                                              allowDoubleUnderscore=True)
         keyType = p[4]
         valueType = keyType
         p[0] = IDLMaplikeOrSetlike(location, identifier, maplikeOrSetlikeType,
                                    readonly, keyType, valueType)
 
     def p_Maplike(self, p):
         """
-            Maplike : ReadOnly MAPLIKE LT Type COMMA Type GT SEMICOLON
+            Maplike : ReadOnly MAPLIKE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON
         """
         readonly = p[1]
         maplikeOrSetlikeType = p[2]
         location = self.getLocation(p, 2)
         identifier = IDLUnresolvedIdentifier(location, "__maplike",
                                              allowDoubleUnderscore=True)
         keyType = p[4]
         valueType = p[6]
@@ -6084,17 +6179,17 @@ class Parser(Tokenizer):
         """
             Attribute : AttributeRest
         """
         (location, identifier, type, readonly) = p[1]
         p[0] = IDLAttribute(location, identifier, type, readonly, inherit=False)
 
     def p_AttributeRest(self, p):
         """
-            AttributeRest : ReadOnly ATTRIBUTE Type AttributeName SEMICOLON
+            AttributeRest : ReadOnly ATTRIBUTE TypeWithExtendedAttributes AttributeName SEMICOLON
         """
         location = self.getLocation(p, 2)
         readonly = p[1]
         t = p[3]
         identifier = IDLUnresolvedIdentifier(self.getLocation(p, 4), p[4])
         p[0] = (location, identifier, t, readonly)
 
     def p_ReadOnly(self, p):
@@ -6566,16 +6661,22 @@ class Parser(Tokenizer):
         p[0] = p[1]
 
     def p_TypeUnionType(self, p):
         """
             Type : UnionType Null
         """
         p[0] = self.handleNullable(p[1], p[2])
 
+    def p_TypeWithExtendedAttributes(self, p):
+        """
+            TypeWithExtendedAttributes : ExtendedAttributeList Type
+        """
+        p[0] = p[2].withExtendedAttributes(p[1])
+
     def p_SingleTypeNonAnyType(self, p):
         """
             SingleType : NonAnyType
         """
         p[0] = p[1]
 
     def p_SingleTypeAnyType(self, p):
         """
@@ -6588,19 +6689,19 @@ class Parser(Tokenizer):
             UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN
         """
         types = [p[2], p[4]]
         types.extend(p[5])
         p[0] = IDLUnionType(self.getLocation(p, 1), types)
 
     def p_UnionMemberTypeNonAnyType(self, p):
         """
-            UnionMemberType : NonAnyType
-        """
-        p[0] = p[1]
+            UnionMemberType : ExtendedAttributeList NonAnyType
+        """
+        p[0] = p[2].withExtendedAttributes(p[1])
 
     def p_UnionMemberType(self, p):
         """
             UnionMemberType : UnionType Null
         """
         p[0] = self.handleNullable(p[1], p[2])
 
     def p_UnionMemberTypes(self, p):
@@ -6640,33 +6741,33 @@ class Parser(Tokenizer):
     def p_NonAnyTypeStringType(self, p):
         """
             NonAnyType : StringType Null
         """
         p[0] = self.handleNullable(p[1], p[2])
 
     def p_NonAnyTypeSequenceType(self, p):
         """
-            NonAnyType : SEQUENCE LT Type GT Null
+            NonAnyType : SEQUENCE LT TypeWithExtendedAttributes GT Null
         """
         innerType = p[3]
         type = IDLSequenceType(self.getLocation(p, 1), innerType)
         p[0] = self.handleNullable(type, p[5])
 
     # Note: Promise<void> is allowed, so we want to parametrize on ReturnType,
     # not Type.  Promise types can't be null, hence no "Null" in there.
     def p_NonAnyTypePromiseType(self, p):
         """
             NonAnyType : PROMISE LT ReturnType GT
         """
         p[0] = IDLPromiseType(self.getLocation(p, 1), p[3])
 
     def p_NonAnyTypeRecordType(self, p):
         """
-            NonAnyType : RECORD LT StringType COMMA Type GT Null
+            NonAnyType : RECORD LT StringType COMMA TypeWithExtendedAttributes GT Null
         """
         keyType = p[3]
         valueType = p[5]
         type = IDLRecordType(self.getLocation(p, 1), keyType, valueType)
         p[0] = self.handleNullable(type, p[7])
 
     def p_NonAnyTypeScopedName(self, p):
         """