Skip to content

Commit 7ba00df

Browse files
committed
Fix handling of typedef unions and function pointers
Typedef unions were not properly being parsed when the member types were unknown. These will now be parsed, but the types will fall back to "int", per clang. typedef of functions or function pointers with unknown types were not properly being parsed now they will come back though they will default to int return types for unknown types.
1 parent 1f66113 commit 7ba00df

File tree

3 files changed

+93
-3
lines changed

3 files changed

+93
-3
lines changed

src/sphinx_c_autodoc/loader.py

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,12 @@ def get_soup_declaration(self) -> Optional[str]:
203203

204204
root = self.soup.contents[0]
205205

206-
return root.declaration.text
206+
# It seems with different versions of clang the newlines will at times
207+
# be kept around from some declarations. This causes problems with
208+
# sphinx as the signature should remain all in one line.
209+
lines = root.declaration.text.splitlines()
210+
declaration = " ".join(l.strip() for l in lines)
211+
return declaration
207212

208213
@property
209214
def soup(self) -> Optional[BeautifulSoup]:
@@ -507,6 +512,27 @@ def get_parsed_declaration(self) -> str:
507512
For things like functions and others this will include the return type.
508513
"""
509514
parent_type = self.node.underlying_typedef_type.spelling
515+
516+
# Function prototypes need to be handled different. When clang can't
517+
# successfully parse the file it falls back to naming the return type
518+
# as the display name.
519+
# Unfortunatly some versions of clang behave a little differently, some
520+
# will return a POINTER while others will return FUNCITONNOPROTO. The
521+
# POINTER's are easy to derive the real type from, but the test
522+
# environment doesn't use that version of clang.
523+
type_ = self.node.underlying_typedef_type
524+
if type_.kind == cindex.TypeKind.POINTER: # pragma: no cover
525+
type_ = type_.get_pointee()
526+
527+
if type_.kind in (
528+
cindex.TypeKind.FUNCTIONPROTO,
529+
cindex.TypeKind.FUNCTIONNOPROTO,
530+
):
531+
ret_value, paren, signature = parent_type.partition(")")
532+
signature = "".join((ret_value, self.name, paren, signature))
533+
534+
return f"typedef {signature}"
535+
510536
return f"typedef {parent_type} {self.name}"
511537

512538

@@ -522,7 +548,7 @@ def soup(self) -> None:
522548
"""
523549
Since structures like objects use the "Members:" and
524550
"Enumerations:" sections do *not* use the clang xml comments as they
525-
don't preseve newlines, so the sections get lost.
551+
don't preserve newlines, so the sections get lost.
526552
"""
527553
return None
528554

@@ -664,6 +690,7 @@ def get_nested_node(cursor: Cursor) -> Cursor:
664690
underlying_node = next(cursor.get_children())
665691
if underlying_node.kind in (
666692
cindex.CursorKind.STRUCT_DECL,
693+
cindex.CursorKind.UNION_DECL,
667694
cindex.CursorKind.ENUM_DECL,
668695
):
669696
return underlying_node

tests/assets/c_source_2/nested/types.c

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,4 +97,34 @@ struct nested_struct
9797
int nested_two;
9898
} two;
9999
float three;
100-
};
100+
};
101+
102+
/**
103+
* A typedefed union
104+
*/
105+
typedef union
106+
{
107+
unknown_type one;
108+
another_unknown two;
109+
} a_union_typedef;
110+
111+
/**
112+
* A function type with unknown return type. This will for the generic parsing
113+
* to happen instead of the clang soup
114+
*/
115+
116+
typedef unknown_return_type (function_type)(const unknown_type * foo, const unknonw_two * yes);
117+
118+
/**
119+
* A function pointer type with unknown return type
120+
*/
121+
typedef what (*function_pointer_type)(const int * hello, const foo_type baz);
122+
123+
/**
124+
* A function pointer wrapped on multiple lines.
125+
*/
126+
typedef int (*wrapped_function_pointer)
127+
(
128+
const int * hello,
129+
const float baz
130+
);

tests/directives/test_autoctype.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,35 @@ class TestAutoCType:
106106
float three
107107
The third member of parent struct"""
108108

109+
# Not fond of the int, but clang reduces it to this, need to find a way to
110+
# read those back when clang fails.
111+
a_union_typedef = """\
112+
union a_union_typedef
113+
A typedefed union
114+
115+
int one
116+
117+
118+
int two
119+
"""
120+
121+
# Similar to the unknown members of the union clang just falls back to
122+
# int's
123+
function_type = """\
124+
typedef intunknown_return_type
125+
A function type with unknown return type. This will for the generic parsing
126+
to happen instead of the clang soup"""
127+
128+
# Similar to the unknown members of the union clang just falls back to
129+
# int's
130+
function_pointer_type = """\
131+
typedef intint\xa0*what
132+
A function pointer type with unknown return type"""
133+
134+
wrapped_function_pointer = """\
135+
typedef int (*wrapped_function_pointer)const int *, const float
136+
A function pointer wrapped on multiple lines."""
137+
109138
doc_data = [
110139
("types.c::my_int", my_int),
111140
("types.c::my_struct_type", my_struct_type),
@@ -116,6 +145,10 @@ class TestAutoCType:
116145
("types.c::a_union_type", a_union_type),
117146
("types.c::a_multiply_documented_union_type", a_multiply_documented_union_type),
118147
("types.c::nested_struct", nested_struct),
148+
("types.c::a_union_typedef", a_union_typedef),
149+
("types.c::unknown_return_type", function_type),
150+
("types.c::what", function_pointer_type),
151+
("types.c::wrapped_function_pointer", wrapped_function_pointer),
119152
]
120153

121154
@pytest.mark.parametrize("type_, expected_doc", doc_data)

0 commit comments

Comments
 (0)