Bug 767926 - Implement unions as member types of sequences or dictionaries for WebIDL r=bz
authorDavid Zbarsky <dzbarsky@gmail.com>
Mon, 09 Sep 2013 14:39:17 -0400
changeset 159172 2fce3bcfb3c83e922f652175daa8ec0f292a8738
parent 159171 6bfbf7c7160eb9dabdec4013a36c9cd89029ebc1
child 159173 12d7c0e889d150081d4b5baf6394a77532e078e6
push id2961
push userlsblakk@mozilla.com
push dateMon, 28 Oct 2013 21:59:28 +0000
treeherdermozilla-beta@73ef4f13486f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs767926
milestone26.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 767926 - Implement unions as member types of sequences or dictionaries for WebIDL r=bz
dom/bindings/BindingUtils.h
dom/bindings/Codegen.py
dom/bindings/test/TestCodeGen.webidl
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -1630,16 +1630,19 @@ public:
       return *storage.addr();
     }
     template <typename T1, typename T2>
     T& SetValue(const T1 &t1, const T2 &t2)
     {
       new (storage.addr()) T(t1, t2);
       return *storage.addr();
     }
+    T& Value() {
+      return *storage.addr();
+    }
     const T& Value() const {
       return *storage.addr();
     }
     void Destroy() {
       storage.addr()->~T();
     }
 };
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -2892,24 +2892,21 @@ for (uint32_t i = 0; i < length; ++i) {
             holderArgs = None
 
         return JSToNativeConversionInfo(templateBody, declType=typeName,
                                         holderType=holderType,
                                         dealWithOptional=isOptional,
                                         holderArgs=holderArgs)
 
     if type.isUnion():
-        if isMember:
-            raise TypeError("Can't handle unions as members, we have a "
-                            "holderType")
         nullable = type.nullable();
         if nullable:
             type = type.inner
 
-        unionArgumentObj = "${holderName}"
+        unionArgumentObj = "${declName}" if isMember else "${holderName}"
         if nullable:
             unionArgumentObj += ".ref()"
 
         memberTypes = type.flatMemberTypes
         names = []
 
         interfaceMemberTypes = filter(lambda t: t.isNonCallbackInterface(), memberTypes)
         if len(interfaceMemberTypes) > 0:
@@ -3062,33 +3059,33 @@ for (uint32_t i = 0; i < length; ++i) {
                           '  ThrowErrorMessage(cx, MSG_NOT_IN_UNION, "%s", "%s");\n'
                           "%s\n"
                           "}" % (exceptionCodeIndented.define(),
                                  firstCap(sourceDescription),
                                  ", ".join(names),
                                  exceptionCodeIndented.define()))
         templateBody = CGWrapper(CGIndenter(CGList([templateBody, throw], "\n")), pre="{\n", post="\n}")
 
-        typeName = type.name
+        typeName = type.name + ("ReturnValue" if isMember else "")
         argumentTypeName = typeName + "Argument"
         if nullable:
             typeName = "Nullable<" + typeName + " >"
 
         def handleNull(templateBody, setToNullVar, extraConditionForNull=""):
             null = CGGeneric("if (%s${val}.isNullOrUndefined()) {\n"
                              "  %s.SetNull();\n"
                              "}" % (extraConditionForNull, setToNullVar))
             templateBody = CGWrapper(CGIndenter(templateBody), pre="{\n", post="\n}")
             return CGList([null, templateBody], " else ")
 
         if type.hasNullableType:
             templateBody = handleNull(templateBody, unionArgumentObj)
 
         declType = CGGeneric(typeName)
-        holderType = CGGeneric(argumentTypeName)
+        holderType = CGGeneric(argumentTypeName) if not isMember else None
 
         # If we're isOptional and not nullable the normal optional handling will
         # handle lazy construction of our holder.  If we're nullable we do it
         # all by hand because we do not want our holder constructed if we're
         # null.
         declLoc = "${declName}"
         constructDecl = None
         if nullable:
@@ -3411,20 +3408,26 @@ for (uint32_t i = 0; i < length; ++i) {
         if isOptional:
             declType = "Optional<nsAString>"
         elif isInUnionReturnValue:
             declType = "nsString"
         else:
             declType = "NonNull<nsAString>"
 
         # No need to deal with optional here; we handled it already
+        decl = ""
+        if isInUnionReturnValue:
+            decl += "FakeDependentString str;\n"
         return JSToNativeConversionInfo(
+            "%s"
             "%s\n"
-            "${declName} = &${holderName};" %
-            getConversionCode("${holderName}"),
+            "${declName} = %s" %
+              (decl,
+               getConversionCode("str" if isInUnionReturnValue else "${holderName}"),
+               ("str;" if isInUnionReturnValue else "&${holderName};")),
             declType=CGGeneric(declType),
             holderType=CGGeneric("FakeDependentString"))
 
     if type.isByteString():
         assert not isEnforceRange and not isClamp
 
         nullable = toStringBool(type.nullable())
 
@@ -4623,16 +4626,25 @@ class CGCallGenerator(CGThing):
             self.cgRoot.append(CGGeneric("rv.WouldReportJSException();"))
             self.cgRoot.append(CGGeneric("if (rv.Failed()) {"))
             self.cgRoot.append(CGIndenter(errorReport))
             self.cgRoot.append(CGGeneric("}"))
 
     def define(self):
         return self.cgRoot.define()
 
+def getUnionMemberName(type):
+    if type.isGeckoInterface():
+        return type.inner.identifier.name
+    if type.isEnum():
+        return type.inner.identifier.name
+    if type.isArray() or type.isSequence():
+        return str(type)
+    return type.name
+
 class MethodNotCreatorError(Exception):
     def __init__(self, typename):
         self.typename = typename
 
 # A counter for making sure that when we're wrapping up things in
 # nested sequences we don't use the same variable name to iterate over
 # different sequences.
 sequenceWrapLevel = 0
@@ -4705,18 +4717,25 @@ def wrapTypeIntoCurrentCompartment(type,
                     member,
                     "%s.%s" % (value, CGDictionary.makeMemberName(member.identifier.name)))
                 if memberWrap:
                     memberWraps.append(memberWrap)
             myDict = myDict.parent
         return CGList(memberWraps, "\n") if len(memberWraps) != 0 else None
 
     if type.isUnion():
-        raise TypeError("Can't handle wrapping of unions in constructor "
-                        "arguments yet")
+        memberWraps = []
+        for member in type.flatMemberTypes:
+            memberWrap = wrapTypeIntoCurrentCompartment(
+               member,
+               "%s.%s" % (value, getUnionMemberName(member)))
+            if memberWrap:
+                memberWrap = CGIfWrapper(memberWrap, "mType == %s" % member)
+                memberWraps.append(memberWrap)
+        return CGList(memberWraps, "else ") if len(memberWraps) != 0 else None
 
     if (type.isString() or type.isPrimitive() or type.isEnum() or
         type.isGeckoInterface() or type.isCallback() or type.isDate()):
         # All of these don't need wrapping
         return None
 
     raise TypeError("Unknown type; we don't know how to wrap it in constructor "
                     "arguments: %s" % type)
@@ -6117,86 +6136,75 @@ def getUnionAccessorSignatureType(type, 
 def getUnionTypeTemplateVars(unionType, type, descriptorProvider, isReturnValue=False):
     # For dictionaries and sequences we need to pass None as the failureCode
     # for getJSToNativeConversionInfo.
     # Also, for dictionaries we would need to handle conversion of
     # null/undefined to the dictionary correctly.
     if type.isDictionary() or type.isSequence():
         raise TypeError("Can't handle dictionaries or sequences in unions")
 
-    if type.isGeckoInterface():
-        name = type.inner.identifier.name
-    elif type.isEnum():
-        name = type.inner.identifier.name
-    elif type.isArray() or type.isSequence():
-        name = str(type)
-    else:
-        name = type.name
+    name = getUnionMemberName(type)
 
     ctorArgs = "cx" if type.isSpiderMonkeyInterface() else ""
 
     tryNextCode = ("tryNext = true;\n"
                    "return true;")
     if type.isGeckoInterface():
-         tryNextCode = ("if (mUnion.mType != mUnion.eUninitialized) {"
-                        "  mUnion.Destroy%s();"
-                        "}" % name) + tryNextCode
+         prefix = "" if isReturnValue else "mUnion."
+         tryNextCode = ("if (%smType != %seUninitialized) {"
+                        "  %sDestroy%s();"
+                        "}") % (prefix, prefix, prefix, name) + tryNextCode
     conversionInfo = getJSToNativeConversionInfo(
         type, descriptorProvider, failureCode=tryNextCode,
         isDefinitelyObject=True, isInUnionReturnValue=isReturnValue,
         sourceDescription="member of %s" % unionType)
 
     # This is ugly, but UnionMember needs to call a constructor with no
     # arguments so the type can't be const.
     structType = conversionInfo.declType.define()
     if structType.startswith("const "):
         structType = structType[6:]
     externalType = getUnionAccessorSignatureType(type, descriptorProvider).define()
 
     if type.isObject():
         body = ("mUnion.mValue.mObject.SetValue(cx, obj);\n"
                 "mUnion.mType = mUnion.eObject;")
-        setters = [ClassMethod("SetToObject", "void",
-                               [Argument("JSContext*", "cx"),
-                                Argument("JSObject*", "obj")],
-                               inline=True, bodyInHeader=True,
-                               body=body)]
+        setter = ClassMethod("SetToObject", "void",
+                             [Argument("JSContext*", "cx"),
+                              Argument("JSObject*", "obj")],
+                             inline=True, bodyInHeader=True,
+                             body=body)
 
     else:
         jsConversion = string.Template(conversionInfo.template).substitute(
             {
                 "val": "value",
                 "mutableVal": "pvalue",
                 "declName": "SetAs" + name + "(%s)" % ctorArgs,
                 "holderName": "m" + name + "Holder",
                 }
             )
         jsConversion = CGWrapper(CGGeneric(jsConversion),
                                  pre="tryNext = false;\n",
                                  post="\n"
                                       "return true;")
-        setters = [ClassMethod("TrySetTo" + name, "bool",
-                               [Argument("JSContext*", "cx"),
-                                Argument("JS::Handle<JS::Value>", "value"),
-                                Argument("JS::MutableHandle<JS::Value>", "pvalue"),
-                                Argument("bool&", "tryNext")],
-                               inline=True, bodyInHeader=True,
-                               body=jsConversion.define())]
-        if type.isString():
-            setters.append(ClassMethod("SetStringData", "void",
-                [Argument("const nsDependentString::char_type*", "aData"),
-                 Argument("nsDependentString::size_type", "aLength")],
-                inline=True, bodyInHeader=True,
-                body="mStringHolder.SetData(aData, aLength);"))
+        setter = ClassMethod("TrySetTo" + name, "bool",
+                              [Argument("JSContext*", "cx"),
+                               Argument("JS::Handle<JS::Value>", "value"),
+                               Argument("JS::MutableHandle<JS::Value>", "pvalue"),
+                               Argument("bool&", "tryNext")],
+                              inline=not isReturnValue,
+                              bodyInHeader=not isReturnValue,
+                              body=jsConversion.define())
 
     return {
                 "name": name,
                 "structType": structType,
                 "externalType": externalType,
-                "setters": setters,
+                "setter": setter,
                 "holderType": conversionInfo.holderType.define() if conversionInfo.holderType else None,
                 "ctorArgs": ctorArgs,
                 "ctorArgList": [Argument("JSContext*", "cx")] if type.isSpiderMonkeyInterface() else []
                 }
 
 def mapTemplate(template, templateVarArray):
     return map(lambda v: string.Template(template).substitute(v),
                templateVarArray)
@@ -6249,16 +6257,26 @@ class CGUnionStruct(CGThing):
                 # bodyInHeader must be false for return values because they own
                 # their union members and we don't want include headers in
                 # UnionTypes.h just to call Addref/Release
                 methods.append(ClassMethod("SetAs" + vars["name"],
                                            vars["structType"] + "&",
                                            vars["ctorArgList"],
                                            bodyInHeader=not self.isReturnValue,
                                            body=body))
+                if self.isReturnValue:
+                    methods.append(vars["setter"])
+                    if t.isString():
+                        methods.append(
+                            ClassMethod("SetStringData", "void",
+                                [Argument("const nsString::char_type*", "aData"),
+                                 Argument("nsString::size_type", "aLength")],
+                                inline=True, bodyInHeader=True,
+                                body="mValue.mString.Value().Assign(aData, aLength);"))
+
             body = string.Template('MOZ_ASSERT(Is${name}(), "Wrong type!");\n'
                                    'mValue.m${name}.Destroy();\n'
                                    'mType = eUninitialized;').substitute(vars)
             methods.append(ClassMethod("Destroy" + vars["name"],
                                        "void",
                                        [],
                                        visibility="private",
                                        bodyInHeader=not self.isReturnValue,
@@ -6347,26 +6365,33 @@ class CGUnionConversionStruct(CGThing):
             methods.append(ClassMethod("SetNull", "bool", [],
                                        body=("mUnion.mType = mUnion.eNull;\n"
                                              "return true;"),
                                        inline=True, bodyInHeader=True))
 
         for t in self.type.flatMemberTypes:
             vars = getUnionTypeTemplateVars(self.type,
                                             t, self.descriptorProvider)
-            methods.extend(vars["setters"])
+            methods.append(vars["setter"])
             if vars["name"] != "Object":
                 body=string.Template("mUnion.mType = mUnion.e${name};\n"
                                      "return mUnion.mValue.m${name}.SetValue(${ctorArgs});").substitute(vars)
                 methods.append(ClassMethod("SetAs" + vars["name"],
                                            vars["structType"] + "&",
                                            vars["ctorArgList"],
                                            bodyInHeader=True,
                                            body=body,
                                            visibility="private"))
+                if t.isString():
+                    methods.append(ClassMethod("SetStringData", "void",
+                                     [Argument("const nsDependentString::char_type*", "aData"),
+                                      Argument("nsDependentString::size_type", "aLength")],
+                                     inline=True, bodyInHeader=True,
+                                     body="mStringHolder.SetData(aData, aLength);"))
+
             if vars["holderType"] is not None:
                 members.append(ClassMember("m%sHolder" % vars["name"],
                                            vars["holderType"]))
 
         return CGClass(structName + "Argument",
                        members=members,
                        constructors=[ctor],
                        methods=methods,
@@ -8112,16 +8137,18 @@ class CGDictionary(CGThing):
         self.needToInitIds = len(dictionary.members) > 0
         self.memberInfo = [
             (member,
              getJSToNativeConversionInfo(
                             member.type,
                             descriptorProvider,
                             isMember="Dictionary",
                             isOptional=(not member.defaultValue),
+                            # Set this to true so that we get an owning union.
+                            isInUnionReturnValue=True,
                             defaultValue=member.defaultValue,
                             sourceDescription=("'%s' member of %s" %
                                                (member.identifier.name,
                                                 dictionary.identifier.name))))
             for member in dictionary.members ]
         self.structs = self.getStructs()
 
     def declare(self):
@@ -10517,16 +10544,17 @@ struct PrototypeTraits;
     def UnionTypes(config):
 
         (includes, implincludes,
          declarations, unions) = UnionTypes(config.getDescriptors(),
                                             config.getDictionaries(),
                                             config.getCallbacks(),
                                             config)
         includes.add("mozilla/dom/BindingUtils.h")
+        implincludes.add("mozilla/dom/PrimitiveConversions.h")
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'], unions)
 
         curr = CGWrapper(curr, post='\n')
 
         namespaces = []
         stack = [CGList([])]
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -731,16 +731,18 @@ dictionary Dict : ParentDict {
   unrestricted double  urDouble = 0;
   unrestricted double  urDouble2 = 1.1;
   unrestricted double  urDouble3 = -1.1;
   unrestricted double? urDouble4 = null;
   unrestricted double  infUrDouble = Infinity;
   unrestricted double  negativeInfUrDouble = -Infinity;
   unrestricted double  nanUrDouble = NaN;
 
+  (float or DOMString) floatOrString = "str";
+
   ArrayBuffer arrayBuffer;
   ArrayBuffer? nullableArrayBuffer;
   Uint8Array uint8Array;
   Float64Array? float64Array = null;
 };
 
 dictionary ParentDict : GrandparentDict {
   long c = 5;
@@ -759,16 +761,17 @@ dictionary DictContainingSequence {
   sequence<TestInterface> ourSequence2;
   sequence<any> ourSequence3;
   sequence<object> ourSequence4;
   sequence<object?> ourSequence5;
   sequence<object>? ourSequence6;
   sequence<object?>? ourSequence7;
   sequence<object>? ourSequence8 = null;
   sequence<object?>? ourSequence9 = null;
+  sequence<(float or DOMString)> ourSequence10;
 };
 
 dictionary DictForConstructor {
   Dict dict;
   DictContainingDict dict2;
   sequence<Dict> seq1;
   sequence<sequence<Dict>>? seq2;
   sequence<sequence<Dict>?> seq3;