Skip to content

Commit 4bcadba

Browse files
authored
Merge pull request #216 from robotpy/2024-beta
Various features required for beta
2 parents 51950e6 + 2d3cac9 commit 4bcadba

32 files changed

+449
-57
lines changed

.github/workflows/dist.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ jobs:
7171
- '3.9'
7272
- '3.10'
7373
- '3.11'
74+
- '3.12'
7475
architecture: [x86, x64]
7576
exclude:
7677
- os: macos-latest

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[build-system]
2-
requires = ["setuptools>=45,<64", "wheel", "setuptools_scm>=6.2,<8"]
2+
requires = ["setuptools>=45", "wheel", "setuptools_scm>=6.2,<8"]
33
build-backend = "setuptools.build_meta"
44

55
[tool.setuptools_scm]

robotpy_build/autowrap/cls_tmpl_impl.hpp.j2

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
#}
44
{% import "pybind11.cpp.j2" as pybind11 %}
55

6+
{% for inc in type_caster_includes %}
7+
#include <{{ inc }}>
8+
{% endfor %}
9+
610
namespace rpygen {
711

812
{% if cls.namespace %}

robotpy_build/autowrap/cls_trampoline.hpp.j2

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727

2828
{%- set trampoline = cls.trampoline -%}
2929
{%- if cls.template -%}
30+
{%- set template_argument_list = cls.template.argument_list -%}
3031
{%- set template_parameter_list = cls.template.parameter_list -%}
3132
{%- else -%}
33+
{%- set template_argument_list = "" %}
3234
{%- set template_parameter_list = "" %}
3335
{%- endif -%}
3436

@@ -63,20 +65,14 @@ using {{ decl.format() }};
6365
and so if it's not defined in this config, then it falls back to the
6466
parent configuration)
6567
#}
68+
template <{{ postcomma(template_parameter_list) }}typename CfgBase = EmptyTrampolineCfg>
69+
struct PyTrampolineCfg_{{ cls.full_cpp_name_identifier }} :
6670
{% if cls.bases %}
67-
template <{{ postcomma(trampoline.tmpl_params) }}typename CfgBase>
68-
using PyTrampolineCfgBase_{{ cls.full_cpp_name_identifier }} =
6971
{% for base in cls.bases %}
7072
PyTrampolineCfg_{{ base.full_cpp_name_identifier }}<{{ postcomma(base.template_params) }}
7173
{% endfor %}
7274
CfgBase
73-
{% for base in cls.bases %}>{% endfor %};
74-
{% endif %}
75-
76-
template <{{ postcomma(template_parameter_list) }}typename CfgBase = EmptyTrampolineCfg>
77-
struct PyTrampolineCfg_{{ cls.full_cpp_name_identifier }} :
78-
{% if cls.bases %}
79-
PyTrampolineCfgBase_{{ cls.full_cpp_name_identifier }}<{{ postcomma(trampoline.tmpl_args) }} CfgBase>
75+
{% for base in cls.bases %}>{% endfor %}
8076
{% else %}
8177
CfgBase
8278
{% endif %}
@@ -97,7 +93,7 @@ struct PyTrampolineCfg_{{ cls.full_cpp_name_identifier }} :
9793
9894
PyTrampolineBase is another trampoline or our base class
9995
#}
100-
template <typename PyTrampolineBase{{ precomma(trampoline.tmpl_params) }}, typename PyTrampolineCfg>
96+
template <typename PyTrampolineBase{{ precomma(template_parameter_list) }}, typename PyTrampolineCfg>
10197
using PyTrampolineBase_{{ cls.full_cpp_name_identifier }} =
10298
{% for base in cls.bases %}
10399
PyTrampoline_{{ base.full_cpp_name_identifier }}<
@@ -111,8 +107,8 @@ using PyTrampolineBase_{{ cls.full_cpp_name_identifier }} =
111107
;
112108

113109
template <typename PyTrampolineBase{{ precomma(template_parameter_list) }}, typename PyTrampolineCfg>
114-
struct PyTrampoline_{{ cls.full_cpp_name_identifier }} : PyTrampolineBase_{{ cls.full_cpp_name_identifier }}<PyTrampolineBase{{ precomma(trampoline.tmpl_args) }}, PyTrampolineCfg> {
115-
using PyTrampolineBase_{{ cls.full_cpp_name_identifier }}<PyTrampolineBase{{ precomma(trampoline.tmpl_args) }}, PyTrampolineCfg>::PyTrampolineBase_{{ cls.full_cpp_name_identifier }};
110+
struct PyTrampoline_{{ cls.full_cpp_name_identifier }} : PyTrampolineBase_{{ cls.full_cpp_name_identifier }}<PyTrampolineBase{{ precomma(template_argument_list) }}, PyTrampolineCfg> {
111+
using PyTrampolineBase_{{ cls.full_cpp_name_identifier }}<PyTrampolineBase{{ precomma(template_argument_list) }}, PyTrampolineCfg>::PyTrampolineBase_{{ cls.full_cpp_name_identifier }};
116112
{% else %}
117113
template <typename PyTrampolineBase{{ precomma(template_parameter_list) }}, typename PyTrampolineCfg>
118114
struct PyTrampoline_{{ cls.full_cpp_name_identifier }} : PyTrampolineBase, virtual py::trampoline_self_life_support {

robotpy_build/autowrap/cxxparser.py

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
AnonymousName,
3030
Array,
3131
ClassDecl,
32+
Concept,
3233
DecoratedType,
3334
EnumDecl,
3435
Field,
@@ -381,6 +382,9 @@ def on_namespace_alias(
381382
# TODO: add to some sort of resolver?
382383
pass
383384

385+
def on_concept(self, state: AWNonClassBlockState, concept: Concept) -> None:
386+
pass
387+
384388
def on_forward_decl(self, state: AWState, fdecl: ForwardDecl) -> None:
385389
# TODO: add to some sort of resolver?
386390
pass
@@ -447,6 +451,16 @@ def on_using_declaration(self, state: AWState, using: UsingDecl) -> None:
447451

448452
if using.access is None:
449453
self.hctx.using_declarations.append(using.typename)
454+
elif isinstance(state, ClassBlockState):
455+
# A using declaration might bring in a colliding name for a function,
456+
# so mark it as overloaded
457+
lseg = using.typename.segments[-1]
458+
if isinstance(lseg, NameSpecifier):
459+
cdata = state.user_data
460+
name = lseg.name
461+
self.gendata.add_using_decl(
462+
name, cdata.cls_key, cdata.data, state.access == "private"
463+
)
450464

451465
#
452466
# Enums
@@ -566,7 +580,11 @@ def on_class_start(self, state: AWClassBlockState) -> typing.Optional[bool]:
566580
):
567581
return False
568582

569-
cls_key, cls_name, cls_namespace, parent_ctx = self._process_class_name(state)
583+
cls_name_result = self._process_class_name(state)
584+
if cls_name_result is None:
585+
return False
586+
587+
cls_key, cls_name, cls_namespace, parent_ctx = cls_name_result
570588
class_data = self.gendata.get_class_data(cls_key)
571589

572590
# Ignore explicitly ignored classes
@@ -647,6 +665,7 @@ def on_class_start(self, state: AWClassBlockState) -> typing.Optional[bool]:
647665
template_parameter_list = ", ".join(template_params)
648666

649667
template_data = ClassTemplateData(
668+
argument_list=template_argument_list,
650669
parameter_list=template_parameter_list,
651670
inline_code=class_data.template_inline_code,
652671
)
@@ -746,23 +765,24 @@ def on_class_start(self, state: AWClassBlockState) -> typing.Optional[bool]:
746765

747766
def _process_class_name(
748767
self, state: AWClassBlockState
749-
) -> typing.Tuple[str, str, str, typing.Optional[ClassContext]]:
768+
) -> typing.Optional[typing.Tuple[str, str, str, typing.Optional[ClassContext]]]:
750769
class_decl = state.class_decl
751770
segments = class_decl.typename.segments
752771
assert len(segments) > 0
753772

754-
name_segment = segments[-1]
755-
if not isinstance(name_segment, NameSpecifier):
756-
raise ValueError(f"not sure how to handle '{class_decl.typename}'")
757-
758-
cls_name = name_segment.name
773+
segment_names: typing.List[str] = []
774+
for segment in segments:
775+
if not isinstance(segment, NameSpecifier):
776+
raise ValueError(
777+
f"not sure how to handle '{class_decl.typename.format()}'"
778+
)
779+
# ignore specializations for now
780+
if segment.specialization is not None:
781+
return None
782+
segment_names.append(segment.name)
759783

760-
# for now, don't support these?
761-
other_segments = segments[:-1]
762-
if other_segments:
763-
raise ValueError(
764-
f"not sure what to do with compound name '{class_decl.typename}'"
765-
)
784+
cls_name = segment_names[-1]
785+
extra_segments = "::".join(segment_names[:-1])
766786

767787
parent_ctx: typing.Optional[ClassContext] = None
768788

@@ -772,13 +792,18 @@ def _process_class_name(
772792
# easy case -- namespace is the next user_data up
773793
cls_key = cls_name
774794
cls_namespace = typing.cast(str, parent.user_data)
795+
if extra_segments:
796+
cls_namespace = f"{cls_namespace}::{extra_segments}"
775797
else:
776798
# Use things the parent already computed
777799
cdata = typing.cast(ClassStateData, parent.user_data)
778800
# parent: AWClassBlockState = state.parent
779801
parent_ctx = cdata.ctx
780802
# the parent context already computed namespace, so use that
781-
cls_key = f"{cdata.cls_key}::{cls_name}"
803+
if extra_segments:
804+
cls_key = f"{cdata.cls_key}::{extra_segments}::{cls_name}"
805+
else:
806+
cls_key = f"{cdata.cls_key}::{cls_name}"
782807
cls_namespace = parent_ctx.namespace
783808

784809
return cls_key, cls_name, cls_namespace, parent_ctx
@@ -1028,6 +1053,23 @@ def _on_class_method(
10281053
self.hctx.need_operators_h = True
10291054
if method_data.no_release_gil is None:
10301055
fctx.release_gil = False
1056+
1057+
# Use cpp_code to setup the operator
1058+
if fctx.cpp_code is None:
1059+
if len(method.parameters) == 0:
1060+
fctx.cpp_code = f"{operator} py::self"
1061+
else:
1062+
ptype, _, _, _ = _count_and_unwrap(method.parameters[0].type)
1063+
if (
1064+
isinstance(ptype, Type)
1065+
and isinstance(ptype.typename.segments[-1], NameSpecifier)
1066+
and ptype.typename.segments[-1].name == cdata.ctx.cpp_name
1067+
):
1068+
# don't try to predict the type, use py::self instead
1069+
fctx.cpp_code = f"py::self {operator} py::self"
1070+
else:
1071+
fctx.cpp_code = f"py::self {operator} {fctx.all_params[0].cpp_type_no_const}()"
1072+
10311073
if method.const:
10321074
fctx.const = True
10331075
if method.static:

robotpy_build/autowrap/generator_data.py

Lines changed: 69 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ class ClsReportData:
4040
functions: FnMissingData = dataclasses.field(default_factory=dict)
4141

4242

43-
_default_enum_data = EnumData()
44-
_default_fn_data = FunctionData()
45-
46-
4743
class GeneratorData:
4844
"""
4945
Used by the hooks to retrieve user-specified generation data, and
@@ -55,6 +51,13 @@ class GeneratorData:
5551
def __init__(self, data: AutowrapConfigYaml):
5652
self.data = data
5753

54+
default_ignore = self.data.defaults.ignore
55+
self._default_enum_data = EnumData(ignore=default_ignore)
56+
self._default_fn_data = FunctionData(ignore=default_ignore)
57+
self._default_method_data = FunctionData()
58+
self._default_class_data = ClassData(ignore=default_ignore)
59+
self._default_class_enum_data = EnumData()
60+
5861
# report data
5962
self.functions: FnMissingData = {}
6063
self.classes: Dict[str, ClsReportData] = {}
@@ -68,7 +71,7 @@ def get_class_data(self, name: str) -> ClassData:
6871
data = self.data.classes.get(name)
6972
missing = data is None
7073
if missing:
71-
data = ClassData()
74+
data = self._default_class_data
7275

7376
self.classes[name] = ClsReportData(missing=missing)
7477
return data
@@ -78,19 +81,19 @@ def get_cls_enum_data(
7881
) -> EnumData:
7982
if name is None:
8083
# TODO
81-
return _default_enum_data
84+
return self._default_class_enum_data
8285
data = cls_data.enums.get(name)
8386
if data is None:
8487
self.classes[cls_key].enums[name] = False
85-
data = _default_enum_data
88+
data = self._default_class_enum_data
8689

8790
return data
8891

8992
def get_enum_data(self, name: str) -> EnumData:
9093
data = self.data.enums.get(name)
9194
if data is None:
9295
self.enums[name] = False
93-
data = _default_enum_data
96+
data = self._default_enum_data
9497
return data
9598

9699
def get_function_data(
@@ -124,7 +127,10 @@ def get_function_data(
124127
# signature each time we defer it until we actually need to use it
125128

126129
if missing:
127-
data = _default_fn_data
130+
if cls_key:
131+
data = self._default_method_data
132+
else:
133+
data = self._default_fn_data
128134
report_data.deferred_signatures.append((fn, is_private))
129135
elif not data.overloads:
130136
report_data.deferred_signatures.append((fn, True))
@@ -145,6 +151,24 @@ def get_function_data(
145151
report_data.tracker.add_overload()
146152
return data, report_data.tracker
147153

154+
def add_using_decl(
155+
self, name: str, cls_key: str, cls_data: ClassData, is_private: bool
156+
):
157+
# copied from get_function_data
158+
data = cls_data.methods.get(name)
159+
report_base = self.classes[cls_key].functions
160+
161+
report_data = report_base.get(name)
162+
if not report_data:
163+
report_data = FnReportData()
164+
report_base[name] = report_data
165+
166+
missing = data is None
167+
report_data.missing = missing and not is_private
168+
169+
# We count this as an overload because it might be
170+
report_data.tracker.add_overload()
171+
148172
def get_cls_prop_data(
149173
self, name: str, cls_key: str, cls_data: ClassData
150174
) -> PropData:
@@ -169,22 +193,39 @@ def report_missing(self, name: str, reporter: "MissingReporter"):
169193
data yaml and print it out if there's missing data
170194
"""
171195

196+
ignore_default = self.data.defaults.ignore
197+
report_missing = True
198+
if ignore_default and not self.data.defaults.report_ignored_missing:
199+
report_missing = False
200+
172201
# note: sometimes we have strings from CppHeaderParser that aren't
173202
# strings, so we need to cast them to str so yaml doesn't complain
174203

175204
data = self._process_missing(
176-
self.attributes, self.functions, self.enums, "functions"
205+
self.attributes,
206+
self.functions,
207+
self.enums,
208+
"functions",
209+
ignore_default,
210+
report_missing,
177211
)
178212

179213
all_cls_data = {}
180214
for cls_key, cls_data in self.classes.items():
215+
if cls_data.missing and not report_missing:
216+
continue
217+
181218
result = self._process_missing(
182219
cls_data.attributes,
183220
cls_data.functions,
184221
cls_data.enums,
185222
"methods",
223+
False,
224+
True,
186225
)
187226
if result or cls_data.missing:
227+
if ignore_default and cls_data.missing:
228+
result["ignore"] = True
188229
all_cls_data[str(cls_key)] = result
189230
if all_cls_data:
190231
data["classes"] = all_cls_data
@@ -200,6 +241,8 @@ def _process_missing(
200241
fns: FnMissingData,
201242
enums: EnumMissingData,
202243
fn_key: str,
244+
ignore_default: bool,
245+
report_missing: bool,
203246
):
204247
data: Dict[str, Dict[str, Dict]] = {}
205248

@@ -212,11 +255,22 @@ def _process_missing(
212255

213256
# enums
214257
if enums:
215-
data["enums"] = {str(n): {} for n in enums.keys()}
258+
enums_report = {}
259+
for en, enum_present in enums.items():
260+
if not enum_present and not report_missing:
261+
continue
262+
enums_report[en] = {}
263+
if ignore_default:
264+
enums_report[en]["ignore"] = True
265+
if enums_report:
266+
data["enums"] = enums_report
216267

217268
# functions
218269
fn_report = {}
219270
for fn, fndata in fns.items():
271+
if fndata.missing and not report_missing:
272+
continue
273+
220274
fn = str(fn)
221275
overloads = fndata.overloads
222276
deferred_signatures = fndata.deferred_signatures
@@ -246,8 +300,12 @@ def _process_missing(
246300
for k, v in fn_report[fn]["overloads"].items():
247301
if "initializer_list" in k:
248302
v["ignore"] = True
303+
if ignore_default:
304+
v["ignore"] = True
249305
else:
250306
fn_report[fn] = d
307+
if ignore_default:
308+
d["ignore"] = True
251309
if fn_report:
252310
data[fn_key] = fn_report
253311

0 commit comments

Comments
 (0)