# -*- coding: utf-8 -*-
"""
    sphinx.domains.cpp
    ~~~~~~~~~~~~~~~~~~

    The C++ language domain.

    :copyright: Copyright 2007-2018 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import re
from copy import deepcopy

from docutils import nodes, utils
from docutils.parsers.rst import Directive, directives
from six import iteritems, text_type

from sphinx import addnodes
from sphinx.directives import ObjectDescription
from sphinx.domains import Domain, ObjType
from sphinx.environment import NoUri
from sphinx.locale import l_, _
from sphinx.roles import XRefRole
from sphinx.util import logging
from sphinx.util.docfields import Field, GroupedField
from sphinx.util.nodes import make_refnode
from sphinx.util.pycompat import UnicodeMixin


if False:
    # For type annotation
    from typing import Any, Callable, Dict, Iterator, List, Match, Pattern, Tuple, Union  # NOQA
    from sphinx.application import Sphinx  # NOQA
    from sphinx.builders import Builder  # NOQA
    from sphinx.config import Config  # NOQA
    from sphinx.environment import BuildEnvironment  # NOQA

logger = logging.getLogger(__name__)

"""
    Important note on ids
    ----------------------------------------------------------------------------

    Multiple id generation schemes are used due to backwards compatibility.
    - v1: 1.2.3 <= version < 1.3
          The style used before the rewrite.
          It is not the actual old code, but a replication of the behaviour.
    - v2: 1.3 <= version < now
          Standardised mangling scheme from
          http://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling
          though not completely implemented.
    All versions are generated and attached to elements. The newest is used for
    the index. All of the versions should work as permalinks.


    Signature Nodes and Tagnames
    ----------------------------------------------------------------------------

    Each signature is in a desc_signature node, where all children are
    desc_signature_line nodes. Each of these lines will have the attribute
    'sphinx_cpp_tagname' set to one of the following (prioritized):
    - 'declarator', if the line contains the name of the declared object.
    - 'templateParams', if the line starts a template parameter list,
    - 'templateParams', if the line has template parameters
      Note: such lines might get a new tag in the future.
    - 'templateIntroduction, if the line is on the form 'conceptName{...}'
    No other desc_signature nodes should exist (so far).


    Grammar
    ----------------------------------------------------------------------------

    See http://www.nongnu.org/hcb/ for the grammar,
    and https://github.com/cplusplus/draft/blob/master/source/grammar.tex,
    and https://github.com/cplusplus/concepts-ts
    for the newest grammar.

    common grammar things:
        template-declaration ->
            "template" "<" template-parameter-list ">" declaration
        template-parameter-list ->
              template-parameter
            | template-parameter-list "," template-parameter
        template-parameter ->
              type-parameter
            | parameter-declaration # i.e., same as a function argument

        type-parameter ->
              "class"    "..."[opt] identifier[opt]
            | "class"               identifier[opt] "=" type-id
            | "typename" "..."[opt] identifier[opt]
            | "typename"            identifier[opt] "=" type-id
            | "template" "<" template-parameter-list ">"
                "class"  "..."[opt] identifier[opt]
            | "template" "<" template-parameter-list ">"
                "class"             identifier[opt] "=" id-expression
            # also, from C++17 we can have "typename" in template templates
        templateDeclPrefix ->
            "template" "<" template-parameter-list ">"

        simple-declaration ->
            attribute-specifier-seq[opt] decl-specifier-seq[opt]
                init-declarator-list[opt] ;
        # Drop the semi-colon. For now: drop the attributes (TODO).
        # Use at most 1 init-declarator.
        -> decl-specifier-seq init-declarator
        -> decl-specifier-seq declarator initializer

        decl-specifier ->
              storage-class-specifier ->
                 (  "static" (only for member_object and function_object)
                  | "extern" (only for member_object and function_object)
                  | "register"
                 )
                 thread_local[opt] (only for member_object)
                                   (it can also appear before the others)

            | type-specifier -> trailing-type-specifier
            | function-specifier -> "inline" | "virtual" | "explicit" (only
              for function_object)
            | "friend" (only for function_object)
            | "constexpr" (only for member_object and function_object)
        trailing-type-specifier ->
              simple-type-specifier
            | elaborated-type-specifier
            | typename-specifier
            | cv-qualifier -> "const" | "volatile"
        stricter grammar for decl-specifier-seq (with everything, each object
        uses a subset):
            visibility storage-class-specifier function-specifier "friend"
            "constexpr" "volatile" "const" trailing-type-specifier
            # where trailing-type-specifier can no be cv-qualifier
        # Inside e.g., template paramters a strict subset is used
        # (see type-specifier-seq)
        trailing-type-specifier ->
              simple-type-specifier ->
                ::[opt] nested-name-specifier[opt] type-name
              | ::[opt] nested-name-specifier "template" simple-template-id
              | "char" | "bool" | ect.
              | decltype-specifier
            | elaborated-type-specifier ->
                class-key attribute-specifier-seq[opt] ::[opt]
                nested-name-specifier[opt] identifier
              | class-key ::[opt] nested-name-specifier[opt] template[opt]
                simple-template-id
              | "enum" ::[opt] nested-name-specifier[opt] identifier
            | typename-specifier ->
                "typename" ::[opt] nested-name-specifier identifier
              | "typename" ::[opt] nested-name-specifier template[opt]
                simple-template-id
        class-key -> "class" | "struct" | "union"
        type-name ->* identifier | simple-template-id
        # ignoring attributes and decltype, and then some left-factoring
        trailing-type-specifier ->
            rest-of-trailing
            ("class" | "struct" | "union" | "typename") rest-of-trailing
            build-in -> "char" | "bool" | ect.
            decltype-specifier
        rest-of-trailing -> (with some simplification)
            "::"[opt] list-of-elements-separated-by-::
        element ->
            "template"[opt] identifier ("<" template-argument-list ">")[opt]
        template-argument-list ->
              template-argument "..."[opt]
            | template-argument-list "," template-argument "..."[opt]
        template-argument ->
              constant-expression
            | type-specifier-seq abstract-declarator
            | id-expression


        declarator ->
              ptr-declarator
            | noptr-declarator parameters-and-qualifiers trailing-return-type
              (TODO: for now we don't support trailing-eturn-type)
        ptr-declarator ->
              noptr-declarator
            | ptr-operator ptr-declarator
        noptr-declarator ->
              declarator-id attribute-specifier-seq[opt] ->
                    "..."[opt] id-expression
                  | rest-of-trailing
            | noptr-declarator parameters-and-qualifiers
            | noptr-declarator "[" constant-expression[opt] "]"
              attribute-specifier-seq[opt]
            | "(" ptr-declarator ")"
        ptr-operator ->
              "*"  attribute-specifier-seq[opt] cv-qualifier-seq[opt]
            | "&   attribute-specifier-seq[opt]
            | "&&" attribute-specifier-seq[opt]
            | "::"[opt] nested-name-specifier "*" attribute-specifier-seq[opt]
                cv-qualifier-seq[opt]
        # function_object must use a parameters-and-qualifiers, the others may
        # use it (e.g., function poitners)
        parameters-and-qualifiers ->
            "(" parameter-clause ")" attribute-specifier-seq[opt]
            cv-qualifier-seq[opt] ref-qualifier[opt]
            exception-specification[opt]
        ref-qualifier -> "&" | "&&"
        exception-specification ->
            "noexcept" ("(" constant-expression ")")[opt]
            "throw" ("(" type-id-list ")")[opt]
        # TODO: we don't implement attributes
        # member functions can have initializers, but we fold them into here
        memberFunctionInit -> "=" "0"
        # (note: only "0" is allowed as the value, according to the standard,
        # right?)

        enum-head ->
            enum-key attribute-specifier-seq[opt] nested-name-specifier[opt]
                identifier enum-base[opt]
        enum-key -> "enum" | "enum struct" | "enum class"
        enum-base ->
            ":" type
        enumerator-definition ->
              identifier
            | identifier "=" constant-expression

    We additionally add the possibility for specifying the visibility as the
    first thing.

    concept_object:
        goal:
            just a declaration of the name (for now)

        grammar: only a single template parameter list, and the nested name
            may not have any template argument lists

            "template" "<" template-parameter-list ">"
            nested-name-specifier

    type_object:
        goal:
            either a single type (e.g., "MyClass:Something_T" or a typedef-like
            thing (e.g. "Something Something_T" or "int I_arr[]"
        grammar, single type: based on a type in a function parameter, but
        without a name:
               parameter-declaration
            -> attribute-specifier-seq[opt] decl-specifier-seq
               abstract-declarator[opt]
            # Drop the attributes
            -> decl-specifier-seq abstract-declarator[opt]
        grammar, typedef-like: no initilizer
            decl-specifier-seq declarator
        Can start with a templateDeclPrefix.

    member_object:
        goal: as a type_object which must have a declarator, and optionally
        with a initializer
        grammar:
            decl-specifier-seq declarator initializer
        Can start with a templateDeclPrefix.

    function_object:
        goal: a function declaration, TODO: what about templates? for now: skip
        grammar: no initializer
           decl-specifier-seq declarator
        Can start with a templateDeclPrefix.

    class_object:
        goal: a class declaration, but with specification of a base class
        grammar:
              nested-name "final"[opt] (":" base-specifier-list)[opt]
            base-specifier-list ->
              base-specifier "..."[opt]
            | base-specifier-list, base-specifier "..."[opt]
            base-specifier ->
              base-type-specifier
            | "virtual" access-spe"cifier[opt]    base-type-specifier
            | access-specifier[opt] "virtual"[opt] base-type-specifier
        Can start with a templateDeclPrefix.

    enum_object:
        goal: an unscoped enum or a scoped enum, optionally with the underlying
              type specified
        grammar:
            ("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt]
    enumerator_object:
        goal: an element in a scoped or unscoped enum. The name should be
              injected according to the scopedness.
        grammar:
            nested-name ("=" constant-expression)

    namespace_object:
        goal: a directive to put all following declarations in a specific scope
        grammar:
            nested-name
"""

# TODO: support hex, oct, etc. work
_integer_literal_re = re.compile(r'[1-9][0-9]*')
_octal_literal_re = re.compile(r'0[0-7]*')
_hex_literal_re = re.compile(r'0[xX][0-7a-fA-F][0-7a-fA-F]*')
_binary_literal_re = re.compile(r'0[bB][01][01]*')
_integer_suffix_re = re.compile(r'')
_float_literal_re = re.compile(r'[+-]?[0-9]*\.[0-9]+')
_identifier_re = re.compile(r'(~?\b[a-zA-Z_][a-zA-Z0-9_]*)\b')
_whitespace_re = re.compile(r'(?u)\s+')
_string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'"
                        r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S)
_visibility_re = re.compile(r'\b(public|private|protected)\b')
_operator_re = re.compile(r'''(?x)
        \[\s*\]
    |   \(\s*\)
    |   \+\+ | --
    |   ->\*? | \,
    |   (<<|>>)=? | && | \|\|
    |   [!<>=/*%+|&^~-]=?
''')
_fold_operator_re = re.compile(r'''(?x)
        ->\*    |    \.\*    |    \,
    |   (<<|>>)=?    |    &&    |    \|\|
    |   !=
    |   [<>=/*%+|&^~-]=?
''')
# see http://en.cppreference.com/w/cpp/keyword
_keywords = [
    'alignas', 'alignof', 'and', 'and_eq', 'asm', 'auto', 'bitand', 'bitor',
    'bool', 'break', 'case', 'catch', 'char', 'char16_t', 'char32_t', 'class',
    'compl', 'concept', 'const', 'constexpr', 'const_cast', 'continue',
    'decltype', 'default', 'delete', 'do', 'double', 'dynamic_cast', 'else',
    'enum', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'friend',
    'goto', 'if', 'inline', 'int', 'long', 'mutable', 'namespace', 'new',
    'noexcept', 'not', 'not_eq', 'nullptr', 'operator', 'or', 'or_eq',
    'private', 'protected', 'public', 'register', 'reinterpret_cast',
    'requires', 'return', 'short', 'signed', 'sizeof', 'static',
    'static_assert', 'static_cast', 'struct', 'switch', 'template', 'this',
    'thread_local', 'throw', 'true', 'try', 'typedef', 'typeid', 'typename',
    'union', 'unsigned', 'using', 'virtual', 'void', 'volatile', 'wchar_t',
    'while', 'xor', 'xor_eq'
]

_max_id = 3
_id_prefix = [None, '', '_CPPv2', '_CPPv3']

# ------------------------------------------------------------------------------
# Id v1 constants
# ------------------------------------------------------------------------------

_id_fundamental_v1 = {
    'char': 'c',
    'signed char': 'c',
    'unsigned char': 'C',
    'int': 'i',
    'signed int': 'i',
    'unsigned int': 'U',
    'long': 'l',
    'signed long': 'l',
    'unsigned long': 'L',
    'bool': 'b'
}  # type: Dict[unicode, unicode]
_id_shorthands_v1 = {
    'std::string': 'ss',
    'std::ostream': 'os',
    'std::istream': 'is',
    'std::iostream': 'ios',
    'std::vector': 'v',
    'std::map': 'm'
}  # type: Dict[unicode, unicode]
_id_operator_v1 = {
    'new': 'new-operator',
    'new[]': 'new-array-operator',
    'delete': 'delete-operator',
    'delete[]': 'delete-array-operator',
    # the arguments will make the difference between unary and binary
    # '+(unary)' : 'ps',
    # '-(unary)' : 'ng',
    # '&(unary)' : 'ad',
    # '*(unary)' : 'de',
    '~': 'inv-operator',
    '+': 'add-operator',
    '-': 'sub-operator',
    '*': 'mul-operator',
    '/': 'div-operator',
    '%': 'mod-operator',
    '&': 'and-operator',
    '|': 'or-operator',
    '^': 'xor-operator',
    '=': 'assign-operator',
    '+=': 'add-assign-operator',
    '-=': 'sub-assign-operator',
    '*=': 'mul-assign-operator',
    '/=': 'div-assign-operator',
    '%=': 'mod-assign-operator',
    '&=': 'and-assign-operator',
    '|=': 'or-assign-operator',
    '^=': 'xor-assign-operator',
    '<<': 'lshift-operator',
    '>>': 'rshift-operator',
    '<<=': 'lshift-assign-operator',
    '>>=': 'rshift-assign-operator',
    '==': 'eq-operator',
    '!=': 'neq-operator',
    '<': 'lt-operator',
    '>': 'gt-operator',
    '<=': 'lte-operator',
    '>=': 'gte-operator',
    '!': 'not-operator',
    '&&': 'sand-operator',
    '||': 'sor-operator',
    '++': 'inc-operator',
    '--': 'dec-operator',
    ',': 'comma-operator',
    '->*': 'pointer-by-pointer-operator',
    '->': 'pointer-operator',
    '()': 'call-operator',
    '[]': 'subscript-operator'
}  # type: Dict[unicode, unicode]

# ------------------------------------------------------------------------------
# Id v > 1 constants
# ------------------------------------------------------------------------------

_id_fundamental_v2 = {
    # not all of these are actually parsed as fundamental types, TODO: do that
    'void': 'v',
    'bool': 'b',
    'char': 'c',
    'signed char': 'a',
    'unsigned char': 'h',
    'wchar_t': 'w',
    'char32_t': 'Di',
    'char16_t': 'Ds',
    'short': 's',
    'short int': 's',
    'signed short': 's',
    'signed short int': 's',
    'unsigned short': 't',
    'unsigned short int': 't',
    'int': 'i',
    'signed': 'i',
    'signed int': 'i',
    'unsigned': 'j',
    'unsigned int': 'j',
    'long': 'l',
    'long int': 'l',
    'signed long': 'l',
    'signed long int': 'l',
    'unsigned long': 'm',
    'unsigned long int': 'm',
    'long long': 'x',
    'long long int': 'x',
    'signed long long': 'x',
    'signed long long int': 'x',
    'unsigned long long': 'y',
    'unsigned long long int': 'y',
    'float': 'f',
    'double': 'd',
    'long double': 'e',
    'auto': 'Da',
    'decltype(auto)': 'Dc',
    'std::nullptr_t': 'Dn'
}  # type: Dict[unicode, unicode]
_id_operator_v2 = {
    'new': 'nw',
    'new[]': 'na',
    'delete': 'dl',
    'delete[]': 'da',
    # the arguments will make the difference between unary and binary
    # in operator definitions
    # '+(unary)' : 'ps',
    # '-(unary)' : 'ng',
    # '&(unary)' : 'ad',
    # '*(unary)' : 'de',
    '~': 'co',
    '+': 'pl',
    '-': 'mi',
    '*': 'ml',
    '/': 'dv',
    '%': 'rm',
    '&': 'an',
    '|': 'or',
    '^': 'eo',
    '=': 'aS',
    '+=': 'pL',
    '-=': 'mI',
    '*=': 'mL',
    '/=': 'dV',
    '%=': 'rM',
    '&=': 'aN',
    '|=': 'oR',
    '^=': 'eO',
    '<<': 'ls',
    '>>': 'rs',
    '<<=': 'lS',
    '>>=': 'rS',
    '==': 'eq',
    '!=': 'ne',
    '<': 'lt',
    '>': 'gt',
    '<=': 'le',
    '>=': 'ge',
    '!': 'nt',
    '&&': 'aa',
    '||': 'oo',
    '++': 'pp',
    '--': 'mm',
    ',': 'cm',
    '->*': 'pm',
    '->': 'pt',
    '()': 'cl',
    '[]': 'ix',
    '.*': 'ds'  # this one is not overloadable, but we need it for expressions
}  # type: Dict[unicode, unicode]
_id_operator_unary_v2 = {
    '++': 'pp_',
    '--': 'mm_',
    '*': 'de',
    '&': 'ad',
    '+': 'ps',
    '-': 'ng',
    '!': 'nt',
    '~': 'co'
}
# these are ordered by preceedence
_expression_bin_ops = [
    ['||'],
    ['&&'],
    ['|'],
    ['^'],
    ['&'],
    ['==', '!='],
    ['<=', '>=', '<', '>'],
    ['<<', '>>'],
    ['+', '-'],
    ['*', '/', '%'],
    ['.*', '->*']
]
_expression_unary_ops = ["++", "--", "*", "&", "+", "-", "!", "~"]
_expression_assignment_ops = ["=", "*=", "/=", "%=", "+=", "-=",
                              ">>=", "<<=", "&=", "^=", "|="]
_id_explicit_cast = {
    'dynamic_cast': 'dc',
    'static_cast': 'sc',
    'const_cast': 'cc',
    'reinterpret_cast': 'rc'
}


class NoOldIdError(UnicodeMixin, Exception):
    # Used to avoid implementing unneeded id generation for old id schmes.
    def __init__(self, description=""):
        # type: (unicode) -> None
        self.description = description

    def __unicode__(self):
        # type: () -> unicode
        return self.description


class DefinitionError(UnicodeMixin, Exception):
    def __init__(self, description):
        # type: (unicode) -> None
        self.description = description

    def __unicode__(self):
        # type: () -> unicode
        return self.description


class _DuplicateSymbolError(UnicodeMixin, Exception):
    def __init__(self, symbol, declaration):
        # type: (Symbol, Any) -> None
        assert symbol
        assert declaration
        self.symbol = symbol
        self.declaration = declaration

    def __unicode__(self):
        # type: () -> unicode
        return "Internal C++ duplicate symbol error:\n%s" % self.symbol.dump(0)


class ASTBase(UnicodeMixin):
    def __eq__(self, other):
        # type: (Any) -> bool
        if type(self) is not type(other):
            return False
        try:
            for key, value in iteritems(self.__dict__):
                if value != getattr(other, key):
                    return False
        except AttributeError:
            return False
        return True

    def __ne__(self, other):
        # type: (Any) -> bool
        return not self.__eq__(other)

    __hash__ = None  # type: Callable[[], int]

    def clone(self):
        # type: () -> ASTBase
        """Clone a definition expression node."""
        return deepcopy(self)

    def get_name(self):
        # type: () -> unicode
        """Return the name.

        Returns either `None` or a node with a name you might call
        :meth:`split_owner` on.
        """
        raise NotImplementedError(repr(self))

    def prefix_nested_name(self, prefix):
        # type: (unicode) -> unicode
        """Prefix a name node (a node returned by :meth:`get_name`)."""
        raise NotImplementedError(repr(self))

    def __unicode__(self):
        # type: () -> unicode
        raise NotImplementedError(repr(self))

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, self)


def _verify_description_mode(mode):
    # type: (unicode) -> None
    if mode not in ('lastIsName', 'noneIsName', 'markType', 'param'):
        raise Exception("Description mode '%s' is invalid." % mode)


################################################################################
# Attributes
################################################################################

class ASTCPPAttribute(ASTBase):
    def __init__(self, arg):
        # type: (unicode) -> None
        self.arg = arg

    def __unicode__(self):
        # type: () -> unicode
        return "[[" + self.arg + "]]"

    def describe_signature(self, signode):
        # type: (addnodes.desc_signature) -> None
        txt = text_type(self)
        signode.append(nodes.Text(txt, txt))


class ASTGnuAttribute(ASTBase):
    def __init__(self, name, args):
        # type: (unicode, Any) -> None
        self.name = name
        self.args = args

    def __unicode__(self):
        # type: () -> unicode
        res = [self.name]  # type: List[unicode]
        if self.args:
            res.append('(')
            res.append(text_type(self.args))
            res.append(')')
        return ''.join(res)


class ASTGnuAttributeList(ASTBase):
    def __init__(self, attrs):
        # type: (List[Any]) -> None
        self.attrs = attrs

    def __unicode__(self):
        # type: () -> unicode
        res = ['__attribute__((']  # type: List[unicode]
        first = True
        for attr in self.attrs:
            if not first:
                res.append(', ')
            first = False
            res.append(text_type(attr))
        res.append('))')
        return ''.join(res)

    def describe_signature(self, signode):
        # type: (addnodes.desc_signature) -> None
        txt = text_type(self)
        signode.append(nodes.Text(txt, txt))


class ASTIdAttribute(ASTBase):
    """For simple attributes defined by the user."""

    def __init__(self, id):
        # type: (unicode) -> None
        self.id = id

    def __unicode__(self):
        # type: () -> unicode
        return self.id

    def describe_signature(self, signode):
        # type: (addnodes.desc_signature) -> None
        signode.append(nodes.Text(self.id, self.id))


class ASTParenAttribute(ASTBase):
    """For paren attributes defined by the user."""

    def __init__(self, id, arg):
        # type: (unicode, unicode) -> None
        self.id = id
        self.arg = arg

    def __unicode__(self):
        # type: () -> unicode
        return self.id + '(' + self.arg + ')'

    def describe_signature(self, signode):
        # type: (addnodes.desc_signature) -> None
        txt = text_type(self)
        signode.append(nodes.Text(txt, txt))


################################################################################
# Expressions and Literals
################################################################################

class ASTPointerLiteral(ASTBase):
    def __unicode__(self):
        return u'nullptr'

    def get_id(self, version):
        return 'LDnE'

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('nullptr'))


class ASTBooleanLiteral(ASTBase):
    def __init__(self, value):
        self.value = value

    def __unicode__(self):
        if self.value:
            return u'true'
        else:
            return u'false'

    def get_id(self, version):
        if self.value:
            return 'L1E'
        else:
            return 'L0E'

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text(text_type(self)))


class ASTNumberLiteral(ASTBase):
    def __init__(self, data):
        # type: (unicode) -> None
        self.data = data

    def __unicode__(self):
        return self.data

    def get_id(self, version):
        return "L%sE" % self.data

    def describe_signature(self, signode, mode, env, symbol):
        txt = text_type(self)
        signode.append(nodes.Text(txt, txt))


class ASTStringLiteral(ASTBase):
    def __init__(self, data):
        # type: (unicode) -> None
        self.data = data

    def __unicode__(self):
        return self.data

    def get_id(self, version):
        # note: the length is not really correct with escaping
        return "LA%d_KcE" % (len(self.data) - 2)

    def describe_signature(self, signode, mode, env, symbol):
        txt = text_type(self)
        signode.append(nodes.Text(txt, txt))


class ASTThisLiteral(ASTBase):
    def __unicode__(self):
        return "this"

    def get_id(self, version):
        return "fpT"

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text("this"))


class ASTParenExpr(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return '(' + text_type(self.expr) + ')'

    def get_id(self, version):
        return self.expr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('(', '('))
        self.expr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')', ')'))


class ASTFoldExpr(ASTBase):
    def __init__(self, leftExpr, op, rightExpr):
        assert leftExpr is not None or rightExpr is not None
        self.leftExpr = leftExpr
        self.op = op
        self.rightExpr = rightExpr

    def __unicode__(self):
        res = [u'(']
        if self.leftExpr:
            res.append(text_type(self.leftExpr))
            res.append(u' ')
            res.append(text_type(self.op))
            res.append(u' ')
        res.append(u'...')
        if self.rightExpr:
            res.append(u' ')
            res.append(text_type(self.op))
            res.append(u' ')
            res.append(text_type(self.rightExpr))
        res.append(u')')
        return u''.join(res)

    def get_id(self, version):
        assert version >= 3
        if version == 3:
            return text_type(self)
        # TODO: find the right mangling scheme
        assert False

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('('))
        if self.leftExpr:
            self.leftExpr.describe_signature(signode, mode, env, symbol)
            signode.append(nodes.Text(' '))
            signode.append(nodes.Text(self.op))
            signode.append(nodes.Text(' '))
        signode.append(nodes.Text('...'))
        if self.rightExpr:
            signode.append(nodes.Text(' '))
            signode.append(nodes.Text(self.op))
            signode.append(nodes.Text(' '))
            self.rightExpr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTBinOpExpr(ASTBase):
    def __init__(self, exprs, ops):
        assert len(exprs) > 0
        assert len(exprs) == len(ops) + 1
        self.exprs = exprs
        self.ops = ops

    def __unicode__(self):
        res = []
        res.append(text_type(self.exprs[0]))
        for i in range(1, len(self.exprs)):
            res.append(' ')
            res.append(self.ops[i - 1])
            res.append(' ')
            res.append(text_type(self.exprs[i]))
        return u''.join(res)

    def get_id(self, version):
        assert version >= 2
        res = []
        for i in range(len(self.ops)):
            res.append(_id_operator_v2[self.ops[i]])
            res.append(self.exprs[i].get_id(version))
        res.append(self.exprs[-1].get_id(version))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        self.exprs[0].describe_signature(signode, mode, env, symbol)
        for i in range(1, len(self.exprs)):
            signode.append(nodes.Text(' '))
            signode.append(nodes.Text(self.ops[i - 1]))
            signode.append(nodes.Text(' '))
            self.exprs[i].describe_signature(signode, mode, env, symbol)


class ASTAssignmentExpr(ASTBase):
    def __init__(self, exprs, ops):
        assert len(exprs) > 0
        assert len(exprs) == len(ops) + 1
        self.exprs = exprs
        self.ops = ops

    def __unicode__(self):
        res = []
        res.append(text_type(self.exprs[0]))
        for i in range(1, len(self.exprs)):
            res.append(' ')
            res.append(self.ops[i - 1])
            res.append(' ')
            res.append(text_type(self.exprs[i]))
        return u''.join(res)

    def get_id(self, version):
        res = []
        for i in range(len(self.ops)):
            res.append(_id_operator_v2[self.ops[i]])
            res.append(self.exprs[i].get_id(version))
        res.append(self.exprs[-1].get_id(version))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        self.exprs[0].describe_signature(signode, mode, env, symbol)
        for i in range(1, len(self.exprs)):
            signode.append(nodes.Text(' '))
            signode.append(nodes.Text(self.ops[i - 1]))
            signode.append(nodes.Text(' '))
            self.exprs[i].describe_signature(signode, mode, env, symbol)


class ASTCastExpr(ASTBase):
    def __init__(self, typ, expr):
        self.typ = typ
        self.expr = expr

    def __unicode__(self):
        res = [u'(']
        res.append(text_type(self.typ))
        res.append(u')')
        res.append(text_type(self.expr))
        return u''.join(res)

    def get_id(self, version):
        return 'cv' + self.typ.get_id(version) + self.expr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('('))
        self.typ.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))
        self.expr.describe_signature(signode, mode, env, symbol)


class ASTUnaryOpExpr(ASTBase):
    def __init__(self, op, expr):
        self.op = op
        self.expr = expr

    def __unicode__(self):
        return text_type(self.op) + text_type(self.expr)

    def get_id(self, version):
        return _id_operator_unary_v2[self.op] + self.expr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text(self.op))
        self.expr.describe_signature(signode, mode, env, symbol)


class ASTSizeofParamPack(ASTBase):
    def __init__(self, identifier):
        self.identifier = identifier

    def __unicode__(self):
        return "sizeof...(" + text_type(self.identifier) + ")"

    def get_id(self, version):
        return 'sZ' + self.identifier.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('sizeof...('))
        self.identifier.describe_signature(signode, mode, env,
                                           symbol=symbol, prefix="", templateArgs="")
        signode.append(nodes.Text(')'))


class ASTSizeofType(ASTBase):
    def __init__(self, typ):
        self.typ = typ

    def __unicode__(self):
        return "sizeof(" + text_type(self.typ) + ")"

    def get_id(self, version):
        return 'st' + self.typ.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('sizeof('))
        self.typ.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTSizeofExpr(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return "sizeof " + text_type(self.expr)

    def get_id(self, version):
        return 'sz' + self.expr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('sizeof '))
        self.expr.describe_signature(signode, mode, env, symbol)


class ASTAlignofExpr(ASTBase):
    def __init__(self, typ):
        self.typ = typ

    def __unicode__(self):
        return "alignof(" + text_type(self.typ) + ")"

    def get_id(self, version):
        return 'at' + self.typ.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('alignof('))
        self.typ.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTNoexceptExpr(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return "noexcept(" + text_type(self.expr) + ")"

    def get_id(self, version):
        return 'nx' + self.expr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('noexcept('))
        self.expr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTExplicitCast(ASTBase):
    def __init__(self, cast, typ, expr):
        assert cast in _id_explicit_cast
        self.cast = cast
        self.typ = typ
        self.expr = expr

    def __unicode__(self):
        res = [self.cast]
        res.append('<')
        res.append(text_type(self.typ))
        res.append('>(')
        res.append(text_type(self.expr))
        res.append(')')
        return u''.join(res)

    def get_id(self, version):
        return (_id_explicit_cast[self.cast] +
                self.typ.get_id(version) +
                self.expr.get_id(version))

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text(self.cast))
        signode.append(nodes.Text('<'))
        self.typ.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text('>'))
        signode.append(nodes.Text('('))
        self.expr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTTypeId(ASTBase):
    def __init__(self, typeOrExpr, isType):
        self.typeOrExpr = typeOrExpr
        self.isType = isType

    def __unicode__(self):
        return 'typeid(' + text_type(self.typeOrExpr) + ')'

    def get_id(self, version):
        prefix = 'ti' if self.isType else 'te'
        return prefix + self.typeOrExpr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('typeid'))
        signode.append(nodes.Text('('))
        self.typeOrExpr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTPostfixCallExpr(ASTBase):
    def __init__(self, exprs):
        self.exprs = exprs

    def __unicode__(self):
        res = [u'(']
        first = True
        for e in self.exprs:
            if not first:
                res.append(u', ')
            first = False
            res.append(text_type(e))
        res.append(u')')
        return u''.join(res)

    def get_id(self, idPrefix, version):
        res = ['cl', idPrefix]
        for e in self.exprs:
            res.append(e.get_id(version))
        res.append('E')
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('('))
        first = True
        for e in self.exprs:
            if not first:
                signode.append(nodes.Text(', '))
            first = False
            e.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTPostfixArray(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return u'[' + text_type(self.expr) + ']'

    def get_id(self, idPrefix, version):
        return 'ix' + idPrefix + self.expr.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('['))
        self.expr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(']'))


class ASTPostfixInc(ASTBase):
    def __unicode__(self):
        return u'++'

    def get_id(self, idPrefix, version):
        return 'pp' + idPrefix

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('++'))


class ASTPostfixDec(ASTBase):
    def __unicode__(self):
        return u'--'

    def get_id(self, idPrefix, version):
        return 'mm' + idPrefix

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('--'))


class ASTPostfixMember(ASTBase):
    def __init__(self, name):
        self.name = name

    def __unicode__(self):
        return u'.' + text_type(self.name)

    def get_id(self, idPrefix, version):
        return 'dt' + idPrefix + self.name.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('.'))
        self.name.describe_signature(signode, 'noneIsName', env, symbol)


class ASTPostfixMemberOfPointer(ASTBase):
    def __init__(self, name):
        self.name = name

    def __unicode__(self):
        return u'->' + text_type(self.name)

    def get_id(self, idPrefix, version):
        return 'pt' + idPrefix + self.name.get_id(version)

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('->'))
        self.name.describe_signature(signode, 'noneIsName', env, symbol)


class ASTPostfixExpr(ASTBase):
    def __init__(self, prefix, postFixes):
        assert len(postFixes) > 0
        self.prefix = prefix
        self.postFixes = postFixes

    def __unicode__(self):
        res = [text_type(self.prefix)]
        for p in self.postFixes:
            res.append(text_type(p))
        return u''.join(res)

    def get_id(self, version):
        id = self.prefix.get_id(version)
        for p in self.postFixes:
            id = p.get_id(id, version)
        return id

    def describe_signature(self, signode, mode, env, symbol):
        self.prefix.describe_signature(signode, mode, env, symbol)
        for p in self.postFixes:
            p.describe_signature(signode, mode, env, symbol)


class ASTPackExpansionExpr(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return text_type(self.expr) + '...'

    def get_id(self, version):
        id = self.expr.get_id(version)
        return 'sp' + id

    def describe_signature(self, signode, mode, env, symbol):
        self.expr.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text('...')


class ASTFallbackExpr(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return self.expr

    def get_id(self, version):
        return text_type(self.expr)

    def describe_signature(self, signode, mode, env, symbol):
        signode += nodes.Text(self.expr)


################################################################################
# The Rest
################################################################################

class ASTIdentifier(ASTBase):
    def __init__(self, identifier):
        # type: (unicode) -> None
        assert identifier is not None
        self.identifier = identifier

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            if self.identifier == 'size_t':
                return 's'
            else:
                return self.identifier
        if self.identifier == "std":
            return 'St'
        elif self.identifier[0] == "~":
            # a destructor, just use an arbitrary version of dtors
            return 'D0'
        else:
            return text_type(len(self.identifier)) + self.identifier

    def __unicode__(self):
        # type: () -> unicode
        return self.identifier

    def describe_signature(self, signode, mode, env, prefix, templateArgs, symbol):
        # type: (Any, unicode, BuildEnvironment, unicode, unicode, Symbol) -> None
        _verify_description_mode(mode)
        if mode == 'markType':
            targetText = prefix + self.identifier + templateArgs
            pnode = addnodes.pending_xref('', refdomain='cpp',
                                          reftype='identifier',
                                          reftarget=targetText, modname=None,
                                          classname=None)
            key = symbol.get_lookup_key()
            pnode['cpp:parent_key'] = key
            pnode += nodes.Text(self.identifier)
            signode += pnode
        elif mode == 'lastIsName':
            signode += addnodes.desc_name(self.identifier, self.identifier)
        elif mode == 'noneIsName':
            signode += nodes.Text(self.identifier)
        else:
            raise Exception('Unknown description mode: %s' % mode)


class ASTTemplateKeyParamPackIdDefault(ASTBase):
    def __init__(self, key, identifier, parameterPack, default):
        # type: (unicode, Any, bool, Any) -> None
        assert key
        if parameterPack:
            assert default is None
        self.key = key
        self.identifier = identifier
        self.parameterPack = parameterPack
        self.default = default

    def get_identifier(self):
        # type: () -> unicode
        return self.identifier

    def get_id(self, version):
        # type: (int) -> unicode
        assert version >= 2
        # this is not part of the normal name mangling in C++
        res = []
        if self.parameterPack:
            res.append('Dp')
        else:
            res.append('0')  # we need to put something
        return ''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = [self.key]  # type: List[unicode]
        if self.parameterPack:
            if self.identifier:
                res.append(' ')
            res.append('...')
        if self.identifier:
            if not self.parameterPack:
                res.append(' ')
            res.append(text_type(self.identifier))
        if self.default:
            res.append(' = ')
            res.append(text_type(self.default))
        return ''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        signode += nodes.Text(self.key)
        if self.parameterPack:
            if self.identifier:
                signode += nodes.Text(' ')
            signode += nodes.Text('...')
        if self.identifier:
            if not self.parameterPack:
                signode += nodes.Text(' ')
            self.identifier.describe_signature(signode, mode, env, '', '', symbol)
        if self.default:
            signode += nodes.Text(' = ')
            self.default.describe_signature(signode, 'markType', env, symbol)


class ASTTemplateParamType(ASTBase):
    def __init__(self, data):
        # type: (Any) -> None
        assert data
        self.data = data

    @property
    def name(self):
        # type: () -> ASTNestedName
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)

    @property
    def isPack(self):
        return self.data.parameterPack

    def get_identifier(self):
        # type: () -> unicode
        return self.data.get_identifier()

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        # this is not part of the normal name mangling in C++
        assert version >= 2
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id(version, prefixed=False)
        else:
            return self.data.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        return text_type(self.data)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        self.data.describe_signature(signode, mode, env, symbol)


class ASTTemplateParamConstrainedTypeWithInit(ASTBase):
    def __init__(self, type, init):
        # type: (Any, Any) -> None
        assert type
        self.type = type
        self.init = init

    @property
    def name(self):
        # type: () -> ASTNestedName
        return self.type.name

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        # this is not part of the normal name mangling in C++
        assert version >= 2
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id(version, prefixed=False)
        else:
            return self.type.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = text_type(self.type)
        if self.init:
            res += " = "
            res += text_type(self.init)
        return res

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        self.type.describe_signature(signode, mode, env, symbol)
        if self.init:
            signode += nodes.Text(" = ")
            self.init.describe_signature(signode, mode, env, symbol)


class ASTTemplateParamTemplateType(ASTBase):
    def __init__(self, nestedParams, data):
        # type: (Any, Any) -> None
        assert nestedParams
        assert data
        self.nestedParams = nestedParams
        self.data = data

    @property
    def name(self):
        # type: () -> ASTNestedName
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)

    def get_identifier(self):
        # type: () -> unicode
        return self.data.get_identifier()

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        assert version >= 2
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id(version, prefixed=None)
        else:
            return self.nestedParams.get_id(version) + self.data.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        return text_type(self.nestedParams) + text_type(self.data)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        self.nestedParams.describe_signature(signode, 'noneIsName', env, symbol)
        signode += nodes.Text(' ')
        self.data.describe_signature(signode, mode, env, symbol)


class ASTTemplateParamNonType(ASTBase):
    def __init__(self, param):
        # type: (Any) -> None
        assert param
        self.param = param

    @property
    def name(self):
        # type: () -> ASTNestedName
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)

    def get_identifier(self):
        # type: () -> unicode
        name = self.param.name
        if name:
            assert len(name.names) == 1
            assert name.names[0].identOrOp
            assert not name.names[0].templateArgs
            return name.names[0].identOrOp
        else:
            return None

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        assert version >= 2
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id(version, prefixed=None)
        else:
            return '_' + self.param.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        return text_type(self.param)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        self.param.describe_signature(signode, mode, env, symbol)


class ASTTemplateParams(ASTBase):
    def __init__(self, params):
        # type: (Any) -> None
        assert params is not None
        self.params = params
        self.isNested = False  # whether it's a template template param

    def get_id(self, version):
        # type: (int) -> unicode
        assert version >= 2
        res = []
        res.append("I")
        for param in self.params:
            res.append(param.get_id(version))
        res.append("E")
        return ''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(u"template<")
        res.append(u", ".join(text_type(a) for a in self.params))
        res.append(u"> ")
        return ''.join(res)

    def describe_signature(self, parentNode, mode, env, symbol, lineSpec=None):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol, bool) -> None
        # 'lineSpec' is defaulted becuase of template template parameters
        def makeLine(parentNode=parentNode):
            signode = addnodes.desc_signature_line()
            parentNode += signode
            signode.sphinx_cpp_tagname = 'templateParams'
            return signode
        if self.isNested:
            lineNode = parentNode
        else:
            lineNode = makeLine()
        lineNode += nodes.Text("template<")
        first = True
        for param in self.params:
            if not first:
                lineNode += nodes.Text(", ")
            first = False
            if lineSpec:
                lineNode = makeLine()
            param.describe_signature(lineNode, mode, env, symbol)
        if lineSpec and not first:
            lineNode = makeLine()
        lineNode += nodes.Text(">")


class ASTTemplateIntroductionParameter(ASTBase):
    def __init__(self, identifier, parameterPack):
        # type: (Any, Any) -> None
        self.identifier = identifier
        self.parameterPack = parameterPack

    @property
    def name(self):
        # type: () -> ASTNestedName
        id = self.get_identifier()
        return ASTNestedName([ASTNestedNameElement(id, None)], [False], rooted=False)

    @property
    def isPack(self):
        return self.parameterPack

    def get_identifier(self):
        # type: () -> unicode
        return self.identifier

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        assert version >= 2
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id(version, prefixed=None)
        else:
            if self.parameterPack:
                return 'Dp'
            else:
                return '0'  # we need to put something

    def get_id_as_arg(self, version):
        # type: (int) -> unicode
        assert version >= 2
        # used for the implicit requires clause
        res = self.identifier.get_id(version)
        if self.parameterPack:
            return u'sp' + res
        else:
            return res

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        if self.parameterPack:
            res.append('...')
        res.append(text_type(self.identifier))
        return ''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        if self.parameterPack:
            signode += nodes.Text('...')
        self.identifier.describe_signature(signode, mode, env, '', '', symbol)


class ASTTemplateIntroduction(ASTBase):
    def __init__(self, concept, params):
        # type: (Any, List[Any]) -> None
        assert len(params) > 0
        self.concept = concept
        self.params = params

    def get_id(self, version):
        # type: (int) -> unicode
        assert version >= 2
        # first do the same as a normal template parameter list
        res = []
        res.append("I")
        for param in self.params:
            res.append(param.get_id(version))
        res.append("E")
        # let's use X expr E, which is otherwise for constant template args
        res.append("X")
        res.append(self.concept.get_id(version))
        res.append("I")
        for param in self.params:
            res.append(param.get_id_as_arg(version))
        res.append("E")
        res.append("E")
        return ''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(text_type(self.concept))
        res.append('{')
        res.append(', '.join(text_type(param) for param in self.params))
        res.append('} ')
        return ''.join(res)

    def describe_signature(self, parentNode, mode, env, symbol, lineSpec):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol, bool) -> None
        # Note: 'lineSpec' has no effect on template introductions.
        signode = addnodes.desc_signature_line()
        parentNode += signode
        signode.sphinx_cpp_tagname = 'templateIntroduction'
        self.concept.describe_signature(signode, 'markType', env, symbol)
        signode += nodes.Text('{')
        first = True
        for param in self.params:
            if not first:
                signode += nodes.Text(', ')
            first = False
            param.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text('}')


class ASTTemplateDeclarationPrefix(ASTBase):
    def __init__(self, templates):
        # type: (List[Any]) -> None
        # template is None means it's an explicit instantiation of a variable
        self.templates = templates

    def get_id(self, version):
        # type: (int) -> unicode
        assert version >= 2
        # this is not part of a normal name mangling system
        res = []
        for t in self.templates:
            res.append(t.get_id(version))
        return u''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        for t in self.templates:
            res.append(text_type(t))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol, lineSpec):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol, bool) -> None
        _verify_description_mode(mode)
        for t in self.templates:
            t.describe_signature(signode, 'lastIsName', env, symbol, lineSpec)


class ASTOperatorBuildIn(ASTBase):
    def __init__(self, op):
        # type: (unicode) -> None
        self.op = op

    def is_operator(self):
        # type: () -> bool
        return True

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            ids = _id_operator_v1
        else:
            ids = _id_operator_v2
        if self.op not in ids:
            raise Exception('Internal error: Build-in operator "%s" can not '
                            'be mapped to an id.' % self.op)
        return ids[self.op]

    def __unicode__(self):
        # type: () -> unicode
        if self.op in ('new', 'new[]', 'delete', 'delete[]'):
            return u'operator ' + self.op
        else:
            return u'operator' + self.op

    def describe_signature(self, signode, mode, env, prefix, templateArgs, symbol):
        # type: (addnodes.desc_signature, unicode, Any, unicode, unicode, Symbol) -> None
        _verify_description_mode(mode)
        identifier = text_type(self)
        if mode == 'lastIsName':
            signode += addnodes.desc_name(identifier, identifier)
        else:
            signode += addnodes.desc_addname(identifier, identifier)


class ASTOperatorType(ASTBase):
    def __init__(self, type):
        # type: (Any) -> None
        self.type = type

    def is_operator(self):
        # type: () -> bool
        return True

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            return u'castto-%s-operator' % self.type.get_id(version)
        else:
            return u'cv' + self.type.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        return u''.join(['operator ', text_type(self.type)])

    def get_name_no_template(self):
        # type: () -> unicode
        return text_type(self)

    def describe_signature(self, signode, mode, env, prefix, templateArgs, symbol):
        # type: (addnodes.desc_signature, unicode, Any, unicode, unicode, Symbol) -> None
        _verify_description_mode(mode)
        identifier = text_type(self)
        if mode == 'lastIsName':
            signode += addnodes.desc_name(identifier, identifier)
        else:
            signode += addnodes.desc_addname(identifier, identifier)


class ASTOperatorLiteral(ASTBase):
    def __init__(self, identifier):
        # type: (Any) -> None
        self.identifier = identifier

    def is_operator(self):
        # type: () -> bool
        return True

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            raise NoOldIdError()
        else:
            return u'li' + self.identifier.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        return u'operator""' + text_type(self.identifier)

    def describe_signature(self, signode, mode, env, prefix, templateArgs, symbol):
        # type: (addnodes.desc_signature, unicode, Any, unicode, unicode, Symbol) -> None
        _verify_description_mode(mode)
        identifier = text_type(self)
        if mode == 'lastIsName':
            signode += addnodes.desc_name(identifier, identifier)
        else:
            signode += addnodes.desc_addname(identifier, identifier)


class ASTTemplateArgConstant(ASTBase):
    def __init__(self, value):
        # type: (Any) -> None
        self.value = value

    def __unicode__(self):
        # type: () -> unicode
        return text_type(self.value)

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            return text_type(self).replace(u' ', u'-')
        if version == 2:
            return u'X' + text_type(self) + u'E'
        return u'X' + self.value.get_id(version) + u'E'

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.value.describe_signature(signode, mode, env, symbol)


class ASTTemplateArgs(ASTBase):
    def __init__(self, args):
        # type: (List[Any]) -> None
        assert args is not None
        self.args = args

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            res = []  # type: List[unicode]
            res.append(':')
            res.append(u'.'.join(a.get_id(version) for a in self.args))
            res.append(':')
            return u''.join(res)

        res = []
        res.append('I')
        for a in self.args:
            res.append(a.get_id(version))
        res.append('E')
        return u''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = ', '.join(text_type(a) for a in self.args)
        return '<' + res + '>'

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        signode += nodes.Text('<')
        first = True
        for a in self.args:
            if not first:
                signode += nodes.Text(', ')
            first = False
            a.describe_signature(signode, 'markType', env, symbol=symbol)
        signode += nodes.Text('>')


class ASTNestedNameElement(ASTBase):
    def __init__(self, identOrOp, templateArgs):
        # type: (Any, Any) -> None
        self.identOrOp = identOrOp
        self.templateArgs = templateArgs

    def is_operator(self):
        # type: () -> bool
        return False

    def get_id(self, version):
        # type: (int) -> unicode
        res = self.identOrOp.get_id(version)
        if self.templateArgs:
            res += self.templateArgs.get_id(version)
        return res

    def __unicode__(self):
        # type: () -> unicode
        res = text_type(self.identOrOp)
        if self.templateArgs:
            res += text_type(self.templateArgs)
        return res

    def describe_signature(self, signode, mode, env, prefix, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, unicode, Symbol) -> None
        tArgs = text_type(self.templateArgs) if self.templateArgs is not None else ''
        self.identOrOp.describe_signature(signode, mode, env, prefix, tArgs, symbol)
        if self.templateArgs is not None:
            self.templateArgs.describe_signature(signode, mode, env, symbol)


class ASTNestedName(ASTBase):
    def __init__(self, names, templates, rooted):
        # type: (List[Any], List[bool], bool) -> None
        assert len(names) > 0
        self.names = names
        self.templates = templates
        assert len(self.names) == len(self.templates)
        self.rooted = rooted

    @property
    def name(self):
        # type: () -> ASTNestedName
        return self

    def num_templates(self):
        # type: () -> int
        count = 0
        for n in self.names:
            if n.is_operator():
                continue
            if n.templateArgs:
                count += 1
        return count

    def get_id(self, version, modifiers=''):
        # type: (int, unicode) -> unicode
        if version == 1:
            tt = text_type(self)
            if tt in _id_shorthands_v1:
                return _id_shorthands_v1[tt]
            else:
                return u'::'.join(n.get_id(version) for n in self.names)
        res = []  # type: List[unicode]
        if len(self.names) > 1 or len(modifiers) > 0:
            res.append('N')
        res.append(modifiers)
        for n in self.names:
            res.append(n.get_id(version))
        if len(self.names) > 1 or len(modifiers) > 0:
            res.append('E')
        return u''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        if self.rooted:
            res.append('')
        for i in range(len(self.names)):
            n = self.names[i]
            t = self.templates[i]
            if t:
                res.append("template " + text_type(n))
            else:
                res.append(text_type(n))
        return '::'.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        # just print the name part, with template args, not template params
        if mode == 'noneIsName':
            signode += nodes.Text(text_type(self))
        elif mode == 'param':
            name = text_type(self)
            signode += nodes.emphasis(name, name)
        elif mode == 'markType' or mode == 'lastIsName':
            # Each element should be a pending xref targeting the complete
            # prefix. however, only the identifier part should be a link, such
            # that template args can be a link as well.
            # For 'lastIsName' we should also prepend template parameter lists.
            templateParams = []  # type: List[Any]
            if mode == 'lastIsName':
                assert symbol is not None
                if symbol.declaration.templatePrefix is not None:
                    templateParams = symbol.declaration.templatePrefix.templates
            iTemplateParams = 0
            templateParamsPrefix = u''
            prefix = ''  # type: unicode
            first = True
            names = self.names[:-1] if mode == 'lastIsName' else self.names
            # If lastIsName, then wrap all of the prefix in a desc_addname,
            # else append directly to signode.
            # NOTE: Breathe relies on the prefix being in the desc_addname node,
            #       so it can remove it in inner declarations.
            dest = signode
            if mode == 'lastIsName':
                dest = addnodes.desc_addname()
            for i in range(len(names)):
                name = names[i]
                template = self.templates[i]
                if not first:
                    dest += nodes.Text('::')
                    prefix += '::'
                if template:
                    dest += nodes.Text("template ")
                first = False
                if name != '':
                    if (name.templateArgs and  # type: ignore
                            iTemplateParams < len(templateParams)):
                        templateParamsPrefix += text_type(templateParams[iTemplateParams])
                        iTemplateParams += 1
                    name.describe_signature(dest, 'markType',  # type: ignore
                                            env, templateParamsPrefix + prefix, symbol)
                prefix += text_type(name)
            if mode == 'lastIsName':
                if len(self.names) > 1:
                    dest += addnodes.desc_addname('::', '::')
                    signode += dest
                if self.templates[-1]:
                    signode += nodes.Text("template ")
                self.names[-1].describe_signature(signode, mode, env, '', symbol)
        else:
            raise Exception('Unknown description mode: %s' % mode)


class ASTTrailingTypeSpecFundamental(ASTBase):
    def __init__(self, name):
        # type: (unicode) -> None
        self.name = name

    def __unicode__(self):
        # type: () -> unicode
        return self.name

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            res = []
            for a in self.name.split(' '):
                if a in _id_fundamental_v1:
                    res.append(_id_fundamental_v1[a])
                else:
                    res.append(a)
            return u'-'.join(res)

        if self.name not in _id_fundamental_v2:
            raise Exception(
                'Semi-internal error: Fundamental type "%s" can not be mapped '
                'to an id. Is it a true fundamental type? If not so, the '
                'parser should have rejected it.' % self.name)
        return _id_fundamental_v2[self.name]

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        signode += nodes.Text(text_type(self.name))


class ASTTrailingTypeSpecName(ASTBase):
    def __init__(self, prefix, nestedName):
        # type: (unicode, Any) -> None
        self.prefix = prefix
        self.nestedName = nestedName

    @property
    def name(self):
        # type: () -> Any
        return self.nestedName

    def get_id(self, version):
        # type: (int) -> unicode
        return self.nestedName.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        if self.prefix:
            res.append(self.prefix)
            res.append(' ')
        res.append(text_type(self.nestedName))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        if self.prefix:
            signode += addnodes.desc_annotation(self.prefix, self.prefix)
            signode += nodes.Text(' ')
        self.nestedName.describe_signature(signode, mode, env, symbol=symbol)


class ASTTrailingTypeSpecDecltypeAuto(ASTBase):
    def __unicode__(self):
        return u'decltype(auto)'

    def get_id(self, version):
        if version == 1:
            raise NoOldIdError()
        return 'Dc'

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        signode.append(nodes.Text(text_type(self)))


class ASTTrailingTypeSpecDecltype(ASTBase):
    def __init__(self, expr):
        self.expr = expr

    def __unicode__(self):
        return u'decltype(' + text_type(self.expr) + ')'

    def get_id(self, version):
        if version == 1:
            raise NoOldIdError()
        return 'DT' + self.expr.get_id(version) + "E"

    def describe_signature(self, signode, mode, env, symbol):
        signode.append(nodes.Text('decltype('))
        self.expr.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text(')'))


class ASTFunctionParameter(ASTBase):
    def __init__(self, arg, ellipsis=False):
        # type: (Any, bool) -> None
        self.arg = arg
        self.ellipsis = ellipsis

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        # this is not part of the normal name mangling in C++
        if symbol:
            # the anchor will be our parent
            return symbol.parent.declaration.get_id(version, prefixed=None)
        # else, do the usual
        if self.ellipsis:
            return 'z'
        else:
            return self.arg.get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        if self.ellipsis:
            return '...'
        else:
            return text_type(self.arg)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        if self.ellipsis:
            signode += nodes.Text('...')
        else:
            self.arg.describe_signature(signode, mode, env, symbol=symbol)


class ASTParametersQualifiers(ASTBase):
    def __init__(self, args, volatile, const, refQual, exceptionSpec, override,
                 final, initializer):
        # type: (List[Any], bool, bool, unicode, unicode, bool, bool, unicode) -> None
        self.args = args
        self.volatile = volatile
        self.const = const
        self.refQual = refQual
        self.exceptionSpec = exceptionSpec
        self.override = override
        self.final = final
        self.initializer = initializer

    @property
    def function_params(self):
        # type: () -> Any
        return self.args

    def get_modifiers_id(self, version):
        # type: (int) -> unicode
        res = []
        if self.volatile:
            res.append('V')
        if self.const:
            if version == 1:
                res.append('C')
            else:
                res.append('K')
        if self.refQual == '&&':
            res.append('O')
        elif self.refQual == '&':
            res.append('R')
        return u''.join(res)

    def get_param_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            if len(self.args) == 0:
                return ''
            else:
                return u'__' + u'.'.join(a.get_id(version) for a in self.args)
        if len(self.args) == 0:
            return 'v'
        else:
            return u''.join(a.get_id(version) for a in self.args)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        res.append('(')
        first = True
        for a in self.args:
            if not first:
                res.append(', ')
            first = False
            res.append(text_type(a))
        res.append(')')
        if self.volatile:
            res.append(' volatile')
        if self.const:
            res.append(' const')
        if self.refQual:
            res.append(' ')
            res.append(self.refQual)
        if self.exceptionSpec:
            res.append(' ')
            res.append(text_type(self.exceptionSpec))
        if self.final:
            res.append(' final')
        if self.override:
            res.append(' override')
        if self.initializer:
            res.append(' = ')
            res.append(self.initializer)
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        paramlist = addnodes.desc_parameterlist()
        for arg in self.args:
            param = addnodes.desc_parameter('', '', noemph=True)
            if mode == 'lastIsName':  # i.e., outer-function params
                arg.describe_signature(param, 'param', env, symbol=symbol)
            else:
                arg.describe_signature(param, 'markType', env, symbol=symbol)
            paramlist += param
        signode += paramlist

        def _add_anno(signode, text):
            signode += nodes.Text(' ')
            signode += addnodes.desc_annotation(text, text)

        def _add_text(signode, text):
            signode += nodes.Text(' ' + text)

        if self.volatile:
            _add_anno(signode, 'volatile')
        if self.const:
            _add_anno(signode, 'const')
        if self.refQual:
            _add_text(signode, self.refQual)
        if self.exceptionSpec:
            _add_anno(signode, text_type(self.exceptionSpec))
        if self.final:
            _add_anno(signode, 'final')
        if self.override:
            _add_anno(signode, 'override')
        if self.initializer:
            _add_text(signode, '= ' + text_type(self.initializer))


class ASTDeclSpecsSimple(ASTBase):
    def __init__(self, storage, threadLocal, inline, virtual, explicit,
                 constexpr, volatile, const, friend, attrs):
        # type: (unicode, bool, bool, bool, bool, bool, bool, bool, bool, List[Any]) -> None
        self.storage = storage
        self.threadLocal = threadLocal
        self.inline = inline
        self.virtual = virtual
        self.explicit = explicit
        self.constexpr = constexpr
        self.volatile = volatile
        self.const = const
        self.friend = friend
        self.attrs = attrs

    def mergeWith(self, other):
        # type: (ASTDeclSpecsSimple) -> ASTDeclSpecsSimple
        if not other:
            return self
        return ASTDeclSpecsSimple(self.storage or other.storage,
                                  self.threadLocal or other.threadLocal,
                                  self.inline or other.inline,
                                  self.virtual or other.virtual,
                                  self.explicit or other.explicit,
                                  self.constexpr or other.constexpr,
                                  self.volatile or other.volatile,
                                  self.const or other.const,
                                  self.friend or other.friend,
                                  self.attrs + other.attrs)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        res.extend(text_type(attr) for attr in self.attrs)
        if self.storage:
            res.append(self.storage)
        if self.threadLocal:
            res.append('thread_local')
        if self.inline:
            res.append('inline')
        if self.friend:
            res.append('friend')
        if self.virtual:
            res.append('virtual')
        if self.explicit:
            res.append('explicit')
        if self.constexpr:
            res.append('constexpr')
        if self.volatile:
            res.append('volatile')
        if self.const:
            res.append('const')
        return u' '.join(res)

    def describe_signature(self, modifiers):
        # type: (List[nodes.Node]) -> None
        def _add(modifiers, text):
            if len(modifiers) > 0:
                modifiers.append(nodes.Text(' '))
            modifiers.append(addnodes.desc_annotation(text, text))
        for attr in self.attrs:
            if len(modifiers) > 0:
                modifiers.append(nodes.Text(' '))
            modifiers.append(attr.describe_signature(modifiers))
        if self.storage:
            _add(modifiers, self.storage)
        if self.threadLocal:
            _add(modifiers, 'thread_local')
        if self.inline:
            _add(modifiers, 'inline')
        if self.friend:
            _add(modifiers, 'friend')
        if self.virtual:
            _add(modifiers, 'virtual')
        if self.explicit:
            _add(modifiers, 'explicit')
        if self.constexpr:
            _add(modifiers, 'constexpr')
        if self.volatile:
            _add(modifiers, 'volatile')
        if self.const:
            _add(modifiers, 'const')


class ASTDeclSpecs(ASTBase):
    def __init__(self, outer, leftSpecs, rightSpecs, trailing):
        # leftSpecs and rightSpecs are used for output
        # allSpecs are used for id generation
        self.outer = outer
        self.leftSpecs = leftSpecs
        self.rightSpecs = rightSpecs
        self.allSpecs = self.leftSpecs.mergeWith(self.rightSpecs)
        self.trailingTypeSpec = trailing

    @property
    def name(self):
        # type: () -> unicode
        return self.trailingTypeSpec.name

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            res = []
            res.append(self.trailingTypeSpec.get_id(version))
            if self.allSpecs.volatile:
                res.append('V')
            if self.allSpecs.const:
                res.append('C')
            return u''.join(res)
        res = []
        if self.leftSpecs.volatile or self.rightSpecs.volatile:
            res.append('V')
        if self.leftSpecs.const or self.rightSpecs.volatile:
            res.append('K')
        res.append(self.trailingTypeSpec.get_id(version))
        return u''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        l = text_type(self.leftSpecs)
        if len(l) > 0:
            if len(res) > 0:
                res.append(" ")
            res.append(l)
        if self.trailingTypeSpec:
            if len(res) > 0:
                res.append(" ")
            res.append(text_type(self.trailingTypeSpec))
            r = text_type(self.rightSpecs)
            if len(r) > 0:
                if len(res) > 0:
                    res.append(" ")
                res.append(r)
        return "".join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        modifiers = []  # type: List[nodes.Node]

        def _add(modifiers, text):
            if len(modifiers) > 0:
                modifiers.append(nodes.Text(' '))
            modifiers.append(addnodes.desc_annotation(text, text))

        self.leftSpecs.describe_signature(modifiers)

        for m in modifiers:
            signode += m
        if self.trailingTypeSpec:
            if len(modifiers) > 0:
                signode += nodes.Text(' ')
            self.trailingTypeSpec.describe_signature(signode, mode, env,
                                                     symbol=symbol)
            modifiers = []
            self.rightSpecs.describe_signature(modifiers)
            if len(modifiers) > 0:
                signode += nodes.Text(' ')
            for m in modifiers:
                signode += m


class ASTArray(ASTBase):
    def __init__(self, size):
        self.size = size

    def __unicode__(self):
        # type: () -> unicode
        if self.size:
            return u''.join(['[', text_type(self.size), ']'])
        else:
            return u'[]'

    def get_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            return u'A'
        if version == 2:
            if self.size:
                return u'A' + text_type(self.size) + u'_'
            else:
                return u'A_'
        if self.size:
            return u'A' + self.size.get_id(version) + u'_'
        else:
            return u'A_'

    def describe_signature(self, signode, mode, env, symbol):
        _verify_description_mode(mode)
        signode.append(nodes.Text("["))
        if self.size:
            self.size.describe_signature(signode, mode, env, symbol)
        signode.append(nodes.Text("]"))


class ASTDeclaratorPtr(ASTBase):
    def __init__(self, next, volatile, const):
        # type: (Any, bool, bool) -> None
        assert next
        self.next = next
        self.volatile = volatile
        self.const = const

    @property
    def name(self):
        # type: () -> unicode
        return self.next.name

    @property
    def function_params(self):
        # type: () -> Any
        return self.next.function_params

    def require_space_after_declSpecs(self):
        # type: () -> bool
        # TODO: if has paramPack, then False ?
        return True

    def __unicode__(self):
        # type: () -> unicode
        res = ['*']  # type: List[unicode]
        if self.volatile:
            res.append('volatile')
        if self.const:
            if self.volatile:
                res.append(' ')
            res.append('const')
        if self.const or self.volatile:
            if self.next.require_space_after_declSpecs:
                res.append(' ')
        res.append(text_type(self.next))
        return u''.join(res)

    def get_modifiers_id(self, version):
        # type: (int) -> unicode
        return self.next.get_modifiers_id(version)

    def get_param_id(self, version):
        # type: (int) -> unicode
        return self.next.get_param_id(version)

    def get_ptr_suffix_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            res = ['P']
            if self.volatile:
                res.append('V')
            if self.const:
                res.append('C')
            res.append(self.next.get_ptr_suffix_id(version))
            return u''.join(res)

        res = [self.next.get_ptr_suffix_id(version)]
        res.append('P')
        if self.volatile:
            res.append('V')
        if self.const:
            res.append('C')
        return u''.join(res)

    def get_type_id(self, version, returnTypeId):
        # type: (int, unicode) -> unicode
        # ReturnType *next, so we are part of the return type of 'next
        res = ['P']  # type: List[unicode]
        if self.volatile:
            res.append('V')
        if self.const:
            res.append('C')
        res.append(returnTypeId)
        return self.next.get_type_id(version, returnTypeId=u''.join(res))

    def is_function_type(self):
        # type: () -> bool
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        signode += nodes.Text("*")

        def _add_anno(signode, text):
            signode += addnodes.desc_annotation(text, text)
        if self.volatile:
            _add_anno(signode, 'volatile')
        if self.const:
            if self.volatile:
                signode += nodes.Text(' ')
            _add_anno(signode, 'const')
        if self.const or self.volatile:
            if self.next.require_space_after_declSpecs:
                signode += nodes.Text(' ')
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorRef(ASTBase):
    def __init__(self, next):
        # type: (Any) -> None
        assert next
        self.next = next

    @property
    def name(self):
        # type: () -> unicode
        return self.next.name

    @property
    def function_params(self):
        # type: () -> Any
        return self.next.function_params

    def require_space_after_declSpecs(self):
        # type: () -> bool
        return self.next.require_space_after_declSpecs()

    def __unicode__(self):
        # type: () -> unicode
        return '&' + text_type(self.next)

    def get_modifiers_id(self, version):
        # type: (int) -> unicode
        return self.next.get_modifiers_id(version)

    def get_param_id(self, version):  # only the parameters (if any)
        # type: (int) -> unicode
        return self.next.get_param_id(version)

    def get_ptr_suffix_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            return u'R' + self.next.get_ptr_suffix_id(version)
        else:
            return self.next.get_ptr_suffix_id(version) + u'R'

    def get_type_id(self, version, returnTypeId):
        # type: (int, unicode) -> unicode
        assert version >= 2
        # ReturnType &next, so we are part of the return type of 'next
        return self.next.get_type_id(version, returnTypeId=u'R' + returnTypeId)

    def is_function_type(self):
        # type: () -> bool
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        signode += nodes.Text("&")
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorParamPack(ASTBase):
    def __init__(self, next):
        # type: (Any) -> None
        assert next
        self.next = next

    @property
    def name(self):
        # type: () -> unicode
        return self.next.name

    @property
    def function_params(self):
        # type: () -> Any
        return self.next.function_params

    def require_space_after_declSpecs(self):
        # type: () -> bool
        return False

    def __unicode__(self):
        # type: () -> unicode
        res = text_type(self.next)
        if self.next.name:
            res = ' ' + res
        return '...' + res

    def get_modifiers_id(self, version):
        # type: (int) -> unicode
        return self.next.get_modifiers_id(version)

    def get_param_id(self, version):  # only the parameters (if any)
        # type: (int) -> unicode
        return self.next.get_param_id(version)

    def get_ptr_suffix_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            return 'Dp' + self.next.get_ptr_suffix_id(version)
        else:
            return self.next.get_ptr_suffix_id(version) + u'Dp'

    def get_type_id(self, version, returnTypeId):
        # type: (int, unicode) -> unicode
        assert version >= 2
        # ReturnType... next, so we are part of the return type of 'next
        return self.next.get_type_id(version, returnTypeId=u'Dp' + returnTypeId)

    def is_function_type(self):
        # type: () -> bool
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        signode += nodes.Text("...")
        if self.next.name:
            signode += nodes.Text(' ')
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorMemPtr(ASTBase):
    def __init__(self, className, const, volatile, next):
        # type: (Any, bool, bool, Any) -> None
        assert className
        assert next
        self.className = className
        self.const = const
        self.volatile = volatile
        self.next = next

    @property
    def name(self):
        # type: () -> unicode
        return self.next.name

    @property
    def function_params(self):
        # type: () -> Any
        return self.next.function_params

    def require_space_after_declSpecs(self):
        # type: () -> bool
        return True

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(text_type(self.className))
        res.append('::*')
        if self.volatile:
            res.append(' volatile')
        if self.const:
            res.append(' const')
        if self.next.require_space_after_declSpecs():
            res.append(' ')
        res.append(text_type(self.next))
        return ''.join(res)

    def get_modifiers_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            raise NoOldIdError()
        else:
            return self.next.get_modifiers_id(version)

    def get_param_id(self, version):  # only the parameters (if any)
        # type: (int) -> unicode
        if version == 1:
            raise NoOldIdError()
        else:
            return self.next.get_param_id(version)

    def get_ptr_suffix_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            raise NoOldIdError()
        else:
            raise NotImplementedError()
            return self.next.get_ptr_suffix_id(version) + u'Dp'

    def get_type_id(self, version, returnTypeId):
        # type: (int, unicode) -> unicode
        assert version >= 2
        # ReturnType name::* next, so we are part of the return type of next
        nextReturnTypeId = ''  # type: unicode
        if self.volatile:
            nextReturnTypeId += 'V'
        if self.const:
            nextReturnTypeId += 'K'
        nextReturnTypeId += 'M'
        nextReturnTypeId += self.className.get_id(version)
        nextReturnTypeId += returnTypeId
        return self.next.get_type_id(version, nextReturnTypeId)

    def is_function_type(self):
        # type: () -> bool
        return self.next.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.className.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text('::*')

        def _add_anno(signode, text):
            signode += addnodes.desc_annotation(text, text)
        if self.volatile:
            _add_anno(signode, 'volatile')
        if self.const:
            if self.volatile:
                signode += nodes.Text(' ')
            _add_anno(signode, 'const')
        if self.next.require_space_after_declSpecs():
            if self.volatile or self.const:
                signode += nodes.Text(' ')
        self.next.describe_signature(signode, mode, env, symbol)


class ASTDeclaratorParen(ASTBase):
    def __init__(self, inner, next):
        # type: (Any, Any) -> None
        assert inner
        assert next
        self.inner = inner
        self.next = next
        # TODO: we assume the name, params, and qualifiers are in inner

    @property
    def name(self):
        # type: () -> unicode
        return self.inner.name

    @property
    def function_params(self):
        # type: () -> Any
        return self.inner.function_params

    def require_space_after_declSpecs(self):
        # type: () -> bool
        return True

    def __unicode__(self):
        # type: () -> unicode
        res = ['(']  # type: List[unicode]
        res.append(text_type(self.inner))
        res.append(')')
        res.append(text_type(self.next))
        return ''.join(res)

    def get_modifiers_id(self, version):
        # type: (int) -> unicode
        return self.inner.get_modifiers_id(version)

    def get_param_id(self, version):  # only the parameters (if any)
        # type: (int) -> unicode
        return self.inner.get_param_id(version)

    def get_ptr_suffix_id(self, version):
        # type: (int) -> unicode
        if version == 1:
            raise NoOldIdError()  # TODO: was this implemented before?
            return self.next.get_ptr_suffix_id(version) + \
                self.inner.get_ptr_suffix_id(version)
        else:
            return self.inner.get_ptr_suffix_id(version) + \
                self.next.get_ptr_suffix_id(version)

    def get_type_id(self, version, returnTypeId):
        # type: (int, unicode) -> unicode
        assert version >= 2
        # ReturnType (inner)next, so 'inner' returns everything outside
        nextId = self.next.get_type_id(version, returnTypeId)
        return self.inner.get_type_id(version, returnTypeId=nextId)

    def is_function_type(self):
        # type: () -> bool
        return self.inner.is_function_type()

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        signode += nodes.Text('(')
        self.inner.describe_signature(signode, mode, env, symbol)
        signode += nodes.Text(')')
        self.next.describe_signature(signode, "noneIsName", env, symbol)


class ASTDeclaratorNameParamQual(ASTBase):
    def __init__(self, declId, arrayOps, paramQual):
        # type: (Any, List[Any], Any) -> None
        self.declId = declId
        self.arrayOps = arrayOps
        self.paramQual = paramQual

    @property
    def name(self):
        # type: () -> unicode
        return self.declId

    @property
    def function_params(self):
        # type: () -> Any
        return self.paramQual.function_params

    def get_modifiers_id(self, version):  # only the modifiers for a function, e.g.,
        # type: (int) -> unicode
        # cv-qualifiers
        if self.paramQual:
            return self.paramQual.get_modifiers_id(version)
        raise Exception(
            "This should only be called on a function: %s" % text_type(self))

    def get_param_id(self, version):  # only the parameters (if any)
        # type: (int) -> unicode
        if self.paramQual:
            return self.paramQual.get_param_id(version)
        else:
            return ''

    def get_ptr_suffix_id(self, version):  # only the array specifiers
        # type: (int) -> unicode
        return u''.join(a.get_id(version) for a in self.arrayOps)

    def get_type_id(self, version, returnTypeId):
        # type: (int, unicode) -> unicode
        assert version >= 2
        res = []
        # TOOD: can we actually have both array ops and paramQual?
        res.append(self.get_ptr_suffix_id(version))
        if self.paramQual:
            res.append(self.get_modifiers_id(version))
            res.append('F')
            res.append(returnTypeId)
            res.append(self.get_param_id(version))
            res.append('E')
        else:
            res.append(returnTypeId)
        return u''.join(res)

    # ------------------------------------------------------------------------

    def require_space_after_declSpecs(self):
        # type: () -> bool
        return self.declId is not None

    def is_function_type(self):
        # type: () -> bool
        return self.paramQual is not None

    def __unicode__(self):
        # type: () -> unicode
        res = []
        if self.declId:
            res.append(text_type(self.declId))
        for op in self.arrayOps:
            res.append(text_type(op))
        if self.paramQual:
            res.append(text_type(self.paramQual))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        if self.declId:
            self.declId.describe_signature(signode, mode, env, symbol)
        for op in self.arrayOps:
            op.describe_signature(signode, mode, env, symbol)
        if self.paramQual:
            self.paramQual.describe_signature(signode, mode, env, symbol)


class ASTInitializer(ASTBase):
    def __init__(self, value):
        self.value = value

    def __unicode__(self):
        # type: () -> unicode
        return u''.join([' = ', text_type(self.value)])

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        signode.append(nodes.Text(' = '))
        self.value.describe_signature(signode, 'markType', env, symbol)


class ASTType(ASTBase):
    def __init__(self, declSpecs, decl):
        # type: (Any, Any) -> None
        assert declSpecs
        assert decl
        self.declSpecs = declSpecs
        self.decl = decl

    @property
    def name(self):
        # type: () -> unicode
        name = self.decl.name
        return name

    @property
    def function_params(self):
        # type: () -> Any
        return self.decl.function_params

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        if version == 1:
            res = []
            if objectType:  # needs the name
                if objectType == 'function':  # also modifiers
                    res.append(symbol.get_full_nested_name().get_id(version))
                    res.append(self.decl.get_param_id(version))
                    res.append(self.decl.get_modifiers_id(version))
                    if (self.declSpecs.leftSpecs.constexpr or
                            (self.declSpecs.rightSpecs and
                             self.declSpecs.rightSpecs.constexpr)):
                        res.append('CE')
                elif objectType == 'type':  # just the name
                    res.append(symbol.get_full_nested_name().get_id(version))
                else:
                    print(objectType)
                    assert False
            else:  # only type encoding
                if self.decl.is_function_type():
                    raise NoOldIdError()
                res.append(self.declSpecs.get_id(version))
                res.append(self.decl.get_ptr_suffix_id(version))
                res.append(self.decl.get_param_id(version))
            return u''.join(res)
        # other versions
        res = []
        if objectType:  # needs the name
            if objectType == 'function':  # also modifiers
                modifiers = self.decl.get_modifiers_id(version)
                res.append(symbol.get_full_nested_name().get_id(version, modifiers))
                res.append(self.decl.get_param_id(version))
            elif objectType == 'type':  # just the name
                res.append(symbol.get_full_nested_name().get_id(version))
            else:
                print(objectType)
                assert False
        else:  # only type encoding
            # the 'returnType' of a non-function type is simply just the last
            # type, i.e., for 'int*' it is 'int'
            returnTypeId = self.declSpecs.get_id(version)
            typeId = self.decl.get_type_id(version, returnTypeId)
            res.append(typeId)
        return u''.join(res)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        declSpecs = text_type(self.declSpecs)
        res.append(declSpecs)
        if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0:
            res.append(u' ')
        res.append(text_type(self.decl))
        return u''.join(res)

    def get_type_declaration_prefix(self):
        # type: () -> unicode
        if self.declSpecs.trailingTypeSpec:
            return 'typedef'
        else:
            return 'type'

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.declSpecs.describe_signature(signode, 'markType', env, symbol)
        if (self.decl.require_space_after_declSpecs() and
                len(text_type(self.declSpecs)) > 0):
            signode += nodes.Text(' ')
        # for paramters that don't really declare new names we get 'markType',
        # this should not be propagated, but be 'noneIsName'.
        if mode == 'markType':
            mode = 'noneIsName'
        self.decl.describe_signature(signode, mode, env, symbol)


class ASTTypeWithInit(ASTBase):
    def __init__(self, type, init):
        # type: (Any, Any) -> None
        self.type = type
        self.init = init

    @property
    def name(self):
        # type: () -> unicode
        return self.type.name

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        if objectType != 'member':
            return self.type.get_id(version, objectType)
        if version == 1:
            return symbol.get_full_nested_name().get_id(version) + u'__' \
                + self.type.get_id(version)
        return symbol.get_full_nested_name().get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(text_type(self.type))
        if self.init:
            res.append(text_type(self.init))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.type.describe_signature(signode, mode, env, symbol)
        if self.init:
            self.init.describe_signature(signode, mode, env, symbol)


class ASTTypeUsing(ASTBase):
    def __init__(self, name, type):
        # type: (Any, Any) -> None
        self.name = name
        self.type = type

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        if version == 1:
            raise NoOldIdError()
        return symbol.get_full_nested_name().get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(text_type(self.name))
        if self.type:
            res.append(' = ')
            res.append(text_type(self.type))
        return u''.join(res)

    def get_type_declaration_prefix(self):
        # type: () -> unicode
        return 'using'

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.type:
            signode += nodes.Text(' = ')
            self.type.describe_signature(signode, 'markType', env, symbol=symbol)


class ASTConcept(ASTBase):
    def __init__(self, nestedName, initializer):
        # type: (Any, Any) -> None
        self.nestedName = nestedName
        self.initializer = initializer

    @property
    def name(self):
        # type: () -> unicode
        return self.nestedName

    def get_id(self, version, objectType=None, symbol=None):
        # type: (int, unicode, Symbol) -> unicode
        if version == 1:
            raise NoOldIdError()
        return symbol.get_full_nested_name().get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = text_type(self.nestedName)
        if self.initializer:
            res += text_type(self.initializer)
        return res

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        self.nestedName.describe_signature(signode, mode, env, symbol)
        if self.initializer:
            self.initializer.describe_signature(signode, mode, env, symbol)


class ASTBaseClass(ASTBase):
    def __init__(self, name, visibility, virtual, pack):
        # type: (Any, unicode, bool, bool) -> None
        self.name = name
        self.visibility = visibility
        self.virtual = virtual
        self.pack = pack

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        if self.visibility != 'private':
            res.append(self.visibility)
            res.append(' ')
        if self.virtual:
            res.append('virtual ')
        res.append(text_type(self.name))
        if self.pack:
            res.append('...')
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        if self.visibility != 'private':
            signode += addnodes.desc_annotation(self.visibility,
                                                self.visibility)
            signode += nodes.Text(' ')
        if self.virtual:
            signode += addnodes.desc_annotation('virtual', 'virtual')
            signode += nodes.Text(' ')
        self.name.describe_signature(signode, 'markType', env, symbol=symbol)
        if self.pack:
            signode += nodes.Text('...')


class ASTClass(ASTBase):
    def __init__(self, name, final, bases):
        # type: (Any, bool, List[Any]) -> None
        self.name = name
        self.final = final
        self.bases = bases

    def get_id(self, version, objectType, symbol):
        # type: (int, unicode, Symbol) -> unicode
        return symbol.get_full_nested_name().get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(text_type(self.name))
        if self.final:
            res.append(' final')
        if len(self.bases) > 0:
            res.append(' : ')
            first = True
            for b in self.bases:
                if not first:
                    res.append(', ')
                first = False
                res.append(text_type(b))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.final:
            signode += nodes.Text(' ')
            signode += addnodes.desc_annotation('final', 'final')
        if len(self.bases) > 0:
            signode += nodes.Text(' : ')
            for b in self.bases:
                b.describe_signature(signode, mode, env, symbol=symbol)
                signode += nodes.Text(', ')
            signode.pop()


class ASTEnum(ASTBase):
    def __init__(self, name, scoped, underlyingType):
        # type: (Any, unicode, Any) -> None
        self.name = name
        self.scoped = scoped
        self.underlyingType = underlyingType

    def get_id(self, version, objectType, symbol):
        # type: (int, unicode, Symbol) -> unicode
        if version == 1:
            raise NoOldIdError()
        return symbol.get_full_nested_name().get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        if self.scoped:
            res.append(self.scoped)
            res.append(' ')
        res.append(text_type(self.name))
        if self.underlyingType:
            res.append(' : ')
            res.append(text_type(self.underlyingType))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        # self.scoped has been done by the CPPEnumObject
        self.name.describe_signature(signode, mode, env, symbol=symbol)
        if self.underlyingType:
            signode += nodes.Text(' : ')
            self.underlyingType.describe_signature(signode, 'noneIsName',
                                                   env, symbol=symbol)


class ASTEnumerator(ASTBase):
    def __init__(self, name, init):
        # type: (Any, Any) -> None
        self.name = name
        self.init = init

    def get_id(self, version, objectType, symbol):
        # type: (int, unicode, Symbol) -> unicode
        if version == 1:
            raise NoOldIdError()
        return symbol.get_full_nested_name().get_id(version)

    def __unicode__(self):
        # type: () -> unicode
        res = []
        res.append(text_type(self.name))
        if self.init:
            res.append(text_type(self.init))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, symbol):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
        _verify_description_mode(mode)
        self.name.describe_signature(signode, mode, env, symbol)
        if self.init:
            self.init.describe_signature(signode, 'markType', env, symbol)


class ASTDeclaration(ASTBase):
    def __init__(self, objectType, visibility, templatePrefix, declaration):
        # type: (unicode, unicode, Any, Any) -> None
        self.objectType = objectType
        self.visibility = visibility
        self.templatePrefix = templatePrefix
        self.declaration = declaration

        self.symbol = None  # type: Symbol
        # set by CPPObject._add_enumerator_to_parent
        self.enumeratorScopedSymbol = None  # type: Any

    def clone(self):
        # type: () -> ASTDeclaration
        if self.templatePrefix:
            templatePrefixClone = self.templatePrefix.clone()
        else:
            templatePrefixClone = None
        return ASTDeclaration(self.objectType, self.visibility,
                              templatePrefixClone,
                              self.declaration.clone())

    @property
    def name(self):
        # type: () -> unicode
        return self.declaration.name

    @property
    def function_params(self):
        # type: () -> Any
        if self.objectType != 'function':
            return None
        return self.declaration.function_params

    def get_id(self, version, prefixed=True):
        # type: (int, bool) -> unicode
        if version == 1:
            if self.templatePrefix:
                raise NoOldIdError()
            if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
                return self.enumeratorScopedSymbol.declaration.get_id(version)
            return self.declaration.get_id(version, self.objectType, self.symbol)
        # version >= 2
        if self.objectType == 'enumerator' and self.enumeratorScopedSymbol:
            return self.enumeratorScopedSymbol.declaration.get_id(version, prefixed)
        if prefixed:
            res = [_id_prefix[version]]
        else:
            res = []
        if self.templatePrefix:
            res.append(self.templatePrefix.get_id(version))
        res.append(self.declaration.get_id(version, self.objectType, self.symbol))
        return u''.join(res)

    def get_newest_id(self):
        # type: () -> unicode
        return self.get_id(_max_id, True)

    def __unicode__(self):
        # type: () -> unicode
        res = []  # type: List[unicode]
        if self.visibility and self.visibility != "public":
            res.append(self.visibility)
            res.append(u' ')
        if self.templatePrefix:
            res.append(text_type(self.templatePrefix))
        res.append(text_type(self.declaration))
        return u''.join(res)

    def describe_signature(self, signode, mode, env, options):
        # type: (addnodes.desc_signature, unicode, BuildEnvironment, Dict) -> None
        _verify_description_mode(mode)
        assert self.symbol
        # The caller of the domain added a desc_signature node.
        # Always enable multiline:
        signode['is_multiline'] = True
        # Put each line in a desc_signature_line node.
        mainDeclNode = addnodes.desc_signature_line()
        mainDeclNode.sphinx_cpp_tagname = 'declarator'
        mainDeclNode['add_permalink'] = not self.symbol.isRedeclaration

        if self.templatePrefix:
            self.templatePrefix.describe_signature(signode, mode, env,
                                                   symbol=self.symbol,
                                                   lineSpec=options.get('tparam-line-spec'))
        signode += mainDeclNode
        if self.visibility and self.visibility != "public":
            mainDeclNode += addnodes.desc_annotation(self.visibility + " ",
                                                     self.visibility + " ")
        if self.objectType == 'type':
            prefix = self.declaration.get_type_declaration_prefix()
            prefix += ' '
            mainDeclNode += addnodes.desc_annotation(prefix, prefix)
        elif self.objectType == 'concept':
            mainDeclNode += addnodes.desc_annotation('concept ', 'concept ')
        elif self.objectType == 'member':
            pass
        elif self.objectType == 'function':
            pass
        elif self.objectType == 'class':
            mainDeclNode += addnodes.desc_annotation('class ', 'class ')
        elif self.objectType == 'enum':
            prefix = 'enum '
            if self.scoped:  # type: ignore
                prefix += self.scoped  # type: ignore
                prefix += ' '
            mainDeclNode += addnodes.desc_annotation(prefix, prefix)
        elif self.objectType == 'enumerator':
            mainDeclNode += addnodes.desc_annotation('enumerator ', 'enumerator ')
        else:
            assert False
        self.declaration.describe_signature(mainDeclNode, mode, env, self.symbol)


class ASTNamespace(ASTBase):
    def __init__(self, nestedName, templatePrefix):
        # type: (Any, Any) -> None
        self.nestedName = nestedName
        self.templatePrefix = templatePrefix


class Symbol(object):
    def _assert_invariants(self):
        # type: () -> None
        if not self.parent:
            # parent == None means global scope, so declaration means a parent
            assert not self.identOrOp
            assert not self.templateParams
            assert not self.templateArgs
            assert not self.declaration
            assert not self.docname
        else:
            if self.declaration:
                assert self.docname

    def __init__(self, parent, identOrOp,
                 templateParams, templateArgs, declaration, docname):
        # type: (Any, Any, Any, Any, Any, unicode) -> None
        self.parent = parent
        self.identOrOp = identOrOp
        self.templateParams = templateParams  # template<templateParams>
        self.templateArgs = templateArgs  # identifier<templateArgs>
        self.declaration = declaration
        self.docname = docname
        self.isRedeclaration = False
        self._assert_invariants()

        self.children = []  # type: List[Any]
        if self.parent:
            self.parent.children.append(self)
        if self.declaration:
            self.declaration.symbol = self
        # add symbols for the template params
        # (do it after self.children has been initialised
        if self.templateParams:
            for p in self.templateParams.params:
                if not p.get_identifier():
                    continue
                # only add a declaration if we our selfs are from a declaration
                if declaration:
                    decl = ASTDeclaration('templateParam', None, None, p)
                else:
                    decl = None
                nne = ASTNestedNameElement(p.get_identifier(), None)
                nn = ASTNestedName([nne], [False], rooted=False)
                self._add_symbols(nn, [], decl, docname)
        # add symbols for function parameters, if any
        if declaration is not None and declaration.function_params is not None:
            for p in declaration.function_params:
                if p.arg is None:
                    continue
                nn = p.arg.name
                if nn is None:
                    continue
                # (comparing to the template params: we have checked that we are a declaration)
                decl = ASTDeclaration('functionParam', None, None, p)
                assert not nn.rooted
                assert len(nn.names) == 1
                identOrOp = nn.names[0].identOrOp
                Symbol(parent=self, identOrOp=identOrOp,
                       templateParams=None, templateArgs=None,
                       declaration=decl, docname=docname)

    def _fill_empty(self, declaration, docname):
        # type: (Any, unicode) -> None
        self._assert_invariants()
        assert not self.declaration
        assert not self.docname
        assert declaration
        assert docname
        self.declaration = declaration
        self.declaration.symbol = self
        self.docname = docname
        self._assert_invariants()

    def clear_doc(self, docname):
        # type: (unicode) -> None
        newChildren = []
        for sChild in self.children:
            sChild.clear_doc(docname)
            if sChild.declaration and sChild.docname == docname:
                sChild.declaration = None
                sChild.docname = None
            newChildren.append(sChild)
        self.children = newChildren

    def get_all_symbols(self):
        # type: () -> Iterator[Any]
        yield self
        for sChild in self.children:
            for s in sChild.get_all_symbols():
                yield s

    def get_lookup_key(self):
        # type: () -> List[Tuple[ASTNestedNameElement, Any]]
        symbols = []
        s = self
        while s.parent:
            symbols.append(s)
            s = s.parent
        symbols.reverse()
        key = []
        for s in symbols:
            nne = ASTNestedNameElement(s.identOrOp, s.templateArgs)
            key.append((nne, s.templateParams))
        return key

    def get_full_nested_name(self):
        # type: () -> ASTNestedName
        names = []
        templates = []
        for nne, templateParams in self.get_lookup_key():
            names.append(nne)
            templates.append(False)
        return ASTNestedName(names, templates, rooted=False)

    def _find_named_symbol(self, identOrOp, templateParams, templateArgs,
                           templateShorthand, matchSelf):
        # type: (Any, Any, Any, Any, bool) -> Symbol

        def isSpecialization():
            # the names of the template parameters must be given exactly as args
            # and params that are packs must in the args be the name expanded
            if len(templateParams.params) != len(templateArgs.args):
                return True
            # having no template params and no arguments is also a specialization
            if len(templateParams.params) == 0:
                return True
            for i in range(len(templateParams.params)):
                param = templateParams.params[i]
                arg = templateArgs.args[i]
                # TODO: doing this by string manipulation is probably not the most efficient
                paramName = text_type(param.name)
                argTxt = text_type(arg)
                isArgPackExpansion = argTxt.endswith('...')
                if param.isPack != isArgPackExpansion:
                    return True
                argName = argTxt[:-3] if isArgPackExpansion else argTxt
                if paramName != argName:
                    return True
            return False
        if templateParams is not None and templateArgs is not None:
            # If both are given, but it's not a specialization, then do lookup as if
            # there is no argument list.
            # For example: template<typename T> int A<T>::var;
            if not isSpecialization():
                templateArgs = None

        def matches(s):
            if s.identOrOp != identOrOp:
                return False
            if (s.templateParams is None) != (templateParams is None):
                if templateParams is not None:
                    # we query with params, they must match params
                    return False
                if not templateShorthand:
                    # we don't query with params, and we do care about them
                    return False
            if templateParams:
                # TODO: do better comparison
                if text_type(s.templateParams) != text_type(templateParams):
                    return False
            if (s.templateArgs is None) != (templateArgs is None):
                return False
            if s.templateArgs:
                # TODO: do better comparison
                if text_type(s.templateArgs) != text_type(templateArgs):
                    return False
            return True
        if matchSelf and matches(self):
            return self
        for s in self.children:
            if matches(s):
                return s
        return None

    def _add_symbols(self, nestedName, templateDecls, declaration, docname):
        # type: (Any, List[Any], Any, unicode) -> Symbol
        # This condition should be checked at the parser level.
        # Each template argument list must have a template parameter list.
        # But to declare a template there must be an additional template parameter list.
        assert(nestedName.num_templates() == len(templateDecls) or
               nestedName.num_templates() + 1 == len(templateDecls))

        parentSymbol = self
        if nestedName.rooted:
            while parentSymbol.parent:
                parentSymbol = parentSymbol.parent
        names = nestedName.names
        iTemplateDecl = 0
        for name in names[:-1]:
            identOrOp = name.identOrOp
            templateArgs = name.templateArgs
            if templateArgs:
                assert iTemplateDecl < len(templateDecls)
                templateParams = templateDecls[iTemplateDecl]
                iTemplateDecl += 1
            else:
                templateParams = None
            symbol = parentSymbol._find_named_symbol(identOrOp,
                                                     templateParams,
                                                     templateArgs,
                                                     templateShorthand=False,
                                                     matchSelf=False)
            if symbol is None:
                symbol = Symbol(parent=parentSymbol, identOrOp=identOrOp,
                                templateParams=templateParams,
                                templateArgs=templateArgs, declaration=None,
                                docname=None)
            parentSymbol = symbol
        name = names[-1]
        identOrOp = name.identOrOp
        templateArgs = name.templateArgs
        if iTemplateDecl < len(templateDecls):
            if iTemplateDecl + 1 != len(templateDecls):
                print(text_type(templateDecls))
                print(text_type(nestedName))
            assert iTemplateDecl + 1 == len(templateDecls)
            templateParams = templateDecls[iTemplateDecl]
        else:
            assert iTemplateDecl == len(templateDecls)
            templateParams = None
        symbol = parentSymbol._find_named_symbol(identOrOp,
                                                 templateParams,
                                                 templateArgs,
                                                 templateShorthand=False,
                                                 matchSelf=False)
        if symbol:
            if not declaration:
                # good, just a scope creation
                return symbol
            if not symbol.declaration:
                # If someone first opened the scope, and then later
                # declares it, e.g,
                # .. namespace:: Test
                # .. namespace:: nullptr
                # .. class:: Test
                symbol._fill_empty(declaration, docname)
                return symbol
            # It may simply be a function overload, so let's compare ids.
            isRedeclaration = True
            candSymbol = Symbol(parent=parentSymbol, identOrOp=identOrOp,
                                templateParams=templateParams,
                                templateArgs=templateArgs,
                                declaration=declaration,
                                docname=docname)
            if declaration.objectType == "function":
                newId = declaration.get_newest_id()
                oldId = symbol.declaration.get_newest_id()
                if newId != oldId:
                    # we already inserted the symbol, so return the new one
                    symbol = candSymbol
                    isRedeclaration = False
            if isRedeclaration:
                # Redeclaration of the same symbol.
                # Let the new one be there, but raise an error to the client
                # so it can use the real symbol as subscope.
                # This will probably result in a duplicate id warning.
                candSymbol.isRedeclaration = True
                raise _DuplicateSymbolError(symbol, declaration)
        else:
            symbol = Symbol(parent=parentSymbol, identOrOp=identOrOp,
                            templateParams=templateParams,
                            templateArgs=templateArgs,
                            declaration=declaration,
                            docname=docname)
        return symbol

    def merge_with(self, other, docnames, env):
        # type: (Any, List[unicode], BuildEnvironment) -> None
        assert other is not None
        for otherChild in other.children:
            ourChild = self._find_named_symbol(otherChild.identOrOp,
                                               otherChild.templateParams,
                                               otherChild.templateArgs,
                                               templateShorthand=False,
                                               matchSelf=False)
            if ourChild is None:
                # TODO: hmm, should we prune by docnames?
                self.children.append(otherChild)
                otherChild.parent = self
                otherChild._assert_invariants()
                continue
            if otherChild.declaration and otherChild.docname in docnames:
                if not ourChild.declaration:
                    ourChild._fill_empty(otherChild.declaration, otherChild.docname)
                elif ourChild.docname != otherChild.docname:
                    name = text_type(ourChild.declaration)
                    msg = "Duplicate declaration, also defined in '%s'.\n"
                    msg += "Declaration is '%s'."
                    msg = msg % (ourChild.docname, name)
                    logger.warning(msg, location=otherChild.docname)
                else:
                    # Both have declarations, and in the same docname.
                    # This can apparently happen, it should be safe to
                    # just ignore it, right?
                    pass
            ourChild.merge_with(otherChild, docnames, env)

    def add_name(self, nestedName, templatePrefix=None):
        # type: (unicode, Any) -> Symbol
        if templatePrefix:
            templateDecls = templatePrefix.templates
        else:
            templateDecls = []
        return self._add_symbols(nestedName, templateDecls,
                                 declaration=None, docname=None)

    def add_declaration(self, declaration, docname):
        # type: (Any, unicode) -> Symbol
        assert declaration
        assert docname
        nestedName = declaration.name
        if declaration.templatePrefix:
            templateDecls = declaration.templatePrefix.templates
        else:
            templateDecls = []
        return self._add_symbols(nestedName, templateDecls, declaration, docname)

    def find_identifier(self, identOrOp, matchSelf):
        # type: (Any, bool) -> Symbol
        if matchSelf and self.identOrOp == identOrOp:
            return self
        for s in self.children:
            if s.identOrOp == identOrOp:
                return s
        return None

    def direct_lookup(self, key):
        # type: (List[Tuple[Any, Any]]) -> Symbol
        s = self
        for name, templateParams in key:
            identOrOp = name.identOrOp
            templateArgs = name.templateArgs
            s = s._find_named_symbol(identOrOp,
                                     templateParams, templateArgs,
                                     templateShorthand=False,
                                     matchSelf=False)
            if not s:
                return None
        return s

    def find_name(self, nestedName, templateDecls, typ, templateShorthand, matchSelf):
        # type: (Any, Any, Any, Any, bool) -> Symbol
        # templateShorthand: missing template parameter lists for templates is ok

        # TODO: unify this with the _add_symbols
        # This condition should be checked at the parser level.
        assert len(templateDecls) <= nestedName.num_templates() + 1
        parentSymbol = self
        if nestedName.rooted:
            while parentSymbol.parent:
                parentSymbol = parentSymbol.parent
        names = nestedName.names

        # walk up until we find the first identifier
        firstName = names[0]
        if not firstName.is_operator():
            while parentSymbol.parent:
                if parentSymbol.find_identifier(firstName.identOrOp,
                                                matchSelf=matchSelf):
                    # if we are in the scope of a constructor but wants to reference the class
                    # we need to walk one extra up
                    if (len(names) == 1 and typ == 'class' and matchSelf and
                            parentSymbol.parent and
                            parentSymbol.parent.identOrOp == firstName.identOrOp):
                        pass
                    else:
                        break
                parentSymbol = parentSymbol.parent
        iTemplateDecl = 0
        for iName in range(len(names)):
            name = names[iName]
            if iName + 1 == len(names):
                identOrOp = name.identOrOp
                templateArgs = name.templateArgs
                if iTemplateDecl < len(templateDecls):
                    assert iTemplateDecl + 1 == len(templateDecls)
                    templateParams = templateDecls[iTemplateDecl]
                else:
                    assert iTemplateDecl == len(templateDecls)
                    templateParams = None
                symbol = parentSymbol._find_named_symbol(identOrOp,
                                                         templateParams, templateArgs,
                                                         templateShorthand=templateShorthand,
                                                         matchSelf=matchSelf)
                if symbol is not None:
                    return symbol
                # try without template params and args
                symbol = parentSymbol._find_named_symbol(identOrOp,
                                                         None, None,
                                                         templateShorthand=templateShorthand,
                                                         matchSelf=matchSelf)
                return symbol
            else:
                identOrOp = name.identOrOp
                templateArgs = name.templateArgs
                if templateArgs and iTemplateDecl < len(templateDecls):
                    templateParams = templateDecls[iTemplateDecl]
                    iTemplateDecl += 1
                else:
                    templateParams = None
                symbol = parentSymbol._find_named_symbol(identOrOp,
                                                         templateParams, templateArgs,
                                                         templateShorthand=templateShorthand,
                                                         matchSelf=matchSelf)
                if symbol is None:
                    # TODO: maybe search without template args
                    return None
                # We have now matched part of a nested name, and need to match more
                # so even if we should matchSelf before, we definitely shouldn't
                # even more. (see also issue #2666)
                matchSelf = False
            parentSymbol = symbol
        assert False  # should have returned in the loop

    def to_string(self, indent):
        # type: (int) -> unicode
        res = ['\t' * indent]  # type: List[unicode]
        if not self.parent:
            res.append('::')
        else:
            if self.templateParams:
                res.append(text_type(self.templateParams))
                res.append('\n')
                res.append('\t' * indent)
            if self.identOrOp:
                res.append(text_type(self.identOrOp))
            else:
                res.append(text_type(self.declaration))
            if self.templateArgs:
                res.append(text_type(self.templateArgs))
            if self.declaration:
                res.append(": ")
                res.append(text_type(self.declaration))
        if self.docname:
            res.append('\t(')
            res.append(self.docname)
            res.append(')')
        res.append('\n')
        return ''.join(res)

    def dump(self, indent):
        # type: (int) -> unicode
        res = [self.to_string(indent)]
        for c in self.children:
            res.append(c.dump(indent + 1))
        return ''.join(res)


class DefinitionParser(object):
    # those without signedness and size modifiers
    # see http://en.cppreference.com/w/cpp/language/types
    _simple_fundemental_types = (
        'void', 'bool', 'char', 'wchar_t', 'char16_t', 'char32_t', 'int',
        'float', 'double', 'auto'
    )

    _prefix_keys = ('class', 'struct', 'enum', 'union', 'typename')

    def __init__(self, definition, warnEnv, config):
        # type: (Any, Any, Config) -> None
        self.definition = definition.strip()
        self.pos = 0
        self.end = len(self.definition)
        self.last_match = None  # type: Match
        self._previous_state = (0, None)  # type: Tuple[int, Match]
        self.otherErrors = []  # type: List[DefinitionError]
        # in our tests the following is set to False to capture bad parsing
        self.allowFallbackExpressionParsing = True

        self.warnEnv = warnEnv
        self.config = config

    def _make_multi_error(self, errors, header):
        # type: (List[Any], unicode) -> DefinitionError
        if len(errors) == 1:
            if len(header) > 0:
                return DefinitionError(header + '\n' + errors[0][0].description)
            else:
                return DefinitionError(errors[0][0].description)
        result = [header, '\n']
        for e in errors:
            if len(e[1]) > 0:
                ident = '  '
                result.append(e[1])
                result.append(':\n')
                for line in e[0].description.split('\n'):
                    if len(line) == 0:
                        continue
                    result.append(ident)
                    result.append(line)
                    result.append('\n')
            else:
                result.append(e[0].description)
        return DefinitionError(''.join(result))

    def status(self, msg):
        # type: (unicode) -> None
        # for debugging
        indicator = '-' * self.pos + '^'
        print("%s\n%s\n%s" % (msg, self.definition, indicator))

    def fail(self, msg):
        # type: (unicode) -> None
        errors = []
        indicator = '-' * self.pos + '^'
        exMain = DefinitionError(
            'Invalid definition: %s [error at %d]\n  %s\n  %s' %
            (msg, self.pos, self.definition, indicator))
        errors.append((exMain, "Main error"))
        for err in self.otherErrors:
            errors.append((err, "Potential other error"))
        self.otherErrors = []
        raise self._make_multi_error(errors, '')

    def warn(self, msg):
        # type: (unicode) -> None
        if self.warnEnv:
            self.warnEnv.warn(msg)
        else:
            print("Warning: %s" % msg)

    def match(self, regex):
        # type: (Pattern) -> bool
        match = regex.match(self.definition, self.pos)
        if match is not None:
            self._previous_state = (self.pos, self.last_match)
            self.pos = match.end()
            self.last_match = match
            return True
        return False

    def backout(self):
        # type: () -> None
        self.pos, self.last_match = self._previous_state

    def skip_string(self, string):
        # type: (unicode) -> bool
        strlen = len(string)
        if self.definition[self.pos:self.pos + strlen] == string:
            self.pos += strlen
            return True
        return False

    def skip_word(self, word):
        # type: (unicode) -> bool
        return self.match(re.compile(r'\b%s\b' % re.escape(word)))

    def skip_ws(self):
        # type: () -> bool
        return self.match(_whitespace_re)

    def skip_word_and_ws(self, word):
        # type: (unicode) -> bool
        if self.skip_word(word):
            self.skip_ws()
            return True
        return False

    def skip_string_and_ws(self, string):
        # type: (unicode) -> bool
        if self.skip_string(string):
            self.skip_ws()
            return True
        return False

    @property
    def eof(self):
        # type: () -> bool
        return self.pos >= self.end

    @property
    def current_char(self):
        # type: () -> unicode
        try:
            return self.definition[self.pos]
        except IndexError:
            return 'EOF'

    @property
    def matched_text(self):
        # type: () -> unicode
        if self.last_match is not None:
            return self.last_match.group()
        else:
            return None

    def read_rest(self):
        # type: () -> unicode
        rv = self.definition[self.pos:]
        self.pos = self.end
        return rv

    def assert_end(self):
        # type: () -> None
        self.skip_ws()
        if not self.eof:
            self.fail('Expected end of definition.')

    def _parse_string(self):
        if self.current_char != '"':
            return None
        startPos = self.pos
        self.pos += 1
        escape = False
        while True:
            if self.eof:
                self.fail("Unexpected end during inside string.")
            elif self.current_char == '"' and not escape:
                self.pos += 1
                break
            elif self.current_char == '\\':
                escape = True
            else:
                escape = False
            self.pos += 1
        return self.definition[startPos:self.pos]

    def _parse_balanced_token_seq(self, end):
        # type: (List[unicode]) -> unicode
        # TODO: add handling of string literals and similar
        brackets = {'(': ')', '[': ']', '{': '}'}  # type: Dict[unicode, unicode]
        startPos = self.pos
        symbols = []  # type: List[unicode]
        while not self.eof:
            if len(symbols) == 0 and self.current_char in end:
                break
            if self.current_char in brackets.keys():
                symbols.append(brackets[self.current_char])
            elif len(symbols) > 0 and self.current_char == symbols[-1]:
                symbols.pop()
            elif self.current_char in ")]}":
                self.fail("Unexpected '%s' in balanced-token-seq." % self.current_char)
            self.pos += 1
        if self.eof:
            self.fail("Could not find end of balanced-token-seq starting at %d."
                      % startPos)
        return self.definition[startPos:self.pos]

    def _parse_attribute(self):
        # type: () -> Any
        self.skip_ws()
        # try C++11 style
        startPos = self.pos
        if self.skip_string_and_ws('['):
            if not self.skip_string('['):
                self.pos = startPos
            else:
                # TODO: actually implement the correct grammar
                arg = self._parse_balanced_token_seq(end=[']'])
                if not self.skip_string_and_ws(']'):
                    self.fail("Expected ']' in end of attribute.")
                if not self.skip_string_and_ws(']'):
                    self.fail("Expected ']' in end of attribute after [[...]")
                return ASTCPPAttribute(arg)

        # try GNU style
        if self.skip_word_and_ws('__attribute__'):
            if not self.skip_string_and_ws('('):
                self.fail("Expected '(' after '__attribute__'.")
            if not self.skip_string_and_ws('('):
                self.fail("Expected '(' after '__attribute__('.")
            attrs = []
            while 1:
                if self.match(_identifier_re):
                    name = self.matched_text
                    self.skip_ws()
                    if self.skip_string_and_ws('('):
                        self.fail('Parameterized GNU style attribute not yet supported.')
                    attrs.append(ASTGnuAttribute(name, None))
                    # TODO: parse arguments for the attribute
                if self.skip_string_and_ws(','):
                    continue
                elif self.skip_string_and_ws(')'):
                    break
                else:
                    self.fail("Expected identifier, ')', or ',' in __attribute__.")
            if not self.skip_string_and_ws(')'):
                self.fail("Expected ')' after '__attribute__((...)'")
            return ASTGnuAttributeList(attrs)

        # try the simple id attributes defined by the user
        for id in self.config.cpp_id_attributes:
            if self.skip_word_and_ws(id):
                return ASTIdAttribute(id)

        # try the paren attributes defined by the user
        for id in self.config.cpp_paren_attributes:
            if not self.skip_string_and_ws(id):
                continue
            if not self.skip_string('('):
                self.fail("Expected '(' after user-defined paren-attribute.")
            arg = self._parse_balanced_token_seq(end=[')'])
            if not self.skip_string(')'):
                self.fail("Expected ')' to end user-defined paren-attribute.")
            return ASTParenAttribute(id, arg)

        return None

    def _parse_literal(self):
        # -> integer-literal
        #  | character-literal
        #  | floating-literal
        #  | string-literal
        #  | boolean-literal -> "false" | "true"
        #  | pointer-literal -> "nullptr"
        #  | user-defined-literal
        self.skip_ws()
        if self.skip_word('nullptr'):
            return ASTPointerLiteral()
        if self.skip_word('true'):
            return ASTBooleanLiteral(True)
        if self.skip_word('false'):
            return ASTBooleanLiteral(False)
        for regex in [_float_literal_re, _binary_literal_re, _hex_literal_re,
                      _integer_literal_re, _octal_literal_re]:
            pos = self.pos
            if self.match(regex):
                while self.current_char in 'uUlLfF':
                    self.pos += 1
                return ASTNumberLiteral(self.definition[pos:self.pos])

        string = self._parse_string()
        if string is not None:
            return ASTStringLiteral(string)
        # TODO: char lit
        # TODO: user-defined lit
        return None

    def _parse_fold_or_paren_expression(self):
        # "(" expression ")"
        # fold-expression
        # -> ( cast-expression fold-operator ... )
        #  | ( ... fold-operator cast-expression )
        #  | ( cast-expression fold-operator ... fold-operator cast-expression
        if self.current_char != '(':
            return None
        self.pos += 1
        self.skip_ws()
        if self.skip_string_and_ws("..."):
            # ( ... fold-operator cast-expression )
            if not self.match(_fold_operator_re):
                self.fail("Expected fold operator after '...' in fold expression.")
            op = self.matched_text
            rightExpr = self._parse_cast_expression()
            if not self.skip_string(')'):
                self.fail("Expected ')' in end of fold expression.")
            return ASTFoldExpr(None, op, rightExpr)
        # TODO: actually try to parse fold expression
        # fall back to a paren expression
        res = self._parse_expression(inTemplate=False)
        self.skip_ws()
        if not self.skip_string(')'):
            self.fail("Expected ')' in end of fold expression or parenthesized expression.")
        return ASTParenExpr(res)

    def _parse_primary_expression(self):
        # literal
        # "this"
        # lambda-expression
        # "(" expression ")"
        # fold-expression
        # id-expression -> we parse this with _parse_nested_name
        self.skip_ws()
        res = self._parse_literal()
        if res is not None:
            return res
        self.skip_ws()
        if self.skip_word("this"):
            return ASTThisLiteral()
        # TODO: try lambda expression
        res = self._parse_fold_or_paren_expression()
        if res is not None:
            return res
        return self._parse_nested_name()

    def _parse_postfix_expression(self):
        # -> primary
        #  | postfix "[" expression "]"
        #  | postfix "[" braced-init-list [opt] "]"
        #  | postfix "(" expression-list [opt] ")"
        #  | postfix "." "template" [opt] id-expression
        #  | postfix "->" "template" [opt] id-expression
        #  | postfix "." pseudo-destructor-name
        #  | postfix "->" pseudo-destructor-name
        #  | postfix "++"
        #  | postfix "--"
        #  | simple-type-specifier "(" expression-list [opt] ")"
        #  | simple-type-specifier braced-init-list
        #  | typename-specifier "(" expression-list [opt] ")"
        #  | typename-specifier braced-init-list
        #  | "dynamic_cast" "<" type-id ">" "(" expression ")"
        #  | "static_cast" "<" type-id ">" "(" expression ")"
        #  | "reinterpret_cast" "<" type-id ">" "(" expression ")"
        #  | "const_cast" "<" type-id ">" "(" expression ")"
        #  | "typeid" "(" expression ")"
        #  | "typeid" "(" type-id ")"

        prefixType = None
        prefix = None  # type: Any
        self.skip_ws()

        cast = None
        for c in _id_explicit_cast:
            if self.skip_word_and_ws(c):
                cast = c
                break
        if cast is not None:
            prefixType = "cast"
            if not self.skip_string("<"):
                self.fail("Expected '<' afer '%s'." % cast)
            typ = self._parse_type(False)
            self.skip_ws()
            if not self.skip_string_and_ws(">"):
                self.fail("Expected '>' after type in '%s'." % cast)
            if not self.skip_string("("):
                self.fail("Expected '(' in '%s'." % cast)

            def parser():
                return self._parse_expression(inTemplate=False)
            expr = self._parse_expression_fallback([')'], parser)
            self.skip_ws()
            if not self.skip_string(")"):
                self.fail("Expected ')' to end '%s'." % cast)
            prefix = ASTExplicitCast(cast, typ, expr)
        elif self.skip_word_and_ws("typeid"):
            prefixType = "typeid"
            if not self.skip_string_and_ws('('):
                self.fail("Expected '(' after 'typeid'.")
            pos = self.pos
            try:
                typ = self._parse_type(False)
                prefix = ASTTypeId(typ, isType=True)
                if not self.skip_string(')'):
                    self.fail("Expected ')' to end 'typeid' of type.")
            except DefinitionError as eType:
                self.pos = pos
                try:

                    def parser():
                        return self._parse_expression(inTemplate=False)
                    expr = self._parse_expression_fallback([')'], parser)
                    prefix = ASTTypeId(expr, isType=False)
                    if not self.skip_string(')'):
                        self.fail("Expected ')' to end 'typeid' of expression.")
                except DefinitionError as eExpr:
                    self.pos = pos
                    header = "Error in 'typeid(...)'."
                    header += " Expected type or expression."
                    errors = []
                    errors.append((eType, "If type"))
                    errors.append((eExpr, "If expression"))
                    raise self._make_multi_error(errors, header)
        else:  # a primary expression or a type
            pos = self.pos
            try:
                prefix = self._parse_primary_expression()
                prefixType = 'expr'
            except DefinitionError as eOuter:
                self.pos = pos
                try:
                    # we are potentially casting, so save parens for us
                    # TODO: hmm, would we need to try both with operatorCast and with None?
                    prefix = self._parse_type(False, 'operatorCast')
                    prefixType = 'typeOperatorCast'
                    #  | simple-type-specifier "(" expression-list [opt] ")"
                    #  | simple-type-specifier braced-init-list
                    #  | typename-specifier "(" expression-list [opt] ")"
                    #  | typename-specifier braced-init-list
                    self.skip_ws()
                    if self.current_char != '(' and self.current_char != '{':
                        self.fail("Expecting '(' or '{' after type in cast expression.")
                except DefinitionError as eInner:
                    self.pos = pos
                    header = "Error in postfix expression,"
                    header += " expected primary expression or type."
                    errors = []
                    errors.append((eOuter, "If primary expression"))
                    errors.append((eInner, "If type"))
                    raise self._make_multi_error(errors, header)

        # and now parse postfixes
        postFixes = []
        while True:
            self.skip_ws()
            if prefixType in ['expr', 'cast', 'typeid']:
                if self.skip_string_and_ws('['):
                    expr = self._parse_expression(inTemplate=False)
                    self.skip_ws()
                    if not self.skip_string(']'):
                        self.fail("Expected ']' in end of postfix expression.")
                    postFixes.append(ASTPostfixArray(expr))
                    continue
                if self.skip_string('.'):
                    if self.skip_string('*'):
                        # don't steal the dot
                        self.pos -= 2
                    elif self.skip_string('..'):
                        # don't steal the dot
                        self.pos -= 3
                    else:
                        name = self._parse_nested_name()
                        postFixes.append(ASTPostfixMember(name))  # type: ignore
                        continue
                if self.skip_string('->'):
                    if self.skip_string('*'):
                        # don't steal the arrow
                        self.pos -= 3
                    else:
                        name = self._parse_nested_name()
                        postFixes.append(ASTPostfixMemberOfPointer(name))  # type: ignore
                        continue
                if self.skip_string('++'):
                    postFixes.append(ASTPostfixInc())  # type: ignore
                    continue
                if self.skip_string('--'):
                    postFixes.append(ASTPostfixDec())  # type: ignore
                    continue
            if self.skip_string_and_ws('('):
                # TODO: handled braced init
                exprs = []
                self.skip_ws()
                if not self.skip_string(')'):
                    while True:
                        self.skip_ws()
                        expr = self._parse_expression(inTemplate=False)
                        self.skip_ws()
                        if self.skip_string('...'):
                            exprs.append(ASTPackExpansionExpr(expr))
                        else:
                            exprs.append(expr)
                        self.skip_ws()
                        if self.skip_string(')'):
                            break
                        if not self.skip_string(','):
                            self.fail("Error in cast or call, expected ',' or ')'.")
                postFixes.append(ASTPostfixCallExpr(exprs))  # type: ignore
                continue
            break
        if len(postFixes) == 0:
            return prefix
        else:
            return ASTPostfixExpr(prefix, postFixes)

    def _parse_unary_expression(self):
        # -> postfix
        #  | "++" cast
        #  | "--" cast
        #  | unary-operator cast -> (* | & | + | - | ! | ~) cast
        # The rest:
        #  | "sizeof" unary
        #  | "sizeof" "(" type-id ")"
        #  | "sizeof" "..." "(" identifier ")"
        #  | "alignof" "(" type-id ")"
        #  | noexcept-expression -> noexcept "(" expression ")"
        #  | new-expression
        #  | delete-expression
        self.skip_ws()
        for op in _expression_unary_ops:
            # TODO: hmm, should we be able to backtrack here?
            if self.skip_string(op):
                expr = self._parse_cast_expression()
                return ASTUnaryOpExpr(op, expr)
        if self.skip_word_and_ws('sizeof'):
            if self.skip_string_and_ws('...'):
                if not self.skip_string_and_ws('('):
                    self.fail("Expecting '(' after 'sizeof...'.")
                if not self.match(_identifier_re):
                    self.fail("Expecting identifier for 'sizeof...'.")
                ident = ASTIdentifier(self.matched_text)
                self.skip_ws()
                if not self.skip_string(")"):
                    self.fail("Expecting ')' to end 'sizeof...'.")
                return ASTSizeofParamPack(ident)
            if self.skip_string_and_ws('('):
                typ = self._parse_type(named=False)
                self.skip_ws()
                if not self.skip_string(')'):
                    self.fail("Expecting ')' to end 'sizeof'.")
                return ASTSizeofType(typ)
            expr = self._parse_unary_expression()
            return ASTSizeofExpr(expr)
        if self.skip_word_and_ws('alignof'):
            if not self.skip_string_and_ws('('):
                self.fail("Expecting '(' after 'alignof'.")
            typ = self._parse_type(named=False)
            self.skip_ws()
            if not self.skip_string(')'):
                self.fail("Expecting ')' to end 'alignof'.")
            return ASTAlignofExpr(typ)
        if self.skip_word_and_ws('noexcept'):
            if not self.skip_string_and_ws('('):
                self.fail("Expecting '(' after 'noexcept'.")
            expr = self._parse_expression(inTemplate=False)
            self.skip_ws()
            if not self.skip_string(')'):
                self.fail("Expecting ')' to end 'noexcept'.")
            return ASTNoexceptExpr(expr)
        # TODO: the rest
        return self._parse_postfix_expression()

    def _parse_cast_expression(self):
        # -> unary  | "(" type-id ")" cast
        pos = self.pos
        self.skip_ws()
        if self.skip_string('('):
            try:
                typ = self._parse_type(False)
                if not self.skip_string(')'):
                    raise DefinitionError("Expected ')' in cast expression.")
                expr = self._parse_cast_expression()
                return ASTCastExpr(typ, expr)
            except DefinitionError as exCast:
                self.pos = pos
                try:
                    return self._parse_unary_expression()
                except DefinitionError as exUnary:
                    errs = []
                    errs.append((exCast, "If type cast expression"))
                    errs.append((exUnary, "If unary expression"))
                    raise self._make_multi_error(errs, "Error in cast expression.")
        else:
            return self._parse_unary_expression()

    def _parse_logical_or_expression(self, inTemplate):
        # logical-or     = logical-and      ||
        # logical-and    = inclusive-or     &&
        # inclusive-or   = exclusive-or     |
        # exclusive-or   = and              ^
        # and            = equality         &
        # equality       = relational       ==, !=
        # relational     = shift            <, >, <=, >=
        # shift          = additive         <<, >>
        # additive       = multiplicative   +, -
        # multiplicative = pm               *, /, %
        # pm             = cast             .*, ->*
        def _parse_bin_op_expr(self, opId, inTemplate):
            if opId + 1 == len(_expression_bin_ops):
                def parser(inTemplate):
                    return self._parse_cast_expression()
            else:
                def parser(inTemplate):
                    return _parse_bin_op_expr(self, opId + 1, inTemplate=inTemplate)
            exprs = []
            ops = []
            exprs.append(parser(inTemplate=inTemplate))
            while True:
                self.skip_ws()
                if inTemplate and self.current_char == '>':
                    break
                pos = self.pos
                oneMore = False
                for op in _expression_bin_ops[opId]:
                    if not self.skip_string(op):
                        continue
                    if op == '&' and self.current_char == '&':
                        # don't split the && 'token'
                        self.pos -= 1
                        # and btw. && has lower precedence, so we are done
                        break
                    try:
                        expr = parser(inTemplate=inTemplate)
                        exprs.append(expr)
                        ops.append(op)
                        oneMore = True
                        break
                    except DefinitionError:
                        self.pos = pos
                if not oneMore:
                    break
            return ASTBinOpExpr(exprs, ops)
        return _parse_bin_op_expr(self, 0, inTemplate=inTemplate)

    def _parse_conditional_expression_tail(self, orExprHead):
        # -> "?" expression ":" assignment-expression
        return None

    def _parse_assignment_expression(self, inTemplate):
        # -> conditional-expression
        #  | logical-or-expression assignment-operator initializer-clause
        #  | throw-expression
        # TODO: parse throw-expression: "throw" assignment-expression [opt]
        # if not a throw expression, then:
        # -> conditional-expression ->
        #     logical-or-expression
        #   | logical-or-expression "?" expression ":" assignment-expression
        #   | logical-or-expression assignment-operator initializer-clause
        exprs = []
        ops = []
        orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
        exprs.append(orExpr)
        # TODO: handle ternary with _parse_conditional_expression_tail
        while True:
            oneMore = False
            self.skip_ws()
            for op in _expression_assignment_ops:
                if not self.skip_string(op):
                    continue
                expr = self._parse_logical_or_expression(False)
                exprs.append(expr)
                ops.append(op)
                oneMore = True
            if not oneMore:
                break
        if len(ops) == 0:
            return orExpr
        else:
            return ASTAssignmentExpr(exprs, ops)

    def _parse_constant_expression(self, inTemplate):
        # -> conditional-expression
        orExpr = self._parse_logical_or_expression(inTemplate=inTemplate)
        # TODO: use _parse_conditional_expression_tail
        return orExpr

    def _parse_expression(self, inTemplate):
        # -> assignment-expression
        #  | expression "," assignment-expresion
        # TODO: actually parse the second production
        return self._parse_assignment_expression(inTemplate=inTemplate)

    def _parse_expression_fallback(self, end, parser, allow=True):
        # Stupidly "parse" an expression.
        # 'end' should be a list of characters which ends the expression.

        # first try to use the provided parser
        prevPos = self.pos
        try:
            return parser()
        except DefinitionError as e:
            # some places (e.g., template parameters) we really don't want to use fallback,
            # and for testing we may want to globally disable it
            if not allow or not self.allowFallbackExpressionParsing:
                raise
            self.warn("Parsing of expression failed. Using fallback parser."
                      " Error was:\n%s" % e.description)
            self.pos = prevPos
        # and then the fallback scanning
        assert end is not None
        self.skip_ws()
        startPos = self.pos
        if self.match(_string_re):
            value = self.matched_text
        else:
            # TODO: add handling of more bracket-like things, and quote handling
            brackets = {'(': ')', '[': ']', '<': '>'}  # type: Dict[unicode, unicode]
            symbols = []  # type: List[unicode]
            while not self.eof:
                if (len(symbols) == 0 and self.current_char in end):
                    break
                if self.current_char in brackets.keys():
                    symbols.append(brackets[self.current_char])
                elif len(symbols) > 0 and self.current_char == symbols[-1]:
                    symbols.pop()
                self.pos += 1
            if len(end) > 0 and self.eof:
                self.fail("Could not find end of expression starting at %d."
                          % startPos)
            value = self.definition[startPos:self.pos].strip()
        return ASTFallbackExpr(value.strip())

    def _parse_operator(self):
        # type: () -> Any
        self.skip_ws()
        # adapted from the old code
        # thank god, a regular operator definition
        if self.match(_operator_re):
            return ASTOperatorBuildIn(self.matched_text)

        # new/delete operator?
        for op in 'new', 'delete':
            if not self.skip_word(op):
                continue
            self.skip_ws()
            if self.skip_string('['):
                self.skip_ws()
                if not self.skip_string(']'):
                    self.fail('Expected "]" after  "operator ' + op + '["')
                op += '[]'
            return ASTOperatorBuildIn(op)

        # user-defined literal?
        if self.skip_string('""'):
            self.skip_ws()
            if not self.match(_identifier_re):
                self.fail("Expected user-defined literal suffix.")
            identifier = ASTIdentifier(self.matched_text)
            return ASTOperatorLiteral(identifier)

        # oh well, looks like a cast operator definition.
        # In that case, eat another type.
        type = self._parse_type(named=False, outer="operatorCast")
        return ASTOperatorType(type)

    def _parse_template_argument_list(self):
        # type: () -> ASTTemplateArgs
        self.skip_ws()
        if not self.skip_string_and_ws('<'):
            return None
        if self.skip_string('>'):
            return ASTTemplateArgs([])
        prevErrors = []
        templateArgs = []  # type: List
        while 1:
            pos = self.pos
            parsedComma = False
            parsedEnd = False
            try:
                type = self._parse_type(named=False)
                self.skip_ws()
                if self.skip_string('>'):
                    parsedEnd = True
                elif self.skip_string(','):
                    parsedComma = True
                else:
                    self.fail('Expected ">" or "," in template argument list.')
                templateArgs.append(type)
            except DefinitionError as e:
                prevErrors.append((e, "If type argument"))
                self.pos = pos
                try:
                    def parser():
                        return self._parse_constant_expression(inTemplate=True)
                    value = self._parse_expression_fallback([',', '>'], parser)
                    self.skip_ws()
                    if self.skip_string('>'):
                        parsedEnd = True
                    elif self.skip_string(','):
                        parsedComma = True
                    else:
                        self.fail('Expected ">" or "," in template argument list.')
                    templateArgs.append(ASTTemplateArgConstant(value))
                except DefinitionError as e:
                    self.pos = pos
                    prevErrors.append((e, "If non-type argument"))
                    header = "Error in parsing template argument list."
                    raise self._make_multi_error(prevErrors, header)
            if parsedEnd:
                assert not parsedComma
                break
        return ASTTemplateArgs(templateArgs)

    def _parse_nested_name(self, memberPointer=False):
        # type: (bool) -> ASTNestedName
        names = []  # type: List[Any]
        templates = []  # type: List[bool]

        self.skip_ws()
        rooted = False
        if self.skip_string('::'):
            rooted = True
        while 1:
            self.skip_ws()
            if len(names) > 0:
                template = self.skip_word_and_ws('template')
            else:
                template = False
            templates.append(template)
            if self.skip_word_and_ws('operator'):
                identOrOp = self._parse_operator()
            else:
                if not self.match(_identifier_re):
                    if memberPointer and len(names) > 0:
                        templates.pop()
                        break
                    self.fail("Expected identifier in nested name.")
                identifier = self.matched_text
                # make sure there isn't a keyword
                if identifier in _keywords:
                    self.fail("Expected identifier in nested name, "
                              "got keyword: %s" % identifier)
                identOrOp = ASTIdentifier(identifier)
            # try greedily to get template arguments,
            # but otherwise a < might be because we are in an expression
            pos = self.pos
            try:
                templateArgs = self._parse_template_argument_list()
            except DefinitionError as ex:
                self.pos = pos
                templateArgs = None
                self.otherErrors.append(ex)
            names.append(ASTNestedNameElement(identOrOp, templateArgs))

            self.skip_ws()
            if not self.skip_string('::'):
                if memberPointer:
                    self.fail("Expected '::' in pointer to member (function).")
                break
        return ASTNestedName(names, templates, rooted)

    def _parse_trailing_type_spec(self):
        # type: () -> Any
        # fundemental types
        self.skip_ws()
        for t in self._simple_fundemental_types:
            if self.skip_word(t):
                return ASTTrailingTypeSpecFundamental(t)

        # TODO: this could/should be more strict
        elements = []
        if self.skip_word_and_ws('signed'):
            elements.append('signed')
        elif self.skip_word_and_ws('unsigned'):
            elements.append('unsigned')
        while 1:
            if self.skip_word_and_ws('short'):
                elements.append('short')
            elif self.skip_word_and_ws('long'):
                elements.append('long')
            else:
                break
        if self.skip_word_and_ws('char'):
            elements.append('char')
        elif self.skip_word_and_ws('int'):
            elements.append('int')
        elif self.skip_word_and_ws('double'):
            elements.append('double')
        if len(elements) > 0:
            return ASTTrailingTypeSpecFundamental(u' '.join(elements))

        # decltype
        self.skip_ws()
        if self.skip_word_and_ws('decltype'):
            if not self.skip_string_and_ws('('):
                self.fail("Expected '(' after 'decltype'.")
            if self.skip_word_and_ws('auto'):
                if not self.skip_string(')'):
                    self.fail("Expected ')' after 'decltype(auto'.")
                return ASTTrailingTypeSpecDecltypeAuto()
            expr = self._parse_expression(inTemplate=False)
            self.skip_ws()
            if not self.skip_string(')'):
                self.fail("Expected ')' after 'decltype(<expr>'.")
            return ASTTrailingTypeSpecDecltype(expr)

        # prefixed
        prefix = None
        self.skip_ws()
        for k in self._prefix_keys:
            if self.skip_word_and_ws(k):
                prefix = k
                break

        nestedName = self._parse_nested_name()
        return ASTTrailingTypeSpecName(prefix, nestedName)

    def _parse_parameters_and_qualifiers(self, paramMode):
        # type: (unicode) -> ASTParametersQualifiers
        self.skip_ws()
        if not self.skip_string('('):
            if paramMode == 'function':
                self.fail('Expecting "(" in parameters_and_qualifiers.')
            else:
                return None
        args = []
        self.skip_ws()
        if not self.skip_string(')'):
            while 1:
                self.skip_ws()
                if self.skip_string('...'):
                    args.append(ASTFunctionParameter(None, True))
                    self.skip_ws()
                    if not self.skip_string(')'):
                        self.fail('Expected ")" after "..." in '
                                  'parameters_and_qualifiers.')
                    break
                # note: it seems that function arguments can always be named,
                # even in function pointers and similar.
                arg = self._parse_type_with_init(outer=None, named='single')
                # TODO: parse default parameters # TODO: didn't we just do that?
                args.append(ASTFunctionParameter(arg))

                self.skip_ws()
                if self.skip_string(','):
                    continue
                elif self.skip_string(')'):
                    break
                else:
                    self.fail(
                        'Expecting "," or ")" in parameters_and_qualifiers, '
                        'got "%s".' % self.current_char)

        # TODO: why did we have this bail-out?
        # does it hurt to parse the extra stuff?
        # it's needed for pointer to member functions
        if paramMode != 'function' and False:
            return ASTParametersQualifiers(
                args, None, None, None, None, None, None, None)

        self.skip_ws()
        const = self.skip_word_and_ws('const')
        volatile = self.skip_word_and_ws('volatile')
        if not const:  # the can be permuted
            const = self.skip_word_and_ws('const')

        refQual = None
        if self.skip_string('&&'):
            refQual = '&&'
        if not refQual and self.skip_string('&'):
            refQual = '&'

        exceptionSpec = None
        override = None
        final = None
        initializer = None
        self.skip_ws()
        if self.skip_string('noexcept'):
            exceptionSpec = 'noexcept'
            self.skip_ws()
            if self.skip_string('('):
                self.fail('Parameterised "noexcept" not implemented.')

        self.skip_ws()
        override = self.skip_word_and_ws('override')
        final = self.skip_word_and_ws('final')
        if not override:
            override = self.skip_word_and_ws(
                'override')  # they can be permuted

        self.skip_ws()
        if self.skip_string('='):
            self.skip_ws()
            valid = ('0', 'delete', 'default')
            for w in valid:
                if self.skip_word_and_ws(w):
                    initializer = w
                    break
            if not initializer:
                self.fail(
                    'Expected "%s" in initializer-specifier.'
                    % u'" or "'.join(valid))

        return ASTParametersQualifiers(
            args, volatile, const, refQual, exceptionSpec, override, final,
            initializer)

    def _parse_decl_specs_simple(self, outer, typed):
        # type: (unicode, bool) -> ASTDeclSpecsSimple
        """Just parse the simple ones."""
        storage = None
        threadLocal = None
        inline = None
        virtual = None
        explicit = None
        constexpr = None
        volatile = None
        const = None
        friend = None
        attrs = []
        while 1:  # accept any permutation of a subset of some decl-specs
            self.skip_ws()
            if not storage:
                if outer in ('member', 'function'):
                    if self.skip_word('static'):
                        storage = 'static'
                        continue
                    if self.skip_word('extern'):
                        storage = 'extern'
                        continue
                if outer == 'member':
                    if self.skip_word('mutable'):
                        storage = 'mutable'
                        continue
                if self.skip_word('register'):
                    storage = 'register'
                    continue
            if not threadLocal and outer == 'member':
                threadLocal = self.skip_word('thread_local')
                if threadLocal:
                    continue

            if outer == 'function':
                # function-specifiers
                if not inline:
                    inline = self.skip_word('inline')
                    if inline:
                        continue
                if not friend:
                    friend = self.skip_word('friend')
                    if friend:
                        continue
                if not virtual:
                    virtual = self.skip_word('virtual')
                    if virtual:
                        continue
                if not explicit:
                    explicit = self.skip_word('explicit')
                    if explicit:
                        continue

            if not constexpr and outer in ('member', 'function'):
                constexpr = self.skip_word("constexpr")
                if constexpr:
                    continue
            if not volatile and typed:
                volatile = self.skip_word('volatile')
                if volatile:
                    continue
            if not const and typed:
                const = self.skip_word('const')
                if const:
                    continue
            attr = self._parse_attribute()
            if attr:
                attrs.append(attr)
                continue
            break
        return ASTDeclSpecsSimple(storage, threadLocal, inline, virtual,
                                  explicit, constexpr, volatile, const,
                                  friend, attrs)

    def _parse_decl_specs(self, outer, typed=True):
        # type: (unicode, bool) -> ASTDeclSpecs
        if outer:
            if outer not in ('type', 'member', 'function', 'templateParam'):
                raise Exception('Internal error, unknown outer "%s".' % outer)
        """
        storage-class-specifier function-specifier "constexpr"
        "volatile" "const" trailing-type-specifier

        storage-class-specifier ->
              "static" (only for member_object and function_object)
            | "register"

        function-specifier -> "inline" | "virtual" | "explicit" (only for
        function_object)

        "constexpr" (only for member_object and function_object)
        """
        leftSpecs = self._parse_decl_specs_simple(outer, typed)
        rightSpecs = None

        if typed:
            trailing = self._parse_trailing_type_spec()
            rightSpecs = self._parse_decl_specs_simple(outer, typed)
        else:
            trailing = None
        return ASTDeclSpecs(outer, leftSpecs, rightSpecs, trailing)

    def _parse_declarator_name_param_qual(self, named, paramMode, typed):
        # type: (Union[bool, unicode], unicode, bool) -> ASTDeclaratorNameParamQual
        # now we should parse the name, and then suffixes
        if named == 'maybe':
            pos = self.pos
            try:
                declId = self._parse_nested_name()
            except DefinitionError:
                self.pos = pos
                declId = None
        elif named == 'single':
            if self.match(_identifier_re):
                identifier = ASTIdentifier(self.matched_text)
                nne = ASTNestedNameElement(identifier, None)
                declId = ASTNestedName([nne], [False], rooted=False)
                # if it's a member pointer, we may have '::', which should be an error
                self.skip_ws()
                if self.current_char == ':':
                    self.fail("Unexpected ':' after identifier.")
            else:
                declId = None
        elif named:
            declId = self._parse_nested_name()
        else:
            declId = None
        arrayOps = []
        while 1:
            self.skip_ws()
            if typed and self.skip_string('['):
                self.skip_ws()
                if self.skip_string(']'):
                    arrayOps.append(ASTArray(None))
                    continue

                def parser():
                    return self._parse_expression(inTemplate=False)
                value = self._parse_expression_fallback([']'], parser)
                if not self.skip_string(']'):
                    self.fail("Expected ']' in end of array operator.")
                arrayOps.append(ASTArray(value))
                continue
            else:
                break
        paramQual = self._parse_parameters_and_qualifiers(paramMode)
        return ASTDeclaratorNameParamQual(declId=declId, arrayOps=arrayOps,
                                          paramQual=paramQual)

    def _parse_declarator(self, named, paramMode, typed=True):
        # type: (Union[bool, unicode], unicode, bool) -> Any
        # 'typed' here means 'parse return type stuff'
        if paramMode not in ('type', 'function', 'operatorCast'):
            raise Exception(
                "Internal error, unknown paramMode '%s'." % paramMode)
        prevErrors = []
        self.skip_ws()
        if typed and self.skip_string('*'):
            self.skip_ws()
            volatile = False
            const = False
            while 1:
                if not volatile:
                    volatile = self.skip_word_and_ws('volatile')
                    if volatile:
                        continue
                if not const:
                    const = self.skip_word_and_ws('const')
                    if const:
                        continue
                break
            next = self._parse_declarator(named, paramMode, typed)
            return ASTDeclaratorPtr(next=next, volatile=volatile, const=const)
        # TODO: shouldn't we parse an R-value ref here first?
        if typed and self.skip_string("&"):
            next = self._parse_declarator(named, paramMode, typed)
            return ASTDeclaratorRef(next=next)
        if typed and self.skip_string("..."):
            next = self._parse_declarator(named, paramMode, False)
            return ASTDeclaratorParamPack(next=next)
        if typed:  # pointer to member
            pos = self.pos
            try:
                name = self._parse_nested_name(memberPointer=True)
                self.skip_ws()
                if not self.skip_string('*'):
                    self.fail("Expected '*' in pointer to member declarator.")
                self.skip_ws()
            except DefinitionError as e:
                self.pos = pos
                prevErrors.append((e, "If pointer to member declarator"))
            else:
                volatile = False
                const = False
                while 1:
                    if not volatile:
                        volatile = self.skip_word_and_ws('volatile')
                        if volatile:
                            continue
                    if not const:
                        const = self.skip_word_and_ws('const')
                        if const:
                            continue
                    break
                next = self._parse_declarator(named, paramMode, typed)
                return ASTDeclaratorMemPtr(name, const, volatile, next=next)
        if typed and self.current_char == '(':  # note: peeking, not skipping
            if paramMode == "operatorCast":
                # TODO: we should be able to parse cast operators which return
                # function pointers. For now, just hax it and ignore.
                return ASTDeclaratorNameParamQual(declId=None, arrayOps=[],
                                                  paramQual=None)
            # maybe this is the beginning of params and quals,try that first,
            # otherwise assume it's noptr->declarator > ( ptr-declarator )
            pos = self.pos
            try:
                # assume this is params and quals
                res = self._parse_declarator_name_param_qual(named, paramMode,
                                                             typed)
                return res
            except DefinitionError as exParamQual:
                prevErrors.append((exParamQual, "If declId, parameters, and qualifiers"))
                self.pos = pos
                try:
                    assert self.current_char == '('
                    self.skip_string('(')
                    # TODO: hmm, if there is a name, it must be in inner, right?
                    # TODO: hmm, if there must be parameters, they must b
                    # inside, right?
                    inner = self._parse_declarator(named, paramMode, typed)
                    if not self.skip_string(')'):
                        self.fail("Expected ')' in \"( ptr-declarator )\"")
                    next = self._parse_declarator(named=False,
                                                  paramMode="type",
                                                  typed=typed)
                    return ASTDeclaratorParen(inner=inner, next=next)
                except DefinitionError as exNoPtrParen:
                    self.pos = pos
                    prevErrors.append((exNoPtrParen, "If parenthesis in noptr-declarator"))
                    header = "Error in declarator"
                    raise self._make_multi_error(prevErrors, header)
        pos = self.pos
        try:
            return self._parse_declarator_name_param_qual(named, paramMode, typed)
        except DefinitionError as e:
            self.pos = pos
            prevErrors.append((e, "If declarator-id"))
            header = "Error in declarator or parameters and qualifiers"
            raise self._make_multi_error(prevErrors, header)

    def _parse_initializer(self, outer=None, allowFallback=True):
        # type: (unicode, bool) -> ASTInitializer
        self.skip_ws()
        # TODO: support paren and brace initialization for memberObject
        if not self.skip_string('='):
            return None
        else:
            if outer == 'member':
                def parser():
                    return self._parse_assignment_expression(inTemplate=False)
                value = self._parse_expression_fallback([], parser,
                                                        allow=allowFallback)
            elif outer == 'templateParam':
                def parser():
                    return self._parse_assignment_expression(inTemplate=True)
                value = self._parse_expression_fallback([',', '>'], parser,
                                                        allow=allowFallback)
            elif outer is None:  # function parameter
                def parser():
                    return self._parse_assignment_expression(inTemplate=False)
                value = self._parse_expression_fallback([',', ')'], parser,
                                                        allow=allowFallback)
            else:
                self.fail("Internal error, initializer for outer '%s' not "
                          "implemented." % outer)
            return ASTInitializer(value)

    def _parse_type(self, named, outer=None):
        # type: (Union[bool, unicode], unicode) -> ASTType
        """
        named=False|'maybe'|True: 'maybe' is e.g., for function objects which
        doesn't need to name the arguments

        outer == operatorCast: annoying case, we should not take the params
        """
        if outer:  # always named
            if outer not in ('type', 'member', 'function',
                             'operatorCast', 'templateParam'):
                raise Exception('Internal error, unknown outer "%s".' % outer)
            if outer != 'operatorCast':
                assert named

        if outer in ('type', 'function'):
            # We allow type objects to just be a name.
            # Some functions don't have normal return types: constructors,
            # destrutors, cast operators
            prevErrors = []
            startPos = self.pos
            # first try without the type
            try:
                declSpecs = self._parse_decl_specs(outer=outer, typed=False)
                decl = self._parse_declarator(named=True, paramMode=outer,
                                              typed=False)
                self.assert_end()
            except DefinitionError as exUntyped:
                if outer == 'type':
                    desc = "If just a name"
                elif outer == 'function':
                    desc = "If the function has no return type"
                else:
                    assert False
                prevErrors.append((exUntyped, desc))
                self.pos = startPos
                try:
                    declSpecs = self._parse_decl_specs(outer=outer)
                    decl = self._parse_declarator(named=True, paramMode=outer)
                except DefinitionError as exTyped:
                    self.pos = startPos
                    if outer == 'type':
                        desc = "If typedef-like declaration"
                    elif outer == 'function':
                        desc = "If the function has a return type"
                    else:
                        assert False
                    prevErrors.append((exTyped, desc))
                    # Retain the else branch for easier debugging.
                    # TODO: it would be nice to save the previous stacktrace
                    #       and output it here.
                    if True:
                        if outer == 'type':
                            header = "Type must be either just a name or a "
                            header += "typedef-like declaration."
                        elif outer == 'function':
                            header = "Error when parsing function declaration."
                        else:
                            assert False
                        raise self._make_multi_error(prevErrors, header)
                    else:
                        # For testing purposes.
                        # do it again to get the proper traceback (how do you
                        # reliably save a traceback when an exception is
                        # constructed?)
                        self.pos = startPos
                        typed = True
                        declSpecs = self._parse_decl_specs(outer=outer, typed=typed)
                        decl = self._parse_declarator(named=True, paramMode=outer,
                                                      typed=typed)
        else:
            paramMode = 'type'
            if outer == 'member':  # i.e., member
                named = True
            elif outer == 'operatorCast':
                paramMode = 'operatorCast'
                outer = None
            elif outer == 'templateParam':
                named = 'single'
            declSpecs = self._parse_decl_specs(outer=outer)
            decl = self._parse_declarator(named=named, paramMode=paramMode)
        return ASTType(declSpecs, decl)

    def _parse_type_with_init(self, named, outer):
        # type: (Union[bool, unicode], unicode) -> Any
        if outer:
            assert outer in ('type', 'member', 'function', 'templateParam')
        type = self._parse_type(outer=outer, named=named)
        if outer != 'templateParam':
            init = self._parse_initializer(outer=outer)
            return ASTTypeWithInit(type, init)
        # it could also be a constrained type parameter, e.g., C T = int&
        pos = self.pos
        eExpr = None
        try:
            init = self._parse_initializer(outer=outer, allowFallback=False)
            # note: init may be None if there is no =
            if init is None:
                return ASTTypeWithInit(type, None)
            # we parsed an expression, so we must have a , or a >,
            # otherwise the expression didn't get everything
            self.skip_ws()
            if self.current_char != ',' and self.current_char != '>':
                # pretend it didn't happen
                self.pos = pos
                init = None
            else:
                # we assume that it was indeed an expression
                return ASTTypeWithInit(type, init)
        except DefinitionError as e:
            self.pos = pos
            eExpr = e
        if not self.skip_string("="):
            return ASTTypeWithInit(type, None)
        try:
            typeInit = self._parse_type(named=False, outer=None)
            return ASTTemplateParamConstrainedTypeWithInit(type, typeInit)
        except DefinitionError as eType:
            if eExpr is None:
                raise eType
            errs = []
            errs.append((eExpr, "If default is an expression"))
            errs.append((eType, "If default is a type"))
            msg = "Error in non-type template parameter"
            msg += " or constrianted template paramter."
            raise self._make_multi_error(errs, msg)

    def _parse_type_using(self):
        # type: () -> ASTTypeUsing
        name = self._parse_nested_name()
        self.skip_ws()
        if not self.skip_string('='):
            return ASTTypeUsing(name, None)
        type = self._parse_type(False, None)
        return ASTTypeUsing(name, type)

    def _parse_concept(self):
        # type: () -> ASTConcept
        nestedName = self._parse_nested_name()
        self.skip_ws()
        initializer = self._parse_initializer('member')
        return ASTConcept(nestedName, initializer)

    def _parse_class(self):
        # type: () -> ASTClass
        name = self._parse_nested_name()
        self.skip_ws()
        final = self.skip_word_and_ws('final')
        bases = []
        self.skip_ws()
        if self.skip_string(':'):
            while 1:
                self.skip_ws()
                visibility = 'private'  # type: unicode
                virtual = False
                pack = False
                if self.skip_word_and_ws('virtual'):
                    virtual = True
                if self.match(_visibility_re):
                    visibility = self.matched_text
                    self.skip_ws()
                if not virtual and self.skip_word_and_ws('virtual'):
                    virtual = True
                baseName = self._parse_nested_name()
                self.skip_ws()
                pack = self.skip_string('...')
                bases.append(ASTBaseClass(baseName, visibility, virtual, pack))
                self.skip_ws()
                if self.skip_string(','):
                    continue
                else:
                    break
        return ASTClass(name, final, bases)

    def _parse_enum(self):
        # type: () -> ASTEnum
        scoped = None  # type: unicode #  is set by CPPEnumObject
        self.skip_ws()
        name = self._parse_nested_name()
        self.skip_ws()
        underlyingType = None
        if self.skip_string(':'):
            underlyingType = self._parse_type(named=False)
        return ASTEnum(name, scoped, underlyingType)

    def _parse_enumerator(self):
        # type: () -> ASTEnumerator
        name = self._parse_nested_name()
        self.skip_ws()
        init = None
        if self.skip_string('='):
            self.skip_ws()

            def parser():
                return self._parse_constant_expression(inTemplate=False)
            initVal = self._parse_expression_fallback([], parser)
            init = ASTInitializer(initVal)
        return ASTEnumerator(name, init)

    def _parse_template_parameter_list(self):
        # type: () -> ASTTemplateParams
        # only: '<' parameter-list '>'
        # we assume that 'template' has just been parsed
        templateParams = []  # type: List
        self.skip_ws()
        if not self.skip_string("<"):
            self.fail("Expected '<' after 'template'")
        while 1:
            prevErrors = []
            self.skip_ws()
            if self.skip_word('template'):
                # declare a tenplate template parameter
                nestedParams = self._parse_template_parameter_list()
                nestedParams.isNested = True
            else:
                nestedParams = None
            self.skip_ws()
            key = None
            if self.skip_word_and_ws('typename'):
                key = 'typename'
            elif self.skip_word_and_ws('class'):
                key = 'class'
            elif nestedParams:
                self.fail("Expected 'typename' or 'class' after "
                          "template template parameter list.")
            if key:
                # declare a type or template type parameter
                self.skip_ws()
                parameterPack = self.skip_string('...')
                self.skip_ws()
                if self.match(_identifier_re):
                    identifier = ASTIdentifier(self.matched_text)
                else:
                    identifier = None
                self.skip_ws()
                if not parameterPack and self.skip_string('='):
                    default = self._parse_type(named=False, outer=None)
                else:
                    default = None
                data = ASTTemplateKeyParamPackIdDefault(key, identifier,
                                                        parameterPack, default)
                if nestedParams:
                    # template type
                    param = ASTTemplateParamTemplateType(nestedParams, data)  # type: Any
                else:
                    # type
                    param = ASTTemplateParamType(data)
                templateParams.append(param)
            else:
                # declare a non-type parameter, or constrained type parameter
                pos = self.pos
                try:
                    param = self._parse_type_with_init('maybe', 'templateParam')
                    templateParams.append(ASTTemplateParamNonType(param))
                except DefinitionError as e:
                    msg = "If non-type template parameter or constrained template parameter"
                    prevErrors.append((e, msg))
                    self.pos = pos
            self.skip_ws()
            if self.skip_string('>'):
                return ASTTemplateParams(templateParams)
            elif self.skip_string(','):
                continue
            else:
                header = "Error in template parameter list."
                try:
                    self.fail('Expected "=", ",", or ">".')
                except DefinitionError as e:
                    prevErrors.append((e, ""))
                raise self._make_multi_error(prevErrors, header)

    def _parse_template_introduction(self):
        # type: () -> ASTTemplateIntroduction
        pos = self.pos
        try:
            concept = self._parse_nested_name()
        except Exception:
            self.pos = pos
            return None
        self.skip_ws()
        if not self.skip_string('{'):
            self.pos = pos
            return None

        # for sure it must be a template introduction now
        params = []
        while 1:
            self.skip_ws()
            parameterPack = self.skip_string('...')
            self.skip_ws()
            if not self.match(_identifier_re):
                self.fail("Expected identifier in template introduction list.")
            identifier = self.matched_text
            # make sure there isn't a keyword
            if identifier in _keywords:
                self.fail("Expected identifier in template introduction list, "
                          "got keyword: %s" % identifier)
            identifier = ASTIdentifier(identifier)  # type: ignore
            params.append(ASTTemplateIntroductionParameter(identifier, parameterPack))

            self.skip_ws()
            if self.skip_string('}'):
                break
            elif self.skip_string(','):
                continue
            else:
                self.fail("Error in template introduction list. "
                          'Expected ",", or "}".')
        return ASTTemplateIntroduction(concept, params)

    def _parse_template_declaration_prefix(self, objectType):
        # type: (unicode) -> ASTTemplateDeclarationPrefix
        templates = []  # type: List
        while 1:
            self.skip_ws()
            # the saved position is only used to provide a better error message
            pos = self.pos
            if self.skip_word("template"):
                try:
                    params = self._parse_template_parameter_list()  # type: Any
                except DefinitionError as e:
                    if objectType == 'member' and len(templates) == 0:
                        return ASTTemplateDeclarationPrefix(None)
                    else:
                        raise e
            else:
                params = self._parse_template_introduction()
                if not params:
                    break
            if objectType == 'concept' and len(templates) > 0:
                self.pos = pos
                self.fail("More than 1 template parameter list for concept.")
            templates.append(params)
        if len(templates) == 0 and objectType == 'concept':
            self.fail('Missing template parameter list for concept.')
        if len(templates) == 0:
            return None
        else:
            return ASTTemplateDeclarationPrefix(templates)

    def _check_template_consistency(self, nestedName, templatePrefix,
                                    fullSpecShorthand, isMember=False):
        # type: (Any, Any, Any, bool) -> ASTTemplateDeclarationPrefix
        numArgs = nestedName.num_templates()
        isMemberInstantiation = False
        if not templatePrefix:
            numParams = 0
        else:
            if isMember and templatePrefix.templates is None:
                numParams = 0
                isMemberInstantiation = True
            else:
                numParams = len(templatePrefix.templates)
        if numArgs + 1 < numParams:
            self.fail("Too few template argument lists comapred to parameter"
                      " lists. Argument lists: %d, Parameter lists: %d."
                      % (numArgs, numParams))
        if numArgs > numParams:
            numExtra = numArgs - numParams
            if not fullSpecShorthand and not isMemberInstantiation:
                msg = "Too many template argument lists compared to parameter" \
                    " lists. Argument lists: %d, Parameter lists: %d," \
                    " Extra empty parameters lists prepended: %d." \
                    % (numArgs, numParams, numExtra)  # type: unicode
                msg += " Declaration:\n\t"
                if templatePrefix:
                    msg += "%s\n\t" % text_type(templatePrefix)
                msg += text_type(nestedName)
                self.warn(msg)

            newTemplates = []
            for i in range(numExtra):
                newTemplates.append(ASTTemplateParams([]))
            if templatePrefix and not isMemberInstantiation:
                newTemplates.extend(templatePrefix.templates)
            templatePrefix = ASTTemplateDeclarationPrefix(newTemplates)
        return templatePrefix

    def parse_declaration(self, objectType):
        # type: (unicode) -> ASTDeclaration
        if objectType not in ('type', 'concept', 'member',
                              'function', 'class', 'enum', 'enumerator'):
            raise Exception('Internal error, unknown objectType "%s".' % objectType)
        visibility = None
        templatePrefix = None
        declaration = None  # type: Any

        self.skip_ws()
        if self.match(_visibility_re):
            visibility = self.matched_text

        if objectType in ('type', 'concept', 'member', 'function', 'class'):
            templatePrefix = self._parse_template_declaration_prefix(objectType)

        if objectType == 'type':
            prevErrors = []
            pos = self.pos
            try:
                if not templatePrefix:
                    declaration = self._parse_type(named=True, outer='type')
            except DefinitionError as e:
                prevErrors.append((e, "If typedef-like declaration"))
                self.pos = pos
            pos = self.pos
            try:
                if not declaration:
                    declaration = self._parse_type_using()
            except DefinitionError as e:
                self.pos = pos
                prevErrors.append((e, "If type alias or template alias"))
                header = "Error in type declaration."
                raise self._make_multi_error(prevErrors, header)
        elif objectType == 'concept':
            declaration = self._parse_concept()
        elif objectType == 'member':
            declaration = self._parse_type_with_init(named=True, outer='member')
        elif objectType == 'function':
            declaration = self._parse_type(named=True, outer='function')
        elif objectType == 'class':
            declaration = self._parse_class()
        elif objectType == 'enum':
            declaration = self._parse_enum()
        elif objectType == 'enumerator':
            declaration = self._parse_enumerator()
        else:
            assert False
        templatePrefix = self._check_template_consistency(declaration.name,
                                                          templatePrefix,
                                                          fullSpecShorthand=False,
                                                          isMember=objectType == 'member')
        return ASTDeclaration(objectType, visibility,
                              templatePrefix, declaration)

    def parse_namespace_object(self):
        # type: () -> ASTNamespace
        templatePrefix = self._parse_template_declaration_prefix(objectType="namespace")
        name = self._parse_nested_name()
        templatePrefix = self._check_template_consistency(name, templatePrefix,
                                                          fullSpecShorthand=False)
        res = ASTNamespace(name, templatePrefix)
        res.objectType = 'namespace'  # type: ignore
        return res

    def parse_xref_object(self):
        # type: () -> ASTNamespace
        templatePrefix = self._parse_template_declaration_prefix(objectType="xref")
        name = self._parse_nested_name()
        # if there are '()' left, just skip them
        self.skip_ws()
        self.skip_string('()')
        templatePrefix = self._check_template_consistency(name, templatePrefix,
                                                          fullSpecShorthand=True)
        res = ASTNamespace(name, templatePrefix)
        res.objectType = 'xref'  # type: ignore
        return res

    def parse_expression(self):
        pos = self.pos
        try:
            expr = self._parse_expression(False)
            self.skip_ws()
            self.assert_end()
        except DefinitionError as exExpr:
            self.pos = pos
            try:
                expr = self._parse_type(False)
                self.skip_ws()
                self.assert_end()
            except DefinitionError as exType:
                header = "Error when parsing (type) expression."
                errs = []
                errs.append((exExpr, "If expression:"))
                errs.append((exType, "If type:"))
                raise self._make_multi_error(errs, header)
        return expr


def _make_phony_error_name():
    # type: () -> ASTNestedName
    nne = ASTNestedNameElement(ASTIdentifier("PhonyNameDueToError"), None)
    return ASTNestedName([nne], [False], rooted=False)


class CPPObject(ObjectDescription):
    """Description of a C++ language object."""

    doc_field_types = [
        GroupedField('parameter', label=l_('Parameters'),
                     names=('param', 'parameter', 'arg', 'argument'),
                     can_collapse=True),
        GroupedField('template parameter', label=l_('Template Parameters'),
                     names=('tparam', 'template parameter'),
                     can_collapse=True),
        GroupedField('exceptions', label=l_('Throws'), rolename='cpp:class',
                     names=('throws', 'throw', 'exception'),
                     can_collapse=True),
        Field('returnvalue', label=l_('Returns'), has_arg=False,
              names=('returns', 'return')),
    ]

    option_spec = dict(ObjectDescription.option_spec)
    option_spec['tparam-line-spec'] = directives.flag

    def warn(self, msg):
        # type: (unicode) -> None
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def _add_enumerator_to_parent(self, ast):
        # type: (Any) -> None
        assert ast.objectType == 'enumerator'
        # find the parent, if it exists && is an enum
        #                     && it's unscoped,
        #                  then add the name to the parent scope
        symbol = ast.symbol
        assert symbol
        assert symbol.identOrOp is not None
        assert symbol.templateParams is None
        assert symbol.templateArgs is None
        parentSymbol = symbol.parent
        assert parentSymbol
        if parentSymbol.parent is None:
            # TODO: we could warn, but it is somewhat equivalent to unscoped
            # enums, without the enum
            return  # no parent
        parentDecl = parentSymbol.declaration
        if parentDecl is None:
            # the parent is not explicitly declared
            # TODO: we could warn, but it could be a style to just assume
            # enumerator parents to be scoped
            return
        if parentDecl.objectType != 'enum':
            # TODO: maybe issue a warning, enumerators in non-enums is weird,
            # but it is somewhat equivalent to unscoped enums, without the enum
            return
        if parentDecl.scoped:
            return

        targetSymbol = parentSymbol.parent
        s = targetSymbol.find_identifier(symbol.identOrOp, matchSelf=False)
        if s is not None:
            # something is already declared with that name
            return
        declClone = symbol.declaration.clone()
        declClone.enumeratorScopedSymbol = symbol
        Symbol(parent=targetSymbol, identOrOp=symbol.identOrOp,
               templateParams=None, templateArgs=None,
               declaration=declClone,
               docname=self.env.docname)

    def add_target_and_index(self, ast, sig, signode):
        # type: (Any, unicode, addnodes.desc_signature) -> None
        # general note: name must be lstrip(':')'ed, to remove "::"
        ids = []
        for i in range(1, _max_id + 1):
            try:
                id = ast.get_id(version=i)
                ids.append(id)
            except NoOldIdError:
                assert i < _max_id
        # let's keep the newest first
        ids = list(reversed(ids))
        newestId = ids[0]
        assert newestId  # shouldn't be None
        if not re.compile(r'^[a-zA-Z0-9_]*$').match(newestId):
            self.warn('Index id generation for C++ object "%s" failed, please '
                      'report as bug (id=%s).' % (text_type(ast), newestId))

        name = text_type(ast.symbol.get_full_nested_name()).lstrip(':')
        # Add index entry, but not if it's a declaration inside a concept
        isInConcept = False
        s = ast.symbol.parent
        while s is not None:
            decl = s.declaration
            s = s.parent
            if decl is None:
                continue
            if decl.objectType == 'concept':
                isInConcept = True
                break
        if not isInConcept:
            strippedName = name
            for prefix in self.env.config.cpp_index_common_prefix:
                if name.startswith(prefix):
                    strippedName = strippedName[len(prefix):]
                    break
            indexText = self.get_index_text(strippedName)
            self.indexnode['entries'].append(('single', indexText, newestId, '', None))

        if newestId not in self.state.document.ids:
            # if the name is not unique, the first one will win
            names = self.env.domaindata['cpp']['names']
            if name not in names:
                names[name] = ast.symbol.docname
                signode['names'].append(name)
            else:
                # print("[CPP] non-unique name:", name)
                pass
            # always add the newest id
            assert newestId
            signode['ids'].append(newestId)
            # only add compatibility ids when there are no conflicts
            for id in ids[1:]:
                if not id:  # is None when the element didn't exist in that version
                    continue
                if id not in self.state.document.ids:
                    signode['ids'].append(id)
            signode['first'] = (not self.names)  # hmm, what is this about?
            self.state.document.note_explicit_target(signode)

    def parse_definition(self, parser):
        # type: (Any) -> Any
        raise NotImplementedError()

    def describe_signature(self, signode, ast, options):
        # type: (addnodes.desc_signature, Any, Dict) -> None
        ast.describe_signature(signode, 'lastIsName', self.env, options)

    def handle_signature(self, sig, signode):
        # type: (unicode, addnodes.desc_signature) -> Any
        if 'cpp:parent_symbol' not in self.env.temp_data:
            root = self.env.domaindata['cpp']['root_symbol']
            self.env.temp_data['cpp:parent_symbol'] = root
            self.env.ref_context['cpp:parent_key'] = root.get_lookup_key()
        parentSymbol = self.env.temp_data['cpp:parent_symbol']

        parser = DefinitionParser(sig, self, self.env.config)
        try:
            ast = self.parse_definition(parser)
            parser.assert_end()
        except DefinitionError as e:
            self.warn(e.description)
            # It is easier to assume some phony name than handling the error in
            # the possibly inner declarations.
            name = _make_phony_error_name()
            symbol = parentSymbol.add_name(name)
            self.env.temp_data['cpp:last_symbol'] = symbol
            raise ValueError

        try:
            symbol = parentSymbol.add_declaration(ast, docname=self.env.docname)
            self.env.temp_data['cpp:last_symbol'] = symbol
        except _DuplicateSymbolError as e:
            # Assume we are actually in the old symbol,
            # instead of the newly created duplicate.
            self.env.temp_data['cpp:last_symbol'] = e.symbol
            self.warn("Duplicate declaration.")

        if ast.objectType == 'enumerator':
            self._add_enumerator_to_parent(ast)

        # note: handle_signature may be called multiple time per directive,
        # if it has multiple signatures, so don't mess with the original options.
        options = dict(self.options)
        options['tparam-line-spec'] = 'tparam-line-spec' in self.options
        self.describe_signature(signode, ast, options)
        return ast

    def before_content(self):
        # type: () -> None
        lastSymbol = self.env.temp_data['cpp:last_symbol']
        assert lastSymbol
        self.oldParentSymbol = self.env.temp_data['cpp:parent_symbol']
        self.oldParentKey = self.env.ref_context['cpp:parent_key']
        self.env.temp_data['cpp:parent_symbol'] = lastSymbol
        self.env.ref_context['cpp:parent_key'] = lastSymbol.get_lookup_key()

    def after_content(self):
        # type: () -> None
        self.env.temp_data['cpp:parent_symbol'] = self.oldParentSymbol
        self.env.ref_context['cpp:parent_key'] = self.oldParentKey


class CPPTypeObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ type)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        return parser.parse_declaration("type")


class CPPConceptObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ concept)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        return parser.parse_declaration("concept")


class CPPMemberObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ member)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        return parser.parse_declaration("member")


class CPPFunctionObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ function)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        return parser.parse_declaration("function")


class CPPClassObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ class)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        return parser.parse_declaration("class")


class CPPEnumObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ enum)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        ast = parser.parse_declaration("enum")
        # self.objtype is set by ObjectDescription in run()
        if self.objtype == "enum":
            ast.scoped = None
        elif self.objtype == "enum-struct":
            ast.scoped = "struct"
        elif self.objtype == "enum-class":
            ast.scoped = "class"
        else:
            assert False
        return ast


class CPPEnumeratorObject(CPPObject):
    def get_index_text(self, name):
        # type: (unicode) -> unicode
        return _('%s (C++ enumerator)') % name

    def parse_definition(self, parser):
        # type: (Any) -> Any
        return parser.parse_declaration("enumerator")


class CPPNamespaceObject(Directive):
    """
    This directive is just to tell Sphinx that we're documenting stuff in
    namespace foo.
    """

    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}  # type: Dict

    def warn(self, msg):
        # type: (unicode) -> None
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def run(self):
        # type: () -> List[nodes.Node]
        env = self.state.document.settings.env
        rootSymbol = env.domaindata['cpp']['root_symbol']
        if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
            symbol = rootSymbol
            stack = []  # type: List[Symbol]
        else:
            parser = DefinitionParser(self.arguments[0], self, env.config)
            try:
                ast = parser.parse_namespace_object()
                parser.assert_end()
            except DefinitionError as e:
                self.warn(e.description)
                name = _make_phony_error_name()
                ast = ASTNamespace(name, None)
            symbol = rootSymbol.add_name(ast.nestedName, ast.templatePrefix)
            stack = [symbol]
        env.temp_data['cpp:parent_symbol'] = symbol
        env.temp_data['cpp:namespace_stack'] = stack
        env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
        return []


class CPPNamespacePushObject(Directive):
    has_content = False
    required_arguments = 1
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}  # type: Dict

    def warn(self, msg):
        # type: (unicode) -> None
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def run(self):
        # type: () -> List[nodes.Node]
        env = self.state.document.settings.env
        if self.arguments[0].strip() in ('NULL', '0', 'nullptr'):
            return []
        parser = DefinitionParser(self.arguments[0], self, env.config)
        try:
            ast = parser.parse_namespace_object()
            parser.assert_end()
        except DefinitionError as e:
            self.warn(e.description)
            name = _make_phony_error_name()
            ast = ASTNamespace(name, None)
        oldParent = env.temp_data.get('cpp:parent_symbol', None)
        if not oldParent:
            oldParent = env.domaindata['cpp']['root_symbol']
        symbol = oldParent.add_name(ast.nestedName, ast.templatePrefix)
        stack = env.temp_data.get('cpp:namespace_stack', [])
        stack.append(symbol)
        env.temp_data['cpp:parent_symbol'] = symbol
        env.temp_data['cpp:namespace_stack'] = stack
        env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
        return []


class CPPNamespacePopObject(Directive):
    has_content = False
    required_arguments = 0
    optional_arguments = 0
    final_argument_whitespace = True
    option_spec = {}  # type: Dict

    def warn(self, msg):
        # type: (unicode) -> None
        self.state_machine.reporter.warning(msg, line=self.lineno)

    def run(self):
        # type: () -> List[nodes.Node]
        env = self.state.document.settings.env
        stack = env.temp_data.get('cpp:namespace_stack', None)
        if not stack or len(stack) == 0:
            self.warn("C++ namespace pop on empty stack. Defaulting to gobal scope.")
            stack = []
        else:
            stack.pop()
        if len(stack) > 0:
            symbol = stack[-1]
        else:
            symbol = env.domaindata['cpp']['root_symbol']
        env.temp_data['cpp:parent_symbol'] = symbol
        env.temp_data['cpp:namespace_stack'] = stack
        env.ref_context['cpp:parent_key'] = symbol.get_lookup_key()
        return []


class CPPXRefRole(XRefRole):
    def process_link(self, env, refnode, has_explicit_title, title, target):
        # type: (BuildEnvironment, nodes.Node, bool, unicode, unicode) -> Tuple[unicode, unicode]  # NOQA
        refnode.attributes.update(env.ref_context)
        if refnode['reftype'] == 'any':
            # Assume the removal part of fix_parens for :any: refs.
            # The addition part is done with the reference is resolved.
            if not has_explicit_title and title.endswith('()'):
                title = title[:-2]
            if target.endswith('()'):
                target = target[:-2]
        # TODO: should this really be here?
        if not has_explicit_title:
            target = target.lstrip('~')  # only has a meaning for the title
            # if the first character is a tilde, don't display the module/class
            # parts of the contents
            if title[:1] == '~':
                title = title[1:]
                dcolon = title.rfind('::')
                if dcolon != -1:
                    title = title[dcolon + 2:]
        return title, target


class CPPExprRole(object):
    def __call__(self, typ, rawtext, text, lineno, inliner, options={}, content=[]):
        class Warner(object):
            def warn(self, msg):
                inliner.reporter.warning(msg, line=lineno)
        text = utils.unescape(text).replace('\n', ' ')
        env = inliner.document.settings.env
        parser = DefinitionParser(text, Warner(), env.config)
        try:
            ast = parser.parse_expression()
        except DefinitionError as ex:
            Warner().warn('Unparseable C++ expression: %r\n%s'
                          % (text, text_type(ex.description)))
            return [nodes.literal(text)], []
        parentSymbol = env.temp_data.get('cpp:parent_symbol', None)
        if parentSymbol is None:
            parentSymbol = env.domaindata['cpp']['root_symbol']
        p = nodes.literal()
        ast.describe_signature(p, 'markType', env, parentSymbol)
        return [p], []


class CPPDomain(Domain):
    """C++ language domain."""
    name = 'cpp'
    label = 'C++'
    object_types = {
        'class':      ObjType(l_('class'),      'class',             'type', 'identifier'),
        'function':   ObjType(l_('function'),   'function',  'func', 'type', 'identifier'),
        'member':     ObjType(l_('member'),     'member',    'var'),
        'type':       ObjType(l_('type'),                            'type', 'identifier'),
        'concept':    ObjType(l_('concept'),    'concept',                   'identifier'),
        'enum':       ObjType(l_('enum'),       'enum',              'type', 'identifier'),
        'enumerator': ObjType(l_('enumerator'), 'enumerator')
    }

    directives = {
        'class': CPPClassObject,
        'function': CPPFunctionObject,
        'member': CPPMemberObject,
        'var': CPPMemberObject,
        'type': CPPTypeObject,
        'concept': CPPConceptObject,
        'enum': CPPEnumObject,
        'enum-struct': CPPEnumObject,
        'enum-class': CPPEnumObject,
        'enumerator': CPPEnumeratorObject,
        'namespace': CPPNamespaceObject,
        'namespace-push': CPPNamespacePushObject,
        'namespace-pop': CPPNamespacePopObject
    }
    roles = {
        'any': CPPXRefRole(),
        'class': CPPXRefRole(),
        'func': CPPXRefRole(fix_parens=True),
        'member': CPPXRefRole(),
        'var': CPPXRefRole(),
        'type': CPPXRefRole(),
        'concept': CPPXRefRole(),
        'enum': CPPXRefRole(),
        'enumerator': CPPXRefRole(),
        'expr': CPPExprRole()
    }
    initial_data = {
        'root_symbol': Symbol(None, None, None, None, None, None),
        'names': {}  # full name for indexing -> docname
    }

    def clear_doc(self, docname):
        # type: (unicode) -> None
        rootSymbol = self.data['root_symbol']
        rootSymbol.clear_doc(docname)
        for name, nDocname in list(self.data['names'].items()):
            if nDocname == docname:
                del self.data['names'][name]

    def process_doc(self, env, docname, document):
        # type: (BuildEnvironment, unicode, nodes.Node) -> None
        # just for debugging
        # print(docname)
        # print(self.data['root_symbol'].dump(0))
        pass

    def process_field_xref(self, pnode):
        # type: (nodes.Node) -> None
        pnode.attributes.update(self.env.ref_context)

    def merge_domaindata(self, docnames, otherdata):
        # type: (List[unicode], Dict) -> None
        self.data['root_symbol'].merge_with(otherdata['root_symbol'],
                                            docnames, self.env)
        ourNames = self.data['names']
        for name, docname in otherdata['names'].items():
            if docname in docnames:
                if name in ourNames:
                    msg = "Duplicate declaration, also defined in '%s'.\n"
                    msg += "Name of declaration is '%s'."
                    msg = msg % (ourNames[name], name)
                    logger.warning(msg, location=docname)
                else:
                    ourNames[name] = docname

    def _resolve_xref_inner(self, env, fromdocname, builder, typ,
                            target, node, contnode, emitWarnings=True):
        # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node, bool) -> nodes.Node  # NOQA

        class Warner(object):
            def warn(self, msg):
                if emitWarnings:
                    logger.warning(msg, location=node)
        warner = Warner()
        # add parens again for those that could be functions
        if typ == 'any' or typ == 'func':
            target += '()'
        parser = DefinitionParser(target, warner, env.config)
        try:
            ast = parser.parse_xref_object()
            parser.assert_end()
        except DefinitionError as e:
            def findWarning(e):  # as arg to stop flake8 from complaining
                if typ != 'any' and typ != 'func':
                    return target, e
                # hax on top of the paren hax to try to get correct errors
                parser2 = DefinitionParser(target[:-2], warner, env.config)
                try:
                    parser2.parse_xref_object()
                    parser2.assert_end()
                except DefinitionError as e2:
                    return target[:-2], e2
                # strange, that we don't get the error now, use the original
                return target, e
            t, ex = findWarning(e)
            warner.warn('Unparseable C++ cross-reference: %r\n%s'
                        % (t, text_type(ex.description)))
            return None, None
        parentKey = node.get("cpp:parent_key", None)
        rootSymbol = self.data['root_symbol']
        if parentKey:
            parentSymbol = rootSymbol.direct_lookup(parentKey)
            if not parentSymbol:
                print("Target: ", target)
                print("ParentKey: ", parentKey)
            assert parentSymbol  # should be there
        else:
            parentSymbol = rootSymbol

        name = ast.nestedName
        if ast.templatePrefix:
            templateDecls = ast.templatePrefix.templates
        else:
            templateDecls = []
        s = parentSymbol.find_name(name, templateDecls, typ,
                                   templateShorthand=True,
                                   matchSelf=True)
        if s is None or s.declaration is None:
            txtName = text_type(name)
            if txtName.startswith('std::') or txtName == 'std':
                raise NoUri()
            return None, None

        if typ.startswith('cpp:'):
            typ = typ[4:]
        if typ == 'func':
            typ = 'function'
        declTyp = s.declaration.objectType

        def checkType():
            if typ == 'any' or typ == 'identifier':
                return True
            if declTyp == 'templateParam':
                return True
            objtypes = self.objtypes_for_role(typ)
            if objtypes:
                return declTyp in objtypes
            print("Type is %s, declType is %s" % (typ, declTyp))
            assert False
        if not checkType():
            warner.warn("cpp:%s targets a %s (%s)."
                        % (typ, s.declaration.objectType,
                           s.get_full_nested_name()))

        declaration = s.declaration
        fullNestedName = s.get_full_nested_name()
        name = text_type(fullNestedName).lstrip(':')
        docname = s.docname
        assert docname
        # If it's operator(), we need to add '()' if explicit function parens
        # are requested. Then the Sphinx machinery will add another pair.
        # Also, if it's an 'any' ref that resolves to a function, we need to add
        # parens as well.
        addParen = 0
        if not node.get('refexplicit', False) and declaration.objectType == 'function':
            # this is just the normal haxing for 'any' roles
            if env.config.add_function_parentheses and typ == 'any':
                addParen += 1
            # and now this stuff for operator()
            if (env.config.add_function_parentheses and typ == 'function' and
                    contnode[-1].astext().endswith('operator()')):
                addParen += 1
            if ((typ == 'any' or typ == 'function') and
                    contnode[-1].astext().endswith('operator') and
                    name.endswith('operator()')):
                addParen += 1
        if addParen > 0:
            title = contnode.pop(0).astext()
            contnode += nodes.Text(title + '()' * addParen)
        return make_refnode(builder, fromdocname, docname,
                            declaration.get_newest_id(), contnode, name
                            ), declaration.objectType

    def resolve_xref(self, env, fromdocname, builder,
                     typ, target, node, contnode):
        # type: (BuildEnvironment, unicode, Builder, unicode, unicode, nodes.Node, nodes.Node) -> nodes.Node  # NOQA
        return self._resolve_xref_inner(env, fromdocname, builder, typ,
                                        target, node, contnode)[0]

    def resolve_any_xref(self, env, fromdocname, builder, target,
                         node, contnode):
        # type: (BuildEnvironment, unicode, Builder, unicode, nodes.Node, nodes.Node) -> List[Tuple[unicode, nodes.Node]]  # NOQA
        node, objtype = self._resolve_xref_inner(env, fromdocname, builder,
                                                 'any', target, node, contnode,
                                                 emitWarnings=False)
        if node:
            if objtype == 'templateParam':
                return [('cpp:templateParam', node)]
            else:
                return [('cpp:' + self.role_for_objtype(objtype), node)]
        return []

    def get_objects(self):
        # type: () -> Iterator[Tuple[unicode, unicode, unicode, unicode, unicode, int]]
        rootSymbol = self.data['root_symbol']
        for symbol in rootSymbol.get_all_symbols():
            if symbol.declaration is None:
                continue
            assert symbol.docname
            name = text_type(symbol.get_full_nested_name()).lstrip(':')
            objectType = symbol.declaration.objectType
            docname = symbol.docname
            newestId = symbol.declaration.get_newest_id()
            yield (name, name, objectType, docname, newestId, 1)

    def get_full_qualified_name(self, node):
        # type: (nodes.Node) -> unicode
        target = node.get('reftarget', None)
        if target is None:
            return None
        parentKey = node.get("cpp:parent_key", None)
        if parentKey is None or len(parentKey) <= 0:
            return None

        rootSymbol = self.data['root_symbol']
        parentSymbol = rootSymbol.direct_lookup(parentKey)
        parentName = parentSymbol.get_full_nested_name()
        return '::'.join([text_type(parentName), target])


def setup(app):
    # type: (Sphinx) -> Dict[unicode, Any]
    app.add_domain(CPPDomain)
    app.add_config_value("cpp_index_common_prefix", [], 'env')
    app.add_config_value("cpp_id_attributes", [], 'env')
    app.add_config_value("cpp_paren_attributes", [], 'env')

    return {
        'version': 'builtin',
        'parallel_read_safe': True,
        'parallel_write_safe': True,
    }
