From 4f0e246109d6d7c1538f7b9869459f72ba5952b0 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Sun, 1 Mar 2015 15:04:15 +0100 Subject: [PATCH 1/4] C++, add tests for ids. --- tests/test_domain_cpp.py | 254 ++++++++++++++++++++++++--------------- 1 file changed, 160 insertions(+), 94 deletions(-) diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 05e7991a0..82def4296 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -27,7 +27,7 @@ def parse(name, string): raise DefinitionError("") return res -def check(name, input, output=None): +def check(name, input, idv1output=None, idv2output=None, output=None): # first a simple check of the AST if output is None: output = input @@ -42,127 +42,193 @@ def check(name, input, output=None): # Artificially set the prefixedName, otherwise the get_id fails. # It would usually have been set in handle_signarue. ast.prefixedName = ast.name - ast.get_id_v1() - ast.get_id_v2() + + if idv2output: + idv2output = "_CPPv2" + idv2output + idv1 = ast.get_id_v1() + idv2 = ast.get_id_v2() + if idv1 != idv1output or idv2 != idv2output: + print(" %s %s" % ("Id v1".rjust(20), "Id v2".rjust(20))) + print("result: %s %s" % (str(idv1).rjust(20), str(idv2).rjust(20))) + print("expected: %s %s" % (str(idv1output).rjust(20), str(idv2output).rjust(20))) + raise DefinitionError("") ids.append(ast.get_id_v2()) #print ".. %s:: %s" % (name, input) def test_type_definitions(): - check("type", "public bool b", "bool b") - check("type", "bool A::b") - check("type", "bool *b") - check("type", "bool *const b") - check("type", "bool *volatile const b") - check("type", "bool *volatile const b") - check("type", "bool *volatile const *b") - check("type", "bool &b") - check("type", "bool b[]") - check("type", "std::pair coord") - check("type", "long long int foo") - check("type", 'std::vector> module::blah') - check("type", "std::function F") - check("type", "std::function F") - check("type", "std::function F") - check("type", "MyContainer::const_iterator") + check("type", "public bool b", "b", "1b", "bool b") + check("type", "bool A::b", "A::b", "N1A1bE") + check("type", "bool *b", "b", "1b") + check("type", "bool *const b", "b", "1b") + check("type", "bool *volatile const b", "b", "1b") + check("type", "bool *volatile const b", "b", "1b") + check("type", "bool *volatile const *b", "b", "1b") + check("type", "bool &b", "b", "1b") + check("type", "bool b[]", "b", "1b") + check("type", "std::pair coord", "coord", "5coord") + check("type", "long long int foo", "foo", "3foo") + check("type", 'std::vector> module::blah', + "module::blah", "N6module4blahE") + check("type", "std::function F", "F", "1F") + check("type", "std::function F", "F", "1F") + check("type", "std::function F", "F", "1F") + check("type", "MyContainer::const_iterator", + "MyContainer::const_iterator","N11MyContainer14const_iteratorE") check("type", "public MyContainer::const_iterator", - "MyContainer::const_iterator") + "MyContainer::const_iterator", "N11MyContainer14const_iteratorE", + output="MyContainer::const_iterator") # test decl specs on right - check("type", "bool const b") + check("type", "bool const b", "b", "1b") - check('member', - ' const std::string & name = 42', - 'const std::string &name = 42') - check('member', ' const std::string & name', 'const std::string &name') - check('member', - ' const std::string & name [ n ]', - 'const std::string &name[n]') - check('member', - 'const std::vector< unsigned int, long> &name', - 'const std::vector &name') - check('member', 'module::myclass foo[n]') + check('member', ' const std::string & name = 42', + "name__ssCR", "4name", output='const std::string &name = 42') + check('member', ' const std::string & name', "name__ssCR", "4name", + output='const std::string &name') + check('member', ' const std::string & name [ n ]', + "name__ssCRA", "4name", output='const std::string &name[n]') + check('member', 'const std::vector< unsigned int, long> &name', + "name__std::vector:unsigned-i.l:CR", + "4name", output='const std::vector &name') + check('member', 'module::myclass foo[n]', "foo__module::myclassA", "3foo") - check('function', 'operator bool() const') - check('function', 'A::operator bool() const') - check('function', 'A::operator bool() volatile const &') - check('function', 'A::operator bool() volatile const &&') - check('function', 'bool namespaced::theclass::method(arg1, arg2)') + check('function', 'operator bool() const', "castto-b-operatorC", "NKcvbEv") + check('function', 'A::operator bool() const', + "A::castto-b-operatorC", "NK1AcvbEv") + check('function', 'A::operator bool() volatile const &', + "A::castto-b-operatorVCR", "NVKR1AcvbEv") + check('function', 'A::operator bool() volatile const &&', + "A::castto-b-operatorVCO", "NVKO1AcvbEv") + check('function', 'bool namespaced::theclass::method(arg1, arg2)', + "namespaced::theclass::method__arg1.arg2", + "N10namespaced8theclass6methodE4arg14arg2") x = 'std::vector> &module::test(register ' \ 'foo, bar, std::string baz = "foobar, blah, bleh") const = 0' - check('function', x) - check('function', 'void f(std::pair)') - check('function', 'explicit module::myclass::foo::foo()') - check('function', 'module::myclass::foo::~foo()') - check('function', 'int printf(const char *fmt, ...)') - check('function', 'int foo(const unsigned int j)') - check('function', 'int foo(const int *const ptr)') - check('function', 'module::myclass::operator std::vector()') + check('function', x, "module::test__register.bar.ssC", + "NK6module4testE8register3barNSt6stringE") + check('function', 'void f(std::pair)', + "f__std::pair:A.B:", "1fNSt4pairI1A1BEE") + check('function', 'explicit module::myclass::foo::foo()', + "module::myclass::foo::foo", "N6module7myclass3foo3fooEv") + check('function', 'module::myclass::foo::~foo()', + "module::myclass::foo::~foo", "N6module7myclass3fooD0Ev") + check('function', 'int printf(const char *fmt, ...)', + "printf__cCP.z", "6printfPKcz") + check('function', 'int foo(const unsigned int j)', + "foo__unsigned-iC", "3fooKj") + check('function', 'int foo(const int *const ptr)', + "foo__iCPC", "3fooPCKi") + check('function', 'module::myclass::operator std::vector()', + "module::myclass::castto-std::vector:ss:-operator", + "N6module7myclasscvNSt6vectorINSt6stringEEEEv") check('function', - 'void operator()(const boost::array &v) const') + 'void operator()(const boost::array &v) const', + "call-operator__boost::array:VertexID.2:CRC", + "NKclERKN5boost5arrayI8VertexIDX2EEE") check('function', - 'void operator()(const boost::array &v) const') - check('function', 'MyClass::MyClass(MyClass::MyClass&&)') - check('function', 'constexpr int get_value()') - check('function', 'static constexpr int get_value()') - check('function', 'int get_value() const noexcept') - check('function', 'int get_value() const noexcept = delete') - check('function', 'int get_value() volatile const') - check('function', 'MyClass::MyClass(MyClass::MyClass&&) = default') - check('function', 'virtual MyClass::a_virtual_function() const override') - check('function', 'A B() override') - check('function', 'A B() final') - check('function', 'A B() final override') - check('function', 'A B() override final', 'A B() final override') - check('function', 'MyClass::a_member_function() volatile') - check('function', 'MyClass::a_member_function() volatile const') - check('function', 'MyClass::a_member_function() &&') - check('function', 'MyClass::a_member_function() &') - check('function', 'MyClass::a_member_function() const &') - check('function', 'int main(int argc, char *argv[])') - check('function', 'MyClass &MyClass::operator++()') - check('function', 'MyClass::pointer MyClass::operator->()') + 'void operator()(const boost::array &v) const', + 'call-operator__boost::array:VertexID.2."foo,--bar":CRC', + 'NKclERKN5boost5arrayI8VertexIDX2EX"foo, bar"EEE') + check('function', 'MyClass::MyClass(MyClass::MyClass&&)', + "MyClass::MyClass__MyClass::MyClassRR", + "N7MyClass7MyClassERRN7MyClass7MyClassE") + check('function', 'constexpr int get_value()', "get_valueCE", "9get_valuev") + check('function', 'static constexpr int get_value()', + "get_valueCE", "9get_valuev") + check('function', 'int get_value() const noexcept', + "get_valueC", "NK9get_valueEv") + check('function', 'int get_value() const noexcept = delete', + "get_valueC", "NK9get_valueEv") + check('function', 'int get_value() volatile const', + "get_valueVC", "NVK9get_valueEv") + check('function', 'MyClass::MyClass(MyClass::MyClass&&) = default', + "MyClass::MyClass__MyClass::MyClassRR", + "N7MyClass7MyClassERRN7MyClass7MyClassE") + check('function', 'virtual MyClass::a_virtual_function() const override', + "MyClass::a_virtual_functionC", "NK7MyClass18a_virtual_functionEv") + check('function', 'A B() override', "B", "1Bv") + check('function', 'A B() final', "B", "1Bv") + check('function', 'A B() final override', "B", "1Bv") + check('function', 'A B() override final', "B", "1Bv", + output='A B() final override') + check('function', 'MyClass::a_member_function() volatile', + "MyClass::a_member_functionV", "NV7MyClass17a_member_functionEv") + check('function', 'MyClass::a_member_function() volatile const', + "MyClass::a_member_functionVC", "NVK7MyClass17a_member_functionEv") + check('function', 'MyClass::a_member_function() &&', + "MyClass::a_member_functionO", "NO7MyClass17a_member_functionEv") + check('function', 'MyClass::a_member_function() &', + "MyClass::a_member_functionR", "NR7MyClass17a_member_functionEv") + check('function', 'MyClass::a_member_function() const &', + "MyClass::a_member_functionCR", "NKR7MyClass17a_member_functionEv") + check('function', 'int main(int argc, char *argv[])', + "main__i.cPA", "4mainiPA_c") + check('function', 'MyClass &MyClass::operator++()', + "MyClass::inc-operator", "N7MyClassppEv") + check('function', 'MyClass::pointer MyClass::operator->()', + "MyClass::pointer-operator", "N7MyClassptEv") x = 'std::vector> &module::test(register ' \ 'foo, bar[n], std::string baz = "foobar, blah, bleh") const = 0' - check('function', x) + check('function', x, "module::test__register.barA.ssC", + "NK6module4testE8registerAn_3barNSt6stringE") check('function', - 'int foo(Foo f = Foo(double(), std::make_pair(int(2), double(3.4))))') - check('function', 'int foo(A a = x(a))') + 'int foo(Foo f = Foo(double(), std::make_pair(int(2), double(3.4))))', + "foo__Foo", "3foo3Foo") + check('function', 'int foo(A a = x(a))', "foo__A", "3foo1A") raises(DefinitionError, parse, 'function', 'int foo(B b=x(a)') raises(DefinitionError, parse, 'function', 'int foo)C c=x(a))') raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') - check('function', 'int foo(const A&... a)') - check('function', 'virtual void f()') + check('function', 'int foo(const A&... a)', "foo__ACRDp", "3fooRDpK1A") + check('function', 'virtual void f()', "f", "1fv") # test for ::nestedName, from issue 1738 - check("function", "result(int val, ::std::error_category const &cat)") + check("function", "result(int val, ::std::error_category const &cat)", + "result__i.std::error_categoryCR", "6resultiRNSt14error_categoryE") - check('class', 'public A', 'A') - check('class', 'private A') + check('class', 'public A', "A", "1A", output='A') + check('class', 'private A', "A", "1A") - check('enum', 'A') - check('enum', 'A : std::underlying_type::type') - check('enum', 'A : unsigned int') - check('enum', 'public A', 'A') - check('enum', 'private A') + check('enum', 'A', None, "1A") + check('enum', 'A : std::underlying_type::type', None, "1A") + check('enum', 'A : unsigned int', None, "1A") + check('enum', 'public A', None, "1A", output='A') + check('enum', 'private A', None, "1A") - check('enumerator', 'A') - check('enumerator', 'A = std::numeric_limits::max()') + check('enumerator', 'A', None, "1A") + check('enumerator', 'A = std::numeric_limits::max()', + None, "1A") def test_bases(): - check('class', 'A') - check('class', 'A::B::C') - check('class', 'A : B') - check('class', 'A : private B', 'A : B') - check('class', 'A : public B') - check('class', 'A : B, C') - check('class', 'A : B, protected C, D') + check('class', 'A', "A", "1A") + check('class', 'A::B::C', "A::B::C", "N1A1B1CE") + check('class', 'A : B', "A", "1A") + check('class', 'A : private B', "A", "1A", output='A : B') + check('class', 'A : public B', "A", "1A") + check('class', 'A : B, C', "A", "1A") + check('class', 'A : B, protected C, D', "A", "1A") def test_operators(): - check('function', 'void operator new [ ] ()', 'void operator new[]()') - check('function', 'void operator delete ()', 'void operator delete()') - check('function', 'void operator bool() const', 'void operator bool() const') - for op in '*-+=/%!': - check('function', 'void operator %s ()' % op, 'void operator%s()' % op) + check('function', 'void operator new [ ] ()', + "new-array-operator", "nav", output='void operator new[]()') + check('function', 'void operator delete ()', + "delete-operator", "dlv", output='void operator delete()') + check('function', 'void operator bool() const', + "castto-b-operatorC", "NKcvbEv", output='void operator bool() const') + + check('function', 'void operator * ()', + "mul-operator", "mlv", output='void operator*()') + check('function', 'void operator - ()', + "sub-operator", "miv", output='void operator-()') + check('function', 'void operator + ()', + "add-operator", "plv", output='void operator+()') + check('function', 'void operator = ()', + "assign-operator", "aSv", output='void operator=()') + check('function', 'void operator / ()', + "div-operator", "dvv", output='void operator/()') + check('function', 'void operator % ()', + "mod-operator", "rmv", output='void operator%()') + check('function', 'void operator ! ()', + "not-operator", "ntv", output='void operator!()') #def test_print(): # # used for getting all the ids out for checking From 7c240a5ec8dff2784b8509f27387c2c562bbff52 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Mon, 2 Mar 2015 21:04:56 +0100 Subject: [PATCH 2/4] C++, imrpove errors from function parsing. --- sphinx/domains/cpp.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 8e74abc96..7c65f9ba3 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1398,6 +1398,7 @@ class ASTClass(ASTBase): signode += nodes.Text(', ') signode.pop() + class ASTEnum(ASTBase): def __init__(self, name, visibility, scoped, underlyingType): self.name = name @@ -1437,6 +1438,7 @@ class ASTEnum(ASTBase): signode += nodes.Text(' : ') self.underlyingType.describe_signature(signode, 'noneIsName', env) + class ASTEnumerator(ASTBase): def __init__(self, name, init): self.name = name @@ -1977,13 +1979,25 @@ class DefinitionParser(object): declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(named=True, paramMode=outer) except DefinitionError as exTyped: - if outer == 'type': + # 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': + desc = ('Type must be either just a name or a ' + 'typedef-like declaration.\n' + 'Just a name error: %s\n' + 'Typedef-like expression error: %s') + elif outer == 'function': + desc = ('Error when parsing function declaration:\n' + 'Error if no return type: %s\n' + 'Error if return type: %s') + else: + assert False raise DefinitionError( - 'Type must be either just a name or a ' - 'typedef-like declaration.\nJust a name error: ' - '%s\nTypedef-like expression error: %s' - % (exUntyped.description, exTyped.description)) + desc % (exUntyped.description, exTyped.description)) else: + # For testing purposes. # do it again to get the proper traceback (how do you # relieable save a traceback when an exception is # constructed?) From 8ca12f5307e07ad2024e063f524ecaa51e359452 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Thu, 5 Mar 2015 10:51:28 +0100 Subject: [PATCH 3/4] Main revamp for sphinx-doc/sphinx#1753. Support `( ptr-declarator )`, e.g., `int (*f)(double)`, or `int (&a)[42]`. Still missing check for compatibility with old id generation scheme. --- sphinx/domains/cpp.py | 761 ++++++++++++++++++++++++++------------- tests/test_domain_cpp.py | 60 ++- 2 files changed, 561 insertions(+), 260 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index 7c65f9ba3..a47054a3f 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -34,8 +34,9 @@ -> decl-specifier-seq declerator initializer decl-specifier -> - storage-class-specifier -> "static" (only for member_object and - function_object) + storage-class-specifier -> + "static" (only for member_object and function_object) + | "register" | type-specifier -> trailing-type-specifier | function-specifier -> "inline" | "virtual" | "explicit" (only for function_object) @@ -93,7 +94,7 @@ declerator -> ptr-declerator | noptr-declarator parameters-and-qualifiers trailing-return-type - (TODO: for now we don't support it) + (TODO: for now we don't support trailing-eturn-type) ptr-declerator -> noptr-declerator | ptr-operator ptr-declarator @@ -104,7 +105,13 @@ | noptr-declerator parameters-and-qualifiers | noptr-declarator "[" constant-expression[opt] "]" attribute-specifier-seq[opt] - | "(" ptr-declarator ")" # TODO: not implemented yet + | "(" 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] # TOOD: not implemented # function_object must use a parameters-and-qualifiers, the others may # use it (e.g., function poitners) parameters-and-qualifiers -> @@ -206,8 +213,6 @@ _whitespace_re = re.compile(r'\s+(?u)') _string_re = re.compile(r"[LuU8]?('([^'\\]*(?:\\.[^'\\]*)*)'" r'|"([^"\\]*(?:\\.[^"\\]*)*)")', re.S) _visibility_re = re.compile(r'\b(public|private|protected)\b') -_array_def_re = re.compile(r'\[\s*([^\]]+?)?\s*\]') -_template_arg_re = re.compile(r'(%s)|([^,>]+)' % _string_re.pattern, re.S) _operator_re = re.compile(r'''(?x) \[\s*\] | \(\s*\) @@ -216,6 +221,22 @@ _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' +] #------------------------------------------------------------------------------- # Id v1 constants @@ -387,6 +408,15 @@ _id_operator_v2 = { } +class NoOldIdError(UnicodeMixin, Exception): + # Used to avoid implementing unneeded id generation for old id schmes. + def __init__(self, description=""): + self.description = description + + def __unicode__(self): + return self.description + + class DefinitionError(UnicodeMixin, Exception): def __init__(self, description): self.description = description @@ -1054,58 +1084,6 @@ class ASTDeclSpecs(ASTBase): signode += m -class ASTPtrOpPtr(ASTBase): - def __init__(self, volatile, const): - self.volatile = volatile - self.const = const - - def __unicode__(self): - res = ['*'] - if self.volatile: - res.append('volatile ') - if self.const: - res.append('const ') - return u''.join(res) - - def get_id_v1(self): - res = ['P'] - if self.volatile: - res.append('V') - if self.const: - res.append('C') - return u''.join(res) - - def get_id_v2(self): - res = ['P'] - if self.volatile: - res.append('V') - if self.const: - res.append('C') - return u''.join(res) - - -class ASTPtrOpRef(ASTBase): - def __unicode__(self): - return '&' - - def get_id_v1(self): - return 'R' - - def get_id_v2(self): - return 'R' - - -class ASTPtrOpParamPack(ASTBase): - def __unicode__(self): - return '...' - - def get_id_v1(self): - return 'Dp' - - def get_id_v2(self): - return 'Dp' - - class ASTArray(ASTBase): def __init__(self, size): self.size = size @@ -1125,11 +1103,264 @@ class ASTArray(ASTBase): signode += nodes.Text(text_type(self)) -class ASTDeclerator(ASTBase): - def __init__(self, ptrOps, declId, suffixOps): - self.ptrOps = ptrOps +class ASTDeclaratorPtr(ASTBase): + def __init__(self, next, volatile, const): + assert next + self.next = next + self.volatile = volatile + self.const = const + + @property + def name(self): + return self.next.name + + def require_space_after_declSpecs(self): + # TODO: if has paramPack, then False ? + return True + + def __unicode__(self): + res = ['*'] + if self.volatile: + res.append('volatile ') + if self.const: + res.append('const ') + res.append(text_type(self.next)) + return u''.join(res) + + # Id v1 ------------------------------------------------------------------ + + def get_modifiers_id_v1(self): + return self.next.get_modifiers_id_v1() + + def get_param_id_v1(self): + return self.next.get_param_id_v1() + + def get_ptr_suffix_id_v1(self): + res = 'P' + if self.volatile: + res += 'V' + if self.const: + res += 'C' + return res + self.next.get_ptr_suffix_id_v1() + + # Id v2 ------------------------------------------------------------------ + + def get_modifiers_id_v2(self): + return self.next.get_modifiers_id_v2() + + def get_param_id_v2(self): + return self.next.get_param_id_v2() + + def get_ptr_suffix_id_v2(self): + res = [self.next.get_ptr_suffix_id_v2()] + res.append('P') + if self.volatile: + res.append('V') + if self.const: + res.append('C') + return u''.join(res) + + def get_type_id_v2(self, returnTypeId): + # ReturnType *next, so we are part of the return type of 'next + res = ['P'] + if self.volatile: + res.append('V') + if self.const: + res.append('C') + res.append(returnTypeId) + return self.next.get_type_id_v2(returnTypeId=u''.join(res)) + + # ------------------------------------------------------------------------ + + def is_function_type(self): + return self.next.is_function_type() + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + signode += nodes.Text("*") + # TODO: if has ParamPack and hasDeclId: + # signode += nodes.Text(' ') + + +class ASTDeclaratorRef(ASTBase): + def __init__(self, next): + assert next + self.next = next + + @property + def name(self): + return self.next.name + + def require_space_after_declSpecs(self): + return self.next.require_space_after_declSpecs() + + def __unicode__(self): + return '&' + text_type(self.next) + + # Id v1 ------------------------------------------------------------------ + + def get_modifiers_id_v1(self): + return self.next.get_modifiers_id_v1() + + def get_param_id_v1(self): # only the parameters (if any) + return self.next.get_param_id_v1() + + def get_ptr_suffix_id_v1(self): + return u'R' + self.next.get_ptr_suffix_id_v1() + + # Id v2 ------------------------------------------------------------------ + + def get_modifiers_id_v2(self): + return self.next.get_modifiers_id_v2() + + def get_param_id_v2(self): # only the parameters (if any) + return self.next.get_param_id_v2() + + def get_ptr_suffix_id_v2(self): + return self.next.get_ptr_suffix_id_v2() + u'R' + + def get_type_id_v2(self, returnTypeId): + # ReturnType &next, so we are part of the return type of 'next + return self.next.get_type_id_v2(returnTypeId=u'R' + returnTypeId) + + # ------------------------------------------------------------------------ + + def is_function_type(self): + return self.next.is_function_type() + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + signode += nodes.Text("&") + # TODO: if has ParamPack and hasDeclId: + # signode += nodes.Text(' ') + + +class ASTDeclaratorParamPack(ASTBase): + def __init__(self, next): + assert next + self.next = next + + @property + def name(self): + return self.next.name + + def require_space_after_declSpecs(self): + return False + + def __unicode__(self): + res = text_type(self.next) + if self.next.name: + res = ' ' + res + return '...' + res + + # Id v1 ------------------------------------------------------------------ + + def get_modifiers_id_v1(self): + return self.next.get_modifiers_id_v1() + + def get_param_id_v1(self): # only the parameters (if any) + return self.next.get_param_id_v1() + + def get_ptr_suffix_id_v1(self): + return 'Dp' + self.next.get_ptr_suffix_id_v2() + + # Id v2 ------------------------------------------------------------------ + + def get_modifiers_id_v2(self): + return self.next.get_modifiers_id_v2() + + def get_param_id_v2(self): # only the parameters (if any) + return self.next.get_param_id_v2() + + def get_ptr_suffix_id_v2(self): + return self.next.get_ptr_suffix_id_v2() + u'Dp' + + def get_type_id_v2(self, returnTypeId): + # ReturnType... next, so we are part of the return type of 'next + return self.next.get_type_id_v2(returnTypeId=u'Dp' + returnTypeId) + + # ------------------------------------------------------------------------ + + def is_function_type(self): + return self.next.is_function_type() + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + signode += nodes.Text("...") + if self.next.name: + signode += nodes.Text(' ') + + +class ASTDeclaratorParen(ASTBase): + def __init__(self, inner, next): + 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): + return self.inner.name + + def require_space_after_declSpecs(self): + return True + + def __unicode__(self): + res = ['('] + res.append(text_type(self.inner)) + res.append(')') + res.append(text_type(self.next)) + return ''.join(res) + + # Id v1 ------------------------------------------------------------------ + + def get_modifiers_id_v1(self): + return self.inner.get_modifiers_id_v1() + + def get_param_id_v1(self): # only the parameters (if any) + return self.inner.get_param_id_v1() + + def get_ptr_suffix_id_v1(self): + raise NoOldIdError() # TODO: was this implemented before? + return self.next.get_ptr_suffix_id_v2() \ + + self.inner.get_ptr_suffix_id_v2() + + # Id v2 ------------------------------------------------------------------ + + def get_modifiers_id_v2(self): + return self.inner.get_modifiers_id_v2() + + def get_param_id_v2(self): # only the parameters (if any) + return self.inner.get_param_id_v2() + + def get_ptr_suffix_id_v2(self): + return self.inner.get_ptr_suffix_id_v2() \ + + self.next.get_ptr_suffix_id_v2() + + def get_type_id_v2(self, returnTypeId): + # ReturnType (inner)next, so 'inner' returns everything outside + nextId = self.next.get_type_id_v2(returnTypeId) + return self.inner.get_type_id_v2(returnTypeId=nextId) + + # ------------------------------------------------------------------------ + + def is_function_type(self): + return self.inner.is_function_type() + + def describe_signature(self, signode, mode, env): + _verify_description_mode(mode) + signode += nodes.Text('(') + self.inner.describe_signature(signode, mode, env) + signode += nodes.Text(')') + self.next.describe_signature(signode, "noneIsName", env) + + +class ASTDecleratorNameParamQual(ASTBase): + def __init__(self, declId, arrayOps, paramQual): self.declId = declId - self.suffixOps = suffixOps + self.arrayOps = arrayOps + self.paramQual = paramQual @property def name(self): @@ -1139,75 +1370,78 @@ class ASTDeclerator(ASTBase): def get_modifiers_id_v1(self): # only the modifiers for a function, e.g., # cv-qualifiers - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_modifiers_id_v1() + if self.paramQual: + return self.paramQual.get_modifiers_id_v1() raise Exception( "This should only be called on a function: %s" % text_type(self)) def get_param_id_v1(self): # only the parameters (if any) - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_param_id_v1() - return '' + if self.paramQual: + return self.paramQual.get_param_id_v1() + else: + return '' - def get_ptr_suffix_id_v1(self): # only the ptr ops and array specifiers - return u''.join( - a.get_id_v1() - for a in self.ptrOps + self.suffixOps - if not isinstance(a, ASTParametersQualifiers)) + def get_ptr_suffix_id_v1(self): # only the array specifiers + return u''.join(a.get_id_v1() for a in self.arrayOps) # Id v2 ------------------------------------------------------------------ def get_modifiers_id_v2(self): # only the modifiers for a function, e.g., # cv-qualifiers - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_modifiers_id_v2() + if self.paramQual: + return self.paramQual.get_modifiers_id_v2() raise Exception( "This should only be called on a function: %s" % text_type(self)) def get_param_id_v2(self): # only the parameters (if any) - for op in self.suffixOps: - if isinstance(op, ASTParametersQualifiers): - return op.get_param_id_v2() - return '' - - def get_ptr_suffix_id_v2(self): # only the ptr ops and array specifiers - return u''.join( - a.get_id_v2() - for a in self.ptrOps + self.suffixOps - if not isinstance(a, ASTParametersQualifiers)) - - def require_start_space(self): - if (len(self.ptrOps) > 0 and - isinstance(self.ptrOps[-1], ASTPtrOpParamPack)): - return False + if self.paramQual: + return self.paramQual.get_param_id_v2() else: - return self.declId is not None + return '' + + def get_ptr_suffix_id_v2(self): # only the array specifiers + return u''.join(a.get_id_v2() for a in self.arrayOps) + + def get_type_id_v2(self, returnTypeId): + res = [] + # TOOD: can we actually have both array ops and paramQual? + res.append(self.get_ptr_suffix_id_v2()) + if self.paramQual: + res.append(self.get_modifiers_id_v2()) + res.append('F') + res.append(returnTypeId) + res.append(self.get_param_id_v2()) + res.append('E') + else: + res.append(returnTypeId) + return u''.join(res) + + # ------------------------------------------------------------------------ + + def require_space_after_declSpecs(self): + return self.declId is not None + + def is_function_type(self): + return self.paramQual != None def __unicode__(self): res = [] - for op in self.ptrOps: - res.append(text_type(op)) - if isinstance(op, ASTPtrOpParamPack) and self.declId: - res.append(' ') if self.declId: res.append(text_type(self.declId)) - for op in self.suffixOps: + 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): _verify_description_mode(mode) - for op in self.ptrOps: - signode += nodes.Text(text_type(op)) - if isinstance(op, ASTPtrOpParamPack) and self.declId: - signode += nodes.Text(' ') if self.declId: self.declId.describe_signature(signode, mode, env) - for op in self.suffixOps: + for op in self.arrayOps: op.describe_signature(signode, mode, env) + if self.paramQual: + self.paramQual.describe_signature(signode, mode, env) class ASTInitializer(ASTBase): @@ -1252,6 +1486,8 @@ class ASTType(ASTBase): print(self.objectType) assert False else: # only type encoding + if self.decl.is_function_type(): + raise NoOldIdError() res.append(self.declSpecs.get_id_v1()) res.append(self.decl.get_ptr_suffix_id_v1()) res.append(self.decl.get_param_id_v1()) @@ -1271,16 +1507,18 @@ class ASTType(ASTBase): print(self.objectType) assert False else: # only type encoding - res.append(self.decl.get_ptr_suffix_id_v2()) - res.append(self.declSpecs.get_id_v2()) - res.append(self.decl.get_param_id_v2()) + # 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_v2() + typeId = self.decl.get_type_id_v2(returnTypeId) + res.append(typeId) return u''.join(res) def __unicode__(self): res = [] declSpecs = text_type(self.declSpecs) res.append(declSpecs) - if self.decl.require_start_space() and len(declSpecs) > 0: + if self.decl.require_space_after_declSpecs() and len(declSpecs) > 0: res.append(u' ') res.append(text_type(self.decl)) return u''.join(res) @@ -1288,7 +1526,7 @@ class ASTType(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mode(mode) self.declSpecs.describe_signature(signode, 'markType', env) - if (self.decl.require_start_space() and + if (self.decl.require_space_after_declSpecs() and len(text_type(self.declSpecs)) > 0): signode += nodes.Text(' ') self.decl.describe_signature(signode, mode, env) @@ -1407,7 +1645,7 @@ class ASTEnum(ASTBase): self.underlyingType = underlyingType def get_id_v1(self): - return None # did not exist at that time + raise NoOldIdError() def get_id_v2(self): return _id_prefix_v2 + self.prefixedName.get_id_v2() @@ -1445,7 +1683,7 @@ class ASTEnumerator(ASTBase): self.init = init def get_id_v1(self): - return None # did not exist at that time + raise NoOldIdError() def get_id_v2(self): return _id_prefix_v2 + self.prefixedName.get_id_v2() @@ -1545,6 +1783,32 @@ class DefinitionParser(object): self.fail('expected end of definition, got %r' % self.definition[self.pos:]) + def _parse_expression(self, end): + # Stupidly "parse" an expression. + # 'end' should be a list of characters which ends the expression. + assert end + 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 = {'(': ')', '[': ']'} + symbols = [] + 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 self.eof: + self.fail("Could not find end of expression starting at %d." + % startPos) + value = self.definition[startPos:self.pos].strip() + return value.strip() + def _parse_operator(self): self.skip_ws() # adapted from the old code @@ -1566,7 +1830,7 @@ class DefinitionParser(object): # oh well, looks like a cast operator definition. # In that case, eat another type. - type = self._parse_type() + type = self._parse_type(named=False, outer="operatorCast") return ASTOperatorType(type) def _parse_nested_name(self): @@ -1577,14 +1841,19 @@ class DefinitionParser(object): names.append(ASTNestedNameElementEmpty()) while 1: self.skip_ws() - # TODO: parse the "template" keyword - if not self.match(_identifier_re): - self.fail("expected identifier") - identifier = self.matched_text - if identifier == 'operator': + if self.skip_word_and_ws('template'): + self.fail("'template' in nested name not implemented.") + elif self.skip_word_and_ws('operator'): op = self._parse_operator() names.append(op) else: + if not self.match(_identifier_re): + 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) templateArgs = None self.skip_ws() if self.skip_string('<'): @@ -1592,31 +1861,14 @@ class DefinitionParser(object): while 1: pos = self.pos try: - type = self._parse_type(allowParams=True) + type = self._parse_type(named=False) templateArgs.append(type) - except DefinitionError: + except DefinitionError as exType: self.pos = pos - symbols = [] - startPos = self.pos - self.skip_ws() - if self.match(_string_re): - value = self.matched_text - else: - while not self.eof: - if (len(symbols) == 0 and - self.current_char in ( - ',', '>')): - break - # TODO: actually implement nice handling - # of quotes, braces, brackets, parens, and - # whatever - self.pos += 1 - if self.eof: - self.pos = startPos - self.fail( - 'Could not find end of constant ' - 'template argument.') - value = self.definition[startPos:self.pos].strip() + try: + value = self._parse_expression(end=[',', '>']) + except DefinitionError as exConstant: + assert False # TODO: make nice error templateArgs.append(ASTTemplateArgConstant(value)) self.skip_ws() if self.skip_string('>'): @@ -1697,9 +1949,9 @@ class DefinitionParser(object): 'parameters_and_qualifiers.') break if paramMode == 'function': - arg = self._parse_type_with_init(named='maybe') + arg = self._parse_type_with_init(outer=None, named='maybe') else: - arg = self._parse_type() + arg = self._parse_type(named=False) # TODO: parse default parameters args.append(ASTFunctinoParameter(arg)) @@ -1784,11 +2036,9 @@ class DefinitionParser(object): if self.skip_word('mutable'): storage = 'mutable' continue - if outer == 'fuction': - # TODO: maybe in more contexts, missing test cases - if self.skip_word('register'): - storage = 'register' - continue + if self.skip_word('register'): + storage = 'register' + continue if outer == 'function': # function-specifiers @@ -1822,12 +2072,16 @@ class DefinitionParser(object): volatile, const) def _parse_decl_specs(self, outer, typed=True): + if outer: + if outer not in ('type', 'member', 'function'): + raise Exception('Internal error, unknown outer "%s".' % outer) """ visibility storage-class-specifier function-specifier "constexpr" "volatile" "const" trailing-type-specifier - storage-class-specifier -> "static" (only for member_object and - function_object) + storage-class-specifier -> + "static" (only for member_object and function_object) + | "register" function-specifier -> "inline" | "virtual" | "explicit" (only for function_object) @@ -1850,29 +2104,8 @@ class DefinitionParser(object): trailing = None return ASTDeclSpecs(outer, visibility, leftSpecs, rightSpecs, trailing) - def _parse_declerator(self, named, paramMode=None, typed=True): - if paramMode: - if paramMode not in ('type', 'function'): - raise Exception( - "Internal error, unknown paramMode '%s'." % paramMode) - ptrOps = [] - while 1: - if not typed: - break - self.skip_ws() - if self.skip_string('*'): - self.skip_ws() - volatile = self.skip_word_and_ws('volatile') - const = self.skip_word_and_ws('const') - ptrOps.append(ASTPtrOpPtr(volatile=volatile, const=const)) - elif self.skip_string('&'): - ptrOps.append(ASTPtrOpRef()) - elif self.skip_string('...'): - ptrOps.append(ASTPtrOpParamPack()) - break - else: - break - + def _parse_declarator_name_param_qual(self, named, paramMode, typed): + # now we should parse the name, and then suffixes if named == 'maybe': try: declId = self._parse_nested_name() @@ -1882,37 +2115,92 @@ class DefinitionParser(object): declId = self._parse_nested_name() else: declId = None + self.skip_ws() + if typed and declId: + if self.skip_string("*"): + self.fail("Member pointers not implemented.") - suffixOpts = [] + arrayOps = [] while 1: self.skip_ws() if typed and self.skip_string('['): - startPos = self.pos - 1 - openCount = 1 - while not self.eof: - c = self.current_char - if c == '[': - openCount += 1 - elif c == ']': - openCount -= 1 - if openCount == 0: - break - self.pos += 1 - if self.eof: - self.pos = startPos - self.fail( - "Could not find closing square bracket for array.") - self.pos += 1 - suffixOpts.append(ASTArray( - self.definition[startPos + 1:self.pos - 1].strip())) + value = self._parse_expression(end=[']']) + res = self.skip_string(']') + assert res + arrayOps.append(ASTArray(value)) continue - if paramMode: - paramQual = self._parse_parameters_and_qualifiers(paramMode) - if paramQual: - suffixOpts.append(paramQual) - break + else: + break + paramQual = self._parse_parameters_and_qualifiers(paramMode) + return ASTDecleratorNameParamQual(declId=declId, arrayOps=arrayOps, + paramQual=paramQual) - return ASTDeclerator(ptrOps, declId, suffixOpts) + def _parse_declerator(self, named, paramMode, typed=True): + # 'typed' here means 'parse return type stuff' + if paramMode not in ('type', 'function', 'operatorCast'): + raise Exception( + "Internal error, unknown paramMode '%s'." % paramMode) + 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_declerator(named, paramMode, typed) + return ASTDeclaratorPtr(next=next, volatile=volatile, const=const) + # TODO: shouldn't we parse an R-value ref here first? + elif typed and self.skip_string("&"): + next = self._parse_declerator(named, paramMode, typed) + return ASTDeclaratorRef(next=next) + elif typed and self.skip_string("..."): + next = self._parse_declerator(named, paramMode, False) + return ASTDeclaratorParamPack(next=next) + elif 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 ASTDecleratorNameParamQual(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 ) + startPos = 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: + self.pos = startPos + 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_declerator(named, paramMode, typed) + if not self.skip_string(')'): + self.fail("Expected ')' in \"( ptr-declarator )\"") + next = self._parse_declerator(named=False, + paramMode="type", + typed=typed) + return ASTDeclaratorParen(inner=inner, next=next) + except DefinitionError as exNoPtrParen: + raise DefinitionError( + "If declId, parameters, and qualifiers {\n%s\n" + "} else If parenthesis in noptr-declarator {\n%s\n}" + % (exParamQual, exNoPtrParen)) + else: + return self._parse_declarator_name_param_qual(named, paramMode, + typed) def _parse_initializer(self, outer=None): self.skip_ws() @@ -1924,43 +2212,24 @@ class DefinitionParser(object): value = self.read_rest().strip() return ASTInitializer(value) elif outer is None: # function parameter - symbols = [] - startPos = self.pos - self.skip_ws() - if self.match(_string_re): - value = self.matched_text - return ASTInitializer(value) - while not self.eof: - if len(symbols) == 0 and self.current_char in (',', ')'): - break - elif len(symbols) > 0 and self.current_char == symbols[-1]: - symbols.pop() - elif self.current_char == '(': - symbols.append(')') - # TODO: actually implement nice handling of quotes, braces, - # brackets, parens, and whatever - self.pos += 1 - if self.eof: - self.pos = startPos - self.fail( - 'Could not find end of default value for function ' - 'parameter.') - value = self.definition[startPos:self.pos].strip() + value = self._parse_expression(end=[',', ')']) return ASTInitializer(value) else: - self.fail( - "Internal error, initializer for outer '%s' not " - "implemented." % outer) + self.fail("Internal error, initializer for outer '%s' not " + "implemented." % outer) - def _parse_type(self, outer=None, named=False, allowParams=False): + def _parse_type(self, named, outer=None): """ 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'): + if outer not in ('type', 'member', 'function', 'operatorCast'): raise Exception('Internal error, unknown outer "%s".' % outer) - assert not named + if outer != 'operatorCast': + assert named if outer in ('type', 'function'): # We allow type objects to just be a name. @@ -1990,8 +2259,8 @@ class DefinitionParser(object): 'Typedef-like expression error: %s') elif outer == 'function': desc = ('Error when parsing function declaration:\n' - 'Error if no return type: %s\n' - 'Error if return type: %s') + 'If no return type {\n%s\n' + '} else if return type {\n%s\n}') else: assert False raise DefinitionError( @@ -2002,22 +2271,21 @@ class DefinitionParser(object): # relieable save a traceback when an exception is # constructed?) self.pos = startPos - declSpecs = self._parse_decl_specs(outer=outer) - decl = self._parse_declerator(named=True, - paramMode=outer) + declSpecs = self._parse_decl_specs(outer=outer, typed=False) + decl = self._parse_declerator(named=True, paramMode=outer, + typed=False) else: - if outer: + paramMode = 'type' + if outer == 'member': # i.e., member named = True - allowParams = True - if allowParams: - paramMode = 'type' - else: - paramMode = None + elif outer == 'operatorCast': + paramMode = 'operatorCast' + outer = None declSpecs = self._parse_decl_specs(outer=outer) decl = self._parse_declerator(named=named, paramMode=paramMode) return ASTType(declSpecs, decl) - def _parse_type_with_init(self, outer=None, named=False): + def _parse_type_with_init(self, named, outer): if outer: assert outer in ('type', 'member', 'function') type = self._parse_type(outer=outer, named=named) @@ -2057,7 +2325,7 @@ class DefinitionParser(object): self.skip_ws() underlyingType = None if self.skip_string(':'): - underlyingType = self._parse_type() + underlyingType = self._parse_type(named=False) return ASTEnum(name, visibility, scoped, underlyingType) def _parse_enumerator(self): @@ -2070,17 +2338,17 @@ class DefinitionParser(object): return ASTEnumerator(name, init) def parse_type_object(self): - res = self._parse_type(outer='type') + res = self._parse_type(named=True, outer='type') res.objectType = 'type' return res def parse_member_object(self): - res = self._parse_type_with_init(outer='member') + res = self._parse_type_with_init(named=True, outer='member') res.objectType = 'member' return res def parse_function_object(self): - res = self._parse_type(outer='function') + res = self._parse_type(named=True, outer='function') res.objectType = 'function' return res @@ -2125,10 +2393,14 @@ class CPPObject(ObjectDescription): ] def add_target_and_index(self, ast, sig, signode): - ids = [ # the newest should be first - ast.get_id_v2(), - ast.get_id_v1() - ] + try: + id_v1 = ast.get_id_v1() + except NoOldIdError: + id_v1 = None + id_v2 = ast.get_id_v2() + # store them in reverse order, so the newest is first + ids = [id_v2, id_v1] + theid = ids[0] ast.newestId = theid assert theid # shouldn't be None @@ -2369,6 +2641,7 @@ class CPPXRefRole(XRefRole): title = title[dcolon + 2:] return title, target + class CPPDomain(Domain): """C++ language domain.""" name = 'cpp' diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 82def4296..7d08b61ab 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -13,7 +13,7 @@ from six import text_type from util import raises -from sphinx.domains.cpp import DefinitionParser, DefinitionError +from sphinx.domains.cpp import DefinitionParser, DefinitionError, NoOldIdError ids = [] @@ -42,15 +42,25 @@ def check(name, input, idv1output=None, idv2output=None, output=None): # Artificially set the prefixedName, otherwise the get_id fails. # It would usually have been set in handle_signarue. ast.prefixedName = ast.name - + if idv2output: idv2output = "_CPPv2" + idv2output - idv1 = ast.get_id_v1() - idv2 = ast.get_id_v2() + try: + idv1 = ast.get_id_v1() + assert idv1 != None + except NoOldIdError: + idv1 = None + try: + idv2 = ast.get_id_v2() + assert idv2 != None + except NoOldIdError: + idv2 = None if idv1 != idv1output or idv2 != idv2output: + print("input: %s" % text_type(input).rjust(20)) print(" %s %s" % ("Id v1".rjust(20), "Id v2".rjust(20))) print("result: %s %s" % (str(idv1).rjust(20), str(idv2).rjust(20))) - print("expected: %s %s" % (str(idv1output).rjust(20), str(idv2output).rjust(20))) + print("expected: %s %s" % (str(idv1output).rjust(20), + str(idv2output).rjust(20))) raise DefinitionError("") ids.append(ast.get_id_v2()) #print ".. %s:: %s" % (name, input) @@ -70,6 +80,7 @@ def test_type_definitions(): check("type", 'std::vector> module::blah', "module::blah", "N6module4blahE") check("type", "std::function F", "F", "1F") + check("type", "std::function F", "F", "1F") check("type", "std::function F", "F", "1F") check("type", "std::function F", "F", "1F") check("type", "MyContainer::const_iterator", @@ -102,10 +113,10 @@ def test_type_definitions(): check('function', 'bool namespaced::theclass::method(arg1, arg2)', "namespaced::theclass::method__arg1.arg2", "N10namespaced8theclass6methodE4arg14arg2") - x = 'std::vector> &module::test(register ' \ + x = 'std::vector> &module::test(register int ' \ 'foo, bar, std::string baz = "foobar, blah, bleh") const = 0' - check('function', x, "module::test__register.bar.ssC", - "NK6module4testE8register3barNSt6stringE") + check('function', x, "module::test__i.bar.ssC", + "NK6module4testEi3barNSt6stringE") check('function', 'void f(std::pair)', "f__std::pair:A.B:", "1fNSt4pairI1A1BEE") check('function', 'explicit module::myclass::foo::foo()', @@ -162,16 +173,16 @@ def test_type_definitions(): check('function', 'MyClass::a_member_function() const &', "MyClass::a_member_functionCR", "NKR7MyClass17a_member_functionEv") check('function', 'int main(int argc, char *argv[])', - "main__i.cPA", "4mainiPA_c") + "main__i.cPA", "4mainiA_Pc") check('function', 'MyClass &MyClass::operator++()', "MyClass::inc-operator", "N7MyClassppEv") check('function', 'MyClass::pointer MyClass::operator->()', "MyClass::pointer-operator", "N7MyClassptEv") - x = 'std::vector> &module::test(register ' \ + x = 'std::vector> &module::test(register int ' \ 'foo, bar[n], std::string baz = "foobar, blah, bleh") const = 0' - check('function', x, "module::test__register.barA.ssC", - "NK6module4testE8registerAn_3barNSt6stringE") + check('function', x, "module::test__i.barA.ssC", + "NK6module4testEiAn_3barNSt6stringE") check('function', 'int foo(Foo f = Foo(double(), std::make_pair(int(2), double(3.4))))', "foo__Foo", "3foo3Foo") @@ -179,11 +190,28 @@ def test_type_definitions(): raises(DefinitionError, parse, 'function', 'int foo(B b=x(a)') raises(DefinitionError, parse, 'function', 'int foo)C c=x(a))') raises(DefinitionError, parse, 'function', 'int foo(D d=x(a') - check('function', 'int foo(const A&... a)', "foo__ACRDp", "3fooRDpK1A") + check('function', 'int foo(const A&... a)', "foo__ACRDp", "3fooDpRK1A") check('function', 'virtual void f()', "f", "1fv") # test for ::nestedName, from issue 1738 check("function", "result(int val, ::std::error_category const &cat)", "result__i.std::error_categoryCR", "6resultiRNSt14error_categoryE") + check("function", "int *f()", "f", "1fv") + # tests derived from issue #1753 (skip to keep sanity) + # TODO: the v1 ids are speculative, check with older Sphinx version + check("function", "f(int (&array)[10])", None, "1fRA10_i") + check("function", "void f(int (&array)[10])", None, "1fRA10_i") + check("function", "void f(float *q(double))", None, "1fFPfdE") + check("function", "void f(float *(*q)(double))", None, "1fPFPfdE") + check("function", "void f(float (*q)(double))", None, "1fPFfdE") + check("function", "int (*f(double d))(float)", "f__double", "1fd") + check("function", "int (*f(bool b))[5]", "f__b", "1fb") + check("function", "int (*A::f(double d) const)(float)", + "A::f__doubleC", "NK1A1fEd") + check("function", "void f(std::shared_ptr ptr)", + None, "1fNSt10shared_ptrIFidEEE") + + # TODO: make tests for functions in a template, e.g., Test + # such that the id generation for function type types is correct. check('class', 'public A', "A", "1A", output='A') check('class', 'private A', "A", "1A") @@ -212,9 +240,9 @@ def test_operators(): "new-array-operator", "nav", output='void operator new[]()') check('function', 'void operator delete ()', "delete-operator", "dlv", output='void operator delete()') - check('function', 'void operator bool() const', - "castto-b-operatorC", "NKcvbEv", output='void operator bool() const') - + check('function', 'operator bool() const', + "castto-b-operatorC", "NKcvbEv", output='operator bool() const') + check('function', 'void operator * ()', "mul-operator", "mlv", output='void operator*()') check('function', 'void operator - ()', From bbf97fe8fdb0e37ec1e79ddda9e50a289c1f7ad2 Mon Sep 17 00:00:00 2001 From: Jakob Lykke Andersen Date: Thu, 5 Mar 2015 23:34:44 +0100 Subject: [PATCH 4/4] Last details. Closes sphinx-doc/sphinx#1753. --- sphinx/domains/cpp.py | 7 +++---- tests/test_domain_cpp.py | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py index a47054a3f..f8e2a1f8c 100644 --- a/sphinx/domains/cpp.py +++ b/sphinx/domains/cpp.py @@ -1178,8 +1178,7 @@ class ASTDeclaratorPtr(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mode(mode) signode += nodes.Text("*") - # TODO: if has ParamPack and hasDeclId: - # signode += nodes.Text(' ') + self.next.describe_signature(signode, mode, env) class ASTDeclaratorRef(ASTBase): @@ -1231,8 +1230,7 @@ class ASTDeclaratorRef(ASTBase): def describe_signature(self, signode, mode, env): _verify_description_mode(mode) signode += nodes.Text("&") - # TODO: if has ParamPack and hasDeclId: - # signode += nodes.Text(' ') + self.next.describe_signature(signode, mode, env) class ASTDeclaratorParamPack(ASTBase): @@ -1289,6 +1287,7 @@ class ASTDeclaratorParamPack(ASTBase): signode += nodes.Text("...") if self.next.name: signode += nodes.Text(' ') + self.next.describe_signature(signode, mode, env) class ASTDeclaratorParen(ASTBase): diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 7d08b61ab..93f1c153a 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -197,7 +197,6 @@ def test_type_definitions(): "result__i.std::error_categoryCR", "6resultiRNSt14error_categoryE") check("function", "int *f()", "f", "1fv") # tests derived from issue #1753 (skip to keep sanity) - # TODO: the v1 ids are speculative, check with older Sphinx version check("function", "f(int (&array)[10])", None, "1fRA10_i") check("function", "void f(int (&array)[10])", None, "1fRA10_i") check("function", "void f(float *q(double))", None, "1fFPfdE")