Commit 5a707713 authored by Peter Van der Beken's avatar Peter Van der Beken
Browse files

Bug 1765992 - Support regular and static WebIDL operations with the same...

Bug 1765992 - Support regular and static WebIDL operations with the same identifier on the same interface. r=edgar

Differential Revision: https://phabricator.services.mozilla.com/D171703
parent 2d692f58
Loading
Loading
Loading
Loading
+76 −7
Original line number Diff line number Diff line
@@ -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():
+105 −5
Original line number Diff line number Diff line
@@ -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.")
+52 −19
Original line number Diff line number Diff line
@@ -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,6 +623,7 @@ def WebIDLTest(parser, harness):
                   """
            % (conflictName, likeMember),
        )
        if conflictType == WebIDL.IDLAttribute:
            shouldFail(
                "Conflicting static method: %s and %s" % (likeMember, conflictName),
                """
@@ -616,6 +634,19 @@ def WebIDLTest(parser, harness):
                       """
                % (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: