Bug 761772. Add support for 'implements' in WebIDL. r=khuey

This commit is contained in:
Boris Zbarsky 2012-06-11 18:21:35 -04:00
parent a1d0c78df3
commit 5240e21e79
6 changed files with 321 additions and 34 deletions

View File

@ -83,6 +83,7 @@ bindinggen_dependencies := \
Bindings.conf \
Configuration.py \
Codegen.py \
parser/WebIDL.py \
ParserResults.pkl \
$(GLOBAL_DEPS) \
$(NULL)
@ -120,6 +121,7 @@ globalgen_dependencies := \
Bindings.conf \
Configuration.py \
Codegen.py \
parser/WebIDL.py \
$(CACHE_DIR)/.done \
$(GLOBAL_DEPS) \
$(NULL)

View File

@ -51,15 +51,19 @@ def enum(*names):
return Foo()
class WebIDLError(Exception):
def __init__(self, message, location, warning=False):
def __init__(self, message, location, warning=False, extraLocation=""):
self.message = message
self.location = location
self.warning = warning
self.extraLocation = extraLocation
def __str__(self):
return "%s: %s%s%s" % (self.warning and 'warning' or 'error',
self.message, ", " if self.location else "",
self.location)
return "%s: %s%s%s%s%s" % (self.warning and 'warning' or 'error',
self.message,
", " if self.location else "",
self.location,
"\n" if self.extraLocation else "",
self.extraLocation)
class Location(object):
def __init__(self, lexer, lineno, lexpos, filename):
@ -313,7 +317,7 @@ class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope):
IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier)
IDLScope.__init__(self, location, parentScope, self.identifier)
class IDLParentPlaceholder(IDLObjectWithIdentifier):
class IDLInterfacePlaceholder(IDLObjectWithIdentifier):
def __init__(self, location, identifier):
assert isinstance(identifier, IDLUnresolvedIdentifier)
IDLObjectWithIdentifier.__init__(self, location, None, identifier)
@ -354,12 +358,13 @@ class IDLInterface(IDLObjectWithScope):
def __init__(self, location, parentScope, name, parent, members):
assert isinstance(parentScope, IDLScope)
assert isinstance(name, IDLUnresolvedIdentifier)
assert not parent or isinstance(parent, IDLParentPlaceholder)
assert not parent or isinstance(parent, IDLInterfacePlaceholder)
self.parent = parent
self._callback = False
self._finished = False
self.members = list(members) # clone the list
self.implementedInterfaces = set()
IDLObjectWithScope.__init__(self, location, parentScope, name)
@ -398,7 +403,7 @@ class IDLInterface(IDLObjectWithScope):
self._finished = True
assert not self.parent or isinstance(self.parent, IDLParentPlaceholder)
assert not self.parent or isinstance(self.parent, IDLInterfacePlaceholder)
parent = self.parent.finish(scope) if self.parent else None
assert not parent or isinstance(parent, IDLInterface)
@ -408,31 +413,57 @@ class IDLInterface(IDLObjectWithScope):
if self.parent:
self.parent.finish(scope)
assert iter(self.parent.members)
members = list(self.parent.members)
members.extend(self.members)
else:
members = list(self.members)
# Callbacks must not inherit from non-callbacks or inherit from
# anything that has consequential interfaces.
if self.isCallback():
assert(self.parent.isCallback())
assert(len(self.parent.getConsequentialInterfaces()) == 0)
def memberNotOnParentChain(member, iface):
assert iface
for iface in self.implementedInterfaces:
iface.finish(scope)
if not iface.parent:
return True
# Now resolve() and finish() our members before importing the
# ones from our implemented interfaces.
assert isinstance(iface.parent, IDLInterface)
if member in iface.parent.members:
return False
return memberNotOnParentChain(member, iface.parent)
# resolve() will modify self.members, so we need to iterate
# over a copy of the member list here.
for member in list(self.members):
member.resolve(self)
for member in self.members:
member.finish(scope)
ctor = self.ctor()
if ctor is not None:
ctor.finish(scope)
# Make a copy of our member list, so things tht implement us
# can get those without all the stuff we implement ourselves
# admixed.
self.originalMembers = list(self.members)
# Import everything from our consequential interfaces into
# self.members. Sort our consequential interfaces by name
# just so we have a consistent order.
for iface in sorted(self.getConsequentialInterfaces(),
cmp=cmp,
key=lambda x: x.identifier.name):
additionalMembers = iface.originalMembers;
for additionalMember in additionalMembers:
for member in self.members:
if additionalMember.identifier.name == member.identifier.name:
raise WebIDLError(
"Multiple definitions of %s on %s coming from 'implements' statements" %
(member.identifier.name, self),
additionalMember.location,
extraLocation=member.location)
self.members.extend(additionalMembers)
# Ensure that there's at most one of each {named,indexed}
# {getter,setter,creator,deleter}.
specialMembersSeen = set()
for member in members:
if memberNotOnParentChain(member, self):
member.resolve(self)
for member in self.members:
if member.tag != IDLInterfaceMember.Tags.Method:
continue
@ -460,13 +491,6 @@ class IDLInterface(IDLObjectWithScope):
specialMembersSeen.add(memberType)
for member in self.members:
member.finish(scope)
ctor = self.ctor()
if ctor is not None:
ctor.finish(scope)
def isInterface(self):
return True
@ -531,6 +555,39 @@ class IDLInterface(IDLObjectWithScope):
self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
def addImplementedInterface(self, implementedInterface):
assert(isinstance(implementedInterface, IDLInterface))
self.implementedInterfaces.add(implementedInterface)
def getInheritedInterfaces(self):
"""
Returns a list of the interfaces this interface inherits from
(not including this interface itself). The list is in order
from most derived to least derived.
"""
assert(self._finished)
if not self.parent:
return []
parentInterfaces = self.parent.getInheritedInterfaces()
parentInterfaces.insert(0, self.parent)
return parentInterfaces
def getConsequentialInterfaces(self):
assert(self._finished)
# The interfaces we implement directly
consequentialInterfaces = set(self.implementedInterfaces)
# And their inherited interfaces
for iface in self.implementedInterfaces:
consequentialInterfaces |= set(iface.getInheritedInterfaces())
# And now collect up the consequential interfaces of all of those
temp = set()
for iface in consequentialInterfaces:
temp |= iface.getConsequentialInterfaces()
return consequentialInterfaces | temp
class IDLEnum(IDLObjectWithIdentifier):
def __init__(self, location, parentScope, name, values):
assert isinstance(parentScope, IDLScope)
@ -1740,6 +1797,22 @@ class IDLMethod(IDLInterfaceMember, IDLScope):
assert not isinstance(type.name, IDLUnresolvedIdentifier)
argument.type = type
class IDLImplementsStatement(IDLObject):
def __init__(self, location, implementor, implementee):
IDLObject.__init__(self, location)
self.implementor = implementor;
self.implementee = implementee
def finish(self, scope):
assert(isinstance(self.implementor, IDLInterfacePlaceholder))
assert(isinstance(self.implementee, IDLInterfacePlaceholder))
implementor = self.implementor.finish(scope)
implementee = self.implementee.finish(scope)
implementor.addImplementedInterface(implementee)
def addExtendedAttributes(self, attrs):
assert len(attrs) == 0
# Parser
class Tokenizer(object):
@ -1968,7 +2041,7 @@ class Parser(Tokenizer):
"""
Inheritance : COLON ScopedName
"""
p[0] = IDLParentPlaceholder(self.getLocation(p, 2), p[2])
p[0] = IDLInterfacePlaceholder(self.getLocation(p, 2), p[2])
def p_InheritanceEmpty(self, p):
"""
@ -2093,7 +2166,11 @@ class Parser(Tokenizer):
"""
ImplementsStatement : ScopedName IMPLEMENTS ScopedName SEMICOLON
"""
pass
assert(p[2] == "implements")
implementor = IDLInterfacePlaceholder(self.getLocation(p, 1), p[1])
implementee = IDLInterfacePlaceholder(self.getLocation(p, 3), p[3])
p[0] = IDLImplementsStatement(self.getLocation(p, 1), implementor,
implementee)
def p_Const(self, p):
"""
@ -2950,7 +3027,16 @@ class Parser(Tokenizer):
self._filename = None
def finish(self):
for production in self._productions:
# First, finish all the IDLImplementsStatements. In particular, we
# have to make sure we do those before we do the IDLInterfaces.
# XXX khuey hates this bit and wants to nuke it from orbit.
implementsStatements = [ p for p in self._productions if
isinstance(p, IDLImplementsStatement)]
otherStatements = [ p for p in self._productions if
not isinstance(p, IDLImplementsStatement)]
for production in implementsStatements:
production.finish(self.globalScope())
for production in otherStatements:
production.finish(self.globalScope())
# De-duplicate self._productions, without modifying its order.

View File

@ -0,0 +1,143 @@
# Import the WebIDL module, so we can do isinstance checks and whatnot
import WebIDL
def WebIDLTest(parser, harness):
# Basic functionality
threw = False
try:
parser.parse("""
A implements B;
interface B {
attribute long x;
};
interface A {
attribute long y;
};
""")
results = parser.finish()
except:
threw = True
harness.ok(not threw, "Should not have thrown on implements statement "
"before interfaces")
harness.check(len(results), 3, "We have three statements")
harness.ok(isinstance(results[1], WebIDL.IDLInterface), "B is an interface")
harness.check(len(results[1].members), 1, "B has one member")
A = results[2]
harness.ok(isinstance(A, WebIDL.IDLInterface), "A is an interface")
harness.check(len(A.members), 2, "A has two members")
harness.check(A.members[0].identifier.name, "y", "First member is 'y'")
harness.check(A.members[1].identifier.name, "x", "Second member is 'x'")
# Duplicated member names not allowed
threw = False
try:
parser.parse("""
C implements D;
interface D {
attribute long x;
};
interface C {
attribute long x;
};
""")
parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown on implemented interface duplicating "
"a name on base interface")
# Same, but duplicated across implemented interfaces
threw = False
try:
parser.parse("""
E implements F;
E implements G;
interface F {
attribute long x;
};
interface G {
attribute long x;
};
interface E {};
""")
parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown on implemented interfaces "
"duplicating each other's member names")
# Same, but duplicated across indirectly implemented interfaces
threw = False
try:
parser.parse("""
H implements I;
H implements J;
I implements K;
interface K {
attribute long x;
};
interface L {
attribute long x;
};
interface I {};
interface J : L {};
interface H {};
""")
parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown on indirectly implemented interfaces "
"duplicating each other's member names")
# Same, but duplicated across an implemented interface and its parent
threw = False
try:
parser.parse("""
M implements N;
interface O {
attribute long x;
};
interface N : O {
attribute long x;
};
interface M {};
""")
parser.finish()
except:
threw = True
harness.ok(threw, "Should have thrown on implemented interface and its "
"ancestor duplicating member names")
# Reset the parser so we can actually find things where we expect
# them in the list
parser = WebIDL.Parser()
# Diamonds should be allowed
threw = False
try:
parser.parse("""
P implements Q;
P implements R;
Q implements S;
R implements S;
interface Q {};
interface R {};
interface S {
attribute long x;
};
interface P {};
""")
results = parser.finish()
except:
threw = True
harness.ok(not threw, "Diamond inheritance is fine")
harness.check(results[6].identifier.name, "S", "We should be looking at 'S'")
harness.check(len(results[6].members), 1, "S should have one member")
harness.check(results[6].members[0].identifier.name, "x",
"S's member should be 'x'")

View File

@ -43,6 +43,7 @@ bindinggen_dependencies := \
../Bindings.conf \
../Configuration.py \
../Codegen.py \
../parser/WebIDL.py \
../ParserResults.pkl \
../Makefile \
$(GLOBAL_DEPS) \

View File

@ -294,6 +294,18 @@ public:
int8_t GetAttributeRenamedTo(ErrorResult&);
void SetAttributeRenamedTo(int8_t, ErrorResult&);
// Methods and properties imported via "implements"
bool GetImplementedProperty(ErrorResult&);
void SetImplementedProperty(bool, ErrorResult&);
void ImplementedMethod(ErrorResult&);
bool GetImplementedParentProperty(ErrorResult&);
void SetImplementedParentProperty(bool, ErrorResult&);
void ImplementedParentMethod(ErrorResult&);
bool GetIndirectlyImplementedProperty(ErrorResult&);
void SetIndirectlyImplementedProperty(bool, ErrorResult&);
void IndirectlyImplementedMethod(ErrorResult&);
uint32_t GetDiamondImplementedProperty(ErrorResult&);
private:
// We add signatures here that _could_ start matching if the codegen
// got data types wrong. That way if it ever does we'll have a call

View File

@ -16,6 +16,8 @@ enum TestEnum {
callback TestCallback = void();
TestInterface implements ImplementedInterface;
[Constructor,
Constructor(DOMString str),
Constructor(unsigned long num, boolean? bool),
@ -223,3 +225,44 @@ interface TestInterface {
readonly attribute byte attributeGetterRenamedFrom;
attribute byte attributeRenamedFrom;
};
interface ImplementedInterfaceParent {
void implementedParentMethod();
attribute boolean implementedParentProperty;
const long implementedParentConstant = 8;
};
ImplementedInterfaceParent implements IndirectlyImplementedInterface;
interface IndirectlyImplementedInterface {
void indirectlyImplementedMethod();
attribute boolean indirectlyImplementedProperty;
const long indirectlyImplementedConstant = 9;
};
interface ImplementedInterface : ImplementedInterfaceParent {
void implementedMethod();
attribute boolean implementedProperty;
const long implementedConstant = 5;
};
interface DiamondImplements {
readonly attribute long diamondImplementedProperty;
};
interface DiamondBranch1A {
};
interface DiamondBranch1B {
};
interface DiamondBranch2A : DiamondImplements {
};
interface DiamondBranch2B : DiamondImplements {
};
TestInterface implements DiamondBranch1A;
TestInterface implements DiamondBranch1B;
TestInterface implements DiamondBranch2A;
TestInterface implements DiamondBranch2B;
DiamondBranch1A implements DiamondImplements;
DiamondBranch1B implements DiamondImplements;