From eff308009ea5ad9ee330cf510dc2afc8e813eca7 Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Thu, 26 Sep 2013 00:05:00 -0400 Subject: [PATCH] Bug 918011 part 4. Support dictionaries in unions. r=smaug --- dom/bindings/Codegen.py | 89 +++++++++++++++++-------- dom/bindings/parser/WebIDL.py | 5 +- dom/bindings/test/TestBindingHeader.h | 2 + dom/bindings/test/TestCodeGen.webidl | 11 +++ dom/bindings/test/TestExampleGen.webidl | 2 + dom/bindings/test/TestJSImplGen.webidl | 2 + 6 files changed, 82 insertions(+), 29 deletions(-) diff --git a/dom/bindings/Codegen.py b/dom/bindings/Codegen.py index 83abdfe51988..8554f67854f6 100644 --- a/dom/bindings/Codegen.py +++ b/dom/bindings/Codegen.py @@ -826,8 +826,13 @@ def UnionTypes(descriptors, dictionaries, callbacks, config): declarations.add((typeDesc.nativeType, False)) implheaders.add(typeDesc.headerFile) elif f.isDictionary(): - declarations.add((f.inner.identifier.name, True)) - implheaders.add(CGHeaders.getDeclarationFilename(f.inner)) + # For a dictionary, we need to see its declaration in + # UnionTypes.h so we have its sizeof and know how big to + # make our union. + headers.add(CGHeaders.getDeclarationFilename(f.inner)) + # And if it needs rooting, we need RootedDictionary too + if typeNeedsRooting(f): + headers.add("mozilla/dom/RootedDictionary.h") elif t.isPrimitive(): implheaders.add("mozilla/dom/PrimitiveConversions.h") @@ -2727,18 +2732,20 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None, declType = CGGeneric("JS::Handle") else: declType = CGGeneric("JS::Rooted") + declArgs="cx" else: assert (isMember == "Sequence" or isMember == "Variadic" or isMember == "Dictionary" or isMember == "OwningUnion") # We'll get traced by the sequence or dictionary or union tracer declType = CGGeneric("JSObject*") + declArgs = None templateBody = "${declName} = &${val}.toObject();" setToNullCode = "${declName} = nullptr;" template = wrapObjectTemplate(templateBody, type, setToNullCode, failureCode) return JSToNativeConversionInfo(template, declType=declType, dealWithOptional=isOptional, - declArgs="cx") + declArgs=declArgs) assert not (isEnforceRange and isClamp) # These are mutually exclusive @@ -2924,31 +2931,31 @@ for (uint32_t i = 0; i < length; ++i) { dictionaryMemberTypes = filter(lambda t: t.isDictionary(), memberTypes) if len(dictionaryMemberTypes) > 0: - raise TypeError("No support for unwrapping dictionaries as member " - "of a union") + assert len(dictionaryMemberTypes) == 1 + name = dictionaryMemberTypes[0].inner.identifier.name + setDictionary = CGGeneric("done = (failed = !%s.TrySetTo%s(cx, ${val}, ${mutableVal}, tryNext)) || !tryNext;" % (unionArgumentObj, name)) else: - dictionaryObject = None + setDictionary = None objectMemberTypes = filter(lambda t: t.isObject(), memberTypes) if len(objectMemberTypes) > 0: + assert len(objectMemberTypes) == 1 object = CGGeneric("%s.SetToObject(cx, argObj);\n" "done = true;" % unionArgumentObj) else: object = None - hasObjectTypes = interfaceObject or arrayObject or dateObject or callbackObject or dictionaryObject or object + hasObjectTypes = interfaceObject or arrayObject or dateObject or callbackObject or object if hasObjectTypes: # "object" is not distinguishable from other types - assert not object or not (interfaceObject or arrayObject or dateObject or callbackObject or dictionaryObject) - if arrayObject or dateObject or callbackObject or dictionaryObject: + assert not object or not (interfaceObject or arrayObject or dateObject or callbackObject) + if arrayObject or dateObject or callbackObject: # An object can be both an array object and a callback or # dictionary, but we shouldn't have both in the union's members # because they are not distinguishable. assert not (arrayObject and callbackObject) - assert not (arrayObject and dictionaryObject) - assert not (dictionaryObject and callbackObject) - templateBody = CGList([arrayObject, dateObject, callbackObject, - dictionaryObject], " else ") + templateBody = CGList([arrayObject, dateObject, callbackObject], + " else ") else: templateBody = None if interfaceObject: @@ -2959,13 +2966,17 @@ for (uint32_t i = 0; i < length; ++i) { else: templateBody = CGList([templateBody, object], "\n") - if any([arrayObject, dateObject, callbackObject, dictionaryObject, - object]): + if any([arrayObject, dateObject, callbackObject, object]): templateBody.prepend(CGGeneric("JS::Rooted argObj(cx, &${val}.toObject());")) templateBody = CGIfWrapper(templateBody, "${val}.isObject()") else: templateBody = CGGeneric() + if setDictionary: + assert not object + templateBody = CGList([templateBody, + CGIfWrapper(setDictionary, "!done")], "\n") + stringTypes = [t for t in memberTypes if t.isString() or t.isEnum()] numericTypes = [t for t in memberTypes if t.isNumeric()] booleanTypes = [t for t in memberTypes if t.isBoolean()] @@ -3004,7 +3015,7 @@ for (uint32_t i = 0; i < length; ++i) { other.append(booleanConversion[0]) other = CGWrapper(CGIndenter(other), pre="do {\n", post="\n} while (0);") - if hasObjectTypes: + if hasObjectTypes or setDictionary: other = CGWrapper(CGIndenter(other), "{\n", post="\n}") if object: join = " else " @@ -3518,10 +3529,6 @@ for (uint32_t i = 0; i < length; ++i) { return handleJSObjectType(type, isMember, failureCode) if type.isDictionary(): - if failureCode is not None and not isDefinitelyObject: - raise TypeError("Can't handle dictionaries when failureCode is " - "not None and we don't know we're an object") - # There are no nullable dictionaries assert not type.nullable() # All optional dictionaries always have default values, so we @@ -3546,12 +3553,15 @@ for (uint32_t i = 0; i < length; ++i) { val = "${val}" if failureCode is not None: - assert isDefinitelyObject + if isDefinitelyObject: + dictionaryTest = "IsObjectValueConvertibleToDictionary" + else: + dictionaryTest = "IsConvertibleToDictionary" # Check that the value we have can in fact be converted to # a dictionary, and return failureCode if not. template = CGIfWrapper( CGGeneric(failureCode), - "!IsObjectValueConvertibleToDictionary(cx, ${val})").define() + "\n\n" + "!%s(cx, ${val})" % dictionaryTest).define() + "\n\n" else: template = "" @@ -6081,6 +6091,9 @@ def getUnionAccessorSignatureType(type, descriptorProvider): if type.isObject(): return CGGeneric("JSObject*") + if type.isDictionary(): + return CGGeneric("const %s&" % type.inner.identifier.name) + if not type.isPrimitive(): raise TypeError("Need native type for argument type '%s'" % str(type)) @@ -6095,14 +6108,11 @@ def getUnionTypeTemplateVars(unionType, type, descriptorProvider, # 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.isSequence(): + raise TypeError("Can't handle sequences in unions") name = getUnionMemberName(type) - ctorNeedsCx = type.isSpiderMonkeyInterface() and not ownsMembers - ctorArgs = "cx" if ctorNeedsCx else "" - tryNextCode = ("tryNext = true;\n" "return true;") if type.isGeckoInterface(): @@ -6112,10 +6122,13 @@ def getUnionTypeTemplateVars(unionType, type, descriptorProvider, "}") % (prefix, prefix, prefix, name) + tryNextCode conversionInfo = getJSToNativeConversionInfo( type, descriptorProvider, failureCode=tryNextCode, - isDefinitelyObject=True, + isDefinitelyObject=not type.isDictionary(), isMember=("OwningUnion" if ownsMembers else None), sourceDescription="member of %s" % unionType) + ctorNeedsCx = conversionInfo.declArgs == "cx" + ctorArgs = "cx" if ctorNeedsCx else "" + # This is ugly, but UnionMember needs to call a constructor with no # arguments so the type can't be const. structType = conversionInfo.declType.define() @@ -6282,6 +6295,11 @@ class CGUnionStruct(CGThing): CGGeneric('JS_CallObjectTracer(trc, %s, "%s");' % ("&mValue.m" + vars["name"] + ".Value()", "mValue.m" + vars["name"])))) + elif t.isDictionary(): + traceCases.append( + CGCase("e" + vars["name"], + CGGeneric("mValue.m%s.Value().TraceDictionary(trc);" % + vars["name"]))) else: assert t.isSpiderMonkeyInterface() traceCases.append( @@ -8145,6 +8163,21 @@ class CGDictionary(CGThing): (member.identifier.name, dictionary.identifier.name)))) for member in dictionary.members ] + # If we have a union member containing something in the same + # file as us, bail: the C++ includes won't work out. + for member in dictionary.members: + type = member.type.unroll() + if type.isUnion(): + for t in type.flatMemberTypes: + if (t.isDictionary() and + CGHeaders.getDeclarationFilename(t.inner) == + CGHeaders.getDeclarationFilename(dictionary)): + raise TypeError( + "Dictionary contains a union that contains a " + "dictionary in the same WebIDL file. This won't " + "compile. Move the inner dictionary to a " + "different file.\n%s\n%s" % + (t.location, t.inner.location)) self.structs = self.getStructs() def declare(self): diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 33d6c79d3709..31052171af0b 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -2501,6 +2501,7 @@ class IDLNullValue(IDLObject): def coerceToType(self, type, location): if (not isinstance(type, IDLNullableType) and not (type.isUnion() and type.hasNullableType) and + not (type.isUnion() and type.hasDictionaryType) and not type.isDictionary() and not type.isAny()): raise WebIDLError("Cannot coerce null value to type %s." % type, @@ -2840,7 +2841,9 @@ class IDLArgument(IDLObjectWithIdentifier): assert not isinstance(type.name, IDLUnresolvedIdentifier) self.type = type - if self.type.isDictionary() and self.optional and not self.defaultValue: + if ((self.type.isDictionary() or + self.type.isUnion() and self.type.unroll().hasDictionaryType) and + self.optional and not self.defaultValue): # Default optional dictionaries to null, for simplicity, # so the codegen doesn't have to special-case this. self.defaultValue = IDLNullValue(self.location) diff --git a/dom/bindings/test/TestBindingHeader.h b/dom/bindings/test/TestBindingHeader.h index 3e7e5c5e5802..a413d81d74af 100644 --- a/dom/bindings/test/TestBindingHeader.h +++ b/dom/bindings/test/TestBindingHeader.h @@ -511,6 +511,8 @@ public: void PassUnion7(JSContext*, const ObjectOrStringOrLong& arg); void PassUnion8(JSContext*, const ObjectOrStringOrBoolean& arg); void PassUnion9(JSContext*, const ObjectOrStringOrLongOrBoolean& arg); + void PassUnion10(const EventInitOrLong& arg); + void PassUnion11(JSContext*, const CustomEventInitOrLong& arg); #endif void PassNullableUnion(JSContext*, const Nullable&); void PassOptionalUnion(JSContext*, const Optional&); diff --git a/dom/bindings/test/TestCodeGen.webidl b/dom/bindings/test/TestCodeGen.webidl index d9d6414b2bea..08ffe0df886a 100644 --- a/dom/bindings/test/TestCodeGen.webidl +++ b/dom/bindings/test/TestCodeGen.webidl @@ -451,6 +451,8 @@ interface TestInterface { void passUnion7((object or DOMString or long) arg); void passUnion8((object or DOMString or boolean) arg); void passUnion9((object or DOMString or long or boolean) arg); + void passUnion10(optional (EventInit or long) arg); + void passUnion11(optional (CustomEventInit or long) arg); #endif void passUnionWithNullable((object? or long) arg); void passNullableUnion((object or long)? arg); @@ -747,6 +749,15 @@ dictionary Dict : ParentDict { (float or DOMString) floatOrString = "str"; (object or long) objectOrLong; +#ifdef DEBUG + (EventInit or long) eventInitOrLong; + // CustomEventInit is useful to test because it needs rooting. + (CustomEventInit or long) eventInitOrLong2; + (EventInit or long) eventInitOrLongWithDefaultValue = null; + (CustomEventInit or long) eventInitOrLongWithDefaultValue2 = null; + (EventInit or long) eventInitOrLongWithDefaultValue3 = 5; + (CustomEventInit or long) eventInitOrLongWithDefaultValue4 = 5; +#endif ArrayBuffer arrayBuffer; ArrayBuffer? nullableArrayBuffer; diff --git a/dom/bindings/test/TestExampleGen.webidl b/dom/bindings/test/TestExampleGen.webidl index 317c60ec0dbe..9bc9a6d2a06f 100644 --- a/dom/bindings/test/TestExampleGen.webidl +++ b/dom/bindings/test/TestExampleGen.webidl @@ -347,6 +347,8 @@ interface TestExampleInterface { void passUnion7((object or DOMString or long) arg); void passUnion8((object or DOMString or boolean) arg); void passUnion9((object or DOMString or long or boolean) arg); + void passUnion10(optional (EventInit or long) arg); + void passUnion11(optional (CustomEventInit or long) arg); #endif void passUnionWithNullable((object? or long) arg); void passNullableUnion((object or long)? arg); diff --git a/dom/bindings/test/TestJSImplGen.webidl b/dom/bindings/test/TestJSImplGen.webidl index cc823d0ea2cb..a825ac472ebb 100644 --- a/dom/bindings/test/TestJSImplGen.webidl +++ b/dom/bindings/test/TestJSImplGen.webidl @@ -369,6 +369,8 @@ interface TestJSImplInterface { void passUnion7((object or DOMString or long) arg); void passUnion8((object or DOMString or boolean) arg); void passUnion9((object or DOMString or long or boolean) arg); + void passUnion10(optional (EventInit or long) arg); + void passUnion11(optional (CustomEventInit or long) arg); #endif void passUnionWithNullable((object? or long) arg); // FIXME: Bug 863948 Nullable unions not supported yet