Bug 1498059 - Add CEnum types to XPIDL; r=nika,froydnj
authorKyle Machulis <kyle@nonpolynomial.com>
Tue, 06 Nov 2018 00:05:37 +0000
changeset 444501 40c1a7f2500861af2acbb24707e3838a0b779dc7
parent 444500 edfa81173d9d596888b23e136d6357cd7d4e0097
child 444502 58b76f270c221b2433e1b6e543822c9f2640a76e
push id34996
push userrgurzau@mozilla.com
push dateTue, 06 Nov 2018 09:53:23 +0000
treeherdermozilla-central@e160f0a60e4f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika, froydnj
bugs1498059
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 1498059 - Add CEnum types to XPIDL; r=nika,froydnj Add CEnum types to XPIDL, allowing for typed enums in C++ instead of using uintXX_t types. Javascript will still reflect CEnums as interface level consts. Depends on D8593 Differential Revision: https://phabricator.services.mozilla.com/D8594
xpcom/idl-parser/xpidl/header.py
xpcom/idl-parser/xpidl/jsonxpt.py
xpcom/idl-parser/xpidl/xpidl.py
--- a/xpcom/idl-parser/xpidl/header.py
+++ b/xpcom/idl-parser/xpidl/header.py
@@ -385,16 +385,22 @@ def write_interface(iface, fd):
             value = c.getValue()
             enums.append("    %(name)s = %(value)s%(signed)s" % {
                          'name': c.name,
                          'value': value,
                          'signed': (not basetype.signed) and 'U' or ''})
         fd.write(",\n".join(enums))
         fd.write("\n  };\n\n")
 
+    def write_cenum_decl(b):
+        fd.write("  enum %s : uint%d_t {\n" % (b.basename, b.width))
+        for var in b.variants:
+            fd.write("    %s = %s,\n" % (var.name, var.value))
+        fd.write("  };\n\n")
+
     def write_method_decl(m):
         printComments(fd, m.doccomments, '  ')
 
         fd.write("  /* %s */\n" % m.toIDL())
         fd.write("  %s%s = 0;\n\n" % (runScriptAnnotation(m),
                                       methodAsNative(m)))
 
     def write_attr_decl(a):
@@ -466,16 +472,18 @@ def write_interface(iface, fd):
         else:
             for member in group:
                 if key == xpidl.Attribute:
                     write_attr_decl(member)
                 elif key == xpidl.Method:
                     write_method_decl(member)
                 elif key == xpidl.CDATA:
                     fd.write(" %s" % member.data)
+                elif key == xpidl.CEnum:
+                    write_cenum_decl(member)
                 else:
                     raise Exception("Unexpected interface member: %s" % member)
 
     fd.write(iface_epilog % names)
 
     def writeDeclaration(fd, iface, virtual):
         declType = "NS_IMETHOD" if virtual else "nsresult"
         suffix = " override" if virtual else ""
--- a/xpcom/idl-parser/xpidl/jsonxpt.py
+++ b/xpcom/idl-parser/xpidl/jsonxpt.py
@@ -100,16 +100,20 @@ def get_type(type, calltype, iid_is=None
         elif iid_is is not None:
             return {
                 'tag': 'TD_INTERFACE_IS_TYPE',
                 'iid_is': iid_is,
             }
         else:
             return {'tag': 'TD_VOID'}
 
+    if isinstance(type, xpidl.CEnum):
+        # As far as XPConnect is concerned, cenums are just unsigned integers.
+        return {'tag': 'TD_UINT%d' % type.width}
+
     raise Exception("Unknown type!")
 
 
 def mk_param(type, in_=0, out=0, optional=0):
     return {
         'type': type,
         'flags': flags(
             ('in', in_),
@@ -161,16 +165,24 @@ def build_interface(iface):
 
     def build_const(c):
         consts.append({
             'name': c.name,
             'type': get_type(c.basetype, ''),
             'value': c.getValue(),  # All of our consts are numbers
         })
 
+    def build_cenum(b):
+        for var in b.variants:
+            consts.append({
+                'name': var.name,
+                'type': get_type(b, 'in'),
+                'value': var.value,
+            })
+
     def build_method(m):
         params = []
         for p in m.params:
             params.append(mk_param(
                 get_type(
                     p.realtype, p.paramtype,
                     iid_is=attr_param_idx(p, m, 'iid_is'),
                     size_is=attr_param_idx(p, m, 'size_is')),
@@ -205,16 +217,18 @@ def build_interface(iface):
 
     for member in iface.members:
         if isinstance(member, xpidl.ConstMember):
             build_const(member)
         elif isinstance(member, xpidl.Attribute):
             build_attr(member)
         elif isinstance(member, xpidl.Method):
             build_method(member)
+        elif isinstance(member, xpidl.CEnum):
+            build_cenum(member)
         elif isinstance(member, xpidl.CDATA):
             pass
         else:
             raise Exception("Unexpected interface member: %s" % member)
 
     return {
         'name': iface.name,
         'uuid': iface.attributes.uuid,
--- a/xpcom/idl-parser/xpidl/xpidl.py
+++ b/xpcom/idl-parser/xpidl/xpidl.py
@@ -905,16 +905,95 @@ class ConstMember(object):
 
     def __str__(self):
         return "\tconst %s %s = %s\n" % (self.type, self.name, self.getValue())
 
     def count(self):
         return 0
 
 
+# Represents a single name/value pair in a CEnum
+class CEnumVariant(object):
+    # Treat CEnumVariants as consts in terms of value resolution, so we can
+    # do things like binary operation values for enum members.
+    kind = 'const'
+
+    def __init__(self, name, value, location):
+        self.name = name
+        self.value = value
+        self.location = location
+
+    def getValue(self):
+        return self.value
+
+
+class CEnum(object):
+    kind = 'cenum'
+
+    def __init__(self, width, name, variants, location, doccomments):
+        # We have to set a name here, otherwise we won't pass namemap checks on
+        # the interface. This name will change it in resolve(), in order to
+        # namespace the enum within the interface.
+        self.name = name
+        self.basename = name
+        self.width = width
+        self.location = location
+        self.namemap = NameMap()
+        self.doccomments = doccomments
+        self.variants = variants
+        if self.width not in (8, 16, 32):
+            raise IDLError("Width must be one of {8, 16, 32}", self.location)
+
+    def getValue(self):
+        return self.value(self.iface)
+
+    def resolve(self, iface):
+        self.iface = iface
+        # Renaming enum to faux-namespace the enum type to the interface in JS
+        # so we don't collide in the global namespace. Hacky/ugly but it does
+        # the job well enough, and the name will still be interface::variant in
+        # C++.
+        self.name = '%s_%s' % (self.iface.name, self.basename)
+        self.iface.idl.setName(self)
+
+        # Compute the value for each enum variant that doesn't set its own
+        # value
+        next_value = 0
+        for variant in self.variants:
+            # CEnum variants resolve to interface level consts in javascript,
+            # meaning their names could collide with other interface members.
+            # Iterate through all CEnum variants to make sure there are no
+            # collisions.
+            self.iface.namemap.set(variant)
+            # Value may be a lambda. If it is, resolve it.
+            if variant.value:
+                next_value = variant.value = variant.value(self.iface)
+            else:
+                variant.value = next_value
+            next_value += 1
+
+    def count(self):
+        return 0
+
+    def isScriptable(self):
+        return True
+
+    def nativeType(self, calltype):
+        if 'out' in calltype:
+            return "%s::%s *" % (self.iface.name, self.basename)
+        return "%s::%s " % (self.iface.name, self.basename)
+
+    def rustType(self, calltype):
+        raise RustNoncompat('cenums unimplemented')
+
+    def __str__(self):
+        body = ', '.join('%s = %s' % v for v in self.variants)
+        return "\tcenum %s : %d { %s };\n" % (self.name, self.width, body)
+
+
 class Attribute(object):
     kind = 'attribute'
     noscript = False
     readonly = False
     symbol = False
     implicit_jscontext = False
     nostdcall = False
     must_use = False
@@ -1262,16 +1341,17 @@ TypeId.__str__ = lambda self: \
 
 
 # Allow skipping 'params' in TypeId(..)
 TypeId.__new__.__defaults__ = (None,)
 
 
 class IDLParser(object):
     keywords = {
+        'cenum': 'CENUM',
         'const': 'CONST',
         'interface': 'INTERFACE',
         'in': 'IN',
         'inout': 'INOUT',
         'out': 'OUT',
         'attribute': 'ATTRIBUTE',
         'raises': 'RAISES',
         'readonly': 'READONLY',
@@ -1567,16 +1647,44 @@ class IDLParser(object):
             p[0] = lambda i: n1(i) >> n2(i)
 
     def p_number_bitor(self, p):
         """number : number '|' number"""
         n1 = p[1]
         n2 = p[3]
         p[0] = lambda i: n1(i) | n2(i)
 
+    def p_member_cenum(self, p):
+        """member : CENUM IDENTIFIER ':' NUMBER '{' variants '}' ';'"""
+        p[0] = CEnum(name=p[2],
+                     width=int(p[4]),
+                     variants=p[6],
+                     location=self.getLocation(p, 1),
+                     doccomments=p.slice[1].doccomments)
+
+    def p_variants_start(self, p):
+        """variants : """
+        p[0] = []
+
+    def p_variants_single(self, p):
+        """variants : variant"""
+        p[0] = [p[1]]
+
+    def p_variants_continue(self, p):
+        """variants : variant ',' variants"""
+        p[0] = [p[1]] + p[3]
+
+    def p_variant_implicit(self, p):
+        """variant : IDENTIFIER"""
+        p[0] = CEnumVariant(p[1], None, self.getLocation(p, 1))
+
+    def p_variant_explicit(self, p):
+        """variant : IDENTIFIER '=' number"""
+        p[0] = CEnumVariant(p[1], p[3], self.getLocation(p, 1))
+
     def p_member_att(self, p):
         """member : attributes optreadonly ATTRIBUTE type IDENTIFIER ';'"""
         if 'doccomments' in p[1]:
             doccomments = p[1]['doccomments']
         elif p[2] is not None:
             doccomments = p[2]
         else:
             doccomments = p.slice[3].doccomments