# 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/. """ A WebIDL parser. """ import copy import math import os import re import string import traceback from collections import OrderedDict, defaultdict from itertools import chain from ply import lex, yacc # Machinery def parseInt(literal): string = literal sign = 0 base = 0 if string[0] == "-": sign = -1 string = string[1:] else: sign = 1 if string[0] == "0" and len(string) > 1: if string[1] == "x" or string[1] == "X": base = 16 string = string[2:] else: base = 8 string = string[1:] else: base = 10 value = int(string, base) return value * sign def enum(*names, **kw): class Foo(object): attrs = OrderedDict() def __init__(self, names): for v, k in enumerate(names): self.attrs[k] = v def __getattr__(self, attr): if attr in self.attrs: return self.attrs[attr] raise AttributeError def __setattr__(self, name, value): # this makes it read-only raise NotImplementedError if "base" not in kw: return Foo(names) return Foo(chain(kw["base"].attrs.keys(), names)) class WebIDLError(Exception): def __init__(self, message, locations, warning=False): self.message = message self.locations = [str(loc) for loc in locations] self.warning = warning def __str__(self): return "%s: %s%s%s" % ( self.warning and "warning" or "error", self.message, ", " if len(self.locations) != 0 else "", "\n".join(self.locations), ) class Location(object): def __init__(self, lexer, lineno, lexpos, filename): self._line = None self._lineno = lineno self._lexpos = lexpos self._lexdata = lexer.lexdata self._file = filename if filename else "" def __eq__(self, other): return self._lexpos == other._lexpos and self._file == other._file def filename(self): return self._file def resolve(self): if self._line: return startofline = self._lexdata.rfind("\n", 0, self._lexpos) + 1 endofline = self._lexdata.find("\n", self._lexpos, self._lexpos + 80) if endofline != -1: self._line = self._lexdata[startofline:endofline] else: self._line = self._lexdata[startofline:] self._colno = self._lexpos - startofline # Our line number seems to point to the start of self._lexdata self._lineno += self._lexdata.count("\n", 0, startofline) def get(self): self.resolve() return "%s line %s:%s" % (self._file, self._lineno, self._colno) def _pointerline(self): return " " * self._colno + "^" def __str__(self): self.resolve() return "%s line %s:%s\n%s\n%s" % ( self._file, self._lineno, self._colno, self._line, self._pointerline(), ) class BuiltinLocation(object): def __init__(self, text): self.msg = text + "\n" def __eq__(self, other): return isinstance(other, BuiltinLocation) and self.msg == other.msg def filename(self): return "" def resolve(self): pass def get(self): return self.msg def __str__(self): return self.get() # Data Model class IDLObject(object): def __init__(self, location): self.location = location self.userData = dict() def filename(self): return self.location.filename() def isInterface(self): return False def isNamespace(self): return False def isInterfaceMixin(self): return False def isEnum(self): return False def isCallback(self): return False def isType(self): return False def isDictionary(self): return False def isUnion(self): return False def isTypedef(self): return False def getUserData(self, key, default): return self.userData.get(key, default) def setUserData(self, key, value): self.userData[key] = value def addExtendedAttributes(self, attrs): assert False # Override me! def handleExtendedAttribute(self, attr): assert False # Override me! def _getDependentObjects(self): assert False # Override me! def getDeps(self, visited=None): """Return a set of files that this object depends on. If any of these files are changed the parser needs to be rerun to regenerate a new IDLObject. The visited argument is a set of all the objects already visited. We must test to see if we are in it, and if so, do nothing. This prevents infinite recursion.""" # NB: We can't use visited=set() above because the default value is # evaluated when the def statement is evaluated, not when the function # is executed, so there would be one set for all invocations. if visited is None: visited = set() if self in visited: return set() visited.add(self) deps = set() if self.filename() != "": deps.add(self.filename()) for d in self._getDependentObjects(): deps.update(d.getDeps(visited)) return deps class IDLScope(IDLObject): def __init__(self, location, parentScope, identifier): IDLObject.__init__(self, location) self.parentScope = parentScope if identifier: assert isinstance(identifier, IDLIdentifier) self._name = identifier else: self._name = None self._dict = {} self.globalNames = set() # A mapping from global name to the set of global interfaces # that have that global name. self.globalNameMapping = defaultdict(set) def __str__(self): return self.QName() def QName(self): # It's possible for us to be called before __init__ has been called, for # the IDLObjectWithScope case. In that case, self._name won't be set yet. if hasattr(self, "_name"): name = self._name else: name = None if name: return name.QName() + "::" return "::" def ensureUnique(self, identifier, object): """ Ensure that there is at most one 'identifier' in scope ('self'). Note that object can be None. This occurs if we end up here for an interface type we haven't seen yet. """ assert isinstance(identifier, IDLUnresolvedIdentifier) assert not object or isinstance(object, IDLObjectWithIdentifier) assert not object or object.identifier == identifier if identifier.name in self._dict: if not object: return # ensureUnique twice with the same object is not allowed assert id(object) != id(self._dict[identifier.name]) replacement = self.resolveIdentifierConflict( self, identifier, self._dict[identifier.name], object ) self._dict[identifier.name] = replacement return self.addNewIdentifier(identifier, object) def addNewIdentifier(self, identifier, object): assert object self._dict[identifier.name] = object def resolveIdentifierConflict(self, scope, identifier, originalObject, newObject): if ( isinstance(originalObject, IDLExternalInterface) and isinstance(newObject, IDLExternalInterface) and originalObject.identifier.name == newObject.identifier.name ): return originalObject if isinstance(originalObject, IDLExternalInterface) or isinstance( newObject, IDLExternalInterface ): raise WebIDLError( "Name collision between " "interface declarations for identifier '%s' at '%s' and '%s'" % (identifier.name, originalObject.location, newObject.location), [], ) if isinstance(originalObject, IDLDictionary) or isinstance( newObject, IDLDictionary ): raise WebIDLError( "Name collision between dictionary declarations for " "identifier '%s'.\n%s\n%s" % (identifier.name, originalObject.location, newObject.location), [], ) # We do the merging of overloads here as opposed to in IDLInterface # because we need to merge overloads of LegacyFactoryFunctions and we need to # detect conflicts in those across interfaces. See also the comment in # IDLInterface.addExtendedAttributes for "LegacyFactoryFunction". if isinstance(originalObject, IDLMethod) and isinstance(newObject, IDLMethod): 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, newObject, newObject.location, ) return WebIDLError( "Multiple unresolvable definitions of identifier '%s' in scope '%s'%s" % (identifier.name, str(self), conflictdesc), [], ) def _lookupIdentifier(self, identifier): return self._dict[identifier.name] def lookupIdentifier(self, identifier): assert isinstance(identifier, IDLIdentifier) assert identifier.scope == self return self._lookupIdentifier(identifier) def addIfaceGlobalNames(self, interfaceName, globalNames): """Record the global names (from |globalNames|) that can be used in [Exposed] to expose things in a global named |interfaceName|""" self.globalNames.update(globalNames) for name in globalNames: self.globalNameMapping[name].add(interfaceName) class IDLIdentifier(IDLObject): def __init__(self, location, scope, name): IDLObject.__init__(self, location) self.name = name assert isinstance(scope, IDLScope) self.scope = scope def __str__(self): return self.QName() def QName(self): return self.scope.QName() + self.name def __hash__(self): return self.QName().__hash__() def __eq__(self, other): return self.QName() == other.QName() def object(self): return self.scope.lookupIdentifier(self) class IDLUnresolvedIdentifier(IDLObject): def __init__( self, location, name, allowDoubleUnderscore=False, allowForbidden=False ): IDLObject.__init__(self, location) assert len(name) > 0 if name == "__noSuchMethod__": raise WebIDLError("__noSuchMethod__ is deprecated", [location]) if name[:2] == "__" and not allowDoubleUnderscore: raise WebIDLError("Identifiers beginning with __ are reserved", [location]) if name[0] == "_" and not allowDoubleUnderscore: name = name[1:] if name in ["constructor", "toString"] and not allowForbidden: raise WebIDLError( "Cannot use reserved identifier '%s'" % (name), [location] ) self.name = name def __str__(self): return self.QName() def QName(self): return "::" + self.name def resolve(self, scope, object): assert isinstance(scope, IDLScope) assert not object or isinstance(object, IDLObjectWithIdentifier) assert not object or object.identifier == self scope.ensureUnique(self, object) identifier = IDLIdentifier(self.location, scope, self.name) if object: object.identifier = identifier return identifier def finish(self): assert False # Should replace with a resolved identifier first. class IDLObjectWithIdentifier(IDLObject): def __init__(self, location, parentScope, identifier): IDLObject.__init__(self, location) assert isinstance(identifier, IDLUnresolvedIdentifier) self.identifier = identifier if parentScope: self.resolve(parentScope) def resolve(self, parentScope): assert isinstance(parentScope, IDLScope) assert isinstance(self.identifier, IDLUnresolvedIdentifier) self.identifier.resolve(parentScope, self) class IDLObjectWithScope(IDLObjectWithIdentifier, IDLScope): def __init__(self, location, parentScope, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) IDLScope.__init__(self, location, parentScope, self.identifier) class IDLIdentifierPlaceholder(IDLObjectWithIdentifier): def __init__(self, location, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) IDLObjectWithIdentifier.__init__(self, location, None, identifier) def finish(self, scope): try: scope._lookupIdentifier(self.identifier) except Exception: raise WebIDLError( "Unresolved type '%s'." % self.identifier, [self.location] ) obj = self.identifier.resolve(scope, None) return scope.lookupIdentifier(obj) class IDLExposureMixins: def __init__(self, location): # _exposureGlobalNames are the global names listed in our [Exposed] # extended attribute. exposureSet is the exposure set as defined in the # Web IDL spec: it contains interface names. self._exposureGlobalNames = set() self.exposureSet = set() self._location = location self._globalScope = None def finish(self, scope): assert scope.parentScope is None self._globalScope = scope if "*" in self._exposureGlobalNames: self._exposureGlobalNames = scope.globalNames else: # Verify that our [Exposed] value, if any, makes sense. for globalName in self._exposureGlobalNames: if globalName not in scope.globalNames: raise WebIDLError( "Unknown [Exposed] value %s" % globalName, [self._location] ) # Verify that we are exposed _somwhere_ if we have some place to be # exposed. We don't want to assert that we're definitely exposed # because a lot of our parser tests have small-enough IDL snippets that # they don't include any globals, and we don't really want to go through # and add global interfaces and [Exposed] annotations to all those # tests. if len(scope.globalNames) != 0 and len(self._exposureGlobalNames) == 0: raise WebIDLError( ( "'%s' is not exposed anywhere even though we have " "globals to be exposed to" ) % self, [self.location], ) globalNameSetToExposureSet(scope, self._exposureGlobalNames, self.exposureSet) def isExposedInWindow(self): return "Window" in self.exposureSet def isExposedInAnyWorker(self): return len(self.getWorkerExposureSet()) > 0 def isExposedInWorkerDebugger(self): return len(self.getWorkerDebuggerExposureSet()) > 0 def isExposedInAnyWorklet(self): return len(self.getWorkletExposureSet()) > 0 def isExposedInSomeButNotAllWorkers(self): """ Returns true if the Exposed extended attribute for this interface exposes it in some worker globals but not others. The return value does not depend on whether the interface is exposed in Window or System globals. """ if not self.isExposedInAnyWorker(): return False workerScopes = self.parentScope.globalNameMapping["Worker"] return len(workerScopes.difference(self.exposureSet)) > 0 def isExposedInShadowRealms(self): return "ShadowRealmGlobalScope" in self.exposureSet def getWorkerExposureSet(self): workerScopes = self._globalScope.globalNameMapping["Worker"] return workerScopes.intersection(self.exposureSet) def getWorkletExposureSet(self): workletScopes = self._globalScope.globalNameMapping["Worklet"] return workletScopes.intersection(self.exposureSet) def getWorkerDebuggerExposureSet(self): workerDebuggerScopes = self._globalScope.globalNameMapping["WorkerDebugger"] return workerDebuggerScopes.intersection(self.exposureSet) class IDLExternalInterface(IDLObjectWithIdentifier): def __init__(self, location, parentScope, identifier): assert isinstance(identifier, IDLUnresolvedIdentifier) assert isinstance(parentScope, IDLScope) self.parent = None IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) IDLObjectWithIdentifier.resolve(self, parentScope) def finish(self, scope): pass def validate(self): pass def isIteratorInterface(self): return False def isAsyncIteratorInterface(self): return False def isExternal(self): return True def isInterface(self): return True def addExtendedAttributes(self, attrs): if len(attrs) != 0: raise WebIDLError( "There are no extended attributes that are " "allowed on external interfaces", [attrs[0].location, self.location], ) def resolve(self, parentScope): pass def getJSImplementation(self): return None def isJSImplemented(self): return False def hasProbablyShortLivingWrapper(self): return False def _getDependentObjects(self): return set() class IDLPartialDictionary(IDLObject): def __init__(self, location, name, members, nonPartialDictionary): assert isinstance(name, IDLUnresolvedIdentifier) IDLObject.__init__(self, location) self.identifier = name self.members = members self._nonPartialDictionary = nonPartialDictionary self._finished = False nonPartialDictionary.addPartialDictionary(self) def addExtendedAttributes(self, attrs): pass def finish(self, scope): if self._finished: return self._finished = True # Need to make sure our non-partial dictionary gets # finished so it can report cases when we only have partial # dictionaries. self._nonPartialDictionary.finish(scope) def validate(self): pass class IDLPartialInterfaceOrNamespace(IDLObject): def __init__(self, location, name, members, nonPartialInterfaceOrNamespace): assert isinstance(name, IDLUnresolvedIdentifier) IDLObject.__init__(self, location) self.identifier = name self.members = members # propagatedExtendedAttrs are the ones that should get # propagated to our non-partial interface. self.propagatedExtendedAttrs = [] self._haveSecureContextExtendedAttribute = False self._nonPartialInterfaceOrNamespace = nonPartialInterfaceOrNamespace self._finished = False nonPartialInterfaceOrNamespace.addPartial(self) def addExtendedAttributes(self, attrs): for attr in attrs: identifier = attr.identifier() if identifier == "LegacyFactoryFunction": self.propagatedExtendedAttrs.append(attr) elif identifier == "SecureContext": self._haveSecureContextExtendedAttribute = True # This gets propagated to all our members. for member in self.members: if member.getExtendedAttribute("SecureContext"): raise WebIDLError( "[SecureContext] specified on both a " "partial interface member and on the " "partial interface itself", [member.location, attr.location], ) member.addExtendedAttributes([attr]) elif identifier == "Exposed": # This just gets propagated to all our members. for member in self.members: if len(member._exposureGlobalNames) != 0: raise WebIDLError( "[Exposed] specified on both a " "partial interface member and on the " "partial interface itself", [member.location, attr.location], ) member.addExtendedAttributes([attr]) else: raise WebIDLError( "Unknown extended attribute %s on partial " "interface" % identifier, [attr.location], ) def finish(self, scope): if self._finished: return self._finished = True if ( not self._haveSecureContextExtendedAttribute and self._nonPartialInterfaceOrNamespace.getExtendedAttribute( "SecureContext" ) ): # This gets propagated to all our members. for member in self.members: if member.getExtendedAttribute("SecureContext"): raise WebIDLError( "[SecureContext] specified on both a " "partial interface member and on the " "non-partial interface", [ member.location, self._nonPartialInterfaceOrNamespace.location, ], ) member.addExtendedAttributes( [ IDLExtendedAttribute( self._nonPartialInterfaceOrNamespace.location, ("SecureContext",), ) ] ) # Need to make sure our non-partial interface or namespace gets # finished so it can report cases when we only have partial # interfaces/namespaces. self._nonPartialInterfaceOrNamespace.finish(scope) def validate(self): pass def convertExposedAttrToGlobalNameSet(exposedAttr, targetSet): assert len(targetSet) == 0 if exposedAttr.hasValue(): targetSet.add(exposedAttr.value()) else: assert exposedAttr.hasArgs() targetSet.update(exposedAttr.args()) def globalNameSetToExposureSet(globalScope, nameSet, exposureSet): for name in nameSet: 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) assert isinstance(name, IDLUnresolvedIdentifier) self._finished = False self.members = [] self._partials = [] self._extendedAttrDict = {} self._isKnownNonPartial = False IDLObjectWithScope.__init__(self, location, parentScope, name) IDLExposureMixins.__init__(self, location) def finish(self, scope): if not self._isKnownNonPartial: raise WebIDLError( "%s does not have a non-partial declaration" % str(self), [self.location], ) IDLExposureMixins.finish(self, scope) # Now go ahead and merge in our partials. for partial in self._partials: partial.finish(scope) 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(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) return retval def typeName(self): if self.isInterface(): return "interface" if self.isNamespace(): return "namespace" assert self.isInterfaceMixin() return "interface mixin" def getExtendedAttribute(self, name): return self._extendedAttrDict.get(name, None) def setNonPartial(self, location, members): if self._isKnownNonPartial: raise WebIDLError( "Two non-partial definitions for the " "same %s" % self.typeName(), [location, self.location], ) self._isKnownNonPartial = True # Now make it look like we were parsed at this new location, since # that's the place where the interface is "really" defined self.location = location # Put the new members at the beginning self.members = members + self.members def addPartial(self, partial): assert self.identifier.name == partial.identifier.name self._partials.append(partial) def getPartials(self): # Don't let people mutate our guts. return list(self._partials) def finishMembers(self, scope): # Assuming we've merged in our partials, set the _exposureGlobalNames on # any members that don't have it set yet. Note that any partial # interfaces that had [Exposed] set have already set up # _exposureGlobalNames on all the members coming from them, so this is # just implementing the "members default to interface or interface mixin # that defined them" and "partial interfaces or interface mixins default # to interface or interface mixin they're a partial for" rules from the # spec. for m in self.members: # If m, or the partial m came from, had [Exposed] # specified, it already has a nonempty exposure global names set. if len(m._exposureGlobalNames) == 0: m._exposureGlobalNames.update(self._exposureGlobalNames) if m.isAttr() and m.stringifier: m.expand(self.members) # 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) # Now that we've finished our members, which has updated their exposure # sets, make sure they aren't exposed in places where we are not. for member in self.members: if not member.exposureSet.issubset(self.exposureSet): raise WebIDLError( "Interface or interface mixin member has " "larger exposure set than its container", [member.location, self.location], ) def isExternal(self): return False class IDLInterfaceMixin(IDLInterfaceOrInterfaceMixinOrNamespace): def __init__(self, location, parentScope, name, members, isKnownNonPartial): self.actualExposureGlobalNames = set() assert isKnownNonPartial or len(members) == 0 IDLInterfaceOrInterfaceMixinOrNamespace.__init__( self, location, parentScope, name ) if isKnownNonPartial: self.setNonPartial(location, members) def __str__(self): return "Interface mixin '%s'" % self.identifier.name def isInterfaceMixin(self): return True def finish(self, scope): if self._finished: return self._finished = True # Expose to the globals of interfaces that includes this mixin if this # mixin has no explicit [Exposed] so that its members can be exposed # based on the base interface exposure set. # # Make sure this is done before IDLExposureMixins.finish call, since # that converts our set of exposure global names to an actual exposure # set. hasImplicitExposure = len(self._exposureGlobalNames) == 0 if hasImplicitExposure: self._exposureGlobalNames.update(self.actualExposureGlobalNames) IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope) self.finishMembers(scope) def validate(self): for member in self.members: if member.isAttr(): if member.inherit: raise WebIDLError( "Interface mixin member cannot include " "an inherited attribute", [member.location, self.location], ) if member.isStatic(): raise WebIDLError( "Interface mixin member cannot include " "a static member", [member.location, self.location], ) if member.isMethod(): if member.isStatic(): raise WebIDLError( "Interface mixin member cannot include " "a static operation", [member.location, self.location], ) if ( member.isGetter() or member.isSetter() or member.isDeleter() or member.isLegacycaller() ): raise WebIDLError( "Interface mixin member cannot include a " "special operation", [member.location, self.location], ) def addExtendedAttributes(self, attrs): for attr in attrs: identifier = attr.identifier() if identifier == "SecureContext": if not attr.noArguments(): raise WebIDLError( "[%s] must take no arguments" % identifier, [attr.location] ) # This gets propagated to all our members. for member in self.members: if member.getExtendedAttribute("SecureContext"): raise WebIDLError( "[SecureContext] specified on both " "an interface mixin member and on" "the interface mixin itself", [member.location, attr.location], ) member.addExtendedAttributes([attr]) elif identifier == "Exposed": convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) else: raise WebIDLError( "Unknown extended attribute %s on interface" % identifier, [attr.location], ) attrlist = attr.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True def _getDependentObjects(self): return set(self.members) class IDLInterfaceOrNamespace(IDLInterfaceOrInterfaceMixinOrNamespace): def __init__(self, location, parentScope, name, parent, members, isKnownNonPartial): assert isKnownNonPartial or not parent assert isKnownNonPartial or len(members) == 0 self.parent = None self._callback = False self.maplikeOrSetlikeOrIterable = None # legacyFactoryFunctions needs deterministic ordering because bindings code # outputs the constructs in the order that legacyFactoryFunctions enumerates # them. self.legacyFactoryFunctions = list() self.legacyWindowAliases = [] self.includedMixins = set() # self.interfacesBasedOnSelf is the set of interfaces that inherit from # self, including self itself. # Used for distinguishability checking. self.interfacesBasedOnSelf = set([self]) self._hasChildInterfaces = False self._isOnGlobalProtoChain = False # Tracking of the number of reserved slots we need for our # members and those of ancestor interfaces. self.totalMembersInSlots = 0 # Tracking of the number of own own members we have in slots self._ownMembersInSlots = 0 # If this is an iterator interface, we need to know what iterable # interface we're iterating for in order to get its nativeType. self.iterableInterface = None self.asyncIterableInterface = None # True if we have cross-origin members. self.hasCrossOriginMembers = False # True if some descendant (including ourselves) has cross-origin members self.hasDescendantWithCrossOriginMembers = False IDLInterfaceOrInterfaceMixinOrNamespace.__init__( self, location, parentScope, name ) if isKnownNonPartial: self.setNonPartial(location, parent, members) def ctor(self): identifier = IDLUnresolvedIdentifier( self.location, "constructor", allowForbidden=True ) try: return self._lookupIdentifier(identifier).static except Exception: return None def isIterable(self): return ( self.maplikeOrSetlikeOrIterable and self.maplikeOrSetlikeOrIterable.isIterable() ) def isAsyncIterable(self): return ( self.maplikeOrSetlikeOrIterable and self.maplikeOrSetlikeOrIterable.isAsyncIterable() ) def isIteratorInterface(self): return self.iterableInterface is not None def isAsyncIteratorInterface(self): return self.asyncIterableInterface is not None def getClassName(self): return self.identifier.name def finish(self, scope): if self._finished: return self._finished = True IDLInterfaceOrInterfaceMixinOrNamespace.finish(self, scope) if len(self.legacyWindowAliases) > 0: if not self.hasInterfaceObject(): raise WebIDLError( "Interface %s unexpectedly has [LegacyWindowAlias] " "and [LegacyNoInterfaceObject] together" % self.identifier.name, [self.location], ) if not self.isExposedInWindow(): raise WebIDLError( "Interface %s has [LegacyWindowAlias] " "but not exposed in Window" % self.identifier.name, [self.location], ) # Generate maplike/setlike interface members. Since generated members # need to be treated like regular interface members, do this before # things like exposure setting. for member in self.members: if member.isMaplikeOrSetlikeOrIterable(): if self.isJSImplemented(): raise WebIDLError( "%s declaration used on " "interface that is implemented in JS" % (member.maplikeOrSetlikeOrIterableType), [member.location], ) if member.valueType.isObservableArray() or ( member.hasKeyType() and member.keyType.isObservableArray() ): raise WebIDLError( "%s declaration uses ObservableArray as value or key type" % (member.maplikeOrSetlikeOrIterableType), [member.location], ) # Check that we only have one interface declaration (currently # there can only be one maplike/setlike declaration per # interface) if self.maplikeOrSetlikeOrIterable: raise WebIDLError( "%s declaration used on " "interface that already has %s " "declaration" % ( member.maplikeOrSetlikeOrIterableType, self.maplikeOrSetlikeOrIterable.maplikeOrSetlikeOrIterableType, ), [self.maplikeOrSetlikeOrIterable.location, member.location], ) self.maplikeOrSetlikeOrIterable = member # If we've got a maplike or setlike declaration, we'll be building all of # our required methods in Codegen. Generate members now. self.maplikeOrSetlikeOrIterable.expand(self.members) assert not self.parent or isinstance(self.parent, IDLIdentifierPlaceholder) parent = self.parent.finish(scope) if self.parent else None if parent and isinstance(parent, IDLExternalInterface): raise WebIDLError( "%s inherits from %s which does not have " "a definition" % (self.identifier.name, self.parent.identifier.name), [self.location], ) if parent and not isinstance(parent, IDLInterface): raise WebIDLError( "%s inherits from %s which is not an interface " % (self.identifier.name, self.parent.identifier.name), [self.location, parent.location], ) self.parent = parent assert iter(self.members) if self.isNamespace(): assert not self.parent for m in self.members: if m.isAttr() or m.isMethod(): if m.isStatic(): raise WebIDLError( "Don't mark things explicitly static " "in namespaces", [self.location, m.location], ) # Just mark all our methods/attributes as static. The other # option is to duplicate the relevant InterfaceMembers # production bits but modified to produce static stuff to # start with, but that sounds annoying. m.forceStatic() if self.parent: self.parent.finish(scope) self.parent._hasChildInterfaces = True self.totalMembersInSlots = self.parent.totalMembersInSlots # Interfaces with [Global] must not have anything inherit from them if self.parent.getExtendedAttribute("Global"): # Note: This is not a self.parent.isOnGlobalProtoChain() check # because ancestors of a [Global] interface can have other # descendants. raise WebIDLError( "[Global] interface has another interface " "inheriting from it", [self.location, self.parent.location], ) # Make sure that we're not exposed in places where our parent is not if not self.exposureSet.issubset(self.parent.exposureSet): raise WebIDLError( "Interface %s is exposed in globals where its " "parent interface %s is not exposed." % (self.identifier.name, self.parent.identifier.name), [self.location, self.parent.location], ) # Callbacks must not inherit from non-callbacks. # XXXbz Can non-callbacks inherit from callbacks? Spec issue pending. if self.isCallback(): if not self.parent.isCallback(): raise WebIDLError( "Callback interface %s inheriting from " "non-callback interface %s" % (self.identifier.name, self.parent.identifier.name), [self.location, self.parent.location], ) elif self.parent.isCallback(): raise WebIDLError( "Non-callback interface %s inheriting from " "callback interface %s" % (self.identifier.name, self.parent.identifier.name), [self.location, self.parent.location], ) # Interfaces which have interface objects can't inherit # from [LegacyNoInterfaceObject] interfaces. if self.parent.getExtendedAttribute( "LegacyNoInterfaceObject" ) and not self.getExtendedAttribute("LegacyNoInterfaceObject"): raise WebIDLError( "Interface %s does not have " "[LegacyNoInterfaceObject] but inherits from " "interface %s which does" % (self.identifier.name, self.parent.identifier.name), [self.location, self.parent.location], ) # Interfaces that are not [SecureContext] can't inherit # from [SecureContext] interfaces. if self.parent.getExtendedAttribute( "SecureContext" ) and not self.getExtendedAttribute("SecureContext"): raise WebIDLError( "Interface %s does not have " "[SecureContext] but inherits from " "interface %s which does" % (self.identifier.name, self.parent.identifier.name), [self.location, self.parent.location], ) for mixin in self.includedMixins: mixin.finish(scope) cycleInGraph = self.findInterfaceLoopPoint(self) if cycleInGraph: raise WebIDLError( "Interface %s has itself as ancestor" % self.identifier.name, [self.location, cycleInGraph.location], ) self.finishMembers(scope) ctor = self.ctor() if ctor is not None: if not self.hasInterfaceObject(): raise WebIDLError( "Can't have both a constructor and [LegacyNoInterfaceObject]", [self.location, ctor.location], ) if self.globalNames: raise WebIDLError( "Can't have both a constructor and [Global]", [self.location, ctor.location], ) assert ctor._exposureGlobalNames == self._exposureGlobalNames ctor._exposureGlobalNames.update(self._exposureGlobalNames) # Remove the constructor operation from our member list so # it doesn't get in the way later. self.members.remove(ctor) for ctor in self.legacyFactoryFunctions: if self.globalNames: raise WebIDLError( "Can't have both a legacy factory function and [Global]", [self.location, ctor.location], ) assert len(ctor._exposureGlobalNames) == 0 ctor._exposureGlobalNames.update(self._exposureGlobalNames) ctor.finish(scope) # Make a copy of our member list, so things that implement us # can get those without all the stuff we implement ourselves # admixed. self.originalMembers = list(self.members) for mixin in sorted(self.includedMixins, key=lambda x: x.identifier.name): for mixinMember in mixin.members: for member in self.members: if mixinMember.identifier.name == member.identifier.name and ( not mixinMember.isMethod() or not member.isMethod() or mixinMember.isStatic() == member.isStatic() ): raise WebIDLError( "Multiple definitions of %s on %s coming from 'includes' statements" % (member.identifier.name, self), [mixinMember.location, member.location], ) self.members.extend(mixin.members) for ancestor in self.getInheritedInterfaces(): ancestor.interfacesBasedOnSelf.add(self) if ( ancestor.maplikeOrSetlikeOrIterable is not None and self.maplikeOrSetlikeOrIterable is not None ): raise WebIDLError( "Cannot have maplike/setlike on %s that " "inherits %s, which is already " "maplike/setlike" % (self.identifier.name, ancestor.identifier.name), [ self.maplikeOrSetlikeOrIterable.location, ancestor.maplikeOrSetlikeOrIterable.location, ], ) # Deal with interfaces marked [LegacyUnforgeable], now that we have our full # member list, except unforgeables pulled in from parents. We want to # do this before we set "originatingInterface" on our unforgeable # members. if self.getExtendedAttribute("LegacyUnforgeable"): # Check that the interface already has all the things the # spec would otherwise require us to synthesize and is # missing the ones we plan to synthesize. if not any(m.isMethod() and m.isStringifier() for m in self.members): raise WebIDLError( "LegacyUnforgeable interface %s does not have a " "stringifier" % self.identifier.name, [self.location], ) for m in self.members: if m.identifier.name == "toJSON": raise WebIDLError( "LegacyUnforgeable interface %s has a " "toJSON so we won't be able to add " "one ourselves" % self.identifier.name, [self.location, m.location], ) if m.identifier.name == "valueOf" and not m.isStatic(): raise WebIDLError( "LegacyUnforgeable interface %s has a valueOf " "member so we won't be able to add one " "ourselves" % self.identifier.name, [self.location, m.location], ) for member in self.members: if ( (member.isAttr() or member.isMethod()) and member.isLegacyUnforgeable() and not hasattr(member, "originatingInterface") ): member.originatingInterface = self for member in self.members: if ( member.isMethod() and member.getExtendedAttribute("CrossOriginCallable") ) or ( member.isAttr() and ( member.getExtendedAttribute("CrossOriginReadable") or member.getExtendedAttribute("CrossOriginWritable") ) ): self.hasCrossOriginMembers = True break if self.hasCrossOriginMembers: parent = self while parent: parent.hasDescendantWithCrossOriginMembers = True parent = parent.parent # Compute slot indices for our members before we pull in unforgeable # members from our parent. Also, maplike/setlike declarations get a # slot to hold their backing object. for member in self.members: if ( member.isAttr() and ( member.getExtendedAttribute("StoreInSlot") or member.getExtendedAttribute("Cached") or member.type.isObservableArray() ) ) or member.isMaplikeOrSetlike(): if self.isJSImplemented() and not member.isMaplikeOrSetlike(): raise WebIDLError( "Interface %s is JS-implemented and we " "don't support [Cached] or [StoreInSlot] or ObservableArray " "on JS-implemented interfaces" % self.identifier.name, [self.location, member.location], ) if member.slotIndices is None: member.slotIndices = dict() member.slotIndices[self.identifier.name] = self.totalMembersInSlots self.totalMembersInSlots += 1 if member.getExtendedAttribute("StoreInSlot"): self._ownMembersInSlots += 1 if self.parent: # Make sure we don't shadow any of the [LegacyUnforgeable] attributes on our # ancestor interfaces. We don't have to worry about mixins here, because # those have already been imported into the relevant .members lists. And # we don't have to worry about anything other than our parent, because it # has already imported its ancestors' unforgeable attributes into its # member list. for unforgeableMember in ( member for member in self.parent.members if (member.isAttr() or member.isMethod()) and member.isLegacyUnforgeable() ): shadows = [ m for m in self.members if (m.isAttr() or m.isMethod()) and not m.isStatic() and m.identifier.name == unforgeableMember.identifier.name ] if len(shadows) != 0: locs = [unforgeableMember.location] + [s.location for s in shadows] raise WebIDLError( "Interface %s shadows [LegacyUnforgeable] " "members of %s" % (self.identifier.name, ancestor.identifier.name), locs, ) # And now just stick it in our members, since we won't be # inheriting this down the proto chain. If we really cared we # could try to do something where we set up the unforgeable # attributes/methods of ancestor interfaces, with their # corresponding getters, on our interface, but that gets pretty # complicated and seems unnecessary. self.members.append(unforgeableMember) # At this point, we have all of our members. If the current interface # uses maplike/setlike, check for collisions anywhere in the current # interface or higher in the inheritance chain. if self.maplikeOrSetlikeOrIterable: testInterface = self isAncestor = False while testInterface: self.maplikeOrSetlikeOrIterable.checkCollisions( testInterface.members, isAncestor ) isAncestor = True testInterface = testInterface.parent # Ensure that there's at most one of each {named,indexed} # {getter,setter,deleter}, at most one stringifier, # and at most one legacycaller. Note that this last is not # quite per spec, but in practice no one overloads # legacycallers. Also note that in practice we disallow # indexed deleters, but it simplifies some other code to # treat deleter analogously to getter/setter by # prefixing it with "named". specialMembersSeen = {} for member in self.members: if not member.isMethod(): continue if member.isGetter(): memberType = "getters" elif member.isSetter(): memberType = "setters" elif member.isDeleter(): memberType = "deleters" elif member.isStringifier(): memberType = "stringifiers" elif member.isLegacycaller(): memberType = "legacycallers" else: continue if memberType != "stringifiers" and memberType != "legacycallers": if member.isNamed(): memberType = "named " + memberType else: assert member.isIndexed() memberType = "indexed " + memberType if memberType in specialMembersSeen: raise WebIDLError( "Multiple " + memberType + " on %s" % (self), [ self.location, specialMembersSeen[memberType].location, member.location, ], ) specialMembersSeen[memberType] = member if self.getExtendedAttribute("LegacyUnenumerableNamedProperties"): # Check that we have a named getter. if "named getters" not in specialMembersSeen: raise WebIDLError( "Interface with [LegacyUnenumerableNamedProperties] does " "not have a named getter", [self.location], ) ancestor = self.parent while ancestor: if ancestor.getExtendedAttribute("LegacyUnenumerableNamedProperties"): raise WebIDLError( "Interface with [LegacyUnenumerableNamedProperties] " "inherits from another interface with " "[LegacyUnenumerableNamedProperties]", [self.location, ancestor.location], ) ancestor = ancestor.parent if self._isOnGlobalProtoChain: # Make sure we have no named setters or deleters for memberType in ["setter", "deleter"]: memberId = "named " + memberType + "s" if memberId in specialMembersSeen: raise WebIDLError( "Interface with [Global] has a named %s" % memberType, [self.location, specialMembersSeen[memberId].location], ) # Make sure we're not [LegacyOverrideBuiltIns] if self.getExtendedAttribute("LegacyOverrideBuiltIns"): raise WebIDLError( "Interface with [Global] also has " "[LegacyOverrideBuiltIns]", [self.location], ) # Mark all of our ancestors as being on the global's proto chain too parent = self.parent while parent: # Must not inherit from an interface with [LegacyOverrideBuiltIns] if parent.getExtendedAttribute("LegacyOverrideBuiltIns"): raise WebIDLError( "Interface with [Global] inherits from " "interface with [LegacyOverrideBuiltIns]", [self.location, parent.location], ) parent._isOnGlobalProtoChain = True parent = parent.parent def validate(self): def checkDuplicateNames(member, name, attributeName): for m in self.members: if m.identifier.name == name: raise WebIDLError( "[%s=%s] has same name as interface member" % (attributeName, name), [member.location, m.location], ) if m.isMethod() and m != member and name in m.aliases: raise WebIDLError( "conflicting [%s=%s] definitions" % (attributeName, name), [member.location, m.location], ) if m.isAttr() and m != member and name in m.bindingAliases: raise WebIDLError( "conflicting [%s=%s] definitions" % (attributeName, name), [member.location, m.location], ) # We also don't support inheriting from unforgeable interfaces. if self.getExtendedAttribute("LegacyUnforgeable") and self.hasChildInterfaces(): locations = [self.location] + list( i.location for i in self.interfacesBasedOnSelf if i.parent == self ) raise WebIDLError( "%s is an unforgeable ancestor interface" % self.identifier.name, locations, ) ctor = self.ctor() if ctor is not None: ctor.validate() for namedCtor in self.legacyFactoryFunctions: namedCtor.validate() indexedGetter = None hasLengthAttribute = False for member in self.members: member.validate() if self.isCallback() and member.getExtendedAttribute("Replaceable"): raise WebIDLError( "[Replaceable] used on an attribute on " "interface %s which is a callback interface" % self.identifier.name, [self.location, member.location], ) # Check that PutForwards refers to another attribute and that no # cycles exist in forwarded assignments. Also check for a # integer-typed "length" attribute. if member.isAttr(): if member.identifier.name == "length" and member.type.isInteger(): hasLengthAttribute = True iface = self attr = member putForwards = attr.getExtendedAttribute("PutForwards") if putForwards and self.isCallback(): raise WebIDLError( "[PutForwards] used on an attribute " "on interface %s which is a callback " "interface" % self.identifier.name, [self.location, member.location], ) while putForwards is not None: forwardIface = attr.type.unroll().inner fowardAttr = None for forwardedMember in forwardIface.members: if ( not forwardedMember.isAttr() or forwardedMember.identifier.name != putForwards[0] ): continue if forwardedMember == member: raise WebIDLError( "Cycle detected in forwarded " "assignments for attribute %s on " "%s" % (member.identifier.name, self), [member.location], ) fowardAttr = forwardedMember break if fowardAttr is None: raise WebIDLError( "Attribute %s on %s forwards to " "missing attribute %s" % (attr.identifier.name, iface, putForwards), [attr.location], ) iface = forwardIface attr = fowardAttr putForwards = attr.getExtendedAttribute("PutForwards") # Check that the name of an [Alias] doesn't conflict with an # interface member and whether we support indexed properties. if member.isMethod(): if member.isGetter() and member.isIndexed(): indexedGetter = member for alias in member.aliases: if self.isOnGlobalProtoChain(): raise WebIDLError( "[Alias] must not be used on a " "[Global] interface operation", [member.location], ) if ( member.getExtendedAttribute("Exposed") or member.getExtendedAttribute("ChromeOnly") or member.getExtendedAttribute("Pref") or member.getExtendedAttribute("Func") or member.getExtendedAttribute("Trial") or member.getExtendedAttribute("SecureContext") ): raise WebIDLError( "[Alias] must not be used on a " "conditionally exposed operation", [member.location], ) if member.isStatic(): raise WebIDLError( "[Alias] must not be used on a " "static operation", [member.location], ) if member.isIdentifierLess(): raise WebIDLError( "[Alias] must not be used on an " "identifierless operation", [member.location], ) if member.isLegacyUnforgeable(): raise WebIDLError( "[Alias] must not be used on an " "[LegacyUnforgeable] operation", [member.location], ) checkDuplicateNames(member, alias, "Alias") # Check that the name of a [BindingAlias] doesn't conflict with an # interface member. if member.isAttr(): for bindingAlias in member.bindingAliases: checkDuplicateNames(member, bindingAlias, "BindingAlias") # Conditional exposure makes no sense for interfaces with no # interface object. # And SecureContext makes sense for interfaces with no interface object, # since it is also propagated to interface members. if ( self.isExposedConditionally(exclusions=["SecureContext"]) and not self.hasInterfaceObject() ): raise WebIDLError( "Interface with no interface object is " "exposed conditionally", [self.location], ) # Value iterators are only allowed on interfaces with indexed getters, # and pair iterators are only allowed on interfaces without indexed # getters. if self.isIterable(): iterableDecl = self.maplikeOrSetlikeOrIterable if iterableDecl.isValueIterator(): if not indexedGetter: raise WebIDLError( "Interface with value iterator does not " "support indexed properties", [self.location, iterableDecl.location], ) if iterableDecl.valueType != indexedGetter.signatures()[0][0]: raise WebIDLError( "Iterable type does not match indexed " "getter type", [iterableDecl.location, indexedGetter.location], ) if not hasLengthAttribute: raise WebIDLError( "Interface with value iterator does not " 'have an integer-typed "length" attribute', [self.location, iterableDecl.location], ) else: assert iterableDecl.isPairIterator() if indexedGetter: raise WebIDLError( "Interface with pair iterator supports " "indexed properties", [self.location, iterableDecl.location, indexedGetter.location], ) if indexedGetter and not hasLengthAttribute: raise WebIDLError( "Interface with an indexed getter does not have " 'an integer-typed "length" attribute', [self.location, indexedGetter.location], ) def setCallback(self, value): self._callback = value def isCallback(self): return self._callback def isSingleOperationInterface(self): assert self.isCallback() or self.isJSImplemented() return ( # JS-implemented things should never need the # this-handling weirdness of single-operation interfaces. not self.isJSImplemented() and # Not inheriting from another interface not self.parent and # No attributes of any kinds not any(m.isAttr() for m in self.members) and # There is at least one regular operation, and all regular # operations have the same identifier len( set( m.identifier.name for m in self.members if m.isMethod() and not m.isStatic() ) ) == 1 ) def inheritanceDepth(self): depth = 0 parent = self.parent while parent: depth = depth + 1 parent = parent.parent return depth def hasConstants(self): return any(m.isConst() for m in self.members) def hasInterfaceObject(self): if self.isCallback(): return self.hasConstants() return not hasattr(self, "_noInterfaceObject") def hasInterfacePrototypeObject(self): return ( not self.isCallback() and not self.isNamespace() and self.getUserData("hasConcreteDescendant", False) ) def addIncludedMixin(self, includedMixin): assert isinstance(includedMixin, IDLInterfaceMixin) self.includedMixins.add(includedMixin) 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 findInterfaceLoopPoint(self, otherInterface): """ Finds an interface amongst our ancestors that inherits from otherInterface. If there is no such interface, returns None. """ if self.parent: if self.parent == otherInterface: return self loopPoint = self.parent.findInterfaceLoopPoint(otherInterface) if loopPoint: return loopPoint return None def setNonPartial(self, location, parent, members): assert not parent or isinstance(parent, IDLIdentifierPlaceholder) IDLInterfaceOrInterfaceMixinOrNamespace.setNonPartial(self, location, members) assert not self.parent self.parent = parent def getJSImplementation(self): classId = self.getExtendedAttribute("JSImplementation") if not classId: return classId assert isinstance(classId, list) assert len(classId) == 1 return classId[0] def isJSImplemented(self): return bool(self.getJSImplementation()) def hasProbablyShortLivingWrapper(self): current = self while current: if current.getExtendedAttribute("ProbablyShortLivingWrapper"): return True current = current.parent return False def hasChildInterfaces(self): return self._hasChildInterfaces def isOnGlobalProtoChain(self): return self._isOnGlobalProtoChain def _getDependentObjects(self): deps = set(self.members) deps.update(self.includedMixins) if self.parent: deps.add(self.parent) return deps def hasMembersInSlots(self): return self._ownMembersInSlots != 0 conditionExtendedAttributes = [ "Pref", "ChromeOnly", "Func", "Trial", "SecureContext", ] def isExposedConditionally(self, exclusions=[]): return any( ((a not in exclusions) and self.getExtendedAttribute(a)) for a in self.conditionExtendedAttributes ) class IDLInterface(IDLInterfaceOrNamespace): def __init__( self, location, parentScope, name, parent, members, isKnownNonPartial, classNameOverride=None, ): IDLInterfaceOrNamespace.__init__( self, location, parentScope, name, parent, members, isKnownNonPartial ) self.classNameOverride = classNameOverride def __str__(self): return "Interface '%s'" % self.identifier.name def isInterface(self): return True def getClassName(self): if self.classNameOverride: return self.classNameOverride return IDLInterfaceOrNamespace.getClassName(self) def addExtendedAttributes(self, attrs): for attr in attrs: identifier = attr.identifier() # Special cased attrs if identifier == "TreatNonCallableAsNull": raise WebIDLError( "TreatNonCallableAsNull cannot be specified on interfaces", [attr.location, self.location], ) if identifier == "LegacyTreatNonObjectAsNull": raise WebIDLError( "LegacyTreatNonObjectAsNull cannot be specified on interfaces", [attr.location, self.location], ) elif identifier == "LegacyNoInterfaceObject": if not attr.noArguments(): raise WebIDLError( "[LegacyNoInterfaceObject] must take no arguments", [attr.location], ) self._noInterfaceObject = True elif identifier == "LegacyFactoryFunction": if not attr.hasValue(): raise WebIDLError( ( "LegacyFactoryFunction must either take an " "identifier or take a named argument list" ), [attr.location], ) args = attr.args() if attr.hasArgs() else [] method = IDLConstructor(attr.location, args, attr.value()) method.reallyInit(self) # Legacy factory functions are always assumed to be able to # throw (since there's no way to indicate otherwise). method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("Throws",))] ) # We need to detect conflicts for LegacyFactoryFunctions across # interfaces. We first call resolve on the parentScope, # which will merge all LegacyFactoryFunctions with the same # identifier accross interfaces as overloads. method.resolve(self.parentScope) # Then we look up the identifier on the parentScope. If the # result is the same as the method we're adding then it # hasn't been added as an overload and it's the first time # we've encountered a LegacyFactoryFunction with that identifier. # If the result is not the same as the method we're adding # then it has been added as an overload and we need to check # whether the result is actually one of our existing # LegacyFactoryFunctions. newMethod = self.parentScope.lookupIdentifier(method.identifier) if newMethod == method: self.legacyFactoryFunctions.append(method) elif newMethod not in self.legacyFactoryFunctions: raise WebIDLError( "LegacyFactoryFunction conflicts with a " "LegacyFactoryFunction of a different interface", [method.location, newMethod.location], ) elif identifier == "ExceptionClass": if not attr.noArguments(): raise WebIDLError( "[ExceptionClass] must take no arguments", [attr.location] ) if self.parent: raise WebIDLError( "[ExceptionClass] must not be specified on " "an interface with inherited interfaces", [attr.location, self.location], ) elif identifier == "Global": if attr.hasValue(): self.globalNames = [attr.value()] elif attr.hasArgs(): self.globalNames = attr.args() else: self.globalNames = [self.identifier.name] self.parentScope.addIfaceGlobalNames( self.identifier.name, self.globalNames ) self._isOnGlobalProtoChain = True elif identifier == "LegacyWindowAlias": if attr.hasValue(): self.legacyWindowAliases = [attr.value()] elif attr.hasArgs(): self.legacyWindowAliases = attr.args() else: raise WebIDLError( "[%s] must either take an identifier " "or take an identifier list" % identifier, [attr.location], ) for alias in self.legacyWindowAliases: unresolved = IDLUnresolvedIdentifier(attr.location, alias) IDLObjectWithIdentifier(attr.location, self.parentScope, unresolved) elif identifier == "SecureContext": if not attr.noArguments(): raise WebIDLError( "[%s] must take no arguments" % identifier, [attr.location] ) # This gets propagated to all our members. for member in self.members: if member.getExtendedAttribute("SecureContext"): raise WebIDLError( "[SecureContext] specified on both " "an interface member and on the " "interface itself", [member.location, attr.location], ) member.addExtendedAttributes([attr]) elif ( identifier == "NeedResolve" or identifier == "LegacyOverrideBuiltIns" or identifier == "ChromeOnly" or identifier == "LegacyUnforgeable" or identifier == "LegacyEventInit" or identifier == "ProbablyShortLivingWrapper" or identifier == "LegacyUnenumerableNamedProperties" or identifier == "RunConstructorInCallerCompartment" or identifier == "WantsEventListenerHooks" or identifier == "Serializable" ): # Known extended attributes that do not take values if not attr.noArguments(): raise WebIDLError( "[%s] must take no arguments" % identifier, [attr.location] ) elif identifier == "Exposed": convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) elif ( identifier == "Pref" or identifier == "JSImplementation" or identifier == "HeaderFile" or identifier == "Func" or identifier == "Trial" or identifier == "Deprecated" ): # Known extended attributes that take a string value if not attr.hasValue(): raise WebIDLError( "[%s] must have a value" % identifier, [attr.location] ) elif identifier == "InstrumentedProps": # Known extended attributes that take a list if not attr.hasArgs(): raise WebIDLError( "[%s] must have arguments" % identifier, [attr.location] ) else: raise WebIDLError( "Unknown extended attribute %s on interface" % identifier, [attr.location], ) attrlist = attr.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True def validate(self): IDLInterfaceOrNamespace.validate(self) if self.parent and self.isSerializable() and not self.parent.isSerializable(): raise WebIDLError( "Serializable interface inherits from non-serializable " "interface. Per spec, that means the object should not be " "serializable, so chances are someone made a mistake here " "somewhere.", [self.location, self.parent.location], ) def isSerializable(self): return self.getExtendedAttribute("Serializable") def setNonPartial(self, location, parent, members): # Before we do anything else, finish initializing any constructors that # might be in "members", so we don't have partially-initialized objects # hanging around. We couldn't do it before now because we needed to have # to have the IDLInterface on hand to properly set the return type. for member in members: if isinstance(member, IDLConstructor): member.reallyInit(self) IDLInterfaceOrNamespace.setNonPartial(self, location, parent, members) class IDLNamespace(IDLInterfaceOrNamespace): def __init__(self, location, parentScope, name, members, isKnownNonPartial): IDLInterfaceOrNamespace.__init__( self, location, parentScope, name, None, members, isKnownNonPartial ) def __str__(self): return "Namespace '%s'" % self.identifier.name def isNamespace(self): return True def addExtendedAttributes(self, attrs): # The set of things namespaces support is small enough it's simpler # to factor out into a separate method than it is to sprinkle # isNamespace() checks all through # IDLInterfaceOrNamespace.addExtendedAttributes. for attr in attrs: identifier = attr.identifier() if identifier == "Exposed": convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) elif identifier == "ClassString": # Takes a string value to override the default "Object" if # desired. if not attr.hasValue(): raise WebIDLError( "[%s] must have a value" % identifier, [attr.location] ) elif identifier == "ProtoObjectHack" or identifier == "ChromeOnly": if not attr.noArguments(): raise WebIDLError( "[%s] must not have arguments" % identifier, [attr.location] ) elif ( identifier == "Pref" or identifier == "HeaderFile" or identifier == "Func" or identifier == "Trial" ): # Known extended attributes that take a string value if not attr.hasValue(): raise WebIDLError( "[%s] must have a value" % identifier, [attr.location] ) else: raise WebIDLError( "Unknown extended attribute %s on namespace" % identifier, [attr.location], ) attrlist = attr.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True def isSerializable(self): return False class IDLDictionary(IDLObjectWithScope): def __init__(self, location, parentScope, name, parent, members): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) assert not parent or isinstance(parent, IDLIdentifierPlaceholder) self.parent = parent self._finished = False self.members = list(members) self._partialDictionaries = [] self._extendedAttrDict = {} self.needsConversionToJS = False self.needsConversionFromJS = False IDLObjectWithScope.__init__(self, location, parentScope, name) def __str__(self): return "Dictionary '%s'" % self.identifier.name def isDictionary(self): return True def canBeEmpty(self): """ Returns true if this dictionary can be empty (that is, it has no required members and neither do any of its ancestors). """ return all(member.optional for member in self.members) and ( not self.parent or self.parent.canBeEmpty() ) def finish(self, scope): if self._finished: return self._finished = True if self.parent: assert isinstance(self.parent, IDLIdentifierPlaceholder) oldParent = self.parent self.parent = self.parent.finish(scope) if not isinstance(self.parent, IDLDictionary): raise WebIDLError( "Dictionary %s has parent that is not a dictionary" % self.identifier.name, [oldParent.location, self.parent.location], ) # Make sure the parent resolves all its members before we start # looking at them. self.parent.finish(scope) # Now go ahead and merge in our partial dictionaries. for partial in self._partialDictionaries: partial.finish(scope) self.members.extend(partial.members) for member in self.members: member.resolve(self) if not member.isComplete(): member.complete(scope) assert member.type.isComplete() # Members of a dictionary are sorted in lexicographic order, # unless the dictionary opts out. if not self.getExtendedAttribute("Unsorted"): self.members.sort(key=lambda x: x.identifier.name) inheritedMembers = [] ancestor = self.parent while ancestor: if ancestor == self: raise WebIDLError( "Dictionary %s has itself as an ancestor" % self.identifier.name, [self.identifier.location], ) inheritedMembers.extend(ancestor.members) ancestor = ancestor.parent # Catch name duplication for inheritedMember in inheritedMembers: for member in self.members: if member.identifier.name == inheritedMember.identifier.name: raise WebIDLError( "Dictionary %s has two members with name %s" % (self.identifier.name, member.identifier.name), [member.location, inheritedMember.location], ) def validate(self): def typeContainsDictionary(memberType, dictionary): """ Returns a tuple whose: - First element is a Boolean value indicating whether memberType contains dictionary. - Second element is: A list of locations that leads from the type that was passed in the memberType argument, to the dictionary being validated, if the boolean value in the first element is True. None, if the boolean value in the first element is False. """ if ( memberType.nullable() or memberType.isSequence() or memberType.isRecord() ): return typeContainsDictionary(memberType.inner, dictionary) if memberType.isDictionary(): if memberType.inner == dictionary: return (True, [memberType.location]) (contains, locations) = dictionaryContainsDictionary( memberType.inner, dictionary ) if contains: return (True, [memberType.location] + locations) if memberType.isUnion(): for member in memberType.flatMemberTypes: (contains, locations) = typeContainsDictionary(member, dictionary) if contains: return (True, locations) return (False, None) def dictionaryContainsDictionary(dictMember, dictionary): for member in dictMember.members: (contains, locations) = typeContainsDictionary(member.type, dictionary) if contains: return (True, [member.location] + locations) if dictMember.parent: if dictMember.parent == dictionary: return (True, [dictMember.location]) else: (contains, locations) = dictionaryContainsDictionary( dictMember.parent, dictionary ) if contains: return (True, [dictMember.location] + locations) return (False, None) for member in self.members: if member.type.isDictionary() and member.type.nullable(): raise WebIDLError( "Dictionary %s has member with nullable " "dictionary type" % self.identifier.name, [member.location], ) (contains, locations) = typeContainsDictionary(member.type, self) if contains: raise WebIDLError( "Dictionary %s has member with itself as type." % self.identifier.name, [member.location] + locations, ) if member.type.isUndefined(): raise WebIDLError( "Dictionary %s has member with undefined as its type." % self.identifier.name, [member.location], ) elif member.type.isUnion(): for unionMember in member.type.unroll().flatMemberTypes: if unionMember.isUndefined(): raise WebIDLError( "Dictionary %s has member with a union containing " "undefined as a type." % self.identifier.name, [unionMember.location], ) def getExtendedAttribute(self, name): return self._extendedAttrDict.get(name, None) def addExtendedAttributes(self, attrs): for attr in attrs: identifier = attr.identifier() if identifier == "GenerateInitFromJSON" or identifier == "GenerateInit": if not attr.noArguments(): raise WebIDLError( "[%s] must not have arguments" % identifier, [attr.location] ) self.needsConversionFromJS = True elif ( identifier == "GenerateConversionToJS" or identifier == "GenerateToJSON" ): if not attr.noArguments(): raise WebIDLError( "[%s] must not have arguments" % identifier, [attr.location] ) # ToJSON methods require to-JS conversion, because we # implement ToJSON by converting to a JS object and # then using JSON.stringify. self.needsConversionToJS = True elif identifier == "Unsorted": if not attr.noArguments(): raise WebIDLError( "[Unsorted] must take no arguments", [attr.location] ) else: raise WebIDLError( "[%s] extended attribute not allowed on " "dictionaries" % identifier, [attr.location], ) self._extendedAttrDict[identifier] = True def _getDependentObjects(self): deps = set(self.members) if self.parent: deps.add(self.parent) return deps def addPartialDictionary(self, partial): assert self.identifier.name == partial.identifier.name self._partialDictionaries.append(partial) class IDLEnum(IDLObjectWithIdentifier): def __init__(self, location, parentScope, name, values): assert isinstance(parentScope, IDLScope) assert isinstance(name, IDLUnresolvedIdentifier) if len(values) != len(set(values)): raise WebIDLError( "Enum %s has multiple identical strings" % name.name, [location] ) IDLObjectWithIdentifier.__init__(self, location, parentScope, name) self._values = values def values(self): return self._values def finish(self, scope): pass def validate(self): pass def isEnum(self): return True def addExtendedAttributes(self, attrs): if len(attrs) != 0: raise WebIDLError( "There are no extended attributes that are " "allowed on enums", [attrs[0].location, self.location], ) def _getDependentObjects(self): return set() class IDLType(IDLObject): Tags = enum( # The integer types "int8", "uint8", "int16", "uint16", "int32", "uint32", "int64", "uint64", # Additional primitive types "bool", "unrestricted_float", "float", "unrestricted_double", # "double" last primitive type to match IDLBuiltinType "double", # Other types "any", "undefined", "domstring", "bytestring", "usvstring", "utf8string", "jsstring", "object", # Funny stuff "interface", "dictionary", "enum", "callback", "union", "sequence", "record", "promise", "observablearray", ) def __init__(self, location, name): IDLObject.__init__(self, location) self.name = name self.builtin = False self.legacyNullToEmptyString = False self._clamp = False self._enforceRange = False self._allowShared = False self._extendedAttrDict = {} def __hash__(self): return ( hash(self.builtin) + hash(self.name) + hash(self._clamp) + hash(self._enforceRange) + hash(self.legacyNullToEmptyString) + hash(self._allowShared) ) def __eq__(self, other): return ( other and self.builtin == other.builtin and self.name == other.name and self._clamp == other.hasClamp() and self._enforceRange == other.hasEnforceRange() and self.legacyNullToEmptyString == other.legacyNullToEmptyString and self._allowShared == other.hasAllowShared() ) def __ne__(self, other): return not self == other def __str__(self): return str(self.name) def prettyName(self): """ A name that looks like what this type is named in the IDL spec. By default this is just our .name, but types that have more interesting spec representations should override this. """ return str(self.name) def isType(self): return True def nullable(self): return False def isPrimitive(self): return False def isBoolean(self): return False def isNumeric(self): return False def isString(self): return False def isByteString(self): return False def isDOMString(self): return False def isUSVString(self): return False def isUTF8String(self): return False def isJSString(self): return False def isUndefined(self): return False def isSequence(self): return False def isRecord(self): return False def isArrayBuffer(self): return False def isArrayBufferView(self): return False def isTypedArray(self): return False def isBufferSource(self): return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray() def isCallbackInterface(self): return False def isNonCallbackInterface(self): return False def isGeckoInterface(self): """Returns a boolean indicating whether this type is an 'interface' type that is implemented in Gecko. At the moment, this returns true for all interface types that are not types from the TypedArray spec.""" return self.isInterface() and not self.isSpiderMonkeyInterface() def isSpiderMonkeyInterface(self): """Returns a boolean indicating whether this type is an 'interface' type that is implemented in SpiderMonkey.""" return self.isInterface() and self.isBufferSource() def isAny(self): return self.tag() == IDLType.Tags.any def isObject(self): return self.tag() == IDLType.Tags.object def isPromise(self): return False def isComplete(self): return True def includesRestrictedFloat(self): return False def isFloat(self): return False def isUnrestricted(self): # Should only call this on float types assert self.isFloat() def isJSONType(self): return False def isObservableArray(self): return False def isDictionaryLike(self): return self.isDictionary() or self.isRecord() or self.isCallbackInterface() def hasClamp(self): return self._clamp def hasEnforceRange(self): return self._enforceRange def hasAllowShared(self): return self._allowShared def tag(self): assert False # Override me! def treatNonCallableAsNull(self): assert self.tag() == IDLType.Tags.callback return self.nullable() and self.inner.callback._treatNonCallableAsNull def treatNonObjectAsNull(self): assert self.tag() == IDLType.Tags.callback return self.nullable() and self.inner.callback._treatNonObjectAsNull def withExtendedAttributes(self, attrs): if len(attrs) > 0: raise WebIDLError( "Extended attributes on types only supported for builtins", [attrs[0].location, self.location], ) return self def getExtendedAttribute(self, name): return self._extendedAttrDict.get(name, None) def resolveType(self, parentScope): pass def unroll(self): return self def isDistinguishableFrom(self, other): raise TypeError( "Can't tell whether a generic type is or is not " "distinguishable from other things" ) def isExposedInAllOf(self, exposureSet): return True class IDLUnresolvedType(IDLType): """ Unresolved types are interface types """ def __init__(self, location, name, attrs=[]): IDLType.__init__(self, location, name) self.extraTypeAttributes = attrs def isComplete(self): return False def complete(self, scope): obj = None try: obj = scope._lookupIdentifier(self.name) except Exception: raise WebIDLError("Unresolved type '%s'." % self.name, [self.location]) assert obj assert not obj.isType() if obj.isTypedef(): assert self.name.name == obj.identifier.name typedefType = IDLTypedefType(self.location, obj.innerType, obj.identifier) assert not typedefType.isComplete() return typedefType.complete(scope).withExtendedAttributes( self.extraTypeAttributes ) elif obj.isCallback() and not obj.isInterface(): assert self.name.name == obj.identifier.name return IDLCallbackType(self.location, obj) return IDLWrapperType(self.location, obj) def withExtendedAttributes(self, attrs): return IDLUnresolvedType(self.location, self.name, attrs) def isDistinguishableFrom(self, other): raise TypeError( "Can't tell whether an unresolved type is or is not " "distinguishable from other things" ) class IDLParametrizedType(IDLType): def __init__(self, location, name, innerType): IDLType.__init__(self, location, name) self.builtin = False self.inner = innerType def includesRestrictedFloat(self): return self.inner.includesRestrictedFloat() def resolveType(self, parentScope): assert isinstance(parentScope, IDLScope) self.inner.resolveType(parentScope) def isComplete(self): return self.inner.isComplete() def unroll(self): return self.inner.unroll() def _getDependentObjects(self): return self.inner._getDependentObjects() class IDLNullableType(IDLParametrizedType): def __init__(self, location, innerType): assert not innerType == BuiltinTypes[IDLBuiltinType.Types.any] IDLParametrizedType.__init__(self, location, None, innerType) def __hash__(self): return hash(self.inner) def __eq__(self, other): return isinstance(other, IDLNullableType) and self.inner == other.inner def __str__(self): return self.inner.__str__() + "OrNull" def prettyName(self): return self.inner.prettyName() + "?" def nullable(self): return True def isCallback(self): return self.inner.isCallback() def isPrimitive(self): return self.inner.isPrimitive() def isBoolean(self): return self.inner.isBoolean() def isNumeric(self): return self.inner.isNumeric() def isString(self): return self.inner.isString() def isByteString(self): return self.inner.isByteString() def isDOMString(self): return self.inner.isDOMString() def isUSVString(self): return self.inner.isUSVString() def isUTF8String(self): return self.inner.isUTF8String() def isJSString(self): return self.inner.isJSString() def isFloat(self): return self.inner.isFloat() def isUnrestricted(self): return self.inner.isUnrestricted() def isInteger(self): return self.inner.isInteger() def isUndefined(self): return self.inner.isUndefined() def isSequence(self): return self.inner.isSequence() def isRecord(self): return self.inner.isRecord() def isArrayBuffer(self): return self.inner.isArrayBuffer() def isArrayBufferView(self): return self.inner.isArrayBufferView() def isTypedArray(self): return self.inner.isTypedArray() def isDictionary(self): return self.inner.isDictionary() def isInterface(self): return self.inner.isInterface() def isPromise(self): # There is no such thing as a nullable Promise. assert not self.inner.isPromise() return False def isCallbackInterface(self): return self.inner.isCallbackInterface() def isNonCallbackInterface(self): return self.inner.isNonCallbackInterface() def isEnum(self): return self.inner.isEnum() def isUnion(self): return self.inner.isUnion() def isJSONType(self): return self.inner.isJSONType() def isObservableArray(self): return self.inner.isObservableArray() def hasClamp(self): return self.inner.hasClamp() def hasEnforceRange(self): return self.inner.hasEnforceRange() def hasAllowShared(self): return self.inner.hasAllowShared() def isComplete(self): return self.name is not None def tag(self): return self.inner.tag() def complete(self, scope): if not self.inner.isComplete(): self.inner = self.inner.complete(scope) assert self.inner.isComplete() if self.inner.nullable(): raise WebIDLError( "The inner type of a nullable type must not be a nullable type", [self.location, self.inner.location], ) if self.inner.isUnion(): if self.inner.hasNullableType: raise WebIDLError( "The inner type of a nullable type must not " "be a union type that itself has a nullable " "type as a member type", [self.location], ) if self.inner.isDOMString(): if self.inner.legacyNullToEmptyString: raise WebIDLError( "[LegacyNullToEmptyString] not allowed on a nullable DOMString", [self.location, self.inner.location], ) if self.inner.isObservableArray(): raise WebIDLError( "The inner type of a nullable type must not be an ObservableArray type", [self.location, self.inner.location], ) self.name = self.inner.name + "OrNull" return self def isDistinguishableFrom(self, other): if ( other.nullable() or other.isDictionary() or ( other.isUnion() and (other.hasNullableType or other.hasDictionaryType()) ) ): # Can't tell which type null should become return False return self.inner.isDistinguishableFrom(other) def withExtendedAttributes(self, attrs): # See https://github.com/heycam/webidl/issues/827#issuecomment-565131350 # Allowing extended attributes to apply to a nullable type is an intermediate solution. # A potential longer term solution is to introduce a null type and get rid of nullables. # For example, we could do `([Clamp] long or null) foo` in the future. return IDLNullableType(self.location, self.inner.withExtendedAttributes(attrs)) class IDLSequenceType(IDLParametrizedType): def __init__(self, location, parameterType): assert not parameterType.isUndefined() IDLParametrizedType.__init__(self, location, parameterType.name, parameterType) # Need to set self.name up front if our inner type is already complete, # since in that case our .complete() won't be called. if self.inner.isComplete(): self.name = self.inner.name + "Sequence" def __hash__(self): return hash(self.inner) def __eq__(self, other): return isinstance(other, IDLSequenceType) and self.inner == other.inner def __str__(self): return self.inner.__str__() + "Sequence" def prettyName(self): return "sequence<%s>" % self.inner.prettyName() def isSequence(self): return True def isJSONType(self): return self.inner.isJSONType() def tag(self): return IDLType.Tags.sequence def complete(self, scope): if self.inner.isObservableArray(): raise WebIDLError( "The inner type of a sequence type must not be an ObservableArray type", [self.location, self.inner.location], ) self.inner = self.inner.complete(scope) self.name = self.inner.name + "Sequence" return self def isDistinguishableFrom(self, other): if other.isPromise(): return False if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return ( other.isUndefined() or other.isPrimitive() or other.isString() or other.isEnum() or other.isInterface() or other.isDictionary() or other.isCallback() or other.isRecord() ) class IDLRecordType(IDLParametrizedType): def __init__(self, location, keyType, valueType): assert keyType.isString() assert keyType.isComplete() if valueType.isUndefined(): raise WebIDLError( "We don't support undefined as a Record's values' type", [location, valueType.location], ) IDLParametrizedType.__init__(self, location, valueType.name, valueType) self.keyType = keyType # Need to set self.name up front if our inner type is already complete, # since in that case our .complete() won't be called. if self.inner.isComplete(): self.name = self.keyType.name + self.inner.name + "Record" def __hash__(self): return hash(self.inner) def __eq__(self, other): return isinstance(other, IDLRecordType) and self.inner == other.inner def __str__(self): return self.keyType.__str__() + self.inner.__str__() + "Record" def prettyName(self): return "record<%s, %s>" % (self.keyType.prettyName(), self.inner.prettyName()) def isRecord(self): return True def isJSONType(self): return self.inner.isJSONType() def tag(self): return IDLType.Tags.record def complete(self, scope): if self.inner.isObservableArray(): raise WebIDLError( "The value type of a record type must not be an ObservableArray type", [self.location, self.inner.location], ) self.inner = self.inner.complete(scope) self.name = self.keyType.name + self.inner.name + "Record" return self def unroll(self): # We do not unroll our inner. Just stop at ourselves. That # lets us add headers for both ourselves and our inner as # needed. return self def isDistinguishableFrom(self, other): if other.isPromise(): return False if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return ( other.isPrimitive() or other.isString() or other.isEnum() or other.isNonCallbackInterface() or other.isSequence() ) def isExposedInAllOf(self, exposureSet): return self.inner.unroll().isExposedInAllOf(exposureSet) class IDLObservableArrayType(IDLParametrizedType): def __init__(self, location, innerType): assert not innerType.isUndefined() IDLParametrizedType.__init__(self, location, None, innerType) def __hash__(self): return hash(self.inner) def __eq__(self, other): return isinstance(other, IDLObservableArrayType) and self.inner == other.inner def __str__(self): return self.inner.__str__() + "ObservableArray" def prettyName(self): return "ObservableArray<%s>" % self.inner.prettyName() def isJSONType(self): return self.inner.isJSONType() def isObservableArray(self): return True def isComplete(self): return self.name is not None def tag(self): return IDLType.Tags.observablearray def complete(self, scope): if not self.inner.isComplete(): self.inner = self.inner.complete(scope) assert self.inner.isComplete() if self.inner.isDictionary(): raise WebIDLError( "The inner type of an ObservableArray type must not " "be a dictionary type", [self.location, self.inner.location], ) if self.inner.isSequence(): raise WebIDLError( "The inner type of an ObservableArray type must not " "be a sequence type", [self.location, self.inner.location], ) if self.inner.isRecord(): raise WebIDLError( "The inner type of an ObservableArray type must not be a record type", [self.location, self.inner.location], ) if self.inner.isObservableArray(): raise WebIDLError( "The inner type of an ObservableArray type must not " "be an ObservableArray type", [self.location, self.inner.location], ) self.name = self.inner.name + "ObservableArray" return self def isDistinguishableFrom(self, other): # ObservableArrays are not distinguishable from anything. return False class IDLUnionType(IDLType): def __init__(self, location, memberTypes): IDLType.__init__(self, location, "") self.memberTypes = memberTypes self.hasNullableType = False self._dictionaryType = None self.flatMemberTypes = None self.builtin = False def __eq__(self, other): return isinstance(other, IDLUnionType) and self.memberTypes == other.memberTypes def __hash__(self): assert self.isComplete() return self.name.__hash__() def prettyName(self): return "(" + " or ".join(m.prettyName() for m in self.memberTypes) + ")" def isUnion(self): return True def isJSONType(self): return all(m.isJSONType() for m in self.memberTypes) def includesRestrictedFloat(self): return any(t.includesRestrictedFloat() for t in self.memberTypes) def tag(self): return IDLType.Tags.union def resolveType(self, parentScope): assert isinstance(parentScope, IDLScope) for t in self.memberTypes: t.resolveType(parentScope) def isComplete(self): return self.flatMemberTypes is not None def complete(self, scope): def typeName(type): if isinstance(type, IDLNullableType): return typeName(type.inner) + "OrNull" if isinstance(type, IDLWrapperType): return typeName(type._identifier.object()) if isinstance(type, IDLObjectWithIdentifier): return typeName(type.identifier) if isinstance(type, IDLBuiltinType) and type.hasAllowShared(): assert type.isBufferSource() return "MaybeShared" + type.name return type.name for i, type in enumerate(self.memberTypes): if not type.isComplete(): self.memberTypes[i] = type.complete(scope) self.name = "Or".join(typeName(type) for type in self.memberTypes) self.flatMemberTypes = list(self.memberTypes) i = 0 while i < len(self.flatMemberTypes): if self.flatMemberTypes[i].nullable(): if self.hasNullableType: raise WebIDLError( "Can't have more than one nullable types in a union", [nullableType.location, self.flatMemberTypes[i].location], ) if self.hasDictionaryType(): raise WebIDLError( "Can't have a nullable type and a " "dictionary type in a union", [ self._dictionaryType.location, self.flatMemberTypes[i].location, ], ) self.hasNullableType = True nullableType = self.flatMemberTypes[i] self.flatMemberTypes[i] = self.flatMemberTypes[i].inner continue if self.flatMemberTypes[i].isDictionary(): if self.hasNullableType: raise WebIDLError( "Can't have a nullable type and a " "dictionary type in a union", [nullableType.location, self.flatMemberTypes[i].location], ) self._dictionaryType = self.flatMemberTypes[i] self.flatMemberTypes[i].inner.needsConversionFromJS = True elif self.flatMemberTypes[i].isUnion(): self.flatMemberTypes[i : i + 1] = self.flatMemberTypes[i].memberTypes continue i += 1 for i, t in enumerate(self.flatMemberTypes[:-1]): for u in self.flatMemberTypes[i + 1 :]: if not t.isDistinguishableFrom(u): raise WebIDLError( "Flat member types of a union should be " "distinguishable, " + str(t) + " is not " "distinguishable from " + str(u), [self.location, t.location, u.location], ) return self def isDistinguishableFrom(self, other): if self.hasNullableType and other.nullable(): # Can't tell which type null should become return False if other.isUnion(): otherTypes = other.unroll().memberTypes else: otherTypes = [other] # For every type in otherTypes, check that it's distinguishable from # every type in our types for u in otherTypes: if any(not t.isDistinguishableFrom(u) for t in self.memberTypes): return False return True def isExposedInAllOf(self, exposureSet): # We could have different member types in different globals. # Just make sure that each thing in exposureSet has one of our member types exposed in it. for globalName in exposureSet: if not any( t.unroll().isExposedInAllOf(set([globalName])) for t in self.flatMemberTypes ): return False return True def hasDictionaryType(self): return self._dictionaryType is not None def hasPossiblyEmptyDictionaryType(self): return ( self._dictionaryType is not None and self._dictionaryType.inner.canBeEmpty() ) def _getDependentObjects(self): return set(self.memberTypes) class IDLTypedefType(IDLType): def __init__(self, location, innerType, name): IDLType.__init__(self, location, name) self.inner = innerType self.builtin = False def __hash__(self): return hash(self.inner) def __eq__(self, other): return isinstance(other, IDLTypedefType) and self.inner == other.inner def __str__(self): return self.name def nullable(self): return self.inner.nullable() def isPrimitive(self): return self.inner.isPrimitive() def isBoolean(self): return self.inner.isBoolean() def isNumeric(self): return self.inner.isNumeric() def isString(self): return self.inner.isString() def isByteString(self): return self.inner.isByteString() def isDOMString(self): return self.inner.isDOMString() def isUSVString(self): return self.inner.isUSVString() def isUTF8String(self): return self.inner.isUTF8String() def isJSString(self): return self.inner.isJSString() def isUndefined(self): return self.inner.isUndefined() def isJSONType(self): return self.inner.isJSONType() def isSequence(self): return self.inner.isSequence() def isRecord(self): return self.inner.isRecord() def isDictionary(self): return self.inner.isDictionary() def isArrayBuffer(self): return self.inner.isArrayBuffer() def isArrayBufferView(self): return self.inner.isArrayBufferView() def isTypedArray(self): return self.inner.isTypedArray() def isInterface(self): return self.inner.isInterface() def isCallbackInterface(self): return self.inner.isCallbackInterface() def isNonCallbackInterface(self): return self.inner.isNonCallbackInterface() def isComplete(self): return False def complete(self, parentScope): if not self.inner.isComplete(): self.inner = self.inner.complete(parentScope) assert self.inner.isComplete() return self.inner # Do we need a resolveType impl? I don't think it's particularly useful.... def tag(self): return self.inner.tag() def unroll(self): return self.inner.unroll() def isDistinguishableFrom(self, other): return self.inner.isDistinguishableFrom(other) def _getDependentObjects(self): return self.inner._getDependentObjects() def withExtendedAttributes(self, attrs): return IDLTypedefType( self.location, self.inner.withExtendedAttributes(attrs), self.name ) class IDLTypedef(IDLObjectWithIdentifier): def __init__(self, location, parentScope, innerType, name): # Set self.innerType first, because IDLObjectWithIdentifier.__init__ # will call our __str__, which wants to use it. self.innerType = innerType identifier = IDLUnresolvedIdentifier(location, name) IDLObjectWithIdentifier.__init__(self, location, parentScope, identifier) def __str__(self): return "Typedef %s %s" % (self.identifier.name, self.innerType) def finish(self, parentScope): if not self.innerType.isComplete(): self.innerType = self.innerType.complete(parentScope) def validate(self): pass def isTypedef(self): return True def addExtendedAttributes(self, attrs): if len(attrs) != 0: raise WebIDLError( "There are no extended attributes that are " "allowed on typedefs", [attrs[0].location, self.location], ) def _getDependentObjects(self): return self.innerType._getDependentObjects() class IDLWrapperType(IDLType): def __init__(self, location, inner): IDLType.__init__(self, location, inner.identifier.name) self.inner = inner self._identifier = inner.identifier self.builtin = False def __hash__(self): return hash(self._identifier) + hash(self.builtin) def __eq__(self, other): return ( isinstance(other, IDLWrapperType) and self._identifier == other._identifier and self.builtin == other.builtin ) def __str__(self): return str(self.name) + " (Wrapper)" def isDictionary(self): return isinstance(self.inner, IDLDictionary) def isInterface(self): return isinstance(self.inner, IDLInterface) or isinstance( self.inner, IDLExternalInterface ) def isCallbackInterface(self): return self.isInterface() and self.inner.isCallback() def isNonCallbackInterface(self): return self.isInterface() and not self.inner.isCallback() def isEnum(self): return isinstance(self.inner, IDLEnum) def isJSONType(self): if self.isInterface(): if self.inner.isExternal(): return False iface = self.inner while iface: if any(m.isMethod() and m.isToJSON() for m in iface.members): return True iface = iface.parent return False elif self.isEnum(): return True elif self.isDictionary(): dictionary = self.inner while dictionary: if not all(m.type.isJSONType() for m in dictionary.members): return False dictionary = dictionary.parent return True else: raise WebIDLError( "IDLWrapperType wraps type %s that we don't know if " "is serializable" % type(self.inner), [self.location], ) def resolveType(self, parentScope): assert isinstance(parentScope, IDLScope) self.inner.resolve(parentScope) def isComplete(self): return True def tag(self): if self.isInterface(): return IDLType.Tags.interface elif self.isEnum(): return IDLType.Tags.enum elif self.isDictionary(): return IDLType.Tags.dictionary else: assert False def isDistinguishableFrom(self, other): if other.isPromise(): return False if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) assert self.isInterface() or self.isEnum() or self.isDictionary() if self.isEnum(): return ( other.isUndefined() or other.isPrimitive() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or other.isSequence() or other.isRecord() ) if self.isDictionary() and (other.nullable() or other.isUndefined()): return False if ( other.isPrimitive() or other.isString() or other.isEnum() or other.isSequence() ): return True if self.isDictionary(): return other.isNonCallbackInterface() assert self.isInterface() if other.isInterface(): if other.isSpiderMonkeyInterface(): # Just let |other| handle things return other.isDistinguishableFrom(self) assert self.isGeckoInterface() and other.isGeckoInterface() if self.inner.isExternal() or other.unroll().inner.isExternal(): return self != other return len( self.inner.interfacesBasedOnSelf & other.unroll().inner.interfacesBasedOnSelf ) == 0 and (self.isNonCallbackInterface() or other.isNonCallbackInterface()) if ( other.isUndefined() or other.isDictionary() or other.isCallback() or other.isRecord() ): return self.isNonCallbackInterface() # Not much else |other| can be. # any is the union of all non-union types, so it's not distinguishable # from other unions (because it is a union itself), or from all # non-union types (because it has all of them as its members). assert other.isAny() or other.isObject() return False def isExposedInAllOf(self, exposureSet): if not self.isInterface(): return True iface = self.inner if iface.isExternal(): # Let's say true, so we don't have to implement exposure mixins on # external interfaces and sprinkle [Exposed=Window] on every single # external interface declaration. return True return iface.exposureSet.issuperset(exposureSet) def _getDependentObjects(self): # NB: The codegen for an interface type depends on # a) That the identifier is in fact an interface (as opposed to # a dictionary or something else). # b) The native type of the interface. # If we depend on the interface object we will also depend on # anything the interface depends on which is undesirable. We # considered implementing a dependency just on the interface type # file, but then every modification to an interface would cause this # to be regenerated which is still undesirable. We decided not to # depend on anything, reasoning that: # 1) Changing the concrete type of the interface requires modifying # Bindings.conf, which is still a global dependency. # 2) Changing an interface to a dictionary (or vice versa) with the # same identifier should be incredibly rare. # # On the other hand, if our type is a dictionary, we should # depend on it, because the member types of a dictionary # affect whether a method taking the dictionary as an argument # takes a JSContext* argument or not. if self.isDictionary(): return set([self.inner]) return set() class IDLPromiseType(IDLParametrizedType): def __init__(self, location, innerType): IDLParametrizedType.__init__(self, location, "Promise", innerType) def __hash__(self): return hash(self.promiseInnerType()) def __eq__(self, other): return ( isinstance(other, IDLPromiseType) and self.promiseInnerType() == other.promiseInnerType() ) def __str__(self): return self.inner.__str__() + "Promise" def prettyName(self): return "Promise<%s>" % self.inner.prettyName() def isPromise(self): return True def promiseInnerType(self): return self.inner def tag(self): return IDLType.Tags.promise def complete(self, scope): if self.inner.isObservableArray(): raise WebIDLError( "The inner type of a promise type must not be an ObservableArray type", [self.location, self.inner.location], ) self.inner = self.promiseInnerType().complete(scope) return self def unroll(self): # We do not unroll our inner. Just stop at ourselves. That # lets us add headers for both ourselves and our inner as # needed. return self def isDistinguishableFrom(self, other): # Promises are not distinguishable from anything. return False def isExposedInAllOf(self, exposureSet): # Check the internal type return self.promiseInnerType().unroll().isExposedInAllOf(exposureSet) class IDLBuiltinType(IDLType): Types = enum( # The integer types "byte", "octet", "short", "unsigned_short", "long", "unsigned_long", "long_long", "unsigned_long_long", # Additional primitive types "boolean", "unrestricted_float", "float", "unrestricted_double", # IMPORTANT: "double" must be the last primitive type listed "double", # Other types "any", "undefined", "domstring", "bytestring", "usvstring", "utf8string", "jsstring", "object", # Funny stuff "ArrayBuffer", "ArrayBufferView", "Int8Array", "Uint8Array", "Uint8ClampedArray", "Int16Array", "Uint16Array", "Int32Array", "Uint32Array", "Float32Array", "Float64Array", ) TagLookup = { Types.byte: IDLType.Tags.int8, Types.octet: IDLType.Tags.uint8, Types.short: IDLType.Tags.int16, Types.unsigned_short: IDLType.Tags.uint16, Types.long: IDLType.Tags.int32, Types.unsigned_long: IDLType.Tags.uint32, Types.long_long: IDLType.Tags.int64, Types.unsigned_long_long: IDLType.Tags.uint64, Types.boolean: IDLType.Tags.bool, Types.unrestricted_float: IDLType.Tags.unrestricted_float, Types.float: IDLType.Tags.float, Types.unrestricted_double: IDLType.Tags.unrestricted_double, Types.double: IDLType.Tags.double, Types.any: IDLType.Tags.any, Types.undefined: IDLType.Tags.undefined, Types.domstring: IDLType.Tags.domstring, Types.bytestring: IDLType.Tags.bytestring, Types.usvstring: IDLType.Tags.usvstring, Types.utf8string: IDLType.Tags.utf8string, Types.jsstring: IDLType.Tags.jsstring, Types.object: IDLType.Tags.object, Types.ArrayBuffer: IDLType.Tags.interface, Types.ArrayBufferView: IDLType.Tags.interface, Types.Int8Array: IDLType.Tags.interface, Types.Uint8Array: IDLType.Tags.interface, Types.Uint8ClampedArray: IDLType.Tags.interface, Types.Int16Array: IDLType.Tags.interface, Types.Uint16Array: IDLType.Tags.interface, Types.Int32Array: IDLType.Tags.interface, Types.Uint32Array: IDLType.Tags.interface, Types.Float32Array: IDLType.Tags.interface, Types.Float64Array: IDLType.Tags.interface, } PrettyNames = { Types.byte: "byte", Types.octet: "octet", Types.short: "short", Types.unsigned_short: "unsigned short", Types.long: "long", Types.unsigned_long: "unsigned long", Types.long_long: "long long", Types.unsigned_long_long: "unsigned long long", Types.boolean: "boolean", Types.unrestricted_float: "unrestricted float", Types.float: "float", Types.unrestricted_double: "unrestricted double", Types.double: "double", Types.any: "any", Types.undefined: "undefined", Types.domstring: "DOMString", Types.bytestring: "ByteString", Types.usvstring: "USVString", Types.utf8string: "USVString", # That's what it is in spec terms Types.jsstring: "USVString", # Again, that's what it is in spec terms Types.object: "object", Types.ArrayBuffer: "ArrayBuffer", Types.ArrayBufferView: "ArrayBufferView", Types.Int8Array: "Int8Array", Types.Uint8Array: "Uint8Array", Types.Uint8ClampedArray: "Uint8ClampedArray", Types.Int16Array: "Int16Array", Types.Uint16Array: "Uint16Array", Types.Int32Array: "Int32Array", Types.Uint32Array: "Uint32Array", Types.Float32Array: "Float32Array", Types.Float64Array: "Float64Array", } def __init__( self, location, name, type, clamp=False, enforceRange=False, legacyNullToEmptyString=False, allowShared=False, attrLocation=[], ): """ The mutually exclusive clamp/enforceRange/legacyNullToEmptyString/allowShared arguments are used to create instances of this type with the appropriate attributes attached. Use .clamped(), .rangeEnforced(), .withLegacyNullToEmptyString() and .withAllowShared(). attrLocation is an array of source locations of these attributes for error reporting. """ IDLType.__init__(self, location, name) self.builtin = True self._typeTag = type self._clamped = None self._rangeEnforced = None self._withLegacyNullToEmptyString = None self._withAllowShared = None if self.isInteger(): if clamp: self._clamp = True self.name = "Clamped" + self.name self._extendedAttrDict["Clamp"] = True elif enforceRange: self._enforceRange = True self.name = "RangeEnforced" + self.name self._extendedAttrDict["EnforceRange"] = True elif clamp or enforceRange: raise WebIDLError( "Non-integer types cannot be [Clamp] or [EnforceRange]", attrLocation ) if self.isDOMString() or self.isUTF8String(): if legacyNullToEmptyString: self.legacyNullToEmptyString = True self.name = "NullIsEmpty" + self.name self._extendedAttrDict["LegacyNullToEmptyString"] = True elif legacyNullToEmptyString: raise WebIDLError( "Non-string types cannot be [LegacyNullToEmptyString]", attrLocation ) if self.isBufferSource(): if allowShared: self._allowShared = True self._extendedAttrDict["AllowShared"] = True elif allowShared: raise WebIDLError( "Types that are not buffer source types cannot be [AllowShared]", attrLocation, ) def __str__(self): if self._allowShared: assert self.isBufferSource() return "MaybeShared" + str(self.name) return str(self.name) def prettyName(self): return IDLBuiltinType.PrettyNames[self._typeTag] def clamped(self, attrLocation): if not self._clamped: self._clamped = IDLBuiltinType( self.location, self.name, self._typeTag, clamp=True, attrLocation=attrLocation, ) return self._clamped def rangeEnforced(self, attrLocation): if not self._rangeEnforced: self._rangeEnforced = IDLBuiltinType( self.location, self.name, self._typeTag, enforceRange=True, attrLocation=attrLocation, ) return self._rangeEnforced def withLegacyNullToEmptyString(self, attrLocation): if not self._withLegacyNullToEmptyString: self._withLegacyNullToEmptyString = IDLBuiltinType( self.location, self.name, self._typeTag, legacyNullToEmptyString=True, attrLocation=attrLocation, ) return self._withLegacyNullToEmptyString def withAllowShared(self, attrLocation): if not self._withAllowShared: self._withAllowShared = IDLBuiltinType( self.location, self.name, self._typeTag, allowShared=True, attrLocation=attrLocation, ) return self._withAllowShared def isPrimitive(self): return self._typeTag <= IDLBuiltinType.Types.double def isBoolean(self): return self._typeTag == IDLBuiltinType.Types.boolean def isUndefined(self): return self._typeTag == IDLBuiltinType.Types.undefined def isNumeric(self): return self.isPrimitive() and not self.isBoolean() def isString(self): return ( self._typeTag == IDLBuiltinType.Types.domstring or self._typeTag == IDLBuiltinType.Types.bytestring or self._typeTag == IDLBuiltinType.Types.usvstring or self._typeTag == IDLBuiltinType.Types.utf8string or self._typeTag == IDLBuiltinType.Types.jsstring ) def isByteString(self): return self._typeTag == IDLBuiltinType.Types.bytestring def isDOMString(self): return self._typeTag == IDLBuiltinType.Types.domstring def isUSVString(self): return self._typeTag == IDLBuiltinType.Types.usvstring def isUTF8String(self): return self._typeTag == IDLBuiltinType.Types.utf8string def isJSString(self): return self._typeTag == IDLBuiltinType.Types.jsstring def isInteger(self): return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long def isArrayBuffer(self): return self._typeTag == IDLBuiltinType.Types.ArrayBuffer def isArrayBufferView(self): return self._typeTag == IDLBuiltinType.Types.ArrayBufferView def isTypedArray(self): return ( self._typeTag >= IDLBuiltinType.Types.Int8Array and self._typeTag <= IDLBuiltinType.Types.Float64Array ) def isInterface(self): # TypedArray things are interface types per the TypedArray spec, # but we handle them as builtins because SpiderMonkey implements # all of it internally. return self.isArrayBuffer() or self.isArrayBufferView() or self.isTypedArray() def isNonCallbackInterface(self): # All the interfaces we can be are non-callback return self.isInterface() def isFloat(self): return ( self._typeTag == IDLBuiltinType.Types.float or self._typeTag == IDLBuiltinType.Types.double or self._typeTag == IDLBuiltinType.Types.unrestricted_float or self._typeTag == IDLBuiltinType.Types.unrestricted_double ) def isUnrestricted(self): assert self.isFloat() return ( self._typeTag == IDLBuiltinType.Types.unrestricted_float or self._typeTag == IDLBuiltinType.Types.unrestricted_double ) def isJSONType(self): return self.isPrimitive() or self.isString() or self.isObject() def includesRestrictedFloat(self): return self.isFloat() and not self.isUnrestricted() def tag(self): return IDLBuiltinType.TagLookup[self._typeTag] def isDistinguishableFrom(self, other): if other.isPromise(): return False if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) if self.isUndefined(): return not (other.isUndefined() or other.isDictionaryLike()) if self.isPrimitive(): if ( other.isUndefined() or other.isString() or other.isEnum() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or other.isSequence() or other.isRecord() ): return True if self.isBoolean(): return other.isNumeric() assert self.isNumeric() return other.isBoolean() if self.isString(): return ( other.isUndefined() or other.isPrimitive() or other.isInterface() or other.isObject() or other.isCallback() or other.isDictionary() or other.isSequence() or other.isRecord() ) if self.isAny(): # Can't tell "any" apart from anything return False if self.isObject(): return ( other.isUndefined() or other.isPrimitive() or other.isString() or other.isEnum() ) # Not much else we could be! assert self.isSpiderMonkeyInterface() # Like interfaces, but we know we're not a callback return ( other.isUndefined() or other.isPrimitive() or other.isString() or other.isEnum() or other.isCallback() or other.isDictionary() or other.isSequence() or other.isRecord() or ( other.isInterface() and ( # ArrayBuffer is distinguishable from everything # that's not an ArrayBuffer or a callback interface (self.isArrayBuffer() and not other.isArrayBuffer()) or # ArrayBufferView is distinguishable from everything # that's not an ArrayBufferView or typed array. ( self.isArrayBufferView() and not other.isArrayBufferView() and not other.isTypedArray() ) or # Typed arrays are distinguishable from everything # except ArrayBufferView and the same type of typed # array ( self.isTypedArray() and not other.isArrayBufferView() and not (other.isTypedArray() and other.name == self.name) ) ) ) ) def _getDependentObjects(self): return set() def withExtendedAttributes(self, attrs): ret = self for attribute in attrs: identifier = attribute.identifier() if identifier == "Clamp": if not attribute.noArguments(): raise WebIDLError( "[Clamp] must take no arguments", [attribute.location] ) if ret.hasEnforceRange() or self._enforceRange: raise WebIDLError( "[EnforceRange] and [Clamp] are mutually exclusive", [self.location, attribute.location], ) ret = self.clamped([self.location, attribute.location]) elif identifier == "EnforceRange": if not attribute.noArguments(): raise WebIDLError( "[EnforceRange] must take no arguments", [attribute.location] ) if ret.hasClamp() or self._clamp: raise WebIDLError( "[EnforceRange] and [Clamp] are mutually exclusive", [self.location, attribute.location], ) ret = self.rangeEnforced([self.location, attribute.location]) elif identifier == "LegacyNullToEmptyString": if not (self.isDOMString() or self.isUTF8String()): raise WebIDLError( "[LegacyNullToEmptyString] only allowed on DOMStrings and UTF8Strings", [self.location, attribute.location], ) assert not self.nullable() if attribute.hasValue(): raise WebIDLError( "[LegacyNullToEmptyString] must take no identifier argument", [attribute.location], ) ret = self.withLegacyNullToEmptyString( [self.location, attribute.location] ) elif identifier == "AllowShared": if not attribute.noArguments(): raise WebIDLError( "[AllowShared] must take no arguments", [attribute.location] ) if not self.isBufferSource(): raise WebIDLError( "[AllowShared] only allowed on buffer source types", [self.location, attribute.location], ) ret = self.withAllowShared([self.location, attribute.location]) else: raise WebIDLError( "Unhandled extended attribute on type", [self.location, attribute.location], ) return ret BuiltinTypes = { IDLBuiltinType.Types.byte: IDLBuiltinType( BuiltinLocation(""), "Byte", IDLBuiltinType.Types.byte ), IDLBuiltinType.Types.octet: IDLBuiltinType( BuiltinLocation(""), "Octet", IDLBuiltinType.Types.octet ), IDLBuiltinType.Types.short: IDLBuiltinType( BuiltinLocation(""), "Short", IDLBuiltinType.Types.short ), IDLBuiltinType.Types.unsigned_short: IDLBuiltinType( BuiltinLocation(""), "UnsignedShort", IDLBuiltinType.Types.unsigned_short, ), IDLBuiltinType.Types.long: IDLBuiltinType( BuiltinLocation(""), "Long", IDLBuiltinType.Types.long ), IDLBuiltinType.Types.unsigned_long: IDLBuiltinType( BuiltinLocation(""), "UnsignedLong", IDLBuiltinType.Types.unsigned_long, ), IDLBuiltinType.Types.long_long: IDLBuiltinType( BuiltinLocation(""), "LongLong", IDLBuiltinType.Types.long_long ), IDLBuiltinType.Types.unsigned_long_long: IDLBuiltinType( BuiltinLocation(""), "UnsignedLongLong", IDLBuiltinType.Types.unsigned_long_long, ), IDLBuiltinType.Types.undefined: IDLBuiltinType( BuiltinLocation(""), "Undefined", IDLBuiltinType.Types.undefined ), IDLBuiltinType.Types.boolean: IDLBuiltinType( BuiltinLocation(""), "Boolean", IDLBuiltinType.Types.boolean ), IDLBuiltinType.Types.float: IDLBuiltinType( BuiltinLocation(""), "Float", IDLBuiltinType.Types.float ), IDLBuiltinType.Types.unrestricted_float: IDLBuiltinType( BuiltinLocation(""), "UnrestrictedFloat", IDLBuiltinType.Types.unrestricted_float, ), IDLBuiltinType.Types.double: IDLBuiltinType( BuiltinLocation(""), "Double", IDLBuiltinType.Types.double ), IDLBuiltinType.Types.unrestricted_double: IDLBuiltinType( BuiltinLocation(""), "UnrestrictedDouble", IDLBuiltinType.Types.unrestricted_double, ), IDLBuiltinType.Types.any: IDLBuiltinType( BuiltinLocation(""), "Any", IDLBuiltinType.Types.any ), IDLBuiltinType.Types.domstring: IDLBuiltinType( BuiltinLocation(""), "String", IDLBuiltinType.Types.domstring ), IDLBuiltinType.Types.bytestring: IDLBuiltinType( BuiltinLocation(""), "ByteString", IDLBuiltinType.Types.bytestring ), IDLBuiltinType.Types.usvstring: IDLBuiltinType( BuiltinLocation(""), "USVString", IDLBuiltinType.Types.usvstring ), IDLBuiltinType.Types.utf8string: IDLBuiltinType( BuiltinLocation(""), "UTF8String", IDLBuiltinType.Types.utf8string ), IDLBuiltinType.Types.jsstring: IDLBuiltinType( BuiltinLocation(""), "JSString", IDLBuiltinType.Types.jsstring ), IDLBuiltinType.Types.object: IDLBuiltinType( BuiltinLocation(""), "Object", IDLBuiltinType.Types.object ), IDLBuiltinType.Types.ArrayBuffer: IDLBuiltinType( BuiltinLocation(""), "ArrayBuffer", IDLBuiltinType.Types.ArrayBuffer, ), IDLBuiltinType.Types.ArrayBufferView: IDLBuiltinType( BuiltinLocation(""), "ArrayBufferView", IDLBuiltinType.Types.ArrayBufferView, ), IDLBuiltinType.Types.Int8Array: IDLBuiltinType( BuiltinLocation(""), "Int8Array", IDLBuiltinType.Types.Int8Array ), IDLBuiltinType.Types.Uint8Array: IDLBuiltinType( BuiltinLocation(""), "Uint8Array", IDLBuiltinType.Types.Uint8Array ), IDLBuiltinType.Types.Uint8ClampedArray: IDLBuiltinType( BuiltinLocation(""), "Uint8ClampedArray", IDLBuiltinType.Types.Uint8ClampedArray, ), IDLBuiltinType.Types.Int16Array: IDLBuiltinType( BuiltinLocation(""), "Int16Array", IDLBuiltinType.Types.Int16Array ), IDLBuiltinType.Types.Uint16Array: IDLBuiltinType( BuiltinLocation(""), "Uint16Array", IDLBuiltinType.Types.Uint16Array, ), IDLBuiltinType.Types.Int32Array: IDLBuiltinType( BuiltinLocation(""), "Int32Array", IDLBuiltinType.Types.Int32Array ), IDLBuiltinType.Types.Uint32Array: IDLBuiltinType( BuiltinLocation(""), "Uint32Array", IDLBuiltinType.Types.Uint32Array, ), IDLBuiltinType.Types.Float32Array: IDLBuiltinType( BuiltinLocation(""), "Float32Array", IDLBuiltinType.Types.Float32Array, ), IDLBuiltinType.Types.Float64Array: IDLBuiltinType( BuiltinLocation(""), "Float64Array", IDLBuiltinType.Types.Float64Array, ), } integerTypeSizes = { IDLBuiltinType.Types.byte: (-128, 127), IDLBuiltinType.Types.octet: (0, 255), IDLBuiltinType.Types.short: (-32768, 32767), IDLBuiltinType.Types.unsigned_short: (0, 65535), IDLBuiltinType.Types.long: (-2147483648, 2147483647), IDLBuiltinType.Types.unsigned_long: (0, 4294967295), IDLBuiltinType.Types.long_long: (-9223372036854775808, 9223372036854775807), IDLBuiltinType.Types.unsigned_long_long: (0, 18446744073709551615), } def matchIntegerValueToType(value): for type, extremes in integerTypeSizes.items(): (min, max) = extremes if value <= max and value >= min: return BuiltinTypes[type] return None class NoCoercionFoundError(WebIDLError): """ A class we use to indicate generic coercion failures because none of the types worked out in IDLValue.coerceToType. """ class IDLValue(IDLObject): def __init__(self, location, type, value): IDLObject.__init__(self, location) self.type = type assert isinstance(type, IDLType) self.value = value def coerceToType(self, type, location): if type == self.type: return self # Nothing to do # We first check for unions to ensure that even if the union is nullable # we end up with the right flat member type, not the union's type. if type.isUnion(): # We use the flat member types here, because if we have a nullable # member type, or a nested union, we want the type the value # actually coerces to, not the nullable or nested union type. for subtype in type.unroll().flatMemberTypes: try: coercedValue = self.coerceToType(subtype, location) # Create a new IDLValue to make sure that we have the # correct float/double type. This is necessary because we # use the value's type when it is a default value of a # union, and the union cares about the exact float type. return IDLValue(self.location, subtype, coercedValue.value) except Exception as e: # Make sure to propagate out WebIDLErrors that are not the # generic "hey, we could not coerce to this type at all" # exception, because those are specific "coercion failed for # reason X" exceptions. Note that we want to swallow # non-WebIDLErrors here, because those can just happen if # "type" is not something that can have a default value at # all. if isinstance(e, WebIDLError) and not isinstance( e, NoCoercionFoundError ): raise e # If the type allows null, rerun this matching on the inner type, except # nullable enums. We handle those specially, because we want our # default string values to stay strings even when assigned to a nullable # enum. elif type.nullable() and not type.isEnum(): innerValue = self.coerceToType(type.inner, location) return IDLValue(self.location, type, innerValue.value) elif self.type.isInteger() and type.isInteger(): # We're both integer types. See if we fit. (min, max) = integerTypeSizes[type._typeTag] if self.value <= max and self.value >= min: # Promote return IDLValue(self.location, type, self.value) else: raise WebIDLError( "Value %s is out of range for type %s." % (self.value, type), [location], ) elif self.type.isInteger() and type.isFloat(): # Convert an integer literal into float if -(2**24) <= self.value <= 2**24: return IDLValue(self.location, type, float(self.value)) else: raise WebIDLError( "Converting value %s to %s will lose precision." % (self.value, type), [location], ) elif self.type.isString() and type.isEnum(): # Just keep our string, but make sure it's a valid value for this enum enum = type.unroll().inner if self.value not in enum.values(): raise WebIDLError( "'%s' is not a valid default value for enum %s" % (self.value, enum.identifier.name), [location, enum.location], ) return self elif self.type.isFloat() and type.isFloat(): if not type.isUnrestricted() and ( self.value == float("inf") or self.value == float("-inf") or math.isnan(self.value) ): raise WebIDLError( "Trying to convert unrestricted value %s to non-unrestricted" % self.value, [location], ) return IDLValue(self.location, type, self.value) elif self.type.isString() and type.isUSVString(): # Allow USVStrings to use default value just like # DOMString. No coercion is required in this case as Codegen.py # treats USVString just like DOMString, but with an # extra normalization step. assert self.type.isDOMString() return self elif self.type.isString() and ( type.isByteString() or type.isJSString() or type.isUTF8String() ): # Allow ByteStrings, UTF8String, and JSStrings to use a default # value like DOMString. # No coercion is required as Codegen.py will handle the # extra steps. We want to make sure that our string contains # only valid characters, so we check that here. valid_ascii_lit = ( " " + string.ascii_letters + string.digits + string.punctuation ) for idx, c in enumerate(self.value): if c not in valid_ascii_lit: raise WebIDLError( "Coercing this string literal %s to a ByteString is not supported yet. " "Coercion failed due to an unsupported byte %d at index %d." % (self.value.__repr__(), ord(c), idx), [location], ) return IDLValue(self.location, type, self.value) elif self.type.isDOMString() and type.legacyNullToEmptyString: # LegacyNullToEmptyString is a different type for resolution reasons, # however once you have a value it doesn't matter return self raise NoCoercionFoundError( "Cannot coerce type %s to type %s." % (self.type, type), [location] ) def _getDependentObjects(self): return set() class IDLNullValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) self.type = None self.value = None def coerceToType(self, type, location): if ( not isinstance(type, IDLNullableType) and not (type.isUnion() and type.hasNullableType) and not type.isAny() ): raise WebIDLError("Cannot coerce null value to type %s." % type, [location]) nullValue = IDLNullValue(self.location) if type.isUnion() and not type.nullable() and type.hasDictionaryType(): # We're actually a default value for the union's dictionary member. # Use its type. for t in type.flatMemberTypes: if t.isDictionary(): nullValue.type = t return nullValue nullValue.type = type return nullValue def _getDependentObjects(self): return set() class IDLEmptySequenceValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) self.type = None self.value = None def coerceToType(self, type, location): if type.isUnion(): # We use the flat member types here, because if we have a nullable # member type, or a nested union, we want the type the value # actually coerces to, not the nullable or nested union type. for subtype in type.unroll().flatMemberTypes: try: return self.coerceToType(subtype, location) except Exception: pass if not type.isSequence(): raise WebIDLError( "Cannot coerce empty sequence value to type %s." % type, [location] ) emptySequenceValue = IDLEmptySequenceValue(self.location) emptySequenceValue.type = type return emptySequenceValue def _getDependentObjects(self): return set() class IDLDefaultDictionaryValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) self.type = None self.value = None def coerceToType(self, type, location): if type.isUnion(): # We use the flat member types here, because if we have a nullable # member type, or a nested union, we want the type the value # actually coerces to, not the nullable or nested union type. for subtype in type.unroll().flatMemberTypes: try: return self.coerceToType(subtype, location) except Exception: pass if not type.isDictionary(): raise WebIDLError( "Cannot coerce default dictionary value to type %s." % type, [location] ) defaultDictionaryValue = IDLDefaultDictionaryValue(self.location) defaultDictionaryValue.type = type return defaultDictionaryValue def _getDependentObjects(self): return set() class IDLUndefinedValue(IDLObject): def __init__(self, location): IDLObject.__init__(self, location) self.type = None self.value = None def coerceToType(self, type, location): if not type.isAny(): raise WebIDLError( "Cannot coerce undefined value to type %s." % type, [location] ) undefinedValue = IDLUndefinedValue(self.location) undefinedValue.type = type return undefinedValue def _getDependentObjects(self): return set() class IDLInterfaceMember(IDLObjectWithIdentifier, IDLExposureMixins): Tags = enum( "Const", "Attr", "Method", "MaplikeOrSetlike", "AsyncIterable", "Iterable" ) Special = enum("Static", "Stringifier") AffectsValues = ("Nothing", "Everything") DependsOnValues = ("Nothing", "DOMState", "DeviceState", "Everything") def __init__(self, location, identifier, tag, extendedAttrDict=None): IDLObjectWithIdentifier.__init__(self, location, None, identifier) IDLExposureMixins.__init__(self, location) self.tag = tag if extendedAttrDict is None: self._extendedAttrDict = {} else: self._extendedAttrDict = extendedAttrDict def isMethod(self): return self.tag == IDLInterfaceMember.Tags.Method def isAttr(self): return self.tag == IDLInterfaceMember.Tags.Attr def isConst(self): return self.tag == IDLInterfaceMember.Tags.Const def isMaplikeOrSetlikeOrIterable(self): return ( self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike or self.tag == IDLInterfaceMember.Tags.AsyncIterable or self.tag == IDLInterfaceMember.Tags.Iterable ) def isMaplikeOrSetlike(self): return self.tag == IDLInterfaceMember.Tags.MaplikeOrSetlike def addExtendedAttributes(self, attrs): for attr in attrs: self.handleExtendedAttribute(attr) attrlist = attr.listValue() self._extendedAttrDict[attr.identifier()] = ( attrlist if len(attrlist) else True ) def handleExtendedAttribute(self, attr): pass def getExtendedAttribute(self, name): return self._extendedAttrDict.get(name, None) def finish(self, scope): IDLExposureMixins.finish(self, scope) def validate(self): if self.isAttr() or self.isMethod(): if self.affects == "Everything" and self.dependsOn != "Everything": raise WebIDLError( "Interface member is flagged as affecting " "everything but not depending on everything. " "That seems rather unlikely.", [self.location], ) if self.getExtendedAttribute("NewObject"): if self.dependsOn == "Nothing" or self.dependsOn == "DOMState": raise WebIDLError( "A [NewObject] method is not idempotent, " "so it has to depend on something other than DOM state.", [self.location], ) if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( "StoreInSlot" ): raise WebIDLError( "A [NewObject] attribute shouldnt be " "[Cached] or [StoreInSlot], since the point " "of those is to keep returning the same " "thing across multiple calls, which is not " "what [NewObject] does.", [self.location], ) def _setDependsOn(self, dependsOn): if self.dependsOn != "Everything": raise WebIDLError( "Trying to specify multiple different DependsOn, " "Pure, or Constant extended attributes for " "attribute", [self.location], ) if dependsOn not in IDLInterfaceMember.DependsOnValues: raise WebIDLError( "Invalid [DependsOn=%s] on attribute" % dependsOn, [self.location] ) self.dependsOn = dependsOn def _setAffects(self, affects): if self.affects != "Everything": raise WebIDLError( "Trying to specify multiple different Affects, " "Pure, or Constant extended attributes for " "attribute", [self.location], ) if affects not in IDLInterfaceMember.AffectsValues: raise WebIDLError( "Invalid [Affects=%s] on attribute" % affects, [self.location] ) self.affects = affects def _addAlias(self, alias): if alias in self.aliases: raise WebIDLError( "Duplicate [Alias=%s] on attribute" % alias, [self.location] ) self.aliases.append(alias) def _addBindingAlias(self, bindingAlias): if bindingAlias in self.bindingAliases: raise WebIDLError( "Duplicate [BindingAlias=%s] on attribute" % bindingAlias, [self.location], ) self.bindingAliases.append(bindingAlias) class IDLMaplikeOrSetlikeOrIterableBase(IDLInterfaceMember): def __init__(self, location, identifier, ifaceType, keyType, valueType, ifaceKind): IDLInterfaceMember.__init__(self, location, identifier, ifaceKind) if keyType is not None: assert isinstance(keyType, IDLType) else: assert valueType is not None assert ifaceType in ["maplike", "setlike", "iterable", "asynciterable"] if valueType is not None: assert isinstance(valueType, IDLType) self.keyType = keyType self.valueType = valueType self.maplikeOrSetlikeOrIterableType = ifaceType self.disallowedMemberNames = [] self.disallowedNonMethodNames = [] def isMaplike(self): return self.maplikeOrSetlikeOrIterableType == "maplike" def isSetlike(self): return self.maplikeOrSetlikeOrIterableType == "setlike" def isIterable(self): return self.maplikeOrSetlikeOrIterableType == "iterable" def isAsyncIterable(self): return self.maplikeOrSetlikeOrIterableType == "asynciterable" def hasKeyType(self): return self.keyType is not None def hasValueType(self): return self.valueType is not None def checkCollisions(self, members, isAncestor): for member in members: # Check that there are no disallowed members if member.identifier.name in self.disallowedMemberNames and not ( ( member.isMethod() and ( member.isStatic() or member.isMaplikeOrSetlikeOrIterableMethod() ) ) or (member.isAttr() and member.isMaplikeOrSetlikeAttr()) ): raise WebIDLError( "Member '%s' conflicts " "with reserved %s name." % (member.identifier.name, self.maplikeOrSetlikeOrIterableType), [self.location, member.location], ) # Check that there are no disallowed non-method members. # Ancestor members are always disallowed here; own members # are disallowed only if they're non-methods. if ( isAncestor or member.isAttr() or member.isConst() ) and member.identifier.name in self.disallowedNonMethodNames: raise WebIDLError( "Member '%s' conflicts " "with reserved %s method." % (member.identifier.name, self.maplikeOrSetlikeOrIterableType), [self.location, member.location], ) def addMethod( self, name, members, allowExistingOperations, returnType, args=[], chromeOnly=False, isPure=False, affectsNothing=False, newObject=False, isIteratorAlias=False, ): """ Create an IDLMethod based on the parameters passed in. - members is the member list to add this function to, since this is called during the member expansion portion of interface object building. - chromeOnly is only True for read-only js implemented classes, to implement underscore prefixed convenience functions which would otherwise not be available, unlike the case of C++ bindings. - isPure is only True for idempotent functions, so it is not valid for things like keys, values, etc. that return a new object every time. - affectsNothing means that nothing changes due to this method, which affects JIT optimization behavior - newObject means the method creates and returns a new object. """ # Only add name to lists for collision checks if it's not chrome # only. if chromeOnly: name = "__" + name else: if not allowExistingOperations: self.disallowedMemberNames.append(name) else: 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. if allowExistingOperations: for m in members: if m.identifier.name == name and m.isMethod() and not m.isStatic(): return method = IDLMethod( self.location, IDLUnresolvedIdentifier( self.location, name, allowDoubleUnderscore=chromeOnly ), returnType, args, maplikeOrSetlikeOrIterable=self, ) # We need to be able to throw from declaration methods method.addExtendedAttributes([IDLExtendedAttribute(self.location, ("Throws",))]) if chromeOnly: method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("ChromeOnly",))] ) if isPure: method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("Pure",))] ) # Following attributes are used for keys/values/entries. Can't mark # them pure, since they return a new object each time they are run. if affectsNothing: method.addExtendedAttributes( [ IDLExtendedAttribute(self.location, ("DependsOn", "Everything")), IDLExtendedAttribute(self.location, ("Affects", "Nothing")), ] ) if newObject: method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("NewObject",))] ) if isIteratorAlias: if not self.isAsyncIterable(): method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("Alias", "@@iterator"))] ) else: method.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("Alias", "@@asyncIterator"))] ) members.append(method) def resolve(self, parentScope): if self.keyType: self.keyType.resolveType(parentScope) if self.valueType: self.valueType.resolveType(parentScope) def finish(self, scope): IDLInterfaceMember.finish(self, scope) if self.keyType and not self.keyType.isComplete(): t = self.keyType.complete(scope) assert not isinstance(t, IDLUnresolvedType) assert not isinstance(t, IDLTypedefType) assert not isinstance(t.name, IDLUnresolvedIdentifier) self.keyType = t if self.valueType and not self.valueType.isComplete(): t = self.valueType.complete(scope) assert not isinstance(t, IDLUnresolvedType) assert not isinstance(t, IDLTypedefType) assert not isinstance(t.name, IDLUnresolvedIdentifier) self.valueType = t def validate(self): IDLInterfaceMember.validate(self) def handleExtendedAttribute(self, attr): IDLInterfaceMember.handleExtendedAttribute(self, attr) def _getDependentObjects(self): deps = set() if self.keyType: deps.add(self.keyType) if self.valueType: deps.add(self.valueType) return deps def getForEachArguments(self): return [ IDLArgument( self.location, IDLUnresolvedIdentifier( BuiltinLocation(""), "callback" ), BuiltinTypes[IDLBuiltinType.Types.object], ), IDLArgument( self.location, IDLUnresolvedIdentifier( BuiltinLocation(""), "thisArg" ), BuiltinTypes[IDLBuiltinType.Types.any], optional=True, ), ] # Iterable adds ES6 iterator style functions and traits # (keys/values/entries/@@iterator) to an interface. class IDLIterable(IDLMaplikeOrSetlikeOrIterableBase): def __init__(self, location, identifier, keyType, valueType, scope): IDLMaplikeOrSetlikeOrIterableBase.__init__( self, location, identifier, "iterable", keyType, valueType, IDLInterfaceMember.Tags.Iterable, ) self.iteratorType = None def __str__(self): return "declared iterable with key '%s' and value '%s'" % ( self.keyType, self.valueType, ) def expand(self, members): """ In order to take advantage of all of the method machinery in Codegen, we generate our functions as if they were part of the interface specification during parsing. """ # We only need to add entries/keys/values here if we're a pair iterator. # Value iterators just copy these from %ArrayPrototype% instead. if not self.isPairIterator(): return # object entries() self.addMethod( "entries", members, False, self.iteratorType, affectsNothing=True, newObject=True, isIteratorAlias=True, ) # object keys() self.addMethod( "keys", members, False, self.iteratorType, affectsNothing=True, newObject=True, ) # object values() self.addMethod( "values", members, False, self.iteratorType, affectsNothing=True, newObject=True, ) # undefined forEach(callback(valueType, keyType), optional any thisArg) self.addMethod( "forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.undefined], self.getForEachArguments(), ) def isValueIterator(self): return not self.isPairIterator() def isPairIterator(self): return self.hasKeyType() class IDLAsyncIterable(IDLMaplikeOrSetlikeOrIterableBase): def __init__(self, location, identifier, keyType, valueType, argList, scope): for arg in argList: if not arg.optional: raise WebIDLError( "The arguments of the asynchronously iterable declaration on " "%s must all be optional arguments." % identifier, [arg.location], ) IDLMaplikeOrSetlikeOrIterableBase.__init__( self, location, identifier, "asynciterable", keyType, valueType, IDLInterfaceMember.Tags.AsyncIterable, ) self.iteratorType = None self.argList = argList def __str__(self): return "declared async iterable with key '%s' and value '%s'" % ( self.keyType, self.valueType, ) def expand(self, members): """ In order to take advantage of all of the method machinery in Codegen, we generate our functions as if they were part of the interface specification during parsing. """ # object values() self.addMethod( "values", members, False, self.iteratorType, self.argList, affectsNothing=True, newObject=True, isIteratorAlias=(not self.isPairIterator()), ) # We only need to add entries/keys here if we're a pair iterator. if not self.isPairIterator(): return # Methods can't share their IDLArguments, so we need to make copies here. def copyArgList(argList): return map(copy.copy, argList) # object entries() self.addMethod( "entries", members, False, self.iteratorType, copyArgList(self.argList), affectsNothing=True, newObject=True, isIteratorAlias=True, ) # object keys() self.addMethod( "keys", members, False, self.iteratorType, copyArgList(self.argList), affectsNothing=True, newObject=True, ) def isValueIterator(self): return not self.isPairIterator() def isPairIterator(self): return self.hasKeyType() # MaplikeOrSetlike adds ES6 map-or-set-like traits to an interface. class IDLMaplikeOrSetlike(IDLMaplikeOrSetlikeOrIterableBase): def __init__( self, location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType ): IDLMaplikeOrSetlikeOrIterableBase.__init__( self, location, identifier, maplikeOrSetlikeType, keyType, valueType, IDLInterfaceMember.Tags.MaplikeOrSetlike, ) self.readonly = readonly self.slotIndices = None # When generating JSAPI access code, we need to know the backing object # type prefix to create the correct function. Generate here for reuse. if self.isMaplike(): self.prefix = "Map" elif self.isSetlike(): self.prefix = "Set" def __str__(self): return "declared '%s' with key '%s'" % ( self.maplikeOrSetlikeOrIterableType, self.keyType, ) def expand(self, members): """ In order to take advantage of all of the method machinery in Codegen, we generate our functions as if they were part of the interface specification during parsing. """ # Both maplike and setlike have a size attribute members.append( IDLAttribute( self.location, IDLUnresolvedIdentifier( BuiltinLocation(""), "size" ), BuiltinTypes[IDLBuiltinType.Types.unsigned_long], True, maplikeOrSetlike=self, ) ) self.reserved_ro_names = ["size"] self.disallowedMemberNames.append("size") # object entries() self.addMethod( "entries", members, False, BuiltinTypes[IDLBuiltinType.Types.object], affectsNothing=True, isIteratorAlias=self.isMaplike(), ) # object keys() self.addMethod( "keys", members, False, BuiltinTypes[IDLBuiltinType.Types.object], affectsNothing=True, ) # object values() self.addMethod( "values", members, False, BuiltinTypes[IDLBuiltinType.Types.object], affectsNothing=True, isIteratorAlias=self.isSetlike(), ) # undefined forEach(callback(valueType, keyType), thisVal) self.addMethod( "forEach", members, False, BuiltinTypes[IDLBuiltinType.Types.undefined], self.getForEachArguments(), ) def getKeyArg(): return IDLArgument( self.location, IDLUnresolvedIdentifier(self.location, "key"), self.keyType, ) # boolean has(keyType key) self.addMethod( "has", members, False, BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()], isPure=True, ) if not self.readonly: # undefined clear() self.addMethod( "clear", members, True, BuiltinTypes[IDLBuiltinType.Types.undefined], [] ) # boolean delete(keyType key) self.addMethod( "delete", members, True, BuiltinTypes[IDLBuiltinType.Types.boolean], [getKeyArg()], ) if self.isSetlike(): if not self.readonly: # Add returns the set object it just added to. # object add(keyType key) self.addMethod( "add", members, True, BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg()], ) return # If we get this far, we're a maplike declaration. # valueType get(keyType key) # # Note that instead of the value type, we're using any here. The # validity checks should happen as things are inserted into the map, # and using any as the return type makes code generation much simpler. # # TODO: Bug 1155340 may change this to use specific type to provide # more info to JIT. self.addMethod( "get", members, False, BuiltinTypes[IDLBuiltinType.Types.any], [getKeyArg()], isPure=True, ) def getValueArg(): return IDLArgument( self.location, IDLUnresolvedIdentifier(self.location, "value"), self.valueType, ) if not self.readonly: self.addMethod( "set", members, True, BuiltinTypes[IDLBuiltinType.Types.object], [getKeyArg(), getValueArg()], ) class IDLConst(IDLInterfaceMember): def __init__(self, location, identifier, type, value): IDLInterfaceMember.__init__( self, location, identifier, IDLInterfaceMember.Tags.Const ) assert isinstance(type, IDLType) if type.isDictionary(): raise WebIDLError( "A constant cannot be of a dictionary type", [self.location] ) if type.isRecord(): raise WebIDLError("A constant cannot be of a record type", [self.location]) self.type = type self.value = value if identifier.name == "prototype": raise WebIDLError( "The identifier of a constant must not be 'prototype'", [location] ) def __str__(self): return "'%s' const '%s'" % (self.type, self.identifier) def finish(self, scope): IDLInterfaceMember.finish(self, scope) if not self.type.isComplete(): type = self.type.complete(scope) if not type.isPrimitive() and not type.isString(): locations = [self.type.location, type.location] try: locations.append(type.inner.location) except Exception: pass raise WebIDLError("Incorrect type for constant", locations) self.type = type # The value might not match the type coercedValue = self.value.coerceToType(self.type, self.location) assert coercedValue self.value = coercedValue def validate(self): IDLInterfaceMember.validate(self) def handleExtendedAttribute(self, attr): identifier = attr.identifier() if identifier == "Exposed": convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) elif ( identifier == "Pref" or identifier == "ChromeOnly" or identifier == "Func" or identifier == "Trial" or identifier == "SecureContext" or identifier == "NonEnumerable" ): # Known attributes that we don't need to do anything with here pass else: raise WebIDLError( "Unknown extended attribute %s on constant" % identifier, [attr.location], ) IDLInterfaceMember.handleExtendedAttribute(self, attr) def _getDependentObjects(self): return set([self.type, self.value]) class IDLAttribute(IDLInterfaceMember): def __init__( self, location, identifier, type, readonly, inherit=False, static=False, stringifier=False, maplikeOrSetlike=None, extendedAttrDict=None, ): IDLInterfaceMember.__init__( self, location, identifier, IDLInterfaceMember.Tags.Attr, extendedAttrDict=extendedAttrDict, ) assert isinstance(type, IDLType) self.type = type self.readonly = readonly self.inherit = inherit self._static = static self.legacyLenientThis = False self._legacyUnforgeable = False self.stringifier = stringifier self.slotIndices = None assert maplikeOrSetlike is None or isinstance( maplikeOrSetlike, IDLMaplikeOrSetlike ) self.maplikeOrSetlike = maplikeOrSetlike self.dependsOn = "Everything" self.affects = "Everything" self.bindingAliases = [] if static and identifier.name == "prototype": raise WebIDLError( "The identifier of a static attribute must not be 'prototype'", [location], ) if readonly and inherit: raise WebIDLError( "An attribute cannot be both 'readonly' and 'inherit'", [self.location] ) def isStatic(self): return self._static def forceStatic(self): self._static = True def __str__(self): return "'%s' attribute '%s'" % (self.type, self.identifier) def finish(self, scope): IDLInterfaceMember.finish(self, scope) if not self.type.isComplete(): t = self.type.complete(scope) assert not isinstance(t, IDLUnresolvedType) assert not isinstance(t, IDLTypedefType) assert not isinstance(t.name, IDLUnresolvedIdentifier) self.type = t if self.readonly and ( self.type.hasClamp() or self.type.hasEnforceRange() or self.type.hasAllowShared() or self.type.legacyNullToEmptyString ): raise WebIDLError( "A readonly attribute cannot be [Clamp] or [EnforceRange] or [AllowShared]", [self.location], ) if self.type.isDictionary() and not self.getExtendedAttribute("Cached"): raise WebIDLError( "An attribute cannot be of a dictionary type", [self.location] ) if self.type.isSequence() and not self.getExtendedAttribute("Cached"): raise WebIDLError( "A non-cached attribute cannot be of a sequence " "type", [self.location], ) if self.type.isRecord() and not self.getExtendedAttribute("Cached"): raise WebIDLError( "A non-cached attribute cannot be of a record " "type", [self.location] ) if self.type.isUnion(): for f in self.type.unroll().flatMemberTypes: if f.isDictionary(): raise WebIDLError( "An attribute cannot be of a union " "type if one of its member types (or " "one of its member types's member " "types, and so on) is a dictionary " "type", [self.location, f.location], ) if f.isSequence(): raise WebIDLError( "An attribute cannot be of a union " "type if one of its member types (or " "one of its member types's member " "types, and so on) is a sequence " "type", [self.location, f.location], ) if f.isRecord(): raise WebIDLError( "An attribute cannot be of a union " "type if one of its member types (or " "one of its member types's member " "types, and so on) is a record " "type", [self.location, f.location], ) if not self.type.isInterface() and self.getExtendedAttribute("PutForwards"): raise WebIDLError( "An attribute with [PutForwards] must have an " "interface type as its type", [self.location], ) if not self.type.isInterface() and self.getExtendedAttribute("SameObject"): raise WebIDLError( "An attribute with [SameObject] must have an " "interface type as its type", [self.location], ) if self.type.isPromise() and not self.readonly: raise WebIDLError( "Promise-returning attributes must be readonly", [self.location] ) if self.type.isObservableArray(): if self.isStatic(): raise WebIDLError( "A static attribute cannot have an ObservableArray type", [self.location], ) if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( "StoreInSlot" ): raise WebIDLError( "[Cached] and [StoreInSlot] must not be used " "on an attribute whose type is ObservableArray", [self.location], ) def validate(self): def typeContainsChromeOnlyDictionaryMember(type): if type.nullable() or type.isSequence() or type.isRecord(): return typeContainsChromeOnlyDictionaryMember(type.inner) if type.isUnion(): for memberType in type.flatMemberTypes: (contains, location) = typeContainsChromeOnlyDictionaryMember( memberType ) if contains: return (True, location) if type.isDictionary(): dictionary = type.inner while dictionary: (contains, location) = dictionaryContainsChromeOnlyMember( dictionary ) if contains: return (True, location) dictionary = dictionary.parent return (False, None) def dictionaryContainsChromeOnlyMember(dictionary): for member in dictionary.members: if member.getExtendedAttribute("ChromeOnly"): return (True, member.location) (contains, location) = typeContainsChromeOnlyDictionaryMember( member.type ) if contains: return (True, location) return (False, None) IDLInterfaceMember.validate(self) if self.getExtendedAttribute("Cached") or self.getExtendedAttribute( "StoreInSlot" ): if not self.affects == "Nothing": raise WebIDLError( "Cached attributes and attributes stored in " "slots must be Constant or Pure or " "Affects=Nothing, since the getter won't always " "be called.", [self.location], ) (contains, location) = typeContainsChromeOnlyDictionaryMember(self.type) if contains: raise WebIDLError( "[Cached] and [StoreInSlot] must not be used " "on an attribute whose type contains a " "[ChromeOnly] dictionary member", [self.location, location], ) if self.getExtendedAttribute("Frozen"): if ( not self.type.isSequence() and not self.type.isDictionary() and not self.type.isRecord() ): raise WebIDLError( "[Frozen] is only allowed on " "sequence-valued, dictionary-valued, and " "record-valued attributes", [self.location], ) if not self.type.unroll().isExposedInAllOf(self.exposureSet): raise WebIDLError( "Attribute returns a type that is not exposed " "everywhere where the attribute is exposed", [self.location], ) if self.getExtendedAttribute("CEReactions"): if self.readonly: raise WebIDLError( "[CEReactions] is not allowed on " "readonly attributes", [self.location], ) def handleExtendedAttribute(self, attr): identifier = attr.identifier() if ( identifier == "SetterThrows" or identifier == "SetterCanOOM" or identifier == "SetterNeedsSubjectPrincipal" ) and self.readonly: raise WebIDLError( "Readonly attributes must not be flagged as " "[%s]" % identifier, [self.location], ) elif identifier == "BindingAlias": if not attr.hasValue(): raise WebIDLError( "[BindingAlias] takes an identifier or string", [attr.location] ) self._addBindingAlias(attr.value()) elif ( ( identifier == "Throws" or identifier == "GetterThrows" or identifier == "CanOOM" or identifier == "GetterCanOOM" ) and self.getExtendedAttribute("StoreInSlot") ) or ( identifier == "StoreInSlot" and ( self.getExtendedAttribute("Throws") or self.getExtendedAttribute("GetterThrows") or self.getExtendedAttribute("CanOOM") or self.getExtendedAttribute("GetterCanOOM") ) ): raise WebIDLError("Throwing things can't be [StoreInSlot]", [attr.location]) elif identifier == "LegacyLenientThis": if not attr.noArguments(): raise WebIDLError( "[LegacyLenientThis] must take no arguments", [attr.location] ) if self.isStatic(): raise WebIDLError( "[LegacyLenientThis] is only allowed on non-static " "attributes", [attr.location, self.location], ) if self.getExtendedAttribute("CrossOriginReadable"): raise WebIDLError( "[LegacyLenientThis] is not allowed in combination " "with [CrossOriginReadable]", [attr.location, self.location], ) if self.getExtendedAttribute("CrossOriginWritable"): raise WebIDLError( "[LegacyLenientThis] is not allowed in combination " "with [CrossOriginWritable]", [attr.location, self.location], ) self.legacyLenientThis = True elif identifier == "LegacyUnforgeable": if self.isStatic(): raise WebIDLError( "[LegacyUnforgeable] is only allowed on non-static " "attributes", [attr.location, self.location], ) self._legacyUnforgeable = True elif identifier == "SameObject" and not self.readonly: raise WebIDLError( "[SameObject] only allowed on readonly attributes", [attr.location, self.location], ) elif identifier == "Constant" and not self.readonly: raise WebIDLError( "[Constant] only allowed on readonly attributes", [attr.location, self.location], ) elif identifier == "PutForwards": if not self.readonly: raise WebIDLError( "[PutForwards] is only allowed on readonly " "attributes", [attr.location, self.location], ) if self.type.isPromise(): raise WebIDLError( "[PutForwards] is not allowed on " "Promise-typed attributes", [attr.location, self.location], ) if self.isStatic(): raise WebIDLError( "[PutForwards] is only allowed on non-static " "attributes", [attr.location, self.location], ) if self.getExtendedAttribute("Replaceable") is not None: raise WebIDLError( "[PutForwards] and [Replaceable] can't both " "appear on the same attribute", [attr.location, self.location], ) if not attr.hasValue(): raise WebIDLError( "[PutForwards] takes an identifier", [attr.location, self.location] ) elif identifier == "Replaceable": if not attr.noArguments(): raise WebIDLError( "[Replaceable] must take no arguments", [attr.location] ) if not self.readonly: raise WebIDLError( "[Replaceable] is only allowed on readonly " "attributes", [attr.location, self.location], ) if self.type.isPromise(): raise WebIDLError( "[Replaceable] is not allowed on " "Promise-typed attributes", [attr.location, self.location], ) if self.isStatic(): raise WebIDLError( "[Replaceable] is only allowed on non-static " "attributes", [attr.location, self.location], ) if self.getExtendedAttribute("PutForwards") is not None: raise WebIDLError( "[PutForwards] and [Replaceable] can't both " "appear on the same attribute", [attr.location, self.location], ) elif identifier == "LegacyLenientSetter": if not attr.noArguments(): raise WebIDLError( "[LegacyLenientSetter] must take no arguments", [attr.location] ) if not self.readonly: raise WebIDLError( "[LegacyLenientSetter] is only allowed on readonly " "attributes", [attr.location, self.location], ) if self.type.isPromise(): raise WebIDLError( "[LegacyLenientSetter] is not allowed on " "Promise-typed attributes", [attr.location, self.location], ) if self.isStatic(): raise WebIDLError( "[LegacyLenientSetter] is only allowed on non-static " "attributes", [attr.location, self.location], ) if self.getExtendedAttribute("PutForwards") is not None: raise WebIDLError( "[LegacyLenientSetter] and [PutForwards] can't both " "appear on the same attribute", [attr.location, self.location], ) if self.getExtendedAttribute("Replaceable") is not None: raise WebIDLError( "[LegacyLenientSetter] and [Replaceable] can't both " "appear on the same attribute", [attr.location, self.location], ) elif identifier == "LenientFloat": if self.readonly: raise WebIDLError( "[LenientFloat] used on a readonly attribute", [attr.location, self.location], ) if not self.type.includesRestrictedFloat(): raise WebIDLError( "[LenientFloat] used on an attribute with a " "non-restricted-float type", [attr.location, self.location], ) elif identifier == "StoreInSlot": if self.getExtendedAttribute("Cached"): raise WebIDLError( "[StoreInSlot] and [Cached] must not be " "specified on the same attribute", [attr.location, self.location], ) elif identifier == "Cached": if self.getExtendedAttribute("StoreInSlot"): raise WebIDLError( "[Cached] and [StoreInSlot] must not be " "specified on the same attribute", [attr.location, self.location], ) elif identifier == "CrossOriginReadable" or identifier == "CrossOriginWritable": if not attr.noArguments(): raise WebIDLError( "[%s] must take no arguments" % identifier, [attr.location] ) if self.isStatic(): raise WebIDLError( "[%s] is only allowed on non-static " "attributes" % identifier, [attr.location, self.location], ) if self.getExtendedAttribute("LegacyLenientThis"): raise WebIDLError( "[LegacyLenientThis] is not allowed in combination " "with [%s]" % identifier, [attr.location, self.location], ) elif identifier == "Exposed": convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) elif identifier == "Pure": if not attr.noArguments(): raise WebIDLError("[Pure] must take no arguments", [attr.location]) self._setDependsOn("DOMState") self._setAffects("Nothing") elif identifier == "Constant" or identifier == "SameObject": if not attr.noArguments(): raise WebIDLError( "[%s] must take no arguments" % identifier, [attr.location] ) self._setDependsOn("Nothing") self._setAffects("Nothing") elif identifier == "Affects": if not attr.hasValue(): raise WebIDLError("[Affects] takes an identifier", [attr.location]) self._setAffects(attr.value()) elif identifier == "DependsOn": if not attr.hasValue(): raise WebIDLError("[DependsOn] takes an identifier", [attr.location]) if ( attr.value() != "Everything" and attr.value() != "DOMState" and not self.readonly ): raise WebIDLError( "[DependsOn=%s] only allowed on " "readonly attributes" % attr.value(), [attr.location, self.location], ) self._setDependsOn(attr.value()) elif identifier == "UseCounter": if self.stringifier: raise WebIDLError( "[UseCounter] must not be used on a " "stringifier attribute", [attr.location, self.location], ) elif identifier == "Unscopable": if not attr.noArguments(): raise WebIDLError( "[Unscopable] must take no arguments", [attr.location] ) if self.isStatic(): raise WebIDLError( "[Unscopable] is only allowed on non-static " "attributes and operations", [attr.location, self.location], ) elif identifier == "CEReactions": if not attr.noArguments(): raise WebIDLError( "[CEReactions] must take no arguments", [attr.location] ) elif ( identifier == "Pref" or identifier == "Deprecated" or identifier == "SetterThrows" or identifier == "Throws" or identifier == "GetterThrows" or identifier == "SetterCanOOM" or identifier == "CanOOM" or identifier == "GetterCanOOM" or identifier == "ChromeOnly" or identifier == "Func" or identifier == "Trial" or identifier == "SecureContext" or identifier == "Frozen" or identifier == "NewObject" or identifier == "NeedsSubjectPrincipal" or identifier == "SetterNeedsSubjectPrincipal" or identifier == "GetterNeedsSubjectPrincipal" or identifier == "NeedsCallerType" or identifier == "BinaryName" or identifier == "NonEnumerable" or identifier == "BindingTemplate" ): # Known attributes that we don't need to do anything with here pass else: raise WebIDLError( "Unknown extended attribute %s on attribute" % identifier, [attr.location], ) IDLInterfaceMember.handleExtendedAttribute(self, attr) def getExtendedAttributes(self): return self._extendedAttrDict def resolve(self, parentScope): assert isinstance(parentScope, IDLScope) self.type.resolveType(parentScope) IDLObjectWithIdentifier.resolve(self, parentScope) def hasLegacyLenientThis(self): return self.legacyLenientThis def isMaplikeOrSetlikeAttr(self): """ True if this attribute was generated from an interface with maplike/setlike (e.g. this is the size attribute for maplike/setlike) """ return self.maplikeOrSetlike is not None def isLegacyUnforgeable(self): return self._legacyUnforgeable def _getDependentObjects(self): return set([self.type]) def expand(self, members): assert self.stringifier if ( not self.type.isDOMString() and not self.type.isUSVString() and not self.type.isUTF8String() ): raise WebIDLError( "The type of a stringifer attribute must be " "either DOMString, USVString or UTF8String", [self.location], ) identifier = IDLUnresolvedIdentifier( self.location, "__stringifier", allowDoubleUnderscore=True ) method = IDLMethod( self.location, identifier, returnType=self.type, arguments=[], stringifier=True, underlyingAttr=self, ) allowedExtAttrs = ["Throws", "NeedsSubjectPrincipal", "Pure"] # Safe to ignore these as they are only meaningful for attributes attributeOnlyExtAttrs = [ "CEReactions", "CrossOriginWritable", "SetterThrows", ] for key, value in self._extendedAttrDict.items(): if key in allowedExtAttrs: if value is not True: raise WebIDLError( "[%s] with a value is currently " "unsupported in stringifier attributes, " "please file a bug to add support" % key, [self.location], ) method.addExtendedAttributes( [IDLExtendedAttribute(self.location, (key,))] ) elif key not in attributeOnlyExtAttrs: raise WebIDLError( "[%s] is currently unsupported in " "stringifier attributes, please file a bug " "to add support" % key, [self.location], ) members.append(method) class IDLArgument(IDLObjectWithIdentifier): def __init__( self, location, identifier, type, optional=False, defaultValue=None, variadic=False, dictionaryMember=False, allowTypeAttributes=False, ): IDLObjectWithIdentifier.__init__(self, location, None, identifier) assert isinstance(type, IDLType) self.type = type self.optional = optional self.defaultValue = defaultValue self.variadic = variadic self.dictionaryMember = dictionaryMember self._isComplete = False self._allowTreatNonCallableAsNull = False self._extendedAttrDict = {} self.allowTypeAttributes = allowTypeAttributes assert not variadic or optional assert not variadic or not defaultValue def addExtendedAttributes(self, attrs): for attribute in attrs: identifier = attribute.identifier() if self.allowTypeAttributes and ( identifier == "EnforceRange" or identifier == "Clamp" or identifier == "LegacyNullToEmptyString" or identifier == "AllowShared" ): self.type = self.type.withExtendedAttributes([attribute]) elif identifier == "TreatNonCallableAsNull": self._allowTreatNonCallableAsNull = True elif self.dictionaryMember and ( identifier == "ChromeOnly" or identifier == "Func" or identifier == "Trial" or identifier == "Pref" ): if not self.optional: raise WebIDLError( "[%s] must not be used on a required " "dictionary member" % identifier, [attribute.location], ) elif self.dictionaryMember and identifier == "BinaryType": if not len(attribute.listValue()) == 1: raise WebIDLError( "[%s] BinaryType must take one argument" % identifier, [attribute.location], ) if not self.defaultValue: raise WebIDLError( "[%s] BinaryType can't be used without default value" % identifier, [attribute.location], ) else: raise WebIDLError( "Unhandled extended attribute on %s" % ( "a dictionary member" if self.dictionaryMember else "an argument" ), [attribute.location], ) attrlist = attribute.listValue() self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True def getExtendedAttribute(self, name): return self._extendedAttrDict.get(name, None) def isComplete(self): return self._isComplete def complete(self, scope): if self._isComplete: return self._isComplete = True if not self.type.isComplete(): type = self.type.complete(scope) assert not isinstance(type, IDLUnresolvedType) assert not isinstance(type, IDLTypedefType) assert not isinstance(type.name, IDLUnresolvedIdentifier) self.type = type if self.type.isUndefined(): raise WebIDLError( "undefined must not be used as the type of an argument in any circumstance", [self.location], ) if self.type.isAny(): assert self.defaultValue is None or isinstance( self.defaultValue, IDLNullValue ) # optional 'any' values always have a default value if self.optional and not self.defaultValue and not self.variadic: # Set the default value to undefined, for simplicity, so the # codegen doesn't have to special-case this. self.defaultValue = IDLUndefinedValue(self.location) if self.dictionaryMember and self.type.legacyNullToEmptyString: raise WebIDLError( "Dictionary members cannot be [LegacyNullToEmptyString]", [self.location], ) if self.type.isObservableArray(): raise WebIDLError( "%s cannot have an ObservableArray type" % ("Dictionary members" if self.dictionaryMember else "Arguments"), [self.location], ) # Now do the coercing thing; this needs to happen after the # above creation of a default value. if self.defaultValue: self.defaultValue = self.defaultValue.coerceToType(self.type, self.location) assert self.defaultValue def allowTreatNonCallableAsNull(self): return self._allowTreatNonCallableAsNull def _getDependentObjects(self): deps = set([self.type]) if self.defaultValue: deps.add(self.defaultValue) return deps def canHaveMissingValue(self): return self.optional and not self.defaultValue class IDLCallback(IDLObjectWithScope): def __init__( self, location, parentScope, identifier, returnType, arguments, isConstructor ): assert isinstance(returnType, IDLType) self._returnType = returnType # Clone the list self._arguments = list(arguments) IDLObjectWithScope.__init__(self, location, parentScope, identifier) for returnType, arguments in self.signatures(): for argument in arguments: argument.resolve(self) self._treatNonCallableAsNull = False self._treatNonObjectAsNull = False self._isRunScriptBoundary = False self._isConstructor = isConstructor def isCallback(self): return True def isConstructor(self): return self._isConstructor def signatures(self): return [(self._returnType, self._arguments)] def finish(self, scope): if not self._returnType.isComplete(): type = self._returnType.complete(scope) assert not isinstance(type, IDLUnresolvedType) assert not isinstance(type, IDLTypedefType) assert not isinstance(type.name, IDLUnresolvedIdentifier) self._returnType = type for argument in self._arguments: if argument.type.isComplete(): continue type = argument.type.complete(scope) assert not isinstance(type, IDLUnresolvedType) assert not isinstance(type, IDLTypedefType) assert not isinstance(type.name, IDLUnresolvedIdentifier) argument.type = type def validate(self): for argument in self._arguments: if argument.type.isUndefined(): raise WebIDLError( "undefined must not be used as the type of an argument in any circumstance", [self.location], ) def addExtendedAttributes(self, attrs): unhandledAttrs = [] for attr in attrs: if attr.identifier() == "TreatNonCallableAsNull": self._treatNonCallableAsNull = True elif attr.identifier() == "LegacyTreatNonObjectAsNull": if self._isConstructor: raise WebIDLError( "[LegacyTreatNonObjectAsNull] is not supported " "on constructors", [self.location], ) self._treatNonObjectAsNull = True elif attr.identifier() == "MOZ_CAN_RUN_SCRIPT_BOUNDARY": if self._isConstructor: raise WebIDLError( "[MOZ_CAN_RUN_SCRIPT_BOUNDARY] is not " "permitted on constructors", [self.location], ) self._isRunScriptBoundary = True else: unhandledAttrs.append(attr) if self._treatNonCallableAsNull and self._treatNonObjectAsNull: raise WebIDLError( "Cannot specify both [TreatNonCallableAsNull] " "and [LegacyTreatNonObjectAsNull]", [self.location], ) if len(unhandledAttrs) != 0: IDLType.addExtendedAttributes(self, unhandledAttrs) def _getDependentObjects(self): return set([self._returnType] + self._arguments) def isRunScriptBoundary(self): return self._isRunScriptBoundary class IDLCallbackType(IDLType): def __init__(self, location, callback): IDLType.__init__(self, location, callback.identifier.name) self.callback = callback def isCallback(self): return True def tag(self): return IDLType.Tags.callback def isDistinguishableFrom(self, other): if other.isPromise(): return False if other.isUnion(): # Just forward to the union; it'll deal return other.isDistinguishableFrom(self) return ( other.isUndefined() or other.isPrimitive() or other.isString() or other.isEnum() or other.isNonCallbackInterface() or other.isSequence() ) def _getDependentObjects(self): return self.callback._getDependentObjects() class IDLMethodOverload: """ A class that represents a single overload of a WebIDL method. This is not quite the same as an element of the "effective overload set" in the spec, because separate IDLMethodOverloads are not created based on arguments being optional. Rather, when multiple methods have the same name, there is an IDLMethodOverload for each one, all hanging off an IDLMethod representing the full set of overloads. """ def __init__(self, returnType, arguments, location): self.returnType = returnType # Clone the list of arguments, just in case self.arguments = list(arguments) self.location = location def _getDependentObjects(self): deps = set(self.arguments) deps.add(self.returnType) return deps def includesRestrictedFloatArgument(self): return any(arg.type.includesRestrictedFloat() for arg in self.arguments) class IDLMethod(IDLInterfaceMember, IDLScope): Special = enum( "Getter", "Setter", "Deleter", "LegacyCaller", base=IDLInterfaceMember.Special ) NamedOrIndexed = enum("Neither", "Named", "Indexed") def __init__( self, location, identifier, returnType, arguments, static=False, getter=False, setter=False, deleter=False, specialType=NamedOrIndexed.Neither, legacycaller=False, stringifier=False, maplikeOrSetlikeOrIterable=None, underlyingAttr=None, ): # REVIEW: specialType is NamedOrIndexed -- wow, this is messed up. IDLInterfaceMember.__init__( self, location, identifier, IDLInterfaceMember.Tags.Method ) self._hasOverloads = False assert isinstance(returnType, IDLType) # self._overloads is a list of IDLMethodOverloads self._overloads = [IDLMethodOverload(returnType, arguments, location)] assert isinstance(static, bool) self._static = static assert isinstance(getter, bool) self._getter = getter assert isinstance(setter, bool) self._setter = setter assert isinstance(deleter, bool) self._deleter = deleter assert isinstance(legacycaller, bool) self._legacycaller = legacycaller assert isinstance(stringifier, bool) self._stringifier = stringifier assert maplikeOrSetlikeOrIterable is None or isinstance( maplikeOrSetlikeOrIterable, IDLMaplikeOrSetlikeOrIterableBase ) self.maplikeOrSetlikeOrIterable = maplikeOrSetlikeOrIterable self._htmlConstructor = False self.underlyingAttr = underlyingAttr self._specialType = specialType self._legacyUnforgeable = False self.dependsOn = "Everything" self.affects = "Everything" self.aliases = [] if static and identifier.name == "prototype": raise WebIDLError( "The identifier of a static operation must not be 'prototype'", [location], ) self.assertSignatureConstraints() def __str__(self): return "Method '%s'" % self.identifier def assertSignatureConstraints(self): if self._getter or self._deleter: assert len(self._overloads) == 1 overload = self._overloads[0] arguments = overload.arguments assert len(arguments) == 1 assert ( arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] or arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long] ) assert not arguments[0].optional and not arguments[0].variadic assert not self._getter or not overload.returnType.isUndefined() if self._setter: assert len(self._overloads) == 1 arguments = self._overloads[0].arguments assert len(arguments) == 2 assert ( arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.domstring] or arguments[0].type == BuiltinTypes[IDLBuiltinType.Types.unsigned_long] ) assert not arguments[0].optional and not arguments[0].variadic assert not arguments[1].optional and not arguments[1].variadic if self._stringifier: assert len(self._overloads) == 1 overload = self._overloads[0] assert len(overload.arguments) == 0 if not self.underlyingAttr: assert ( overload.returnType == BuiltinTypes[IDLBuiltinType.Types.domstring] ) def isStatic(self): return self._static def forceStatic(self): self._static = True def isGetter(self): return self._getter def isSetter(self): return self._setter def isDeleter(self): return self._deleter def isNamed(self): assert ( self._specialType == IDLMethod.NamedOrIndexed.Named or self._specialType == IDLMethod.NamedOrIndexed.Indexed ) return self._specialType == IDLMethod.NamedOrIndexed.Named def isIndexed(self): assert ( self._specialType == IDLMethod.NamedOrIndexed.Named or self._specialType == IDLMethod.NamedOrIndexed.Indexed ) return self._specialType == IDLMethod.NamedOrIndexed.Indexed def isLegacycaller(self): return self._legacycaller def isStringifier(self): return self._stringifier def isToJSON(self): return self.identifier.name == "toJSON" def isDefaultToJSON(self): return self.isToJSON() and self.getExtendedAttribute("Default") def isMaplikeOrSetlikeOrIterableMethod(self): """ True if this method was generated as part of a maplike/setlike/etc interface (e.g. has/get methods) """ return self.maplikeOrSetlikeOrIterable is not None def isSpecial(self): return ( self.isGetter() or self.isSetter() or self.isDeleter() or self.isLegacycaller() or self.isStringifier() ) def isHTMLConstructor(self): return self._htmlConstructor def hasOverloads(self): return self._hasOverloads def isIdentifierLess(self): """ True if the method name started with __, and if the method is not a maplike/setlike method. Interfaces with maplike/setlike will generate methods starting with __ for chrome only backing object access in JS implemented interfaces, so while these functions use what is considered an non-identifier name, they actually DO have an identifier. """ return ( self.identifier.name[:2] == "__" and not self.isMaplikeOrSetlikeOrIterableMethod() ) def resolve(self, parentScope): assert isinstance(parentScope, IDLScope) IDLObjectWithIdentifier.resolve(self, parentScope) IDLScope.__init__(self, self.location, parentScope, self.identifier) for returnType, arguments in self.signatures(): for argument in arguments: argument.resolve(self) def addOverload(self, method): assert len(method._overloads) == 1 if self._extendedAttrDict != method._extendedAttrDict: extendedAttrDiff = set(self._extendedAttrDict.keys()) ^ set( method._extendedAttrDict.keys() ) if extendedAttrDiff == {"LenientFloat"}: if "LenientFloat" not in self._extendedAttrDict: for overload in self._overloads: if overload.includesRestrictedFloatArgument(): raise WebIDLError( "Restricted float behavior differs on different " "overloads of %s" % method.identifier, [overload.location, method.location], ) self._extendedAttrDict["LenientFloat"] = method._extendedAttrDict[ "LenientFloat" ] elif method._overloads[0].includesRestrictedFloatArgument(): raise WebIDLError( "Restricted float behavior differs on different " "overloads of %s" % method.identifier, [self.location, method.location], ) else: raise WebIDLError( "Extended attributes differ on different " "overloads of %s" % method.identifier, [self.location, method.location], ) self._overloads.extend(method._overloads) self._hasOverloads = True if self.isStatic() != method.isStatic(): raise WebIDLError( "Overloaded identifier %s appears with different values of the 'static' attribute" % method.identifier, [method.location], ) if self.isLegacycaller() != method.isLegacycaller(): raise WebIDLError( ( "Overloaded identifier %s appears with different " "values of the 'legacycaller' attribute" % method.identifier ), [method.location], ) # Can't overload special things! if ( self.isGetter() or method.isGetter() or self.isSetter() or method.isSetter() or self.isDeleter() or method.isDeleter() or self.isStringifier() or method.isStringifier() ): raise WebIDLError( ("Can't overload a special operation"), [self.location, method.location], ) if self.isHTMLConstructor() or method.isHTMLConstructor(): raise WebIDLError( ( "An interface must contain only a single operation annotated with HTMLConstructor, and no others" ), [self.location, method.location], ) return self def signatures(self): return [ (overload.returnType, overload.arguments) for overload in self._overloads ] def finish(self, scope): IDLInterfaceMember.finish(self, scope) for overload in self._overloads: returnType = overload.returnType if not returnType.isComplete(): returnType = returnType.complete(scope) assert not isinstance(returnType, IDLUnresolvedType) assert not isinstance(returnType, IDLTypedefType) assert not isinstance(returnType.name, IDLUnresolvedIdentifier) overload.returnType = returnType for argument in overload.arguments: if not argument.isComplete(): argument.complete(scope) assert argument.type.isComplete() # Now compute various information that will be used by the # WebIDL overload resolution algorithm. self.maxArgCount = max(len(s[1]) for s in self.signatures()) self.allowedArgCounts = [ i for i in range(self.maxArgCount + 1) if len(self.signaturesForArgCount(i)) != 0 ] def validate(self): IDLInterfaceMember.validate(self) # Make sure our overloads are properly distinguishable and don't have # different argument types before the distinguishing args. for argCount in self.allowedArgCounts: possibleOverloads = self.overloadsForArgCount(argCount) if len(possibleOverloads) == 1: continue distinguishingIndex = self.distinguishingIndexForArgCount(argCount) for idx in range(distinguishingIndex): firstSigType = possibleOverloads[0].arguments[idx].type for overload in possibleOverloads[1:]: if overload.arguments[idx].type != firstSigType: raise WebIDLError( "Signatures for method '%s' with %d arguments have " "different types of arguments at index %d, which " "is before distinguishing index %d" % ( self.identifier.name, argCount, idx, distinguishingIndex, ), [self.location, overload.location], ) overloadWithPromiseReturnType = None overloadWithoutPromiseReturnType = None for overload in self._overloads: returnType = overload.returnType if not returnType.unroll().isExposedInAllOf(self.exposureSet): raise WebIDLError( "Overload returns a type that is not exposed " "everywhere where the method is exposed", [overload.location], ) variadicArgument = None arguments = overload.arguments for idx, argument in enumerate(arguments): assert argument.type.isComplete() if ( argument.type.isDictionary() and argument.type.unroll().inner.canBeEmpty() ) or ( argument.type.isUnion() and argument.type.unroll().hasPossiblyEmptyDictionaryType() ): # Optional dictionaries and unions containing optional # dictionaries at the end of the list or followed by # optional arguments must be optional. if not argument.optional and all( arg.optional for arg in arguments[idx + 1 :] ): raise WebIDLError( "Dictionary argument without any " "required fields or union argument " "containing such dictionary not " "followed by a required argument " "must be optional", [argument.location], ) if not argument.defaultValue and all( arg.optional for arg in arguments[idx + 1 :] ): raise WebIDLError( "Dictionary argument without any " "required fields or union argument " "containing such dictionary not " "followed by a required argument " "must have a default value", [argument.location], ) # An argument cannot be a nullable dictionary or a # nullable union containing a dictionary. if argument.type.nullable() and ( argument.type.isDictionary() or ( argument.type.isUnion() and argument.type.unroll().hasDictionaryType() ) ): raise WebIDLError( "An argument cannot be a nullable " "dictionary or nullable union " "containing a dictionary", [argument.location], ) # Only the last argument can be variadic if variadicArgument: raise WebIDLError( "Variadic argument is not last argument", [variadicArgument.location], ) if argument.variadic: variadicArgument = argument if returnType.isPromise(): overloadWithPromiseReturnType = overload else: overloadWithoutPromiseReturnType = overload # Make sure either all our overloads return Promises or none do if overloadWithPromiseReturnType and overloadWithoutPromiseReturnType: raise WebIDLError( "We have overloads with both Promise and " "non-Promise return types", [ overloadWithPromiseReturnType.location, overloadWithoutPromiseReturnType.location, ], ) if overloadWithPromiseReturnType and self._legacycaller: raise WebIDLError( "May not have a Promise return type for a " "legacycaller.", [overloadWithPromiseReturnType.location], ) if self.getExtendedAttribute("StaticClassOverride") and not ( self.identifier.scope.isJSImplemented() and self.isStatic() ): raise WebIDLError( "StaticClassOverride can be applied to static" " methods on JS-implemented classes only.", [self.location], ) # Ensure that toJSON methods satisfy the spec constraints on them. if self.identifier.name == "toJSON": if len(self.signatures()) != 1: raise WebIDLError( "toJSON method has multiple overloads", [self._overloads[0].location, self._overloads[1].location], ) if len(self.signatures()[0][1]) != 0: raise WebIDLError("toJSON method has arguments", [self.location]) if not self.signatures()[0][0].isJSONType(): raise WebIDLError( "toJSON method has non-JSON return type", [self.location] ) def overloadsForArgCount(self, argc): return [ overload for overload in self._overloads if len(overload.arguments) == argc or ( len(overload.arguments) > argc and all(arg.optional for arg in overload.arguments[argc:]) ) or ( len(overload.arguments) < argc and len(overload.arguments) > 0 and overload.arguments[-1].variadic ) ] def signaturesForArgCount(self, argc): return [ (overload.returnType, overload.arguments) for overload in self.overloadsForArgCount(argc) ] def locationsForArgCount(self, argc): return [overload.location for overload in self.overloadsForArgCount(argc)] def distinguishingIndexForArgCount(self, argc): def isValidDistinguishingIndex(idx, signatures): for firstSigIndex, (firstRetval, firstArgs) in enumerate(signatures[:-1]): for secondRetval, secondArgs in signatures[firstSigIndex + 1 :]: if idx < len(firstArgs): firstType = firstArgs[idx].type else: assert firstArgs[-1].variadic firstType = firstArgs[-1].type if idx < len(secondArgs): secondType = secondArgs[idx].type else: assert secondArgs[-1].variadic secondType = secondArgs[-1].type if not firstType.isDistinguishableFrom(secondType): return False return True signatures = self.signaturesForArgCount(argc) for idx in range(argc): if isValidDistinguishingIndex(idx, signatures): return idx # No valid distinguishing index. Time to throw locations = self.locationsForArgCount(argc) raise WebIDLError( "Signatures with %d arguments for method '%s' are not " "distinguishable" % (argc, self.identifier.name), locations, ) def handleExtendedAttribute(self, attr): identifier = attr.identifier() if ( identifier == "GetterThrows" or identifier == "SetterThrows" or identifier == "GetterCanOOM" or identifier == "SetterCanOOM" or identifier == "SetterNeedsSubjectPrincipal" or identifier == "GetterNeedsSubjectPrincipal" ): raise WebIDLError( "Methods must not be flagged as " "[%s]" % identifier, [attr.location, self.location], ) elif identifier == "LegacyUnforgeable": if self.isStatic(): raise WebIDLError( "[LegacyUnforgeable] is only allowed on non-static " "methods", [attr.location, self.location], ) self._legacyUnforgeable = True elif identifier == "SameObject": raise WebIDLError( "Methods must not be flagged as [SameObject]", [attr.location, self.location], ) elif identifier == "Constant": raise WebIDLError( "Methods must not be flagged as [Constant]", [attr.location, self.location], ) elif identifier == "PutForwards": raise WebIDLError( "Only attributes support [PutForwards]", [attr.location, self.location] ) elif identifier == "LegacyLenientSetter": raise WebIDLError( "Only attributes support [LegacyLenientSetter]", [attr.location, self.location], ) elif identifier == "LenientFloat": # This is called before we've done overload resolution overloads = self._overloads assert len(overloads) == 1 if not overloads[0].returnType.isUndefined(): raise WebIDLError( "[LenientFloat] used on a non-undefined method", [attr.location, self.location], ) if not overloads[0].includesRestrictedFloatArgument(): raise WebIDLError( "[LenientFloat] used on an operation with no " "restricted float type arguments", [attr.location, self.location], ) elif identifier == "Exposed": convertExposedAttrToGlobalNameSet(attr, self._exposureGlobalNames) elif ( identifier == "CrossOriginCallable" or identifier == "WebGLHandlesContextLoss" ): # Known no-argument attributes. if not attr.noArguments(): raise WebIDLError( "[%s] must take no arguments" % identifier, [attr.location] ) if identifier == "CrossOriginCallable" and self.isStatic(): raise WebIDLError( "[CrossOriginCallable] is only allowed on non-static " "attributes", [attr.location, self.location], ) elif identifier == "Pure": if not attr.noArguments(): raise WebIDLError("[Pure] must take no arguments", [attr.location]) self._setDependsOn("DOMState") self._setAffects("Nothing") elif identifier == "Affects": if not attr.hasValue(): raise WebIDLError("[Affects] takes an identifier", [attr.location]) self._setAffects(attr.value()) elif identifier == "DependsOn": if not attr.hasValue(): raise WebIDLError("[DependsOn] takes an identifier", [attr.location]) self._setDependsOn(attr.value()) elif identifier == "Alias": if not attr.hasValue(): raise WebIDLError( "[Alias] takes an identifier or string", [attr.location] ) self._addAlias(attr.value()) elif identifier == "UseCounter": if self.isSpecial(): raise WebIDLError( "[UseCounter] must not be used on a special " "operation", [attr.location, self.location], ) elif identifier == "Unscopable": if not attr.noArguments(): raise WebIDLError( "[Unscopable] must take no arguments", [attr.location] ) if self.isStatic(): raise WebIDLError( "[Unscopable] is only allowed on non-static " "attributes and operations", [attr.location, self.location], ) elif identifier == "CEReactions": if not attr.noArguments(): raise WebIDLError( "[CEReactions] must take no arguments", [attr.location] ) if self.isSpecial() and not self.isSetter() and not self.isDeleter(): raise WebIDLError( "[CEReactions] is only allowed on operation, " "attribute, setter, and deleter", [attr.location, self.location], ) elif identifier == "Default": if not attr.noArguments(): raise WebIDLError("[Default] must take no arguments", [attr.location]) if not self.isToJSON(): raise WebIDLError( "[Default] is only allowed on toJSON operations", [attr.location, self.location], ) if self.signatures()[0][0] != BuiltinTypes[IDLBuiltinType.Types.object]: raise WebIDLError( "The return type of the default toJSON " "operation must be 'object'", [attr.location, self.location], ) elif ( identifier == "Throws" or identifier == "CanOOM" or identifier == "NewObject" or identifier == "ChromeOnly" or identifier == "Pref" or identifier == "Deprecated" or identifier == "Func" or identifier == "Trial" or identifier == "SecureContext" or identifier == "BinaryName" or identifier == "NeedsSubjectPrincipal" or identifier == "NeedsCallerType" or identifier == "StaticClassOverride" or identifier == "NonEnumerable" or identifier == "Unexposed" or identifier == "WebExtensionStub" ): # Known attributes that we don't need to do anything with here pass else: raise WebIDLError( "Unknown extended attribute %s on method" % identifier, [attr.location] ) IDLInterfaceMember.handleExtendedAttribute(self, attr) def returnsPromise(self): return self._overloads[0].returnType.isPromise() def isLegacyUnforgeable(self): return self._legacyUnforgeable def _getDependentObjects(self): deps = set() for overload in self._overloads: deps.update(overload._getDependentObjects()) return deps class IDLConstructor(IDLMethod): def __init__(self, location, args, name): # We can't actually init our IDLMethod yet, because we do not know the # return type yet. Just save the info we have for now and we will init # it later. self._initLocation = location self._initArgs = args self._initName = name self._inited = False self._initExtendedAttrs = [] def addExtendedAttributes(self, attrs): if self._inited: return IDLMethod.addExtendedAttributes(self, attrs) self._initExtendedAttrs.extend(attrs) def handleExtendedAttribute(self, attr): identifier = attr.identifier() if ( identifier == "BinaryName" or identifier == "ChromeOnly" or identifier == "NewObject" or identifier == "SecureContext" or identifier == "Throws" or identifier == "Func" or identifier == "Trial" or identifier == "Pref" or identifier == "UseCounter" ): IDLMethod.handleExtendedAttribute(self, attr) elif identifier == "HTMLConstructor": if not attr.noArguments(): raise WebIDLError( "[HTMLConstructor] must take no arguments", [attr.location] ) # We shouldn't end up here for legacy factory functions. assert self.identifier.name == "constructor" if any(len(sig[1]) != 0 for sig in self.signatures()): raise WebIDLError( "[HTMLConstructor] must not be applied to a " "constructor operation that has arguments.", [attr.location], ) self._htmlConstructor = True else: raise WebIDLError( "Unknown extended attribute %s on method" % identifier, [attr.location] ) def reallyInit(self, parentInterface): name = self._initName location = self._initLocation identifier = IDLUnresolvedIdentifier(location, name, allowForbidden=True) retType = IDLWrapperType(parentInterface.location, parentInterface) IDLMethod.__init__( self, location, identifier, retType, self._initArgs, static=True ) self._inited = True # Propagate through whatever extended attributes we already had self.addExtendedAttributes(self._initExtendedAttrs) self._initExtendedAttrs = [] # Constructors are always NewObject. Whether they throw or not is # indicated by [Throws] annotations in the usual way. self.addExtendedAttributes( [IDLExtendedAttribute(self.location, ("NewObject",))] ) class IDLIncludesStatement(IDLObject): def __init__(self, location, interface, mixin): IDLObject.__init__(self, location) self.interface = interface self.mixin = mixin self._finished = False def finish(self, scope): if self._finished: return self._finished = True assert isinstance(self.interface, IDLIdentifierPlaceholder) assert isinstance(self.mixin, IDLIdentifierPlaceholder) interface = self.interface.finish(scope) mixin = self.mixin.finish(scope) # NOTE: we depend on not setting self.interface and # self.mixin here to keep track of the original # locations. if not isinstance(interface, IDLInterface): raise WebIDLError( "Left-hand side of 'includes' is not an " "interface", [self.interface.location, interface.location], ) if interface.isCallback(): raise WebIDLError( "Left-hand side of 'includes' is a callback " "interface", [self.interface.location, interface.location], ) if not isinstance(mixin, IDLInterfaceMixin): raise WebIDLError( "Right-hand side of 'includes' is not an " "interface mixin", [self.mixin.location, mixin.location], ) mixin.actualExposureGlobalNames.update(interface._exposureGlobalNames) interface.addIncludedMixin(mixin) self.interface = interface self.mixin = mixin def validate(self): pass def addExtendedAttributes(self, attrs): if len(attrs) != 0: raise WebIDLError( "There are no extended attributes that are " "allowed on includes statements", [attrs[0].location, self.location], ) class IDLExtendedAttribute(IDLObject): """ A class to represent IDL extended attributes so we can give them locations """ def __init__(self, location, tuple): IDLObject.__init__(self, location) self._tuple = tuple def identifier(self): return self._tuple[0] def noArguments(self): return len(self._tuple) == 1 def hasValue(self): return len(self._tuple) >= 2 and isinstance(self._tuple[1], str) def value(self): assert self.hasValue() return self._tuple[1] def hasArgs(self): return ( len(self._tuple) == 2 and isinstance(self._tuple[1], list) or len(self._tuple) == 3 ) def args(self): assert self.hasArgs() # Our args are our last element return self._tuple[-1] def listValue(self): """ Backdoor for storing random data in _extendedAttrDict """ return list(self._tuple)[1:] # Parser class Tokenizer(object): tokens = ["INTEGER", "FLOATLITERAL", "IDENTIFIER", "STRING", "WHITESPACE", "OTHER"] def t_FLOATLITERAL(self, t): r"(-?(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([Ee][+-]?[0-9]+)?|[0-9]+[Ee][+-]?[0-9]+|Infinity))|NaN" t.value = float(t.value) return t def t_INTEGER(self, t): r"-?(0([0-7]+|[Xx][0-9A-Fa-f]+)?|[1-9][0-9]*)" try: # Can't use int(), because that doesn't handle octal properly. t.value = parseInt(t.value) except Exception: raise WebIDLError( "Invalid integer literal", [ Location( lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos, filename=self._filename, ) ], ) return t def t_IDENTIFIER(self, t): r"[_-]?[A-Za-z][0-9A-Z_a-z-]*" t.type = self.keywords.get(t.value, "IDENTIFIER") return t def t_STRING(self, t): r'"[^"]*"' t.value = t.value[1:-1] return t def t_WHITESPACE(self, t): r"[\t\n\r ]+|[\t\n\r ]*((//[^\n]*|/\*.*?\*/)[\t\n\r ]*)+" pass def t_ELLIPSIS(self, t): r"\.\.\." t.type = self.keywords.get(t.value) return t def t_OTHER(self, t): r"[^\t\n\r 0-9A-Z_a-z]" t.type = self.keywords.get(t.value, "OTHER") return t keywords = { "interface": "INTERFACE", "partial": "PARTIAL", "mixin": "MIXIN", "dictionary": "DICTIONARY", "exception": "EXCEPTION", "enum": "ENUM", "callback": "CALLBACK", "typedef": "TYPEDEF", "includes": "INCLUDES", "const": "CONST", "null": "NULL", "true": "TRUE", "false": "FALSE", "serializer": "SERIALIZER", "stringifier": "STRINGIFIER", "unrestricted": "UNRESTRICTED", "attribute": "ATTRIBUTE", "readonly": "READONLY", "inherit": "INHERIT", "static": "STATIC", "getter": "GETTER", "setter": "SETTER", "deleter": "DELETER", "legacycaller": "LEGACYCALLER", "optional": "OPTIONAL", "...": "ELLIPSIS", "::": "SCOPE", "DOMString": "DOMSTRING", "ByteString": "BYTESTRING", "USVString": "USVSTRING", "JSString": "JSSTRING", "UTF8String": "UTF8STRING", "any": "ANY", "boolean": "BOOLEAN", "byte": "BYTE", "double": "DOUBLE", "float": "FLOAT", "long": "LONG", "object": "OBJECT", "ObservableArray": "OBSERVABLEARRAY", "octet": "OCTET", "Promise": "PROMISE", "required": "REQUIRED", "sequence": "SEQUENCE", "record": "RECORD", "short": "SHORT", "unsigned": "UNSIGNED", "undefined": "UNDEFINED", ":": "COLON", ";": "SEMICOLON", "{": "LBRACE", "}": "RBRACE", "(": "LPAREN", ")": "RPAREN", "[": "LBRACKET", "]": "RBRACKET", "?": "QUESTIONMARK", "*": "ASTERISK", ",": "COMMA", "=": "EQUALS", "<": "LT", ">": "GT", "ArrayBuffer": "ARRAYBUFFER", "or": "OR", "maplike": "MAPLIKE", "setlike": "SETLIKE", "iterable": "ITERABLE", "namespace": "NAMESPACE", "constructor": "CONSTRUCTOR", "symbol": "SYMBOL", "async": "ASYNC", } tokens.extend(keywords.values()) def t_error(self, t): raise WebIDLError( "Unrecognized Input", [ Location( lexer=self.lexer, lineno=self.lexer.lineno, lexpos=self.lexer.lexpos, filename=self._filename, ) ], ) def __init__(self, outputdir, lexer=None): if lexer: self.lexer = lexer else: self.lexer = lex.lex(object=self, reflags=re.DOTALL) class SqueakyCleanLogger(object): errorWhitelist = [ # Web IDL defines the WHITESPACE token, but doesn't actually # use it ... so far. "Token 'WHITESPACE' defined, but not used", # And that means we have an unused token "There is 1 unused token", # Web IDL defines a OtherOrComma rule that's only used in # ExtendedAttributeInner, which we don't use yet. "Rule 'OtherOrComma' defined, but not used", # And an unused rule "There is 1 unused rule", # And the OtherOrComma grammar symbol is unreachable. "Symbol 'OtherOrComma' is unreachable", # Which means the Other symbol is unreachable. "Symbol 'Other' is unreachable", ] def __init__(self): self.errors = [] def debug(self, msg, *args, **kwargs): pass info = debug def warning(self, msg, *args, **kwargs): if ( msg == "%s:%d: Rule %r defined, but not used" or msg == "%s:%d: Rule '%s' defined, but not used" ): # Munge things so we don't have to hardcode filenames and # line numbers in our whitelist. whitelistmsg = "Rule %r defined, but not used" whitelistargs = args[2:] else: whitelistmsg = msg whitelistargs = args if (whitelistmsg % whitelistargs) not in SqueakyCleanLogger.errorWhitelist: self.errors.append(msg % args) error = warning def reportGrammarErrors(self): if self.errors: raise WebIDLError("\n".join(self.errors), []) class Parser(Tokenizer): def getLocation(self, p, i): return Location(self.lexer, p.lineno(i), p.lexpos(i), self._filename) def globalScope(self): return self._globalScope # The p_Foo functions here must match the WebIDL spec's grammar. # It's acceptable to split things at '|' boundaries. def p_Definitions(self, p): """ Definitions : ExtendedAttributeList Definition Definitions """ if p[2]: p[0] = [p[2]] p[2].addExtendedAttributes(p[1]) else: assert not p[1] p[0] = [] p[0].extend(p[3]) def p_DefinitionsEmpty(self, p): """ Definitions : """ p[0] = [] def p_Definition(self, p): """ Definition : CallbackOrInterfaceOrMixin | Namespace | Partial | Dictionary | Exception | Enum | Typedef | IncludesStatement """ p[0] = p[1] assert p[1] # We might not have implemented something ... def p_CallbackOrInterfaceOrMixinCallback(self, p): """ CallbackOrInterfaceOrMixin : CALLBACK CallbackRestOrInterface """ if p[2].isInterface(): assert isinstance(p[2], IDLInterface) p[2].setCallback(True) p[0] = p[2] def p_CallbackOrInterfaceOrMixinInterfaceOrMixin(self, p): """ CallbackOrInterfaceOrMixin : INTERFACE InterfaceOrMixin """ p[0] = p[2] def p_CallbackRestOrInterface(self, p): """ CallbackRestOrInterface : CallbackRest | CallbackConstructorRest | CallbackInterface """ assert p[1] p[0] = p[1] def handleNonPartialObject( self, location, identifier, constructor, constructorArgs, nonPartialArgs ): """ This handles non-partial objects (interfaces, namespaces and dictionaries) by checking for an existing partial object, and promoting it to non-partial as needed. The return value is the non-partial object. constructorArgs are all the args for the constructor except the last one: isKnownNonPartial. nonPartialArgs are the args for the setNonPartial call. """ # The name of the class starts with "IDL", so strip that off. # Also, starts with a capital letter after that, so nix that # as well. prettyname = constructor.__name__[3:].lower() try: existingObj = self.globalScope()._lookupIdentifier(identifier) if existingObj: if not isinstance(existingObj, constructor): raise WebIDLError( "%s has the same name as " "non-%s object" % (prettyname.capitalize(), prettyname), [location, existingObj.location], ) existingObj.setNonPartial(*nonPartialArgs) return existingObj except Exception as ex: if isinstance(ex, WebIDLError): raise ex pass # True for isKnownNonPartial return constructor(*(constructorArgs + [True])) def p_InterfaceOrMixin(self, p): """ InterfaceOrMixin : InterfaceRest | MixinRest """ p[0] = p[1] def p_CallbackInterface(self, p): """ CallbackInterface : INTERFACE InterfaceRest """ p[0] = p[2] def p_InterfaceRest(self, p): """ InterfaceRest : IDENTIFIER Inheritance LBRACE InterfaceMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(location, p[1]) members = p[4] parent = p[2] p[0] = self.handleNonPartialObject( location, identifier, IDLInterface, [location, self.globalScope(), identifier, parent, members], [location, parent, members], ) def p_InterfaceForwardDecl(self, p): """ InterfaceRest : IDENTIFIER SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(location, p[1]) try: if self.globalScope()._lookupIdentifier(identifier): p[0] = self.globalScope()._lookupIdentifier(identifier) if not isinstance(p[0], IDLExternalInterface): raise WebIDLError( "Name collision between external " "interface declaration for identifier " "%s and %s" % (identifier.name, p[0]), [location, p[0].location], ) return except Exception as ex: if isinstance(ex, WebIDLError): raise ex pass p[0] = IDLExternalInterface(location, self.globalScope(), identifier) def p_MixinRest(self, p): """ MixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) members = p[4] p[0] = self.handleNonPartialObject( location, identifier, IDLInterfaceMixin, [location, self.globalScope(), identifier, members], [location, members], ) def p_Namespace(self, p): """ Namespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) members = p[4] p[0] = self.handleNonPartialObject( location, identifier, IDLNamespace, [location, self.globalScope(), identifier, members], [location, None, members], ) def p_Partial(self, p): """ Partial : PARTIAL PartialDefinition """ p[0] = p[2] def p_PartialDefinitionInterface(self, p): """ PartialDefinition : INTERFACE PartialInterfaceOrPartialMixin """ p[0] = p[2] def p_PartialDefinition(self, p): """ PartialDefinition : PartialNamespace | PartialDictionary """ p[0] = p[1] def handlePartialObject( self, location, identifier, nonPartialConstructor, nonPartialConstructorArgs, partialConstructorArgs, ): """ This handles partial objects (interfaces, namespaces and dictionaries) by checking for an existing non-partial object, and adding ourselves to it as needed. The return value is our partial object. We use IDLPartialInterfaceOrNamespace for partial interfaces or namespaces, and IDLPartialDictionary for partial dictionaries. nonPartialConstructorArgs are all the args for the non-partial constructor except the last two: members and isKnownNonPartial. partialConstructorArgs are the arguments for the partial object constructor, except the last one (the non-partial object). """ # The name of the class starts with "IDL", so strip that off. # Also, starts with a capital letter after that, so nix that # as well. prettyname = nonPartialConstructor.__name__[3:].lower() nonPartialObject = None try: nonPartialObject = self.globalScope()._lookupIdentifier(identifier) if nonPartialObject: if not isinstance(nonPartialObject, nonPartialConstructor): raise WebIDLError( "Partial %s has the same name as " "non-%s object" % (prettyname, prettyname), [location, nonPartialObject.location], ) except Exception as ex: if isinstance(ex, WebIDLError): raise ex pass if not nonPartialObject: nonPartialObject = nonPartialConstructor( # No members, False for isKnownNonPartial *(nonPartialConstructorArgs), members=[], isKnownNonPartial=False ) partialObject = None if isinstance(nonPartialObject, IDLDictionary): partialObject = IDLPartialDictionary( *(partialConstructorArgs + [nonPartialObject]) ) elif isinstance( nonPartialObject, (IDLInterface, IDLInterfaceMixin, IDLNamespace) ): partialObject = IDLPartialInterfaceOrNamespace( *(partialConstructorArgs + [nonPartialObject]) ) else: raise WebIDLError( "Unknown partial object type %s" % type(partialObject), [location] ) return partialObject def p_PartialInterfaceOrPartialMixin(self, p): """ PartialInterfaceOrPartialMixin : PartialInterfaceRest | PartialMixinRest """ p[0] = p[1] def p_PartialInterfaceRest(self, p): """ PartialInterfaceRest : IDENTIFIER LBRACE PartialInterfaceMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(location, p[1]) members = p[3] p[0] = self.handlePartialObject( location, identifier, IDLInterface, [location, self.globalScope(), identifier, None], [location, identifier, members], ) def p_PartialMixinRest(self, p): """ PartialMixinRest : MIXIN IDENTIFIER LBRACE MixinMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) members = p[4] p[0] = self.handlePartialObject( location, identifier, IDLInterfaceMixin, [location, self.globalScope(), identifier], [location, identifier, members], ) def p_PartialNamespace(self, p): """ PartialNamespace : NAMESPACE IDENTIFIER LBRACE InterfaceMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) members = p[4] p[0] = self.handlePartialObject( location, identifier, IDLNamespace, [location, self.globalScope(), identifier], [location, identifier, members], ) def p_PartialDictionary(self, p): """ PartialDictionary : DICTIONARY IDENTIFIER LBRACE DictionaryMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) members = p[4] p[0] = self.handlePartialObject( location, identifier, IDLDictionary, [location, self.globalScope(), identifier], [location, identifier, members], ) def p_Inheritance(self, p): """ Inheritance : COLON ScopedName """ p[0] = IDLIdentifierPlaceholder(self.getLocation(p, 2), p[2]) def p_InheritanceEmpty(self, p): """ Inheritance : """ pass def p_InterfaceMembers(self, p): """ InterfaceMembers : ExtendedAttributeList InterfaceMember InterfaceMembers """ p[0] = [p[2]] assert not p[1] or p[2] p[2].addExtendedAttributes(p[1]) p[0].extend(p[3]) def p_InterfaceMembersEmpty(self, p): """ InterfaceMembers : """ p[0] = [] def p_InterfaceMember(self, p): """ InterfaceMember : PartialInterfaceMember | Constructor """ p[0] = p[1] def p_Constructor(self, p): """ Constructor : CONSTRUCTOR LPAREN ArgumentList RPAREN SEMICOLON """ p[0] = IDLConstructor(self.getLocation(p, 1), p[3], "constructor") def p_PartialInterfaceMembers(self, p): """ PartialInterfaceMembers : ExtendedAttributeList PartialInterfaceMember PartialInterfaceMembers """ p[0] = [p[2]] assert not p[1] or p[2] p[2].addExtendedAttributes(p[1]) p[0].extend(p[3]) def p_PartialInterfaceMembersEmpty(self, p): """ PartialInterfaceMembers : """ p[0] = [] def p_PartialInterfaceMember(self, p): """ PartialInterfaceMember : Const | AttributeOrOperationOrMaplikeOrSetlikeOrIterable """ p[0] = p[1] def p_MixinMembersEmpty(self, p): """ MixinMembers : """ p[0] = [] def p_MixinMembers(self, p): """ MixinMembers : ExtendedAttributeList MixinMember MixinMembers """ p[0] = [p[2]] assert not p[1] or p[2] p[2].addExtendedAttributes(p[1]) p[0].extend(p[3]) def p_MixinMember(self, p): """ MixinMember : Const | Attribute | Operation """ p[0] = p[1] def p_Dictionary(self, p): """ Dictionary : DICTIONARY IDENTIFIER Inheritance LBRACE DictionaryMembers RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) members = p[5] p[0] = IDLDictionary(location, self.globalScope(), identifier, p[3], members) def p_DictionaryMembers(self, p): """ DictionaryMembers : ExtendedAttributeList DictionaryMember DictionaryMembers | """ if len(p) == 1: # We're at the end of the list p[0] = [] return p[2].addExtendedAttributes(p[1]) p[0] = [p[2]] p[0].extend(p[3]) def p_DictionaryMemberRequired(self, p): """ DictionaryMember : REQUIRED TypeWithExtendedAttributes IDENTIFIER SEMICOLON """ # These quack a lot like required arguments, so just treat them that way. t = p[2] assert isinstance(t, IDLType) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) p[0] = IDLArgument( self.getLocation(p, 3), identifier, t, optional=False, defaultValue=None, variadic=False, dictionaryMember=True, ) def p_DictionaryMember(self, p): """ DictionaryMember : Type IDENTIFIER Default SEMICOLON """ # These quack a lot like optional arguments, so just treat them that way. t = p[1] assert isinstance(t, IDLType) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) defaultValue = p[3] # Any attributes that precede this may apply to the type, so # we configure the argument to forward type attributes down instead of producing # a parse error p[0] = IDLArgument( self.getLocation(p, 2), identifier, t, optional=True, defaultValue=defaultValue, variadic=False, dictionaryMember=True, allowTypeAttributes=True, ) def p_Default(self, p): """ Default : EQUALS DefaultValue | """ if len(p) > 1: p[0] = p[2] else: p[0] = None def p_DefaultValue(self, p): """ DefaultValue : ConstValue | LBRACKET RBRACKET | LBRACE RBRACE """ if len(p) == 2: p[0] = p[1] else: assert len(p) == 3 # Must be [] or {} if p[1] == "[": p[0] = IDLEmptySequenceValue(self.getLocation(p, 1)) else: assert p[1] == "{" p[0] = IDLDefaultDictionaryValue(self.getLocation(p, 1)) def p_DefaultValueNull(self, p): """ DefaultValue : NULL """ p[0] = IDLNullValue(self.getLocation(p, 1)) def p_DefaultValueUndefined(self, p): """ DefaultValue : UNDEFINED """ p[0] = IDLUndefinedValue(self.getLocation(p, 1)) def p_Exception(self, p): """ Exception : EXCEPTION IDENTIFIER Inheritance LBRACE ExceptionMembers RBRACE SEMICOLON """ pass def p_Enum(self, p): """ Enum : ENUM IDENTIFIER LBRACE EnumValueList RBRACE SEMICOLON """ location = self.getLocation(p, 1) identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) values = p[4] assert values p[0] = IDLEnum(location, self.globalScope(), identifier, values) def p_EnumValueList(self, p): """ EnumValueList : STRING EnumValueListComma """ p[0] = [p[1]] p[0].extend(p[2]) def p_EnumValueListComma(self, p): """ EnumValueListComma : COMMA EnumValueListString """ p[0] = p[2] def p_EnumValueListCommaEmpty(self, p): """ EnumValueListComma : """ p[0] = [] def p_EnumValueListString(self, p): """ EnumValueListString : STRING EnumValueListComma """ p[0] = [p[1]] p[0].extend(p[2]) def p_EnumValueListStringEmpty(self, p): """ EnumValueListString : """ p[0] = [] def p_CallbackRest(self, p): """ CallbackRest : IDENTIFIER EQUALS Type LPAREN ArgumentList RPAREN SEMICOLON """ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) p[0] = IDLCallback( self.getLocation(p, 1), self.globalScope(), identifier, p[3], p[5], isConstructor=False, ) def p_CallbackConstructorRest(self, p): """ CallbackConstructorRest : CONSTRUCTOR IDENTIFIER EQUALS Type LPAREN ArgumentList RPAREN SEMICOLON """ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 2), p[2]) p[0] = IDLCallback( self.getLocation(p, 2), self.globalScope(), identifier, p[4], p[6], isConstructor=True, ) def p_ExceptionMembers(self, p): """ ExceptionMembers : ExtendedAttributeList ExceptionMember ExceptionMembers | """ pass def p_Typedef(self, p): """ Typedef : TYPEDEF TypeWithExtendedAttributes IDENTIFIER SEMICOLON """ typedef = IDLTypedef(self.getLocation(p, 1), self.globalScope(), p[2], p[3]) p[0] = typedef def p_IncludesStatement(self, p): """ IncludesStatement : ScopedName INCLUDES ScopedName SEMICOLON """ assert p[2] == "includes" interface = IDLIdentifierPlaceholder(self.getLocation(p, 1), p[1]) mixin = IDLIdentifierPlaceholder(self.getLocation(p, 3), p[3]) p[0] = IDLIncludesStatement(self.getLocation(p, 1), interface, mixin) def p_Const(self, p): """ Const : CONST ConstType IDENTIFIER EQUALS ConstValue SEMICOLON """ location = self.getLocation(p, 1) type = p[2] identifier = IDLUnresolvedIdentifier(self.getLocation(p, 3), p[3]) value = p[5] p[0] = IDLConst(location, identifier, type, value) def p_ConstValueBoolean(self, p): """ ConstValue : BooleanLiteral """ location = self.getLocation(p, 1) booleanType = BuiltinTypes[IDLBuiltinType.Types.boolean] p[0] = IDLValue(location, booleanType, p[1]) def p_ConstValueInteger(self, p): """ ConstValue : INTEGER """ location = self.getLocation(p, 1) # We don't know ahead of time what type the integer literal is. # Determine the smallest type it could possibly fit in and use that. integerType = matchIntegerValueToType(p[1]) if integerType is None: raise WebIDLError("Integer literal out of range", [location]) p[0] = IDLValue(location, integerType, p[1]) def p_ConstValueFloat(self, p): """ ConstValue : FLOATLITERAL """ location = self.getLocation(p, 1) p[0] = IDLValue( location, BuiltinTypes[IDLBuiltinType.Types.unrestricted_float], p[1] ) def p_ConstValueString(self, p): """ ConstValue : STRING """ location = self.getLocation(p, 1) stringType = BuiltinTypes[IDLBuiltinType.Types.domstring] p[0] = IDLValue(location, stringType, p[1]) def p_BooleanLiteralTrue(self, p): """ BooleanLiteral : TRUE """ p[0] = True def p_BooleanLiteralFalse(self, p): """ BooleanLiteral : FALSE """ p[0] = False def p_AttributeOrOperationOrMaplikeOrSetlikeOrIterable(self, p): """ AttributeOrOperationOrMaplikeOrSetlikeOrIterable : Attribute | Maplike | Setlike | Iterable | AsyncIterable | Operation """ p[0] = p[1] def p_Iterable(self, p): """ Iterable : ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON | ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON """ location = self.getLocation(p, 2) identifier = IDLUnresolvedIdentifier( location, "__iterable", allowDoubleUnderscore=True ) if len(p) > 6: keyType = p[3] valueType = p[5] else: keyType = None valueType = p[3] p[0] = IDLIterable(location, identifier, keyType, valueType, self.globalScope()) def p_AsyncIterable(self, p): """ AsyncIterable : ASYNC ITERABLE LT TypeWithExtendedAttributes GT SEMICOLON | ASYNC ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON | ASYNC ITERABLE LT TypeWithExtendedAttributes GT LPAREN ArgumentList RPAREN SEMICOLON | ASYNC ITERABLE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT LPAREN ArgumentList RPAREN SEMICOLON """ location = self.getLocation(p, 2) identifier = IDLUnresolvedIdentifier( location, "__iterable", allowDoubleUnderscore=True ) if len(p) == 12: keyType = p[4] valueType = p[6] argList = p[9] elif len(p) == 10: keyType = None valueType = p[4] argList = p[7] elif len(p) == 9: keyType = p[4] valueType = p[6] argList = [] else: keyType = None valueType = p[4] argList = [] p[0] = IDLAsyncIterable( location, identifier, keyType, valueType, argList, self.globalScope() ) def p_Setlike(self, p): """ Setlike : ReadOnly SETLIKE LT TypeWithExtendedAttributes GT SEMICOLON """ readonly = p[1] maplikeOrSetlikeType = p[2] location = self.getLocation(p, 2) identifier = IDLUnresolvedIdentifier( location, "__setlike", allowDoubleUnderscore=True ) keyType = p[4] valueType = keyType p[0] = IDLMaplikeOrSetlike( location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType ) def p_Maplike(self, p): """ Maplike : ReadOnly MAPLIKE LT TypeWithExtendedAttributes COMMA TypeWithExtendedAttributes GT SEMICOLON """ readonly = p[1] maplikeOrSetlikeType = p[2] location = self.getLocation(p, 2) identifier = IDLUnresolvedIdentifier( location, "__maplike", allowDoubleUnderscore=True ) keyType = p[4] valueType = p[6] p[0] = IDLMaplikeOrSetlike( location, identifier, maplikeOrSetlikeType, readonly, keyType, valueType ) def p_AttributeWithQualifier(self, p): """ Attribute : Qualifier AttributeRest """ static = IDLInterfaceMember.Special.Static in p[1] stringifier = IDLInterfaceMember.Special.Stringifier in p[1] (location, identifier, type, readonly) = p[2] p[0] = IDLAttribute( location, identifier, type, readonly, static=static, stringifier=stringifier ) def p_AttributeInherited(self, p): """ Attribute : INHERIT AttributeRest """ (location, identifier, type, readonly) = p[2] p[0] = IDLAttribute(location, identifier, type, readonly, inherit=True) def p_Attribute(self, p): """ Attribute : AttributeRest """ (location, identifier, type, readonly) = p[1] p[0] = IDLAttribute(location, identifier, type, readonly, inherit=False) def p_AttributeRest(self, p): """ AttributeRest : ReadOnly ATTRIBUTE TypeWithExtendedAttributes AttributeName SEMICOLON """ location = self.getLocation(p, 2) readonly = p[1] t = p[3] identifier = IDLUnresolvedIdentifier(self.getLocation(p, 4), p[4]) p[0] = (location, identifier, t, readonly) def p_ReadOnly(self, p): """ ReadOnly : READONLY """ p[0] = True def p_ReadOnlyEmpty(self, p): """ ReadOnly : """ p[0] = False def p_Operation(self, p): """ Operation : Qualifiers OperationRest """ qualifiers = p[1] # Disallow duplicates in the qualifier set if not len(set(qualifiers)) == len(qualifiers): raise WebIDLError( "Duplicate qualifiers are not allowed", [self.getLocation(p, 1)] ) static = IDLInterfaceMember.Special.Static in p[1] # If static is there that's all that's allowed. This is disallowed # by the parser, so we can assert here. assert not static or len(qualifiers) == 1 stringifier = IDLInterfaceMember.Special.Stringifier in p[1] # If stringifier is there that's all that's allowed. This is disallowed # by the parser, so we can assert here. assert not stringifier or len(qualifiers) == 1 getter = True if IDLMethod.Special.Getter in p[1] else False setter = True if IDLMethod.Special.Setter in p[1] else False deleter = True if IDLMethod.Special.Deleter in p[1] else False legacycaller = True if IDLMethod.Special.LegacyCaller in p[1] else False if getter or deleter: if setter: raise WebIDLError( "getter and deleter are incompatible with setter", [self.getLocation(p, 1)], ) (returnType, identifier, arguments) = p[2] assert isinstance(returnType, IDLType) specialType = IDLMethod.NamedOrIndexed.Neither if getter or deleter: if len(arguments) != 1: raise WebIDLError( "%s has wrong number of arguments" % ("getter" if getter else "deleter"), [self.getLocation(p, 2)], ) argType = arguments[0].type if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: specialType = IDLMethod.NamedOrIndexed.Named elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: specialType = IDLMethod.NamedOrIndexed.Indexed if deleter: raise WebIDLError( "There is no such thing as an indexed deleter.", [self.getLocation(p, 1)], ) else: raise WebIDLError( "%s has wrong argument type (must be DOMString or UnsignedLong)" % ("getter" if getter else "deleter"), [arguments[0].location], ) if arguments[0].optional or arguments[0].variadic: raise WebIDLError( "%s cannot have %s argument" % ( "getter" if getter else "deleter", "optional" if arguments[0].optional else "variadic", ), [arguments[0].location], ) if getter: if returnType.isUndefined(): raise WebIDLError( "getter cannot have undefined return type", [self.getLocation(p, 2)] ) if setter: if len(arguments) != 2: raise WebIDLError( "setter has wrong number of arguments", [self.getLocation(p, 2)] ) argType = arguments[0].type if argType == BuiltinTypes[IDLBuiltinType.Types.domstring]: specialType = IDLMethod.NamedOrIndexed.Named elif argType == BuiltinTypes[IDLBuiltinType.Types.unsigned_long]: specialType = IDLMethod.NamedOrIndexed.Indexed else: raise WebIDLError( "settter has wrong argument type (must be DOMString or UnsignedLong)", [arguments[0].location], ) if arguments[0].optional or arguments[0].variadic: raise WebIDLError( "setter cannot have %s argument" % ("optional" if arguments[0].optional else "variadic"), [arguments[0].location], ) if arguments[1].optional or arguments[1].variadic: raise WebIDLError( "setter cannot have %s argument" % ("optional" if arguments[1].optional else "variadic"), [arguments[1].location], ) if stringifier: if len(arguments) != 0: raise WebIDLError( "stringifier has wrong number of arguments", [self.getLocation(p, 2)], ) if not returnType.isDOMString(): raise WebIDLError( "stringifier must have DOMString return type", [self.getLocation(p, 2)], ) # identifier might be None. This is only permitted for special methods. if not identifier: if ( not getter and not setter and not deleter and not legacycaller and not stringifier ): raise WebIDLError( "Identifier required for non-special methods", [self.getLocation(p, 2)], ) location = BuiltinLocation("") identifier = IDLUnresolvedIdentifier( location, "__%s%s%s%s%s%s" % ( "named" if specialType == IDLMethod.NamedOrIndexed.Named else "indexed" if specialType == IDLMethod.NamedOrIndexed.Indexed else "", "getter" if getter else "", "setter" if setter else "", "deleter" if deleter else "", "legacycaller" if legacycaller else "", "stringifier" if stringifier else "", ), allowDoubleUnderscore=True, ) method = IDLMethod( self.getLocation(p, 2), identifier, returnType, arguments, static=static, getter=getter, setter=setter, deleter=deleter, specialType=specialType, legacycaller=legacycaller, stringifier=stringifier, ) p[0] = method def p_Stringifier(self, p): """ Operation : STRINGIFIER SEMICOLON """ identifier = IDLUnresolvedIdentifier( BuiltinLocation(""), "__stringifier", allowDoubleUnderscore=True, ) method = IDLMethod( self.getLocation(p, 1), identifier, returnType=BuiltinTypes[IDLBuiltinType.Types.domstring], arguments=[], stringifier=True, ) p[0] = method def p_QualifierStatic(self, p): """ Qualifier : STATIC """ p[0] = [IDLInterfaceMember.Special.Static] def p_QualifierStringifier(self, p): """ Qualifier : STRINGIFIER """ p[0] = [IDLInterfaceMember.Special.Stringifier] def p_Qualifiers(self, p): """ Qualifiers : Qualifier | Specials """ p[0] = p[1] def p_Specials(self, p): """ Specials : Special Specials """ p[0] = [p[1]] p[0].extend(p[2]) def p_SpecialsEmpty(self, p): """ Specials : """ p[0] = [] def p_SpecialGetter(self, p): """ Special : GETTER """ p[0] = IDLMethod.Special.Getter def p_SpecialSetter(self, p): """ Special : SETTER """ p[0] = IDLMethod.Special.Setter def p_SpecialDeleter(self, p): """ Special : DELETER """ p[0] = IDLMethod.Special.Deleter def p_SpecialLegacyCaller(self, p): """ Special : LEGACYCALLER """ p[0] = IDLMethod.Special.LegacyCaller def p_OperationRest(self, p): """ OperationRest : Type OptionalIdentifier LPAREN ArgumentList RPAREN SEMICOLON """ p[0] = (p[1], p[2], p[4]) def p_OptionalIdentifier(self, p): """ OptionalIdentifier : IDENTIFIER """ p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) def p_OptionalIdentifierEmpty(self, p): """ OptionalIdentifier : """ pass def p_ArgumentList(self, p): """ ArgumentList : Argument Arguments """ p[0] = [p[1]] if p[1] else [] p[0].extend(p[2]) def p_ArgumentListEmpty(self, p): """ ArgumentList : """ p[0] = [] def p_Arguments(self, p): """ Arguments : COMMA Argument Arguments """ p[0] = [p[2]] if p[2] else [] p[0].extend(p[3]) def p_ArgumentsEmpty(self, p): """ Arguments : """ p[0] = [] def p_Argument(self, p): """ Argument : ExtendedAttributeList ArgumentRest """ p[0] = p[2] p[0].addExtendedAttributes(p[1]) def p_ArgumentRestOptional(self, p): """ ArgumentRest : OPTIONAL TypeWithExtendedAttributes ArgumentName Default """ t = p[2] assert isinstance(t, IDLType) # Arg names can be reserved identifiers identifier = IDLUnresolvedIdentifier( self.getLocation(p, 3), p[3], allowForbidden=True ) defaultValue = p[4] # We can't test t.isAny() here and give it a default value as needed, # since at this point t is not a fully resolved type yet (e.g. it might # be a typedef). We'll handle the 'any' case in IDLArgument.complete. p[0] = IDLArgument( self.getLocation(p, 3), identifier, t, True, defaultValue, False ) def p_ArgumentRest(self, p): """ ArgumentRest : Type Ellipsis ArgumentName """ t = p[1] assert isinstance(t, IDLType) # Arg names can be reserved identifiers identifier = IDLUnresolvedIdentifier( self.getLocation(p, 3), p[3], allowForbidden=True ) variadic = p[2] # We can't test t.isAny() here and give it a default value as needed, # since at this point t is not a fully resolved type yet (e.g. it might # be a typedef). We'll handle the 'any' case in IDLArgument.complete. # variadic implies optional # Any attributes that precede this may apply to the type, so # we configure the argument to forward type attributes down instead of producing # a parse error p[0] = IDLArgument( self.getLocation(p, 3), identifier, t, variadic, None, variadic, allowTypeAttributes=True, ) def p_ArgumentName(self, p): """ ArgumentName : IDENTIFIER | ArgumentNameKeyword """ p[0] = p[1] def p_ArgumentNameKeyword(self, p): """ ArgumentNameKeyword : ASYNC | ATTRIBUTE | CALLBACK | CONST | CONSTRUCTOR | DELETER | DICTIONARY | ENUM | EXCEPTION | GETTER | INCLUDES | INHERIT | INTERFACE | ITERABLE | LEGACYCALLER | MAPLIKE | MIXIN | NAMESPACE | PARTIAL | READONLY | REQUIRED | SERIALIZER | SETLIKE | SETTER | STATIC | STRINGIFIER | TYPEDEF | UNRESTRICTED """ p[0] = p[1] def p_AttributeName(self, p): """ AttributeName : IDENTIFIER | AttributeNameKeyword """ p[0] = p[1] def p_AttributeNameKeyword(self, p): """ AttributeNameKeyword : ASYNC | REQUIRED """ p[0] = p[1] def p_Ellipsis(self, p): """ Ellipsis : ELLIPSIS """ p[0] = True def p_EllipsisEmpty(self, p): """ Ellipsis : """ p[0] = False def p_ExceptionMember(self, p): """ ExceptionMember : Const | ExceptionField """ pass def p_ExceptionField(self, p): """ ExceptionField : Type IDENTIFIER SEMICOLON """ pass def p_ExtendedAttributeList(self, p): """ ExtendedAttributeList : LBRACKET ExtendedAttribute ExtendedAttributes RBRACKET """ p[0] = [p[2]] if p[3]: p[0].extend(p[3]) def p_ExtendedAttributeListEmpty(self, p): """ ExtendedAttributeList : """ p[0] = [] def p_ExtendedAttribute(self, p): """ ExtendedAttribute : ExtendedAttributeNoArgs | ExtendedAttributeArgList | ExtendedAttributeIdent | ExtendedAttributeWildcard | ExtendedAttributeNamedArgList | ExtendedAttributeIdentList """ p[0] = IDLExtendedAttribute(self.getLocation(p, 1), p[1]) def p_ExtendedAttributeEmpty(self, p): """ ExtendedAttribute : """ pass def p_ExtendedAttributes(self, p): """ ExtendedAttributes : COMMA ExtendedAttribute ExtendedAttributes """ p[0] = [p[2]] if p[2] else [] p[0].extend(p[3]) def p_ExtendedAttributesEmpty(self, p): """ ExtendedAttributes : """ p[0] = [] def p_Other(self, p): """ Other : INTEGER | FLOATLITERAL | IDENTIFIER | STRING | OTHER | ELLIPSIS | COLON | SCOPE | SEMICOLON | LT | EQUALS | GT | QUESTIONMARK | ASTERISK | DOMSTRING | BYTESTRING | USVSTRING | UTF8STRING | JSSTRING | PROMISE | ANY | BOOLEAN | BYTE | DOUBLE | FALSE | FLOAT | LONG | NULL | OBJECT | OCTET | OR | OPTIONAL | RECORD | SEQUENCE | SHORT | SYMBOL | TRUE | UNSIGNED | UNDEFINED | ArgumentNameKeyword """ pass def p_OtherOrComma(self, p): """ OtherOrComma : Other | COMMA """ pass def p_TypeSingleType(self, p): """ Type : SingleType """ p[0] = p[1] def p_TypeUnionType(self, p): """ Type : UnionType Null """ p[0] = self.handleNullable(p[1], p[2]) def p_TypeWithExtendedAttributes(self, p): """ TypeWithExtendedAttributes : ExtendedAttributeList Type """ p[0] = p[2].withExtendedAttributes(p[1]) def p_SingleTypeDistinguishableType(self, p): """ SingleType : DistinguishableType """ p[0] = p[1] def p_SingleTypeAnyType(self, p): """ SingleType : ANY """ p[0] = BuiltinTypes[IDLBuiltinType.Types.any] def p_SingleTypePromiseType(self, p): """ SingleType : PROMISE LT Type GT """ p[0] = IDLPromiseType(self.getLocation(p, 1), p[3]) def p_UnionType(self, p): """ UnionType : LPAREN UnionMemberType OR UnionMemberType UnionMemberTypes RPAREN """ types = [p[2], p[4]] types.extend(p[5]) p[0] = IDLUnionType(self.getLocation(p, 1), types) def p_UnionMemberTypeDistinguishableType(self, p): """ UnionMemberType : ExtendedAttributeList DistinguishableType """ p[0] = p[2].withExtendedAttributes(p[1]) def p_UnionMemberType(self, p): """ UnionMemberType : UnionType Null """ p[0] = self.handleNullable(p[1], p[2]) def p_UnionMemberTypes(self, p): """ UnionMemberTypes : OR UnionMemberType UnionMemberTypes """ p[0] = [p[2]] p[0].extend(p[3]) def p_UnionMemberTypesEmpty(self, p): """ UnionMemberTypes : """ p[0] = [] def p_DistinguishableType(self, p): """ DistinguishableType : PrimitiveType Null | ARRAYBUFFER Null | OBJECT Null | UNDEFINED Null """ if p[1] == "object": type = BuiltinTypes[IDLBuiltinType.Types.object] elif p[1] == "ArrayBuffer": type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer] elif p[1] == "undefined": type = BuiltinTypes[IDLBuiltinType.Types.undefined] else: type = BuiltinTypes[p[1]] p[0] = self.handleNullable(type, p[2]) def p_DistinguishableTypeStringType(self, p): """ DistinguishableType : StringType Null """ p[0] = self.handleNullable(p[1], p[2]) def p_DistinguishableTypeSequenceType(self, p): """ DistinguishableType : SEQUENCE LT TypeWithExtendedAttributes GT Null """ innerType = p[3] type = IDLSequenceType(self.getLocation(p, 1), innerType) p[0] = self.handleNullable(type, p[5]) def p_DistinguishableTypeRecordType(self, p): """ DistinguishableType : RECORD LT StringType COMMA TypeWithExtendedAttributes GT Null """ keyType = p[3] valueType = p[5] type = IDLRecordType(self.getLocation(p, 1), keyType, valueType) p[0] = self.handleNullable(type, p[7]) def p_DistinguishableTypeObservableArrayType(self, p): """ DistinguishableType : OBSERVABLEARRAY LT TypeWithExtendedAttributes GT Null """ innerType = p[3] type = IDLObservableArrayType(self.getLocation(p, 1), innerType) p[0] = self.handleNullable(type, p[5]) def p_DistinguishableTypeScopedName(self, p): """ DistinguishableType : ScopedName Null """ assert isinstance(p[1], IDLUnresolvedIdentifier) if p[1].name == "Promise": raise WebIDLError( "Promise used without saying what it's " "parametrized over", [self.getLocation(p, 1)], ) type = None try: if self.globalScope()._lookupIdentifier(p[1]): obj = self.globalScope()._lookupIdentifier(p[1]) assert not obj.isType() if obj.isTypedef(): type = IDLTypedefType( self.getLocation(p, 1), obj.innerType, obj.identifier.name ) elif obj.isCallback() and not obj.isInterface(): type = IDLCallbackType(self.getLocation(p, 1), obj) else: type = IDLWrapperType(self.getLocation(p, 1), p[1]) p[0] = self.handleNullable(type, p[2]) return except Exception: pass type = IDLUnresolvedType(self.getLocation(p, 1), p[1]) p[0] = self.handleNullable(type, p[2]) def p_ConstType(self, p): """ ConstType : PrimitiveType """ p[0] = BuiltinTypes[p[1]] def p_ConstTypeIdentifier(self, p): """ ConstType : IDENTIFIER """ identifier = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) p[0] = IDLUnresolvedType(self.getLocation(p, 1), identifier) def p_PrimitiveTypeUint(self, p): """ PrimitiveType : UnsignedIntegerType """ p[0] = p[1] def p_PrimitiveTypeBoolean(self, p): """ PrimitiveType : BOOLEAN """ p[0] = IDLBuiltinType.Types.boolean def p_PrimitiveTypeByte(self, p): """ PrimitiveType : BYTE """ p[0] = IDLBuiltinType.Types.byte def p_PrimitiveTypeOctet(self, p): """ PrimitiveType : OCTET """ p[0] = IDLBuiltinType.Types.octet def p_PrimitiveTypeFloat(self, p): """ PrimitiveType : FLOAT """ p[0] = IDLBuiltinType.Types.float def p_PrimitiveTypeUnrestictedFloat(self, p): """ PrimitiveType : UNRESTRICTED FLOAT """ p[0] = IDLBuiltinType.Types.unrestricted_float def p_PrimitiveTypeDouble(self, p): """ PrimitiveType : DOUBLE """ p[0] = IDLBuiltinType.Types.double def p_PrimitiveTypeUnrestictedDouble(self, p): """ PrimitiveType : UNRESTRICTED DOUBLE """ p[0] = IDLBuiltinType.Types.unrestricted_double def p_StringType(self, p): """ StringType : BuiltinStringType """ p[0] = BuiltinTypes[p[1]] def p_BuiltinStringTypeDOMString(self, p): """ BuiltinStringType : DOMSTRING """ p[0] = IDLBuiltinType.Types.domstring def p_BuiltinStringTypeBytestring(self, p): """ BuiltinStringType : BYTESTRING """ p[0] = IDLBuiltinType.Types.bytestring def p_BuiltinStringTypeUSVString(self, p): """ BuiltinStringType : USVSTRING """ p[0] = IDLBuiltinType.Types.usvstring def p_BuiltinStringTypeUTF8String(self, p): """ BuiltinStringType : UTF8STRING """ p[0] = IDLBuiltinType.Types.utf8string def p_BuiltinStringTypeJSString(self, p): """ BuiltinStringType : JSSTRING """ p[0] = IDLBuiltinType.Types.jsstring def p_UnsignedIntegerTypeUnsigned(self, p): """ UnsignedIntegerType : UNSIGNED IntegerType """ # Adding one to a given signed integer type gets you the unsigned type: p[0] = p[2] + 1 def p_UnsignedIntegerType(self, p): """ UnsignedIntegerType : IntegerType """ p[0] = p[1] def p_IntegerTypeShort(self, p): """ IntegerType : SHORT """ p[0] = IDLBuiltinType.Types.short def p_IntegerTypeLong(self, p): """ IntegerType : LONG OptionalLong """ if p[2]: p[0] = IDLBuiltinType.Types.long_long else: p[0] = IDLBuiltinType.Types.long def p_OptionalLong(self, p): """ OptionalLong : LONG """ p[0] = True def p_OptionalLongEmpty(self, p): """ OptionalLong : """ p[0] = False def p_Null(self, p): """ Null : QUESTIONMARK | """ if len(p) > 1: p[0] = self.getLocation(p, 1) else: p[0] = None def p_ScopedName(self, p): """ ScopedName : AbsoluteScopedName | RelativeScopedName """ p[0] = p[1] def p_AbsoluteScopedName(self, p): """ AbsoluteScopedName : SCOPE IDENTIFIER ScopedNameParts """ assert False pass def p_RelativeScopedName(self, p): """ RelativeScopedName : IDENTIFIER ScopedNameParts """ assert not p[2] # Not implemented! p[0] = IDLUnresolvedIdentifier(self.getLocation(p, 1), p[1]) def p_ScopedNameParts(self, p): """ ScopedNameParts : SCOPE IDENTIFIER ScopedNameParts """ assert False pass def p_ScopedNamePartsEmpty(self, p): """ ScopedNameParts : """ p[0] = None def p_ExtendedAttributeNoArgs(self, p): """ ExtendedAttributeNoArgs : IDENTIFIER """ p[0] = (p[1],) def p_ExtendedAttributeArgList(self, p): """ ExtendedAttributeArgList : IDENTIFIER LPAREN ArgumentList RPAREN """ p[0] = (p[1], p[3]) def p_ExtendedAttributeIdent(self, p): """ ExtendedAttributeIdent : IDENTIFIER EQUALS STRING | IDENTIFIER EQUALS IDENTIFIER """ p[0] = (p[1], p[3]) def p_ExtendedAttributeWildcard(self, p): """ ExtendedAttributeWildcard : IDENTIFIER EQUALS ASTERISK """ p[0] = (p[1], p[3]) def p_ExtendedAttributeNamedArgList(self, p): """ ExtendedAttributeNamedArgList : IDENTIFIER EQUALS IDENTIFIER LPAREN ArgumentList RPAREN """ p[0] = (p[1], p[3], p[5]) def p_ExtendedAttributeIdentList(self, p): """ ExtendedAttributeIdentList : IDENTIFIER EQUALS LPAREN IdentifierList RPAREN """ p[0] = (p[1], p[4]) def p_IdentifierList(self, p): """ IdentifierList : IDENTIFIER Identifiers """ idents = list(p[2]) # This is only used for identifier-list-valued extended attributes, and if # we're going to restrict to IDENTIFIER here we should at least allow # escaping with leading '_' as usual for identifiers. ident = p[1] if ident[0] == "_": ident = ident[1:] idents.insert(0, ident) p[0] = idents def p_IdentifiersList(self, p): """ Identifiers : COMMA IDENTIFIER Identifiers """ idents = list(p[3]) # This is only used for identifier-list-valued extended attributes, and if # we're going to restrict to IDENTIFIER here we should at least allow # escaping with leading '_' as usual for identifiers. ident = p[2] if ident[0] == "_": ident = ident[1:] idents.insert(0, ident) p[0] = idents def p_IdentifiersEmpty(self, p): """ Identifiers : """ p[0] = [] def p_error(self, p): if not p: raise WebIDLError( ( "Syntax Error at end of file. Possibly due to " "missing semicolon(;), braces(}) or both" ), [self._filename], ) else: raise WebIDLError( "invalid syntax", [Location(self.lexer, p.lineno, p.lexpos, self._filename)], ) def __init__(self, outputdir="", lexer=None): Tokenizer.__init__(self, outputdir, lexer) logger = SqueakyCleanLogger() try: self.parser = yacc.yacc( module=self, outputdir=outputdir, errorlog=logger, write_tables=False, # Pickling the grammar is a speedup in # some cases (older Python?) but a # significant slowdown in others. # We're not pickling for now, until it # becomes a speedup again. # , picklefile='WebIDLGrammar.pkl' ) finally: logger.reportGrammarErrors() self._globalScope = IDLScope(BuiltinLocation(""), None, None) self._installBuiltins(self._globalScope) self._productions = [] self._filename = "" self.lexer.input(Parser._builtins) self._filename = None self.parser.parse(lexer=self.lexer, tracking=True) def _installBuiltins(self, scope): assert isinstance(scope, IDLScope) # range omits the last value. for x in range( IDLBuiltinType.Types.ArrayBuffer, IDLBuiltinType.Types.Float64Array + 1 ): builtin = BuiltinTypes[x] name = builtin.name IDLTypedef(BuiltinLocation(""), scope, builtin, name) @staticmethod def handleNullable(type, questionMarkLocation): if questionMarkLocation is not None: type = IDLNullableType(questionMarkLocation, type) return type def parse(self, t, filename=None): self.lexer.input(t) # for tok in iter(self.lexer.token, None): # print tok self._filename = filename self._productions.extend(self.parser.parse(lexer=self.lexer, tracking=True)) self._filename = None def finish(self): # If we have interfaces that are iterable, create their # iterator interfaces and add them to the productions array. interfaceStatements = [] for p in self._productions: if isinstance(p, IDLInterface): interfaceStatements.append(p) for iface in interfaceStatements: iterable = None # We haven't run finish() on the interface yet, so we don't know # whether our interface is maplike/setlike/iterable or not. This # means we have to loop through the members to see if we have an # iterable member. for m in iface.members: if isinstance(m, (IDLIterable, IDLAsyncIterable)): iterable = m break if iterable and (iterable.isPairIterator() or iterable.isAsyncIterable()): def simpleExtendedAttr(str): return IDLExtendedAttribute(iface.location, (str,)) if isinstance(iterable, IDLAsyncIterable): nextReturnType = IDLPromiseType( iterable.location, BuiltinTypes[IDLBuiltinType.Types.any] ) else: nextReturnType = BuiltinTypes[IDLBuiltinType.Types.object] nextMethod = IDLMethod( iterable.location, IDLUnresolvedIdentifier(iterable.location, "next"), nextReturnType, [], ) nextMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) methods = [nextMethod] if iterable.getExtendedAttribute("GenerateReturnMethod"): assert isinstance(iterable, IDLAsyncIterable) returnMethod = IDLMethod( iterable.location, IDLUnresolvedIdentifier(iterable.location, "return"), IDLPromiseType( iterable.location, BuiltinTypes[IDLBuiltinType.Types.any] ), [ IDLArgument( iterable.location, IDLUnresolvedIdentifier( BuiltinLocation(""), "value", ), BuiltinTypes[IDLBuiltinType.Types.any], optional=True, ), ], ) returnMethod.addExtendedAttributes([simpleExtendedAttr("Throws")]) methods.append(returnMethod) if iterable.isIterable(): itr_suffix = "Iterator" else: itr_suffix = "AsyncIterator" itr_ident = IDLUnresolvedIdentifier( iface.location, iface.identifier.name + itr_suffix ) if iterable.isIterable(): classNameOverride = iface.identifier.name + " Iterator" elif iterable.isAsyncIterable(): classNameOverride = iface.identifier.name + " AsyncIterator" itr_iface = IDLInterface( iface.location, self.globalScope(), itr_ident, None, methods, isKnownNonPartial=True, classNameOverride=classNameOverride, ) itr_iface.addExtendedAttributes( [simpleExtendedAttr("LegacyNoInterfaceObject")] ) # Make sure the exposure set for the iterator interface is the # same as the exposure set for the iterable interface, because # we're going to generate methods on the iterable that return # instances of the iterator. itr_iface._exposureGlobalNames = set(iface._exposureGlobalNames) # Always append generated iterable interfaces after the # interface they're a member of, otherwise nativeType generation # won't work correctly. if iterable.isIterable(): itr_iface.iterableInterface = iface else: itr_iface.asyncIterableInterface = iface self._productions.append(itr_iface) iterable.iteratorType = IDLWrapperType(iface.location, itr_iface) # Make sure we finish IDLIncludesStatements before we finish the # IDLInterfaces. # XXX khuey hates this bit and wants to nuke it from orbit. includesStatements = [ p for p in self._productions if isinstance(p, IDLIncludesStatement) ] otherStatements = [ p for p in self._productions if not isinstance(p, IDLIncludesStatement) ] for production in includesStatements: production.finish(self.globalScope()) for production in otherStatements: production.finish(self.globalScope()) # Do any post-finish validation we need to do for production in self._productions: production.validate() # De-duplicate self._productions, without modifying its order. seen = set() result = [] for p in self._productions: if p not in seen: seen.add(p) result.append(p) return result def reset(self): return Parser(lexer=self.lexer) # Builtin IDL defined by WebIDL _builtins = """ typedef (ArrayBufferView or ArrayBuffer) BufferSource; """ def main(): # Parse arguments. from optparse import OptionParser usageString = "usage: %prog [options] files" o = OptionParser(usage=usageString) o.add_option( "--cachedir", dest="cachedir", default=None, help="Directory in which to cache lex/parse tables.", ) o.add_option( "--verbose-errors", action="store_true", default=False, help="When an error happens, display the Python traceback.", ) (options, args) = o.parse_args() if len(args) < 1: o.error(usageString) fileList = args baseDir = os.getcwd() # Parse the WebIDL. parser = Parser(options.cachedir) try: for filename in fileList: fullPath = os.path.normpath(os.path.join(baseDir, filename)) f = open(fullPath, "rb") lines = f.readlines() f.close() print(fullPath) parser.parse("".join(lines), fullPath) parser.finish() except WebIDLError as e: if options.verbose_errors: traceback.print_exc() else: print(e) if __name__ == "__main__": main()