Bug 742153 part 3. Implement codegen for dictionary arguments. r=peterv

Another implementation option would be to put all the dictionaries in a single
file and have a static global set of ids which works across all dictionaries
and is initialized once at startup or so.  That would also handle cross-file
dictionary inheritance better.

One problem that remains is the fake descriptor business.  At the moment this
does not allow interface types inside dictionaries.  We could probably work
around this by either refactoring code to make it possible to get the declType
independently of the actual conversion template (whether because it lives in a
separate function or because the conversion template generator knows to just
return an empty string when the fake descriptor provirder is passed) or by
figuring out a way to pass an actual descriptor provider to dictionary codegen.
This commit is contained in:
Boris Zbarsky 2012-06-12 10:22:05 -04:00
parent d28264d74e
commit 6c6503abf1
9 changed files with 310 additions and 15 deletions

View File

@ -571,6 +571,16 @@ WrapNativeParent(JSContext* cx, JSObject* scope, const T& p)
NULL;
}
static inline bool
InternJSString(JSContext* cx, jsid& id, const char* chars)
{
if (JSString *str = ::JS_InternString(cx, chars)) {
id = INTERNED_STRING_TO_JSID(cx, str);
return true;
}
return false;
}
// Spec needs a name property
template <typename Spec>
static bool
@ -583,12 +593,9 @@ InitIds(JSContext* cx, Prefable<Spec>* prefableSpecs, jsid* ids)
// because this is only done once per application runtime.
Spec* spec = prefableSpecs->specs;
do {
JSString *str = ::JS_InternString(cx, spec->name);
if (!str) {
if (!InternJSString(cx, *ids, spec->name)) {
return false;
}
*ids = INTERNED_STRING_TO_JSID(cx, str);
} while (++ids, (++spec)->name);
// We ran out of ids for that pref. Put a JSID_VOID in on the id

View File

@ -313,7 +313,8 @@ class CGHeaders(CGWrapper):
"""
Generates the appropriate include statements.
"""
def __init__(self, descriptors, declareIncludes, defineIncludes, child):
def __init__(self, descriptors, dictionaries, declareIncludes,
defineIncludes, child):
"""
Builds a set of includes to cover |descriptors|.
@ -329,7 +330,7 @@ class CGHeaders(CGWrapper):
ancestors.append(iface.parent)
iface = iface.parent
interfaceDeps.extend(ancestors)
bindingIncludes = set(self.getInterfaceFilename(d) for d in interfaceDeps)
bindingIncludes = set(self.getDeclarationFilename(d) for d in interfaceDeps)
# Grab all the implementation declaration files we need.
implementationIncludes = set(d.headerFile for d in descriptors)
@ -359,22 +360,30 @@ class CGHeaders(CGWrapper):
typeDesc = d.getDescriptor(t.unroll().inner.identifier.name)
if typeDesc is not None:
implementationIncludes.add(typeDesc.headerFile)
bindingHeaders.add(self.getInterfaceFilename(typeDesc.interface))
bindingHeaders.add(self.getDeclarationFilename(typeDesc.interface))
elif t.unroll().isDictionary():
bindingHeaders.add(self.getDeclarationFilename(t.unroll().inner))
declareIncludes = set(declareIncludes)
for d in dictionaries:
if d.parent:
declareIncludes.add(self.getDeclarationFilename(d.parent))
bindingHeaders.add(self.getDeclarationFilename(d))
# Let the machinery do its thing.
def _includeString(includes):
return ''.join(['#include "%s"\n' % i for i in includes]) + '\n'
CGWrapper.__init__(self, child,
declarePre=_includeString(declareIncludes),
declarePre=_includeString(sorted(declareIncludes)),
definePre=_includeString(sorted(set(defineIncludes) |
bindingIncludes |
bindingHeaders |
implementationIncludes)))
@staticmethod
def getInterfaceFilename(interface):
def getDeclarationFilename(decl):
# Use our local version of the header, not the exported one, so that
# test bindings, which don't export, will work correctly.
basename = os.path.basename(interface.filename())
basename = os.path.basename(decl.filename())
return basename.replace('.webidl', 'Binding.h')
class Argument():
@ -1698,6 +1707,35 @@ for (uint32_t i = 0; i < length; ++i) {
declType = CGGeneric("NonNull<JSObject>")
return (template, declType, None, isOptional)
if type.isDictionary():
if failureCode is not None:
raise TypeError("Can't handle dictionaries when failureCode is not None")
if type.nullable():
typeName = type.inner.inner.identifier.name
declType = CGGeneric("Nullable<%s>" % typeName)
selfRef = "${declName}.Value()"
else:
typeName = type.inner.identifier.name
declType = CGGeneric(typeName)
selfRef = "${declName}"
# If we're optional or a member of something else, the const
# will come from the Optional or our container.
mutableTypeName = declType
if not isOptional and not isMember:
declType = CGWrapper(declType, pre="const ")
selfRef = "const_cast<%s&>(%s)" % (typeName, selfRef)
template = wrapObjectTemplate("if (!%s.Init(cx, &${val}.toObject())) {\n"
" return false;\n"
"}" % selfRef,
isDefinitelyObject, type,
("const_cast<%s&>(${declName}).SetNull()" %
mutableTypeName.define()),
descriptorProvider.workers, None)
return (template, declType, None, isOptional)
if not type.isPrimitive():
raise TypeError("Need conversion for argument type '%s'" % type)
@ -3379,6 +3417,179 @@ class CGNamespacedEnum(CGThing):
def define(self):
assert False # Only for headers.
class CGDictionary(CGThing):
def __init__(self, dictionary, workers):
self.dictionary = dictionary;
self.workers = workers
# Fake a descriptorProvider
# XXXbz this will fail for interface types!
for member in dictionary.members:
if member.type.unroll().isInterface():
raise TypeError("No support for interface members of dictionaries: %s.%s" %
(dictionary.identifier.name, member.identifier.name))
self.memberInfo = [
(member,
getJSToNativeConversionTemplate(member.type,
{ "workers": workers },
isMember=True,
isOptional=(not member.defaultValue)))
for member in dictionary.members ]
def declare(self):
d = self.dictionary
if d.parent:
inheritance = ": public %s " % self.makeClassName(d.parent)
else:
inheritance = ""
memberDecls = [" %s %s;" %
(self.getMemberType(m), m[0].identifier.name)
for m in self.memberInfo]
return (string.Template(
"struct ${selfName} ${inheritance}{\n"
" ${selfName}() {}\n"
" bool Init(JSContext* cx, JSObject* obj);\n"
"\n" +
"\n".join(memberDecls) + "\n"
"private:\n"
" // Disallow copy-construction\n"
" ${selfName}(const ${selfName}&) MOZ_DELETE;\n"
" static bool InitIds(JSContext* cx);\n"
" static bool initedIds;\n" +
"\n".join(" static jsid " +
self.makeIdName(m.identifier.name) + ";" for
m in d.members) + "\n"
"};").substitute( { "selfName": self.makeClassName(d),
"inheritance": inheritance }))
def define(self):
d = self.dictionary
if d.parent:
initParent = ("// Per spec, we init the parent's members first\n"
"if (!%s::Init(cx, obj)) {\n"
" return false;\n"
"}\n" % self.makeClassName(d.parent))
else:
initParent = ""
memberInits = [CGIndenter(self.getMemberConversion(m)).define()
for m in self.memberInfo]
idinit = [CGGeneric('!InternJSString(cx, %s, "%s")' %
(m.identifier.name + "_id", m.identifier.name))
for m in d.members]
idinit = CGList(idinit, " ||\n")
idinit = CGWrapper(idinit, pre="if (",
post=(") {\n"
" return false;\n"
"}"),
reindent=True)
return string.Template(
"bool ${selfName}::initedIds = false;\n" +
"\n".join("jsid ${selfName}::%s = JSID_VOID;" %
self.makeIdName(m.identifier.name)
for m in d.members) + "\n"
"\n"
"bool\n"
"${selfName}::InitIds(JSContext* cx)\n"
"{\n"
" MOZ_ASSERT(!initedIds);\n"
"${idInit}\n"
" initedIds = true;\n"
" return true;\n"
"}\n"
"\n"
"bool\n"
"${selfName}::Init(JSContext* cx, JSObject* obj)\n"
"{\n"
" if (!initedIds && !InitIds(cx)) {\n"
" return false;\n"
" }\n"
"${initParent}"
" JSBool found;\n"
" JS::Value temp;\n"
"\n"
"${initMembers}\n"
" return true;\n"
"}").substitute({
"selfName": self.makeClassName(d),
"initParent": CGIndenter(CGGeneric(initParent)).define(),
"initMembers": "\n\n".join(memberInits),
"idInit": CGIndenter(idinit).define()
})
def makeClassName(self, dictionary):
suffix = "Workers" if self.workers else ""
return dictionary.identifier.name + suffix
def getMemberType(self, memberInfo):
(member, (templateBody, declType,
holderType, dealWithOptional)) = memberInfo
# We can't handle having a holderType here
assert holderType is None
if dealWithOptional:
declType = CGWrapper(declType, pre="Optional< ", post=" >")
return declType.define()
def getMemberConversion(self, memberInfo):
# Fake a descriptorProvider
(member, (templateBody, declType,
holderType, dealWithOptional)) = memberInfo
replacements = { "val": "temp",
"valPtr": "&temp",
# Use this->%s to refer to members, because we don't
# control the member names and want to make sure we're
# talking about the member, not some local that
# shadows the member. Another option would be to move
# the guts of init to a static method which is passed
# an explicit reference to our dictionary object, so
# we couldn't screw this up even if we wanted to....
"declName": ("(this->%s)" % member.identifier.name) }
# We can't handle having a holderType here
assert holderType is None
if dealWithOptional:
replacements["declName"] = "(" + replacements["declName"] + ".Value())"
conversionReplacements = {
"propId" : self.makeIdName(member.identifier.name),
"prop": "(this->%s)" % member.identifier.name,
"convert": string.Template(templateBody).substitute(replacements)
}
conversion = ("if (!JS_HasPropertyById(cx, obj, ${propId}, &found)) {\n"
" return false;\n"
"}\n")
if member.defaultValue:
conversion += (
"if (found) {\n"
" if (!JS_GetPropertyById(cx, obj, ${propId}, &temp)) {\n"
" return false;\n"
" }\n"
"} else {\n"
" temp = ${defaultVal};\n"
"}\n"
"${convert}")
conversionReplacements["defaultVal"] = (
convertIDLDefaultValueToJSVal(member.defaultValue))
else:
conversion += (
"if (found) {\n"
" ${prop}.Construct();\n"
" if (!JS_GetPropertyById(cx, obj, ${propId}, &temp)) {\n"
" return false;\n"
" }\n"
"${convert}\n"
"}")
conversionReplacements["convert"] = CGIndenter(
CGGeneric(conversionReplacements["convert"])).define()
return CGGeneric(
string.Template(conversion).substitute(conversionReplacements)
)
@staticmethod
def makeIdName(name):
return name + "_id"
class CGRegisterProtos(CGAbstractMethod):
def __init__(self, config):
CGAbstractMethod.__init__(self, None, 'Register', 'void',
@ -3409,6 +3620,7 @@ class CGBindingRoot(CGThing):
def __init__(self, config, prefix, webIDLFile):
descriptors = config.getDescriptors(webIDLFile=webIDLFile,
hasInterfaceOrInterfacePrototypeObject=True)
dictionaries = config.getDictionaries(webIDLFile)
forwardDeclares = [CGClassForwardDeclare('XPCWrappedNativeScope')]
@ -3446,7 +3658,7 @@ class CGBindingRoot(CGThing):
else:
traitsClasses = None
# Do codegen for all the descriptors and enums.
# Do codegen for all the enums
def makeEnum(e):
return CGNamespace.build([e.identifier.name + "Values"],
CGEnum(e))
@ -3455,8 +3667,30 @@ class CGBindingRoot(CGThing):
(e.identifier.name, e.identifier.name)))
cgthings = [ fun(e) for e in config.getEnums(webIDLFile)
for fun in [makeEnum, makeEnumTypedef] ]
# Do codegen for all the dictionaries. We have to be a bit careful
# here, because we have to generate these in order from least derived to
# most derived so that class inheritance works out.
#
# XXXbz this will fail if we have two webidl files A and B such that A
# declares a dictionary which inherits from a dictionary in B and B
# declares a dictionary (possibly a different one!) that inherits from a
# dictionary in A. The good news is that I expect this to never happen.
reSortedDictionaries = []
while len(dictionaries) != 0:
toMove = [d for d in dictionaries if d.parent not in dictionaries]
dictionaries = [d for d in dictionaries if d.parent in dictionaries]
reSortedDictionaries.extend(toMove)
dictionaries = reSortedDictionaries
cgthings.extend([CGDictionary(d, workers=True) for d in dictionaries])
cgthings.extend([CGDictionary(d, workers=False) for d in dictionaries])
# Do codegen for all the descriptors
cgthings.extend([CGDescriptor(x) for x in descriptors])
curr = CGList(cgthings, "\n")
# And make sure we have the right number of newlines at the end
curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
# Wrap all of that in our namespaces.
curr = CGNamespace.build(['mozilla', 'dom'],
@ -3470,6 +3704,7 @@ class CGBindingRoot(CGThing):
# Add header includes.
curr = CGHeaders(descriptors,
dictionaries,
['mozilla/dom/BindingUtils.h',
'mozilla/dom/DOMJSClass.h'],
['mozilla/dom/Nullable.h',
@ -3572,12 +3807,12 @@ struct PrototypeIDMap;
curr = CGWrapper(curr, post='\n')
# Add the includes
defineIncludes = [CGHeaders.getInterfaceFilename(desc.interface)
defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
for desc in config.getDescriptors(hasInterfaceObject=True,
workers=False,
register=True)]
defineIncludes.append('nsScriptNameSpaceManager.h')
curr = CGHeaders([], [], defineIncludes, curr)
curr = CGHeaders([], [], [], defineIncludes, curr)
# Add include guards.
curr = CGIncludeGuard('RegisterBindings', curr)

View File

@ -42,6 +42,7 @@ class Configuration:
descriptor.uniqueImplementation = len(otherDescriptors) == 1
self.enums = [e for e in parseData if e.isEnum()]
self.dictionaries = [d for d in parseData if d.isDictionary()]
# Keep the descriptor list sorted for determinism.
self.descriptors.sort(lambda x,y: cmp(x.name, y.name))
@ -72,6 +73,8 @@ class Configuration:
return curr
def getEnums(self, webIDLFile):
return filter(lambda e: e.filename() == webIDLFile, self.enums)
def getDictionaries(self, webIDLFile):
return filter(lambda d: d.filename() == webIDLFile, self.dictionaries)
class Descriptor:
"""

View File

@ -146,3 +146,9 @@ GARBAGE += \
webidlyacc.py \
parser.out \
$(NULL)
# Make sure all binding header files are created during the export stage, so we
# don't have issues with .cpp files being compiled before we've generated the
# headers they depend on. This is really only needed for the test files, since
# the non-test headers are all exported above anyway.
export:: $(binding_header_files)

View File

@ -150,6 +150,9 @@ class IDLObject(object):
def isType(self):
return False
def isDictionary(self):
return False;
def getUserData(self, key, default):
return self.userData.get(key, default)
@ -603,6 +606,9 @@ class IDLDictionary(IDLObjectWithScope):
def __str__(self):
return "Dictionary '%s'" % self.identifier.name
def isDictionary(self):
return True;
def finish(self, scope):
if self._finished:
return

View File

@ -294,6 +294,14 @@ public:
int8_t GetAttributeRenamedTo(ErrorResult&);
void SetAttributeRenamedTo(int8_t, ErrorResult&);
// Dictionary tests
void PassDictionary(const Dict&, ErrorResult&);
void PassOptionalDictionary(const Optional<Dict>&, ErrorResult&);
void PassNullableDictionary(const Nullable<Dict>&, ErrorResult&);
void PassOptionalNullableDictionary(const Optional<Nullable<Dict> >&, ErrorResult&);
void PassOtherDictionary(const GrandparentDict&, ErrorResult&);
void PassSequenceOfDictionaries(const Sequence<Dict>&, ErrorResult&);
// Methods and properties imported via "implements"
bool GetImplementedProperty(ErrorResult&);
void SetImplementedProperty(bool, ErrorResult&);

View File

@ -224,6 +224,13 @@ interface TestInterface {
void methodRenamedFrom(byte argument);
readonly attribute byte attributeGetterRenamedFrom;
attribute byte attributeRenamedFrom;
void passDictionary(Dict x);
void passOptionalDictionary(optional Dict x);
void passNullableDictionary(Dict? x);
void passOptionalNullableDictionary(optional Dict? x);
void passOtherDictionary(GrandparentDict x);
void passSequenceOfDictionaries(sequence<Dict> x);
};
interface ImplementedInterfaceParent {
@ -266,3 +273,14 @@ TestInterface implements DiamondBranch2A;
TestInterface implements DiamondBranch2B;
DiamondBranch1A implements DiamondImplements;
DiamondBranch1B implements DiamondImplements;
dictionary Dict : ParentDict {
long x;
long a;
long b = 8;
long z = 9;
};
dictionary ParentDict : GrandparentDict {
long c = 5;
};

View File

@ -0,0 +1,9 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*/
dictionary GrandparentDict {
double someNum;
};

View File

@ -15,7 +15,10 @@ webidl_files = \
$(NULL)
ifdef ENABLE_TESTS
test_webidl_files := TestCodeGen.webidl
test_webidl_files := \
TestCodeGen.webidl \
TestDictionary.webidl \
$(NULL)
else
test_webidl_files := $(NULL)
endif