diff --git a/dom/bindings/parser/WebIDL.py b/dom/bindings/parser/WebIDL.py index 715e5e3cec2fdf42975965525e4152fc8309f998..f1fafb56c05bcde2c4922d622b7c453f024c7ee4 100644 --- a/dom/bindings/parser/WebIDL.py +++ b/dom/bindings/parser/WebIDL.py @@ -283,6 +283,9 @@ class IDLScope(IDLObject): self._dict[identifier.name] = replacement return + self.addNewIdentifier(identifier, object) + + def addNewIdentifier(self, identifier, object): assert object self._dict[identifier.name] = object @@ -323,6 +326,9 @@ class IDLScope(IDLObject): return originalObject.addOverload(newObject) # Default to throwing, derived classes can override. + raise self.createIdentifierConflictError(identifier, originalObject, newObject) + + def createIdentifierConflictError(self, identifier, originalObject, newObject): conflictdesc = "\n\t%s at %s\n\t%s at %s" % ( originalObject, originalObject.location, @@ -330,7 +336,7 @@ class IDLScope(IDLObject): newObject.location, ) - raise WebIDLError( + return WebIDLError( "Multiple unresolvable definitions of identifier '%s' in scope '%s'%s" % (identifier.name, str(self), conflictdesc), [], @@ -727,6 +733,15 @@ def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): exposureSet.update(globalScope.globalNameMapping[name]) +# Because WebIDL allows static and regular operations with the same identifier +# we use a special class to be able to store them both in the scope for the +# same identifier. +class IDLOperations: + def __init__(self, static=None, regular=None): + self.static = static + self.regular = regular + + class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMixins): def __init__(self, location, parentScope, name): assert isinstance(parentScope, IDLScope) @@ -756,15 +771,66 @@ class IDLInterfaceOrInterfaceMixinOrNamespace(IDLObjectWithScope, IDLExposureMix self.addExtendedAttributes(partial.propagatedExtendedAttrs) self.members.extend(partial.members) + def addNewIdentifier(self, identifier, object): + if isinstance(object, IDLMethod): + if object.isStatic(): + object = IDLOperations(static=object) + else: + object = IDLOperations(regular=object) + + IDLScope.addNewIdentifier(self, identifier, object) + def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): assert isinstance(scope, IDLScope) - assert isinstance(originalObject, IDLInterfaceMember) assert isinstance(newObject, IDLInterfaceMember) + # The identifier of a regular operation or static operation must not be + # the same as the identifier of a constant or attribute. + if isinstance(newObject, IDLMethod) != isinstance( + originalObject, IDLOperations + ): + if isinstance(originalObject, IDLOperations): + if originalObject.regular is not None: + originalObject = originalObject.regular + else: + assert originalObject.static is not None + originalObject = originalObject.static + + raise self.createIdentifierConflictError( + identifier, originalObject, newObject + ) + + if isinstance(newObject, IDLMethod): + originalOperations = originalObject + if newObject.isStatic(): + if originalOperations.static is None: + originalOperations.static = newObject + return originalOperations + + originalObject = originalOperations.static + else: + if originalOperations.regular is None: + originalOperations.regular = newObject + return originalOperations + + originalObject = originalOperations.regular + + assert isinstance(originalObject, IDLMethod) + else: + assert isinstance(originalObject, IDLInterfaceMember) + retval = IDLScope.resolveIdentifierConflict( self, scope, identifier, originalObject, newObject ) + if isinstance(newObject, IDLMethod): + if newObject.isStatic(): + originalOperations.static = retval + else: + originalOperations.regular = retval + + retval = originalOperations + # Might be a ctor, which isn't in self.members if newObject in self.members: self.members.remove(newObject) @@ -995,7 +1061,7 @@ class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): self.location, "constructor", allowForbidden=True ) try: - return self._lookupIdentifier(identifier) + return self._lookupIdentifier(identifier).static except Exception: return None @@ -4568,7 +4634,12 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): for member in members: # Check that there are no disallowed members if member.identifier.name in self.disallowedMemberNames and not ( - (member.isMethod() and member.isMaplikeOrSetlikeOrIterableMethod()) + ( + member.isMethod() + and ( + member.isStatic() or member.isMaplikeOrSetlikeOrIterableMethod() + ) + ) or (member.isAttr() and member.isMaplikeOrSetlikeAttr()) ): raise WebIDLError( @@ -4634,9 +4705,7 @@ class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): self.disallowedNonMethodNames.append(name) # If allowExistingOperations is True, and another operation exists # with the same name as the one we're trying to add, don't add the - # maplike/setlike operation. However, if the operation is static, - # then fail by way of creating the function, which will cause a - # naming conflict, per the spec. + # maplike/setlike operation. if allowExistingOperations: for m in members: if m.identifier.name == name and m.isMethod() and not m.isStatic(): diff --git a/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py index abafda9677f8c19e5b9cb00088f652ce81d8b1bd..d8398d46ba768165f39408e22707d40de937d2e6 100644 --- a/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py +++ b/dom/bindings/parser/tests/test_interface_identifier_conflicts_across_members.py @@ -14,8 +14,9 @@ def WebIDLTest(parser, harness): except Exception: threw = True - harness.ok(threw, "Should have thrown.") + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers1.") + parser = parser.reset() threw = False try: parser.parse( @@ -31,8 +32,9 @@ def WebIDLTest(parser, harness): except Exception: threw = True - harness.ok(threw, "Should have thrown.") + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers2.") + parser = parser.reset() threw = False try: parser.parse( @@ -48,13 +50,14 @@ def WebIDLTest(parser, harness): except Exception: threw = True - harness.ok(threw, "Should have thrown.") + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers3.") + parser = parser.reset() threw = False try: parser.parse( """ - interface IdentifierConflictAcrossMembers1 { + interface IdentifierConflictAcrossMembers4 { const byte thing1 = 1; long thing1(); }; @@ -65,4 +68,101 @@ def WebIDLTest(parser, harness): except Exception: threw = True - harness.ok(threw, "Should have thrown.") + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers4.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers5 { + static long thing1(); + undefined thing1(); + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + not threw, "Should not have thrown for IdentifierConflictAcrossMembers5." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface mixin IdentifierConflictAcrossMembers6Mixin { + undefined thing1(); + }; + interface IdentifierConflictAcrossMembers6 { + static long thing1(); + }; + IdentifierConflictAcrossMembers6 includes IdentifierConflictAcrossMembers6Mixin; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok( + not threw, "Should not have thrown for IdentifierConflictAcrossMembers6." + ) + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers7 { + const byte thing1 = 1; + static readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers7.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers8 { + readonly attribute long thing1 = 1; + static readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers8.") + + parser = parser.reset() + threw = False + try: + parser.parse( + """ + interface IdentifierConflictAcrossMembers9 { + void thing1(); + static readonly attribute long thing1; + }; + """ + ) + + parser.finish() + except Exception: + threw = True + + harness.ok(threw, "Should have thrown for IdentifierConflictAcrossMembers9.") diff --git a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py index dcff52b8c661ff35f69ae432a32ec2d310c572a2..fdd9c009653205393c5d3265eee4bdeb95c1b747 100644 --- a/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py +++ b/dom/bindings/parser/tests/test_interface_maplikesetlikeiterable.py @@ -19,7 +19,10 @@ def WebIDLTest(parser, harness): expectedMembers = list(expectedMembers) for m in results[0].members: name = m.identifier.name - if (name, type(m)) in expectedMembers: + if m.isMethod() and m.isStatic(): + # None of the expected members are static methods, so ignore those. + harness.ok(True, "%s - %s - Should be a %s" % (prefix, name, type(m))) + elif (name, type(m)) in expectedMembers: harness.ok(True, "%s - %s - Should be a %s" % (prefix, name, type(m))) expectedMembers.remove((name, type(m))) else: @@ -91,12 +94,23 @@ def WebIDLTest(parser, harness): valueAsyncIterableMembers = [("__iterable", WebIDL.IDLAsyncIterable)] valueAsyncIterableMembers.append(("values", WebIDL.IDLMethod)) - disallowedIterableNames = ["keys", "entries", "values"] - disallowedMemberNames = ["forEach", "has", "size"] + disallowedIterableNames - mapDisallowedMemberNames = ["get"] + disallowedMemberNames - disallowedNonMethodNames = ["clear", "delete"] - mapDisallowedNonMethodNames = ["set"] + disallowedNonMethodNames - setDisallowedNonMethodNames = ["add"] + disallowedNonMethodNames + disallowedIterableNames = [ + ("keys", WebIDL.IDLMethod), + ("entries", WebIDL.IDLMethod), + ("values", WebIDL.IDLMethod), + ] + disallowedMemberNames = [ + ("forEach", WebIDL.IDLMethod), + ("has", WebIDL.IDLMethod), + ("size", WebIDL.IDLAttribute), + ] + disallowedIterableNames + mapDisallowedMemberNames = [("get", WebIDL.IDLMethod)] + disallowedMemberNames + disallowedNonMethodNames = [ + ("clear", WebIDL.IDLMethod), + ("delete", WebIDL.IDLMethod), + ] + mapDisallowedNonMethodNames = [("set", WebIDL.IDLMethod)] + disallowedNonMethodNames + setDisallowedNonMethodNames = [("add", WebIDL.IDLMethod)] + disallowedNonMethodNames unrelatedMembers = [ ("unrelatedAttribute", WebIDL.IDLAttribute), ("unrelatedMethod", WebIDL.IDLMethod), @@ -560,7 +574,9 @@ def WebIDLTest(parser, harness): # Member name collision tests # - def testConflictingMembers(likeMember, conflictName, expectedMembers, methodPasses): + def testConflictingMembers( + likeMember, conflict, expectedMembers, methodPasses, numProductions=1 + ): """ Tests for maplike/setlike member generation against conflicting member names. If methodPasses is True, this means we expect the interface to @@ -568,6 +584,7 @@ def WebIDLTest(parser, harness): list of interface members to check against on the passing interface. """ + (conflictName, conflictType) = conflict if methodPasses: shouldPass( "Conflicting method: %s and %s" % (likeMember, conflictName), @@ -606,16 +623,30 @@ def WebIDLTest(parser, harness): """ % (conflictName, likeMember), ) - shouldFail( - "Conflicting static method: %s and %s" % (likeMember, conflictName), - """ - interface Foo1 { - %s; - static undefined %s(long test1, double test2, double test3); - }; - """ - % (likeMember, conflictName), - ) + if conflictType == WebIDL.IDLAttribute: + shouldFail( + "Conflicting static method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + static undefined %s(long test1, double test2, double test3); + }; + """ + % (likeMember, conflictName), + ) + else: + shouldPass( + "Conflicting static method: %s and %s" % (likeMember, conflictName), + """ + interface Foo1 { + %s; + static undefined %s(long test1, double test2, double test3); + }; + """ + % (likeMember, conflictName), + expectedMembers, + numProductions=numProductions, + ) shouldFail( "Conflicting attribute: %s and %s" % (likeMember, conflictName), """ @@ -648,7 +679,9 @@ def WebIDLTest(parser, harness): ) for member in disallowedIterableNames: - testConflictingMembers("iterable<long, long>", member, iterableMembers, False) + testConflictingMembers( + "iterable<long, long>", member, iterableMembers, False, numProductions=2 + ) for member in mapDisallowedMemberNames: testConflictingMembers("maplike<long, long>", member, mapRWMembers, False) for member in disallowedMemberNames: