Repository: sphinx-doc/sphinx

Issue:
CUDA Function Decorators Give Warnings
Subject: When compiling the markup files for CUDA code, I see the warnings.


### Problem
The sphinx compiler shows:

Invalid definition: Expected identifier in nested name, got keyword: void [error at 15]
      __global__ void TL::est(float * a)

I'm guessing this is because the parser doesn't recognize the __device__ and __global__ decorators since it's cuda-specific. Is there a workaround for this? There should be two underscores on global and device, but github's markdown seems to make it bold.


Discussion:

Patch:
diff --git a/CHANGES b/CHANGES
index c693ef190..784901fc8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -210,6 +210,7 @@ Bugs fixed
 * #344: autosummary does not understand docstring of module level attributes
 * #5191: C++, prevent nested declarations in functions to avoid lookup problems.
 * #5126: C++, add missing isPack method for certain template parameter types.
+* #5187: C++, parse attributes on declerators as well.
 * #5002: graphviz: SVGs do not adapt to the column width
 
 Testing
diff --git a/sphinx/domains/cpp.py b/sphinx/domains/cpp.py
index 2db416376..598386c7b 100644
--- a/sphinx/domains/cpp.py
+++ b/sphinx/domains/cpp.py
@@ -2539,12 +2539,13 @@ def describe_signature(self, signode, mode, env, symbol):
 
 
 class ASTDeclaratorPtr(ASTBase):
-    def __init__(self, next, volatile, const):
-        # type: (Any, bool, bool) -> None
+    def __init__(self, next, volatile, const, attrs):
+        # type: (Any, bool, bool, Any) -> None
         assert next
         self.next = next
         self.volatile = volatile
         self.const = const
+        self.attrs = attrs
 
     @property
     def name(self):
@@ -2563,13 +2564,17 @@ def require_space_after_declSpecs(self):
 
     def _stringify(self, transform):
         res = ['*']  # type: List[unicode]
+        for a in self.attrs:
+            res.append(transform(a))
+        if len(self.attrs) > 0 and (self.volatile or self.const):
+            res.append(' ')
         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.const or self.volatile or len(self.attrs) > 0:
             if self.next.require_space_after_declSpecs:
                 res.append(' ')
         res.append(transform(self.next))
@@ -2621,6 +2626,10 @@ def describe_signature(self, signode, mode, env, symbol):
         # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
         _verify_description_mode(mode)
         signode += nodes.Text("*")
+        for a in self.attrs:
+            a.describe_signature(signode)
+        if len(self.attrs) > 0 and (self.volatile or self.const):
+            signode += nodes.Text(' ')
 
         def _add_anno(signode, text):
             signode += addnodes.desc_annotation(text, text)
@@ -2630,17 +2639,18 @@ def _add_anno(signode, text):
             if self.volatile:
                 signode += nodes.Text(' ')
             _add_anno(signode, 'const')
-        if self.const or self.volatile:
+        if self.const or self.volatile or len(self.attrs) > 0:
             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
+    def __init__(self, next, attrs):
+        # type: (Any, Any) -> None
         assert next
         self.next = next
+        self.attrs = attrs
 
     @property
     def name(self):
@@ -2661,7 +2671,13 @@ def require_space_after_declSpecs(self):
         return self.next.require_space_after_declSpecs()
 
     def _stringify(self, transform):
-        return '&' + transform(self.next)
+        res = ['&']
+        for a in self.attrs:
+            res.append(transform(a))
+        if len(self.attrs) > 0 and self.next.require_space_after_declSpecs:
+            res.append(' ')
+        res.append(transform(self.next))
+        return u''.join(res)
 
     def get_modifiers_id(self, version):
         # type: (int) -> unicode
@@ -2692,6 +2708,10 @@ def describe_signature(self, signode, mode, env, symbol):
         # type: (addnodes.desc_signature, unicode, BuildEnvironment, Symbol) -> None
         _verify_description_mode(mode)
         signode += nodes.Text("&")
+        for a in self.attrs:
+            a.describe_signature(signode)
+        if len(self.attrs) > 0 and self.next.require_space_after_declSpecs:
+            signode += nodes.Text(' ')
         self.next.describe_signature(signode, mode, env, symbol)
 
 
@@ -5151,6 +5171,7 @@ def _parse_declarator(self, named, paramMode, typed=True):
             self.skip_ws()
             volatile = False
             const = False
+            attrs = []
             while 1:
                 if not volatile:
                     volatile = self.skip_word_and_ws('volatile')
@@ -5160,13 +5181,23 @@ def _parse_declarator(self, named, paramMode, typed=True):
                     const = self.skip_word_and_ws('const')
                     if const:
                         continue
+                attr = self._parse_attribute()
+                if attr is not None:
+                    attrs.append(attr)
+                    continue
                 break
             next = self._parse_declarator(named, paramMode, typed)
-            return ASTDeclaratorPtr(next=next, volatile=volatile, const=const)
+            return ASTDeclaratorPtr(next=next, volatile=volatile, const=const, attrs=attrs)
         # TODO: shouldn't we parse an R-value ref here first?
         if typed and self.skip_string("&"):
+            attrs = []
+            while 1:
+                attr = self._parse_attribute()
+                if attr is None:
+                    break
+                attrs.append(attr)
             next = self._parse_declarator(named, paramMode, typed)
-            return ASTDeclaratorRef(next=next)
+            return ASTDeclaratorRef(next=next, attrs=attrs)
         if typed and self.skip_string("..."):
             next = self._parse_declarator(named, paramMode, False)
             return ASTDeclaratorParamPack(next=next)


Test patch:
diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py
index 06856cebc..89c37aa3c 100644
--- a/tests/test_domain_cpp.py
+++ b/tests/test_domain_cpp.py
@@ -665,6 +665,12 @@ def test_attributes():
     check('function', 'static inline __attribute__(()) void f()',
           {1: 'f', 2: '1fv'},
           output='__attribute__(()) static inline void f()')
+    # position: declarator
+    check('member', 'int *[[attr]] i', {1: 'i__iP', 2:'1i'})
+    check('member', 'int *const [[attr]] volatile i', {1: 'i__iPVC', 2: '1i'},
+          output='int *[[attr]] volatile const i')
+    check('member', 'int &[[attr]] i', {1: 'i__iR', 2: '1i'})
+    check('member', 'int *[[attr]] *i', {1: 'i__iPP', 2: '1i'})
 
 
 # def test_print():