Bug 741125: Update WebIDL parser.
authorKyle Huey <khuey@kylehuey.com>
Thu, 12 Apr 2012 15:14:10 -0700
changeset 91512 d26c0cd1dfa3a2c277d8d3713774febff1054db7
parent 91511 676dca561a2cfbf1ac57a0e35b2455ec2aa2212a
child 91513 b7ff20237032b8a202f9a4ce37491eed7be256ea
push id672
push usertim.taubert@gmx.de
push dateFri, 13 Apr 2012 10:22:59 +0000
treeherderfx-team@cb2e81306595 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs741125
milestone14.0a1
Bug 741125: Update WebIDL parser.
client.py
dom/bindings/Makefile.in
dom/bindings/parser/WebIDL.py
dom/bindings/parser/__init__.py
dom/bindings/parser/runtests.py
dom/bindings/parser/tests/test_array_of_interface.py
dom/bindings/parser/tests/test_builtin_filename.py
dom/bindings/parser/tests/test_constructor.py
dom/bindings/parser/tests/test_deduplicate.py
dom/bindings/parser/tests/test_enum.py
dom/bindings/parser/tests/test_error_colno.py
dom/bindings/parser/tests/test_nullable_equivalency.py
--- a/client.py
+++ b/client.py
@@ -2,49 +2,71 @@
 
 NSPR_DIRS = (('nsprpub', 'mozilla/nsprpub'),)
 NSS_DIRS  = (('dbm', 'mozilla/dbm'),
              ('security/nss', 'mozilla/security/nss'),
              ('security/coreconf', 'mozilla/security/coreconf'),
              ('security/dbm', 'mozilla/security/dbm'))
 NSSCKBI_DIRS = (('security/nss/lib/ckfw/builtins', 'mozilla/security/nss/lib/ckfw/builtins'),)
 LIBFFI_DIRS = (('js/ctypes/libffi', 'libffi'),)
+WEBIDLPARSER_DIR = 'dom/bindings/parser'
+WEBIDLPARSER_REPO = 'https://hg.mozilla.org/users/khuey_mozilla.com/webidl-parser'
+WEBIDLPARSER_EXCLUSIONS = ['.hgignore', '.gitignore', '.hg', 'ply']
 
 CVSROOT_MOZILLA = ':pserver:anonymous@cvs-mirror.mozilla.org:/cvsroot'
 CVSROOT_LIBFFI = ':pserver:anoncvs@sources.redhat.com:/cvs/libffi'
 
 import os
 import sys
 import datetime
 import shutil
+import glob
 from optparse import OptionParser
 from subprocess import check_call
 
 topsrcdir = os.path.dirname(__file__)
 if topsrcdir == '':
     topsrcdir = '.'
 
 def check_call_noisy(cmd, *args, **kwargs):
     print "Executing command:", cmd
     check_call(cmd, *args, **kwargs)
 
 def do_hg_pull(dir, repository, hg):
     fulldir = os.path.join(topsrcdir, dir)
     # clone if the dir doesn't exist, pull if it does
     if not os.path.exists(fulldir):
-        fulldir = os.path.join(topsrcdir, dir)
         check_call_noisy([hg, 'clone', repository, fulldir])
     else:
         cmd = [hg, 'pull', '-u', '-R', fulldir]
         if repository is not None:
             cmd.append(repository)
         check_call_noisy(cmd)
     check_call([hg, 'parent', '-R', fulldir,
                 '--template=Updated to revision {node}.\n'])
 
+def do_hg_replace(dir, repository, tag, exclusions, hg):
+    """
+        Replace the contents of dir with the contents of repository, except for
+        files matching exclusions.
+    """
+    fulldir = os.path.join(topsrcdir, dir)
+    if os.path.exists(fulldir):
+        shutil.rmtree(fulldir)
+
+    assert not os.path.exists(fulldir)
+    check_call_noisy([hg, 'clone', '-u', tag, repository, fulldir])
+
+    for thing in exclusions:
+        for excluded in glob.iglob(os.path.join(fulldir, thing)):
+            if os.path.isdir(excluded):
+                shutil.rmtree(excluded)
+            else:
+                os.remove(excluded)
+
 def do_cvs_export(modules, tag, cvsroot, cvs):
     """Check out a CVS directory without CVS metadata, using "export"
     modules is a list of directories to check out and the corresponding
     cvs module, e.g. (('nsprpub', 'mozilla/nsprpub'))
     """
     for module_tuple in modules:
         module = module_tuple[0]
         cvs_module = module_tuple[1]
@@ -55,25 +77,27 @@ def do_cvs_export(modules, tag, cvsroot,
 
         (parent, leaf) = os.path.split(module)
         print "CVS export begin: " + datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
         check_call_noisy([cvs, '-d', cvsroot,
                           'export', '-r', tag, '-d', leaf, cvs_module],
                          cwd=os.path.join(topsrcdir, parent))
         print "CVS export end: " + datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
 
-o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname | update_libffi tagname")
+o = OptionParser(usage="client.py [options] update_nspr tagname | update_nss tagname | update_libffi tagname | update_webidlparser tagname")
 o.add_option("--skip-mozilla", dest="skip_mozilla",
              action="store_true", default=False,
              help="Obsolete")
 
 o.add_option("--cvs", dest="cvs", default=os.environ.get('CVS', 'cvs'),
              help="The location of the cvs binary")
 o.add_option("--cvsroot", dest="cvsroot",
              help="The CVSROOT (default for mozilla checkouts: %s)" % CVSROOT_MOZILLA)
+o.add_option("--hg", dest="hg", default=os.environ.get('HG', 'hg'),
+             help="The location of the hg binary")
 
 try:
     options, args = o.parse_args()
     action = args[0]
 except IndexError:
     o.print_help()
     sys.exit(2)
 
@@ -99,11 +123,14 @@ elif action in ('update_nssckbi'):
         options.cvsroot = os.environ.get('CVSROOT', CVSROOT_MOZILLA)
     do_cvs_export(NSSCKBI_DIRS, tag, options.cvsroot, options.cvs)
     print >>file("security/nss/TAG-INFO-CKBI", "w"), tag
 elif action in ('update_libffi'):
     tag, = args[1:]
     if not options.cvsroot:
         options.cvsroot = CVSROOT_LIBFFI
     do_cvs_export(LIBFFI_DIRS, tag, options.cvsroot, options.cvs)
+elif action in ('update_webidlparser'):
+    tag, = args[1:]
+    do_hg_replace(WEBIDLPARSER_DIR, WEBIDLPARSER_REPO, tag, WEBIDLPARSER_EXCLUSIONS, options.hg)
 else:
     o.print_help()
     sys.exit(2)
--- a/dom/bindings/Makefile.in
+++ b/dom/bindings/Makefile.in
@@ -70,26 +70,26 @@ bindinggen_dependencies := \
   Codegen.py \
   ParserResults.pkl \
   $(GLOBAL_DEPS) \
   $(NULL)
 
 $(binding_header_files): %Binding.h: $(bindinggen_dependencies) \
                                      $(webidl_base)/%.webidl \
                                      $(NULL)
-	$(PYTHON) $(topsrcdir)/config/pythonpath.py \
+	PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
 	  $(PLY_INCLUDE) -I$(srcdir)/parser \
     	  $(srcdir)/BindingGen.py $(ACCESSOR_OPT) header \
 	  $(srcdir)/Bindings.conf $*Binding \
 	  $(webidl_base)/$*.webidl
 
 $(binding_cpp_files): %Binding.cpp: $(bindinggen_dependencies) \
                                     $(webidl_base)/%.webidl \
                                     $(NULL)
-	$(PYTHON) $(topsrcdir)/config/pythonpath.py \
+	PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
 	  $(PLY_INCLUDE) -I$(srcdir)/parser \
 	  $(srcdir)/BindingGen.py $(ACCESSOR_OPT) cpp \
 	  $(srcdir)/Bindings.conf $*Binding \
 	  $(webidl_base)/$*.webidl
 
 $(globalgen_targets): ParserResults.pkl
 
 CACHE_DIR = _cache
@@ -104,17 +104,17 @@ globalgen_dependencies := \
   $(NULL)
 
 $(CACHE_DIR)/.done:
 	$(MKDIR) -p $(CACHE_DIR)
 	@$(TOUCH) $@
 
 ParserResults.pkl: $(globalgen_dependencies) \
                    $(addprefix $(webidl_base)/, $(webidl_files))
-	$(PYTHON) $(topsrcdir)/config/pythonpath.py \
+	PYTHONDONTWRITEBYTECODE=1 $(PYTHON) $(topsrcdir)/config/pythonpath.py \
     $(PLY_INCLUDE) -I$(srcdir)/parser \
     $(srcdir)/GlobalGen.py $(ACCESSOR_OPT) $(srcdir)/Bindings.conf $(webidl_base) \
     --cachedir=$(CACHE_DIR) \
     $(webidl_files)
 
 GARBAGE += \
   $(binding_header_files) \
   $(binding_cpp_files) \
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -64,98 +64,110 @@ def parseInt(literal):
         base = 10
 
     value = int(string, base)
     return value * sign
 
 # Magic for creating enums
 def M_add_class_attribs(attribs):
     def foo(name, bases, dict_):
-        for v, k in attribs:
+        for v, k in enumerate(attribs):
             dict_[k] = v
+        assert 'length' not in dict_
+        dict_['length'] = len(attribs)
         return type(name, bases, dict_)
     return foo
 
 def enum(*names):
     class Foo(object):
-        __metaclass__ = M_add_class_attribs(enumerate(names))
+        __metaclass__ = M_add_class_attribs(names)
         def __setattr__(self, name, value):  # this makes it read-only
             raise NotImplementedError
     return Foo()
 
 class WebIDLError(Exception):
     def __init__(self, message, location, warning=False):
         self.message = message
         self.location = location
         self.warning = warning
 
     def __str__(self):
         return "%s: %s%s%s" % (self.warning and 'warning' or 'error',
                                self.message, ", " if self.location else "",
                                self.location)
 
 class Location(object):
-    _line = None
-
     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 "<unknown>"
 
     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)
-        self._line = self._lexdata[startofline:endofline]
+        if endofline != -1:
+            self._line = self._lexdata[startofline:endofline]
+        else:
+            self._line = self._lexdata[startofline:]
         self._colno = self._lexpos - startofline
 
-    def pointerline(self):
-        def i():
-            for i in xrange(0, self._colno):
-                yield " "
-            yield "^"
-
-        return "".join(i())
-
     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())
+                                          self._line, self._pointerline())
 
 class BuiltinLocation(object):
     def __init__(self, text):
         self.msg = text
 
+    def __eq__(self, other):
+        return isinstance(other, BuiltinLocation) and \
+               self.msg == other.msg
+
+    def filename(self):
+        return '<builtin>'
+
+    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._file
+        return self.location.filename()
 
     def isInterface(self):
         return False
 
     def isEnum(self):
         return False
 
     def isCallback(self):
@@ -193,16 +205,21 @@ class IDLScope(IDLObject):
         return self.QName()
 
     def QName(self):
         if self._name:
             return self._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
 
@@ -295,16 +312,19 @@ class IDLUnresolvedIdentifier(IDLObject)
 
         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
 
@@ -363,19 +383,18 @@ class IDLExternalInterface(IDLObjectWith
 class IDLInterface(IDLObjectWithScope):
     def __init__(self, location, parentScope, name, parent, members):
         assert isinstance(parentScope, IDLScope)
         assert isinstance(name, IDLUnresolvedIdentifier)
         assert not parent or isinstance(parent, IDLParentPlaceholder)
 
         self.parent = parent
         self._callback = False
-
+        self._finished = False
         self.members = list(members) # clone the list
-        assert iter(self.members) # Assert it's iterable
 
         IDLObjectWithScope.__init__(self, location, parentScope, name)
 
     def __str__(self):
         return "Interface '%s'" % self.identifier.name
 
     def ctor(self):
         identifier = IDLUnresolvedIdentifier(self.location, "constructor",
@@ -399,116 +418,82 @@ class IDLInterface(IDLObjectWithScope):
 
         retval = originalObject.addOverload(newObject)
         # Might be a ctor, which isn't in self.members
         if newObject in self.members:
             self.members.remove(newObject)
         return retval
 
     def finish(self, scope):
-        if hasattr(self, "_finished"):
+        if self._finished:
             return
 
         self._finished = True
 
         assert not self.parent or isinstance(self.parent, IDLParentPlaceholder)
         parent = self.parent.finish(scope) if self.parent else None
         assert not parent or isinstance(parent, IDLInterface)
 
         self.parent = parent
 
         assert iter(self.members)
-        members = None
 
         if self.parent:
             self.parent.finish(scope)
             assert iter(self.parent.members)
 
             members = list(self.parent.members)
             members.extend(self.members)
         else:
             members = list(self.members)
 
-        SpecialType = enum(
-            'NamedGetter',
-            'NamedSetter',
-            'NamedCreator',
-            'NamedDeleter',
-            'IndexedGetter',
-            'IndexedSetter',
-            'IndexedCreator',
-            'IndexedDeleter'
-        )
-
-        specialMembersSeen = [False for i in range(8)]
-
         def memberNotOnParentChain(member, iface):
             assert iface
 
             if not iface.parent:
                 return True
 
             assert isinstance(iface.parent, IDLInterface)
             if member in iface.parent.members:
                 return False
             return memberNotOnParentChain(member, iface.parent)
 
+        # Ensure that there's at most one of each {named,indexed}
+        # {getter,setter,creator,deleter}.
+        specialMembersSeen = set()
         for member in members:
             if memberNotOnParentChain(member, self):
                 member.resolve(self)
-                
-            if member.tag == IDLInterfaceMember.Tags.Method:
-                if member.isGetter():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedGetter]:
-                            raise WebIDLError("Multiple named getters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedGetter] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedGetter]:
-                            raise WebIDLError("Multiple indexed getters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedGetter] = True
-                if member.isSetter():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedSetter]:
-                            raise WebIDLError("Multiple named setters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedSetter] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedSetter]:
-                            raise WebIDLError("Multiple indexed setters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedSetter] = True
-                if member.isCreator():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedCreator]:
-                            raise WebIDLError("Multiple named creators on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedCreator] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedCreator]:
-                            raise WebIDLError("Multiple indexed creators on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedCreator] = True
-                if member.isDeleter():
-                    if member.isNamed():
-                        if specialMembersSeen[SpecialType.NamedDeleter]:
-                            raise WebIDLError("Multiple named deleters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.NamedDeleter] = True
-                    else:
-                        assert member.isIndexed()
-                        if specialMembersSeen[SpecialType.IndexedDeleter]:
-                            raise WebIDLError("Multiple indexed Deleters on %s" % (self),
-                                              self.location)
-                        specialMembersSeen[SpecialType.IndexedDeleter] = True
+
+            if member.tag != IDLInterfaceMember.Tags.Method:
+                continue
+
+            if member.isGetter():
+                memberType = "getters"
+            elif member.isSetter():
+                memberType = "setters"
+            elif member.isCreator():
+                memberType = "creators"
+            elif member.isDeleter():
+                memberType = "deleters"
+            else:
+                continue
+
+            if member.isNamed():
+                memberType = "named " + memberType
+            elif member.isIndexed():
+                memberType = "indexed " + memberType
+            else:
+                continue
+
+            if memberType in specialMembersSeen:
+                raise WebIDLError("Multiple " + memberType + " on %s" % (self),
+                                   self.location)
+
+            specialMembersSeen.add(memberType)
 
         for member in self.members:
             member.finish(scope)
 
     def isInterface(self):
         return True
 
     def isExternal(self):
@@ -524,17 +509,17 @@ class IDLInterface(IDLObjectWithScope):
         depth = 0
         parent = self.parent
         while parent:
             depth = depth + 1
             parent = parent.parent
         return depth
 
     def hasConstants(self):
-        return reduce(lambda b, m: b or m.isConst(), self.members, False)
+        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()
@@ -562,20 +547,17 @@ class IDLInterface(IDLObjectWithScope):
 
                 args = attrlist[0] if len(attrlist) else []
 
                 retType = IDLWrapperType(self.location, self)
                 
                 identifier = IDLUnresolvedIdentifier(self.location, "constructor",
                                                      allowForbidden=True)
 
-                method = IDLMethod(self.location, identifier, retType, args,
-                                   False, False, False, False, False, False,
-                                   False, False)
-
+                method = IDLMethod(self.location, identifier, retType, args)
                 method.resolve(self)
 
             self._extendedAttrDict[identifier] = attrlist if len(attrlist) else True
 
 class IDLEnum(IDLObjectWithIdentifier):
     def __init__(self, location, parentScope, name, values):
         assert isinstance(parentScope, IDLScope)
         assert isinstance(name, IDLUnresolvedIdentifier)
@@ -758,25 +740,34 @@ class IDLNullableType(IDLType):
         return self.inner.isCallback()
 
     def isPrimitive(self):
         return self.inner.isPrimitive()
 
     def isString(self):
         return self.inner.isString()
 
+    def isFloat(self):
+        return self.inner.isFloat()
+
+    def isInteger(self):
+        return self.inner.isInteger()
+
     def isVoid(self):
         return False
 
     def isSequence(self):
         return self.inner.isSequence()
 
     def isArray(self):
         return self.inner.isArray()
 
+    def isArrayBuffer(self):
+        return self.inner.isArrayBuffer()
+
     def isDictionary(self):
         return self.inner.isDictionary()
 
     def isInterface(self):
         return self.inner.isInterface()
 
     def isEnum(self):
         return self.inner.isEnum()
@@ -792,17 +783,17 @@ class IDLNullableType(IDLType):
         return self.inner.isComplete()
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
         self.name = self.inner.name
         return self
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         if other.nullable():
             # Can't tell which type null should become
             return False
         return self.inner.isDistinguishableFrom(other)
 
 class IDLSequenceType(IDLType):
@@ -830,43 +821,45 @@ class IDLSequenceType(IDLType):
 
     def isVoid(self):
         return False
 
     def isSequence(self):
         return True
 
     def isArray(self):
-        return self.inner.isArray()
+        return False
 
     def isDictionary(self):
-        return self.inner.isDictionary()
+        return False
 
     def isInterface(self):
-        return self.inner.isInterface()
+        return False
 
     def isEnum(self):
-        return self.inner.isEnum();
+        return False
 
     def tag(self):
+        # XXXkhuey this is probably wrong.
         return self.inner.tag()
 
     def resolveType(self, parentScope):
         assert isinstance(parentScope, IDLScope)
         self.inner.resolveType(parentScope)
 
     def isComplete(self):
         return self.inner.isComplete()
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
+        self.name = self.inner.name
         return self
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isDictionary() or other.isDate() or
                 # XXXbz we should also be checking for indexed
                 # properties on interfaces
                 (other.isInterface() and not other.isCallback() and
                  not other.isArrayBuffer()))
@@ -890,57 +883,59 @@ class IDLArrayType(IDLType):
 
     def __str__(self):
         return self.inner.__str__() + "Array"
 
     def nullable(self):
         return False
 
     def isPrimitive(self):
-        return self.inner.isPrimitive()
+        return False
 
     def isString(self):
-        return self.inner.isString()
+        return False
 
     def isVoid(self):
         return False
 
     def isSequence(self):
         assert not self.inner.isSequence()
-        return self.inner.isSequence()
+        return False
 
     def isArray(self):
         return True
 
     def isDictionary(self):
         assert not self.inner.isDictionary()
-        return self.inner.isDictionary()
+        return False
 
     def isInterface(self):
-        return self.inner.isInterface()
+        return False
 
     def isEnum(self):
-        return self.inner.isEnum()
+        return False
 
     def tag(self):
+        # XXXkhuey this is probably wrong.
         return self.inner.tag()
 
     def resolveType(self, parentScope):
         assert isinstance(parentScope, IDLScope)
         self.inner.resolveType(parentScope)
 
     def isComplete(self):
         return self.inner.isComplete()
 
     def complete(self, scope):
         self.inner = self.inner.complete(scope)
+        self.name = self.inner.name
         return self
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         return (other.isPrimitive() or other.isString() or other.isEnum() or
                 other.isDictionary() or other.isDate() or
                 # XXXbz we should also be checking for indexed
                 # properties on interfaces
                 (other.isInterface() and not other.isCallback() and
                  not other.isArrayBuffer()))
@@ -990,17 +985,17 @@ class IDLTypedefType(IDLType, IDLObjectW
     def resolve(self, parentScope):
         assert isinstance(parentScope, IDLScope)
         IDLObjectWithIdentifier.resolve(self, parentScope)
 
     def tag(self):
         return self.inner.tag()
 
     def unroll(self):
-        return self.inner
+        return self.inner.unroll()
 
     def isDistinguishableFrom(self, other):
         return self.inner.isDistinguishableFrom(other)
 
 class IDLWrapperType(IDLType):
     def __init__(self, location, inner):
         IDLType.__init__(self, location, inner.identifier.name)
         self.inner = inner
@@ -1036,16 +1031,20 @@ class IDLWrapperType(IDLType):
 
     def isInterface(self):
         return isinstance(self.inner, IDLInterface) or \
                isinstance(self.inner, IDLExternalInterface)
 
     def isEnum(self):
         return isinstance(self.inner, IDLEnum)
 
+    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
@@ -1117,42 +1116,42 @@ class IDLBuiltinType(IDLType):
             Types.date: IDLType.Tags.date,
             Types.void: IDLType.Tags.void,
             Types.ArrayBuffer: IDLType.Tags.interface
         }
 
     def __init__(self, location, name, type):
         IDLType.__init__(self, location, name)
         self.builtin = True
-        self.type = type
+        self._typeTag = type
 
     def isPrimitive(self):
-        return self.type <= IDLBuiltinType.Types.double
+        return self._typeTag <= IDLBuiltinType.Types.double
 
     def isString(self):
-        return self.type == IDLBuiltinType.Types.domstring
+        return self._typeTag == IDLBuiltinType.Types.domstring
 
     def isInteger(self):
-        return self.type <= IDLBuiltinType.Types.unsigned_long_long
+        return self._typeTag <= IDLBuiltinType.Types.unsigned_long_long
 
     def isArrayBuffer(self):
-        return self.type == IDLBuiltinType.Types.ArrayBuffer
+        return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
 
     def isInterface(self):
         # ArrayBuffers are interface types per the TypedArray spec,
         # but we handle them as builtins because SpiderMonkey implements
         # ArrayBuffers.
-        return self.type == IDLBuiltinType.Types.ArrayBuffer
+        return self._typeTag == IDLBuiltinType.Types.ArrayBuffer
 
     def isFloat(self):
-        return self.type == IDLBuiltinType.Types.float or \
-               self.type == IDLBuiltinType.Types.double
+        return self._typeTag == IDLBuiltinType.Types.float or \
+               self._typeTag == IDLBuiltinType.Types.double
 
     def tag(self):
-        return IDLBuiltinType.TagLookup[self.type]
+        return IDLBuiltinType.TagLookup[self._typeTag]
 
     def isDistinguishableFrom(self, other):
         if self.isPrimitive() or self.isString():
             return (other.isInterface() or other.isObject() or
                     other.isCallback() or other.isDictionary() or
                     other.isSequence() or other.isArray() or
                     other.isDate())
         if self.isAny():
@@ -1275,17 +1274,17 @@ class IDLValue(IDLObject):
         # Else, see if we can coerce to 'type'.
         if self.type.isInteger():
             if not self.type.isInteger():
                 raise WebIDLError("Cannot coerce type %s to type %s." %
                                   (self.type, type), location)
 
             # We're both integer types.  See if we fit.
 
-            (min, max) = integerTypeSizes[type.type]
+            (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)
         else:
             pass
@@ -1487,18 +1486,20 @@ class IDLMethod(IDLInterfaceMember, IDLS
 
     NamedOrIndexed = enum(
         'Neither',
         'Named',
         'Indexed'
     )
 
     def __init__(self, location, identifier, returnType, arguments,
-                 static, getter, setter, creator, deleter, specialType, legacycaller,
-                 stringifier):
+                 static=False, getter=False, setter=False, creator=False,
+                 deleter=False, specialType=NamedOrIndexed.Neither,
+                 legacycaller=False, stringifier=False):
+        # 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._returnType = [returnType]
 
@@ -1673,16 +1674,17 @@ class Tokenizer(object):
         "STRING",
         "WHITESPACE",
         "OTHER"
         ]
 
     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:
             raise WebIDLError("Invalid integer literal",
                               Location(lexer=self.lexer,
                                        lineno=self.lexer.lineno,
                                        lexpos=self.lexer.lexpos,
                                        filename=self._filename))
         return t
@@ -2256,18 +2258,19 @@ class Parser(Tokenizer):
                  "indexed" if specialType == IDLMethod.NamedOrIndexed.Indexed else "",
                  "getter" if getter else "",
                  "setter" if setter else "",
                  "deleter" if deleter else "",
                  "creator" if creator else "",
                  "legacycaller" if legacycaller else ""), allowDoubleUnderscore=True)
 
         method = IDLMethod(self.getLocation(p, 2), identifier, returnType, arguments,
-                           static, getter, setter, creator, deleter, specialType,
-                           legacycaller, False)
+                           static=static, getter=getter, setter=setter, creator=creator,
+                           deleter=deleter, specialType=specialType,
+                           legacycaller=legacycaller, stringifier=False)
         p[0] = method
 
     def p_QualifiersStatic(self, p):
         """
             Qualifiers : STATIC
         """
         p[0] = [IDLMethod.Special.Static]
 
@@ -2856,17 +2859,24 @@ class Parser(Tokenizer):
         self._filename = filename
         self._productions.extend(self.parser.parse(lexer=self.lexer))
         self._filename = None
 
     def finish(self):
         for production in self._productions:
             production.finish(self.globalScope())
 
-        return set(self._productions)
+        # 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()
 
     # Builtin IDL defined by WebIDL
     _builtins = """
         typedef unsigned long long DOMTimeStamp;
     """
deleted file mode 100644
--- a/dom/bindings/parser/__init__.py
+++ /dev/null
@@ -1,1 +0,0 @@
-__all__ = ['WebIDL']
--- a/dom/bindings/parser/runtests.py
+++ b/dom/bindings/parser/runtests.py
@@ -32,41 +32,81 @@
 # and other provisions required by the GPL or the LGPL. If you do not delete
 # the provisions above, a recipient may use your version of this file under
 # the terms of any one of the MPL, the GPL or the LGPL.
 #
 # ***** END LICENSE BLOCK *****
 
 import os, sys
 import glob
+import optparse
+import traceback
 import WebIDL
 
 class TestHarness(object):
+    def __init__(self, test, verbose):
+        self.test = test
+        self.verbose = verbose
+        self.printed_intro = False
+
+    def start(self):
+        if self.verbose:
+            self.maybe_print_intro()
+
+    def finish(self):
+        if self.verbose or self.printed_intro:
+            print "Finished test %s" % self.test
+
+    def maybe_print_intro(self):
+        if not self.printed_intro:
+            print "Starting test %s" % self.test
+            self.printed_intro = True
+
+    def test_pass(self, msg):
+        if self.verbose:
+            print "TEST-PASS | %s" % msg
+
+    def test_fail(self, msg):
+        self.maybe_print_intro()
+        print "TEST-UNEXPECTED-FAIL | %s" % msg
+
     def ok(self, condition, msg):
         if condition:
-            print "TEST-PASS | %s" % msg
+            self.test_pass(msg)
         else:
-            print "TEST-UNEXPECTED-FAIL | %s" % msg
+            self.test_fail(msg)
 
     def check(self, a, b, msg):
         if a == b:
-            print "TEST-PASS | %s" % msg
+            self.test_pass(msg)
         else:
-            print "TEST-UNEXPECTED-FAIL | %s" % msg
+            self.test_fail(msg)
             print "\tGot %s expected %s" % (a, b)
 
-def run_tests():
-    harness = TestHarness()
+def run_tests(tests, verbose):
+    testdir = os.path.join(os.path.dirname(__file__), 'tests')
+    if not tests:
+        tests = glob.iglob(os.path.join(testdir, "*.py"))
+    sys.path.append(testdir)
 
-    tests = glob.iglob("tests/*.py")
-    sys.path.append("./tests")
     for test in tests:
         (testpath, ext) = os.path.splitext(os.path.basename(test))
         _test = __import__(testpath, globals(), locals(), ['WebIDLTest'])
-        #try:
-        _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
-        #except:
-        #    print "TEST-UNEXPECTED-FAIL | Unhandled exception in Test %s" % testpath
-        #    print sys.exc_info()[0]
-        print "Test %s Complete\n" % testpath
+
+        harness = TestHarness(test, verbose)
+        harness.start()
+        try:
+            _test.WebIDLTest.__call__(WebIDL.Parser(), harness)
+        except:
+            print "TEST-UNEXPECTED-FAIL | Unhandled exception in test %s" % testpath
+            traceback.print_exc()
+        finally:
+            harness.finish()
 
 if __name__ == '__main__':
-    run_tests()
+    usage = """%prog [OPTIONS] [TESTS]
+               Where TESTS are relative to the tests directory."""
+    parser = optparse.OptionParser(usage=usage)
+    parser.add_option('-q', '--quiet', action='store_false', dest='verbose', default=True,
+                      help="Don't print passing tests.")
+    options, tests = parser.parse_args()
+
+    run_tests(tests, verbose=options.verbose)
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_array_of_interface.py
@@ -0,0 +1,13 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    parser.parse("""
+      interface A {
+        attribute long a;
+      };
+
+      interface B {
+        attribute A[] b;
+      };
+    """);
+    parser.finish()
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_builtin_filename.py
@@ -0,0 +1,11 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    parser.parse("""
+        interface Test {
+          attribute long b;
+        };
+    """);
+
+    attr = parser.finish()[0].members[0]
+    harness.check(attr.type.filename(), '<builtin>', 'Filename on builtin type')
--- a/dom/bindings/parser/tests/test_constructor.py
+++ b/dom/bindings/parser/tests/test_constructor.py
@@ -57,19 +57,19 @@ def WebIDLTest(parser, harness):
     results = parser.finish()
     harness.check(len(results), 3, "Should be two productions")
     harness.ok(isinstance(results[0], WebIDL.IDLInterface),
                "Should be an IDLInterface")
     harness.ok(isinstance(results[1], WebIDL.IDLInterface),
                "Should be an IDLInterface")
 
     checkMethod(results[0].ctor(), "::TestConstructorNoArgs::constructor",
-                "constructor", [("TestConstructorNoArgs", [])])
+                "constructor", [("TestConstructorNoArgs (Wrapper)", [])])
     checkMethod(results[1].ctor(), "::TestConstructorWithArgs::constructor",
                 "constructor",
-                [("TestConstructorWithArgs",
+                [("TestConstructorWithArgs (Wrapper)",
                  [("::TestConstructorWithArgs::constructor::name", "name", "String", False, False)])])
     checkMethod(results[2].ctor(), "::TestConstructorOverloads::constructor",
                 "constructor",
-                [("TestConstructorOverloads",
+                [("TestConstructorOverloads (Wrapper)",
                  [("::TestConstructorOverloads::constructor::foo", "foo", "Object", False, False)]),
-                 ("TestConstructorOverloads",
+                 ("TestConstructorOverloads (Wrapper)",
                  [("::TestConstructorOverloads::constructor::bar", "bar", "Boolean", False, False)])])
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_deduplicate.py
@@ -0,0 +1,15 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    parser.parse("""
+        interface Foo;
+        interface Bar;
+        interface Foo;
+        """);
+
+    results = parser.finish()
+
+    # There should be no duplicate interfaces in the result.
+    expectedNames = sorted(['Foo', 'Bar'])
+    actualNames = sorted(map(lambda iface: iface.identifier.name, results))
+    harness.check(actualNames, expectedNames, "Parser shouldn't output duplicate names.")
--- a/dom/bindings/parser/tests/test_enum.py
+++ b/dom/bindings/parser/tests/test_enum.py
@@ -42,20 +42,20 @@ def WebIDLTest(parser, harness):
     harness.check(method.identifier.QName(), "::TestEnumInterface::doFoo",
                   "Method has correct QName")
     harness.check(method.identifier.name, "doFoo", "Method has correct name")
 
     signatures = method.signatures()
     harness.check(len(signatures), 1, "Expect one signature")
 
     (returnType, arguments) = signatures[0]
-    harness.check(str(returnType), "TestEnum", "Method type is the correct name")
+    harness.check(str(returnType), "TestEnum (Wrapper)", "Method type is the correct name")
     harness.check(len(arguments), 1, "Method has the right number of arguments")
     arg = arguments[0]
     harness.ok(isinstance(arg, WebIDL.IDLArgument), "Should be an IDLArgument")
     harness.check(str(arg.type), "Boolean", "Argument has the right type")
 
     attr = members[1]
     harness.check(attr.identifier.QName(), "::TestEnumInterface::foo",
                   "Attr has correct QName")
     harness.check(attr.identifier.name, "foo", "Attr has correct name")
 
-    harness.check(str(attr.type), "TestEnum", "Attr type is the correct name")
+    harness.check(str(attr.type), "TestEnum (Wrapper)", "Attr type is the correct name")
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_error_colno.py
@@ -0,0 +1,20 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    # Check that error messages put the '^' in the right place.
+
+    threw = False
+    input = 'interface ?'
+    try:
+        parser.parse(input)
+        results = parser.finish()
+    except WebIDL.WebIDLError as e:
+        threw = True
+        lines = str(e).split('\n')
+
+        harness.check(len(lines), 3, 'Expected number of lines in error message')
+        harness.check(lines[1], input, 'Second line shows error')
+        harness.check(lines[2], ' ' * (len(input) - 1) + '^',
+                      'Correct column pointer in error message')
+
+    harness.ok(threw, "Should have thrown.")
new file mode 100644
--- /dev/null
+++ b/dom/bindings/parser/tests/test_nullable_equivalency.py
@@ -0,0 +1,134 @@
+import WebIDL
+
+def WebIDLTest(parser, harness):
+    parser.parse("""
+        interface TestNullableEquivalency1 {
+          attribute long  a;
+          attribute long? b;
+        };
+
+        interface TestNullableEquivalency2 {
+          attribute ArrayBuffer  a;
+          attribute ArrayBuffer? b;
+        };
+
+        /* Not implemented */
+        /*dictionary TestNullableEquivalency3Dict {
+          long foo = 42;
+        };
+
+        interface TestNullableEquivalency3 {
+          attribute Test3Dict  a;
+          attribute Test3Dict? b;
+        };*/
+
+        enum TestNullableEquivalency4Enum {
+          "Foo",
+          "Bar"
+        };
+
+        interface TestNullableEquivalency4 {
+          attribute TestNullableEquivalency4Enum  a;
+          attribute TestNullableEquivalency4Enum? b;
+        };
+
+        interface TestNullableEquivalency5 {
+          attribute TestNullableEquivalency4  a;
+          attribute TestNullableEquivalency4? b;
+        };
+
+        interface TestNullableEquivalency6 {
+          attribute boolean  a;
+          attribute boolean? b;
+        };
+
+        interface TestNullableEquivalency7 {
+          attribute DOMString  a;
+          attribute DOMString? b;
+        };
+
+        /* Not implemented. */
+        /*interface TestNullableEquivalency8 {
+          attribute float  a;
+          attribute float? b;
+        };*/
+
+        interface TestNullableEquivalency8 {
+          attribute double  a;
+          attribute double? b;
+        };
+
+        interface TestNullableEquivalency9 {
+          attribute object  a;
+          attribute object? b;
+        };
+
+        interface TestNullableEquivalency10 {
+          attribute double[]  a;
+          attribute double[]? b;
+        };
+
+        interface TestNullableEquivalency11 {
+          attribute TestNullableEquivalency9[]  a;
+          attribute TestNullableEquivalency9[]? b;
+        };
+    """)
+
+    for decl in parser.finish():
+        if decl.isInterface():
+            checkEquivalent(decl, harness)
+
+def checkEquivalent(iface, harness):
+    type1 = iface.members[0].type
+    type2 = iface.members[1].type
+
+    harness.check(type1.nullable(), False, 'attr1 should not be nullable')
+    harness.check(type2.nullable(), True, 'attr2 should be nullable')
+
+    # We don't know about type1, but type2, the nullable type, definitely
+    # shouldn't be builtin.
+    harness.check(type2.builtin, False, 'attr2 should not be builtin')
+
+    # Ensure that all attributes of type2 match those in type1, except for:
+    #  - names on an ignore list,
+    #  - names beginning with '_',
+    #  - functions which throw when called with no args, and
+    #  - class-level non-callables ("static variables").
+    #
+    # Yes, this is an ugly, fragile hack.  But it finds bugs...
+    for attr in dir(type1):
+        if attr.startswith('_') or \
+           attr in ['nullable', 'builtin', 'filename', 'location',
+                    'inner', 'QName'] or \
+           (hasattr(type(type1), attr) and not callable(getattr(type1, attr))):
+            continue
+
+        a1 = getattr(type1, attr)
+
+        if callable(a1):
+            try:
+                v1 = a1()
+            except:
+                # Can't call a1 with no args, so skip this attriute.
+                continue
+
+            try:
+                a2 = getattr(type2, attr)
+            except:
+                harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
+                continue
+
+            if not callable(a2):
+                harness.ok(False, "%s attribute on type %s in %s wasn't callable" % (attr, type2, iface))
+                continue
+
+            v2 = a2()
+            harness.check(v2, v1, '%s method return value' % attr)
+        else:
+            try:
+                a2 = getattr(type2, attr)
+            except:
+                harness.ok(False, 'Missing %s attribute on type %s in %s' % (attr, type2, iface))
+                continue
+
+            harness.check(a2, a1, '%s attribute should match' % attr)