Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C++] Support attributes on class and union and improve formatting #10252

Merged
merged 12 commits into from Mar 12, 2022
61 changes: 52 additions & 9 deletions sphinx/domains/cpp.py
Expand Up @@ -267,7 +267,8 @@
class_object:
goal: a class declaration, but with specification of a base class
grammar:
nested-name "final"[opt] (":" base-specifier-list)[opt]
attribute-specifier-seq[opt]
nested-name "final"[opt] (":" base-specifier-list)[opt]
base-specifier-list ->
base-specifier "..."[opt]
| base-specifier-list, base-specifier "..."[opt]
Expand All @@ -281,7 +282,8 @@
goal: an unscoped enum or a scoped enum, optionally with the underlying
type specified
grammar:
("class" | "struct")[opt] visibility[opt] nested-name (":" type)[opt]
("class" | "struct")[opt] visibility[opt]
attribute-specifier-seq[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.
Expand Down Expand Up @@ -3318,16 +3320,20 @@ def describe_signature(self, signode: TextElement, mode: str,


class ASTClass(ASTBase):
def __init__(self, name: ASTNestedName, final: bool, bases: List[ASTBaseClass]) -> None:
def __init__(self, name: ASTNestedName, final: bool, bases: List[ASTBaseClass],
attrs: Optional[List[ASTAttribute]] = None) -> None:
jbms marked this conversation as resolved.
Show resolved Hide resolved
self.name = name
self.final = final
self.bases = bases
self.attrs = attrs or []
jbms marked this conversation as resolved.
Show resolved Hide resolved

def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str:
return symbol.get_full_nested_name().get_id(version)

def _stringify(self, transform: StringifyTransform) -> str:
res = []
for attr in self.attrs:
res.append(transform(attr) + ' ')
res.append(transform(self.name))
if self.final:
res.append(' final')
Expand All @@ -3344,6 +3350,9 @@ def _stringify(self, transform: StringifyTransform) -> str:
def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
for attr in self.attrs:
attr.describe_signature(signode)
signode += addnodes.desc_sig_space()
self.name.describe_signature(signode, mode, env, symbol=symbol)
if self.final:
signode += addnodes.desc_sig_space()
Expand All @@ -3361,29 +3370,40 @@ def describe_signature(self, signode: TextElement, mode: str,


class ASTUnion(ASTBase):
def __init__(self, name: ASTNestedName) -> None:
def __init__(self, name: ASTNestedName,
attrs: Optional[List[ASTAttribute]] = None) -> None:
jbms marked this conversation as resolved.
Show resolved Hide resolved
self.name = name
self.attrs = attrs or []
jbms marked this conversation as resolved.
Show resolved Hide resolved

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

def _stringify(self, transform: StringifyTransform) -> str:
return transform(self.name)
res = []
for attr in self.attrs:
res.append(transform(attr) + ' ')
res.append(transform(self.name))
return ''.join(res)

def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
for attr in self.attrs:
attr.describe_signature(signode)
signode += addnodes.desc_sig_space()
self.name.describe_signature(signode, mode, env, symbol=symbol)


class ASTEnum(ASTBase):
def __init__(self, name: ASTNestedName, scoped: str,
underlyingType: ASTType) -> None:
underlyingType: ASTType,
attrs: Optional[List[ASTAttribute]] = None) -> None:
jbms marked this conversation as resolved.
Show resolved Hide resolved
self.name = name
self.scoped = scoped
self.underlyingType = underlyingType
self.attrs = attrs or []
jbms marked this conversation as resolved.
Show resolved Hide resolved

def get_id(self, version: int, objectType: str, symbol: "Symbol") -> str:
if version == 1:
Expand All @@ -3395,6 +3415,8 @@ def _stringify(self, transform: StringifyTransform) -> str:
if self.scoped:
res.append(self.scoped)
res.append(' ')
for attr in self.attrs:
res.append(transform(attr) + ' ')
res.append(transform(self.name))
if self.underlyingType:
res.append(' : ')
Expand All @@ -3405,6 +3427,9 @@ def describe_signature(self, signode: TextElement, mode: str,
env: "BuildEnvironment", symbol: "Symbol") -> None:
verify_description_mode(mode)
# self.scoped has been done by the CPPEnumObject
for attr in self.attrs:
attr.describe_signature(signode)
signode += addnodes.desc_sig_space()
self.name.describe_signature(signode, mode, env, symbol=symbol)
if self.underlyingType:
signode += addnodes.desc_sig_space()
Expand Down Expand Up @@ -6567,6 +6592,12 @@ def _parse_concept(self) -> ASTConcept:
return ASTConcept(nestedName, initializer)

def _parse_class(self) -> ASTClass:
attrs = []
while 1:
attr = self._parse_attribute()
if attr is None:
break
attrs.append(attr)
name = self._parse_nested_name()
self.skip_ws()
final = self.skip_word_and_ws('final')
Expand Down Expand Up @@ -6594,21 +6625,33 @@ def _parse_class(self) -> ASTClass:
continue
else:
break
return ASTClass(name, final, bases)
return ASTClass(name, final, bases, attrs)

def _parse_union(self) -> ASTUnion:
attrs = []
while 1:
attr = self._parse_attribute()
if attr is None:
break
attrs.append(attr)
name = self._parse_nested_name()
return ASTUnion(name)
return ASTUnion(name, attrs)

def _parse_enum(self) -> ASTEnum:
scoped = None # is set by CPPEnumObject
attrs = []
while 1:
attr = self._parse_attribute()
if attr is None:
break
attrs.append(attr)
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)
return ASTEnum(name, scoped, underlyingType, attrs)

def _parse_enumerator(self) -> ASTEnumerator:
name = self._parse_nested_name()
Expand Down
6 changes: 4 additions & 2 deletions sphinx/util/cfamily.py
Expand Up @@ -15,6 +15,7 @@
from docutils import nodes
from docutils.nodes import TextElement

import sphinx.addnodes
jbms marked this conversation as resolved.
Show resolved Hide resolved
from sphinx.config import Config
from sphinx.util import logging

Expand Down Expand Up @@ -134,8 +135,9 @@ def _stringify(self, transform: StringifyTransform) -> str:
return "[[" + self.arg + "]]"

def describe_signature(self, signode: TextElement) -> None:
txt = str(self)
signode.append(nodes.Text(txt, txt))
signode.append(sphinx.addnodes.desc_sig_punctuation('[[', '[['))
signode.append(sphinx.addnodes.desc_sig_keyword(self.arg, self.arg))
signode.append(sphinx.addnodes.desc_sig_punctuation(']]', ']]'))
jbms marked this conversation as resolved.
Show resolved Hide resolved


class ASTGnuAttribute(ASTBaseBase):
Expand Down
9 changes: 9 additions & 0 deletions tests/test_domain_cpp.py
Expand Up @@ -996,6 +996,15 @@ def test_domain_cpp_ast_attributes():
# position: parameters and qualifiers
check('function', 'void f() [[attr1]] [[attr2]]', {1: 'f', 2: '1fv'})

# position: class
check('class', '{key}[[nodiscard]] Foo', {1: 'Foo', 2: '3Foo', 3: '3Foo', 4: '3Foo'},
key='class')
jbms marked this conversation as resolved.
Show resolved Hide resolved
check('union', '{key}[[nodiscard]] Foo', {1: None, 2: '3Foo', 3: '3Foo', 4: '3Foo'},
key='union')
jbms marked this conversation as resolved.
Show resolved Hide resolved
# position: enum
check('enum', '{key}[[nodiscard]] Foo', {1: None, 2: '3Foo', 3: '3Foo', 4: '3Foo'},
key='enum')
jbms marked this conversation as resolved.
Show resolved Hide resolved


def test_domain_cpp_ast_xref_parsing():
def check(target):
Expand Down