Repository: sphinx-doc/sphinx

Issue:
Cross-referencing a function that returns a pointer with :cpp:func: causes error "Invalid definition: Expected end of definition"
**Describe the bug**
While using the :cpp:func: directive to cross-reference a C++ function that returns a pointer, you will receivean error that the * character in the reference is "unparseable". This makes it impossible to cross-reference a function with a pointer return value

**To Reproduce**
Steps to reproduce the behavior:

Source (C++): 
```
class Foo
{
    public:
        Foo* Bar();
}   
```
Sphinx (reStructuredText):
```
:cpp:func:`Foo* Foo::Bar()`
```

Result:
```
WARNING: Unpareseable C++ cross-reference: 'Foo*'
Invalid definition: Expected end of definition. [error at 4]
   Foo*
   ---^
```

**Expected behavior**
The expected behavior is to output a properly cross-referenced link to the Foo* Foo::Bar() function.

**Your project**
N/A

**Screenshots**
N/A

**Environment info**
- OS: [e.g. Unix/Linux/Mac/Win/other with version]
- Python version: 3.7.0
- Sphinx version: 2.0.0+
- Sphinx extensions:  breathe
- Extra tools: N/A

Discussion:
tk0miya: Is this critical bug?

After 2.0 release, we've changed branch model:
https://github.com/sphinx-doc/sphinx/blob/44c49f462c91870c0ef5e3e7dba74cba3edb8e12/CONTRIBUTING.rst#L323-L345

Therefore, `2.0.1` is much better for critical bug fixes.
codecov[bot]: # [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=h1) Report
> Merging [#6226](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=desc) into [2.0](https://codecov.io/gh/sphinx-doc/sphinx/commit/a99d749c56b80613e4a838fbed6120e968266a60?src=pr&el=desc) will **increase** coverage by `0.03%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226/graphs/tree.svg?width=650&token=VFlFSELbkw&height=150&src=pr)](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=tree)

```diff
@@            Coverage Diff            @@
##             2.0    #6226      +/-   ##
=========================================
+ Coverage   83.5%   83.53%   +0.03%     
=========================================
  Files        275      275              
  Lines      39501    39513      +12     
  Branches    5881     5881              
=========================================
+ Hits       32984    33009      +25     
+ Misses      5184     5171      -13     
  Partials    1333     1333
```


| [Impacted Files](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [sphinx/domains/cpp.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226/diff?src=pr&el=tree#diff-c3BoaW54L2RvbWFpbnMvY3BwLnB5) | `82.91% <100%> (+0.28%)` | :arrow_up: |
| [tests/test\_domain\_cpp.py](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9kb21haW5fY3BwLnB5) | `96.21% <100%> (+0.07%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=footer). Last update [a99d749...8854580](https://codecov.io/gh/sphinx-doc/sphinx/pull/6226?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/CHANGES b/CHANGES
index 545cdff8e..64b79ab6e 100644
--- a/CHANGES
+++ b/CHANGES
@@ -22,6 +22,7 @@ Bugs fixed
   - sphinx.application.CONFIG_FILENAME
   - :confval:`viewcode_import`
 
+* #6208: C++, properly parse full xrefs that happen to have a short xref as prefix.
 * #6220, #6225: napoleon: AttributeError is raised for raised section having
   references
 * #6245: circular import error on importing SerializingHTMLBuilder
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index d17e67f82..03e91bcee 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -6391,6 +6391,7 @@ def parse_xref_object(self):
             # if there are '()' left, just skip them
             self.skip_ws()
             self.skip_string('()')
+            self.assert_end()
             templatePrefix = self._check_template_consistency(name, templatePrefix,
                                                               fullSpecShorthand=True)
             res1 = ASTNamespace(name, templatePrefix)
@@ -6403,6 +6404,7 @@ def parse_xref_object(self):
                 # if there are '()' left, just skip them
                 self.skip_ws()
                 self.skip_string('()')
+                self.assert_end()
                 return res2, False
             except DefinitionError as e2:
                 errs = []
@@ -7145,7 +7147,6 @@ def warn(self, msg):
         parser = DefinitionParser(target, warner, env.config)
         try:
             ast, isShorthand = 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':
@@ -7154,7 +7155,6 @@ def findWarning(e):  # as arg to stop flake8 from complaining
                 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


Test patch:
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index 46019b4a9..d6470dc7c 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -755,6 +755,20 @@ def test_attributes():
     check('member', 'int *[[attr]] *i', {1: 'i__iPP', 2: '1i'})
 
 
+def test_xref_parsing():
+    def check(target):
+        class Config:
+            cpp_id_attributes = ["id_attr"]
+            cpp_paren_attributes = ["paren_attr"]
+        parser = DefinitionParser(target, None, Config())
+        ast, isShorthand = parser.parse_xref_object()
+        parser.assert_end()
+    check('f')
+    check('f()')
+    check('void f()')
+    check('T f()')
+
+
 # def test_print():
 #     # used for getting all the ids out for checking
 #     for a in ids: