Skip to content

Commit 4428bd5

Browse files
committed
C++: Updated constructor generation and allocator handling for unions
- Allocators are stored and propagated to union composite type alternative values during construction and emplacement - `in_place_index` constructor is generated for union composite types - `DependencyBuilder` sets `uses_union` flag appropriately
1 parent ecc31fa commit 4428bd5

File tree

20 files changed

+931
-295
lines changed

20 files changed

+931
-295
lines changed

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ jobs:
141141
matrix:
142142
architecture: [native32, native, arm-none-eabi]
143143
compiler: [gcc, clang]
144-
language: [c-11, c-11-arr-override, c-17, c-23, cpp-14, cpp-17, cpp-20]
144+
language: [c-11, c-11-arr-override, c-17, c-23, cpp-14, cetl-14-17, cpp-17, cpp-17-pmr, cpp-20, cpp-20-pmr]
145145
exclude:
146146
- architecture: native32
147147
compiler: clang

docs/templates.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,7 +391,7 @@ C++ Filters
391391

392392
C++ Use Queries
393393
-------------------------------------------------
394-
.. autofunction:: nunavut.lang.cpp.uses_std_variant
394+
.. autofunction:: nunavut.lang.cpp.uses_variant
395395
:noindex:
396396
.. autofunction:: nunavut.lang.cpp.uses_cetl
397397
:noindex:

src/nunavut/_dependencies.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ def _build_dependency_list(
9696
) -> Dependencies:
9797
results = Dependencies()
9898
for dependant in dependant_types:
99-
if isinstance(dependant, pydsdl.UnionType):
99+
is_delimited_union = isinstance(dependant.inner_type, pydsdl.UnionType)
100+
is_service_union = isinstance(dependant, pydsdl.ServiceType) and (
101+
isinstance(dependant.request_type, pydsdl.UnionType)
102+
or isinstance(dependant.response_type, pydsdl.UnionType)
103+
)
104+
if isinstance(dependant, pydsdl.UnionType) or is_delimited_union or is_service_union:
100105
# Unions always require integer for the tag field.
101106
results.uses_integer = True
102107
results.uses_union = True

src/nunavut/cli/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ def extension_type(raw_arg: str) -> str:
913913
).lstrip(),
914914
)
915915

916-
return cast(ParserT, parser)
916+
return parser
917917

918918

919919
def make_argparse_parser() -> argparse.ArgumentParser:

src/nunavut/jinja/extensions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ def _do_assert(
100100

101101
class UseQuery(Extension):
102102
"""
103-
Jinja2 extension that allows conditional blocks like ``{% ifuses "std_variant" %}`` or
104-
``{% ifnuses "std_variant" %}``. These are defined by the :class:`nunavut.lang.Language` object based on the values
103+
Jinja2 extension that allows conditional blocks like ``{% ifuses "variant" %}`` or
104+
``{% ifnuses "variant" %}``. These are defined by the :class:`nunavut.lang.Language` object based on the values
105105
returned from :meth:`nunavut.lang.Language.get_uses_queries`.
106106
107107
.. code-block:: python

src/nunavut/lang/cpp/__init__.py

Lines changed: 123 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ def standard_version(self) -> int:
293293
@property
294294
def has_variant(self) -> bool:
295295
"""
296+
Indicates if the language standard and flavor combo provides a type compliant with std::variant
296297
.. invisible-code-block: python
297298
298299
from nunavut.lang import LanguageClassLoader
@@ -329,7 +330,7 @@ def has_variant(self) -> bool:
329330
330331
assert language.has_variant
331332
"""
332-
return self.standard_version >= 17
333+
return (self.standard_version >= 17) or (self.standard_version == 14 and self.standard_flavor == "cetl")
333334

334335
def get_includes(self, dep_types: Dependencies) -> typing.List[str]:
335336
"""
@@ -361,6 +362,7 @@ def do_includes_test(override_vla_include, override_allocator_include, override_
361362
362363
test_dependencies = Dependencies()
363364
test_dependencies.uses_variable_length_array = True
365+
test_dependencies.uses_union = True
364366
365367
# If we override the include we should not provide the default
366368
# variable array include.
@@ -409,17 +411,16 @@ def do_includes_test(override_vla_include, override_allocator_include, override_
409411
std_includes.append("array")
410412
if dep_types.uses_boolean_static_array:
411413
std_includes.append("bitset")
412-
if dep_types.uses_union and self.has_variant:
413-
std_includes.append("variant")
414414
includes_formatted = [f"<{include}>" for include in sorted(std_includes)]
415415

416416
allocator_include = str(self.get_option("allocator_include", ""))
417417
if len(allocator_include) > 0:
418418
includes_formatted.append(allocator_include)
419419

420-
variant_include = str(self.get_option("variant_include", ""))
421-
if len(variant_include) > 0:
422-
includes_formatted.append(variant_include)
420+
if dep_types.uses_union:
421+
variant_include = str(self.get_option("variant_include", ""))
422+
if len(variant_include) > 0:
423+
includes_formatted.append(variant_include)
423424

424425
if dep_types.uses_variable_length_array:
425426
variable_array_include = str(self.get_option("variable_array_type_include", ""))
@@ -448,50 +449,65 @@ def create_vla_decl(self, data_type: str, max_size: int) -> str:
448449

449450

450451
@template_language_test(__name__)
451-
def uses_std_variant(language: Language) -> bool:
452+
def uses_variant(language: Language) -> bool:
452453
"""
453-
Uses query for std variant.
454+
Uses query for variant.
454455
455-
If the language options contain an ``std`` entry for C++ and the specified standard includes the
456-
``std::variant`` type added to the language at C++17 then this value is true. The logic included
457-
in this filter can be stated as "options has key std and the value for options.std evaluates to
458-
C++ version 17 or greater" but the implementation is able to parse out actual compiler flags like
459-
``gnu++20`` and is aware of any overrides to suppress use of the standard variant type even if
460-
available.
456+
If the language options contain an entry for C++ and the specified standard or flavor includes a type that is
457+
compliant with ``std::variant``, then this value is true.
458+
459+
The logic included in this filter can be stated as:
460+
Options has keys [``std``, ``std_flavor``] and the values stored at those keys resolve to one of the following
461+
combinations:
462+
- {``std``: ``>="c++17"``}
463+
- {``std``: ``"c++14"``, ``std_flavor``: ``"cetl"``}
464+
465+
but the implementation is able to parse out actual compiler flags like ``gnu++20`` and is aware of any overrides
466+
to suppress use of the standard variant type even if available.
461467
462468
Example:
463469
464-
.. code-block:: python
470+
.. code-block:: python
465471
466-
template = '''
467-
{%- ifuses "std_variant" -%}
468-
#include <variant>
469-
{%- else -%}
470-
#include "user_variant.h"
471-
{%- endifuses -%}
472-
'''
472+
template = '''
473+
{%- ifuses "variant" -%}
474+
#include <variant>
475+
{%- else -%}
476+
#include "user_variant.h"
477+
{%- endifuses -%}
478+
'''
473479
474-
.. invisible-code-block: python
480+
.. invisible-code-block: python
475481
476-
# test c++17
477-
options = {"std": "c++17"}
478-
lctx = (
479-
LanguageContextBuilder(include_experimental_languages=True)
480-
.set_target_language("cpp")
481-
.set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options)
482-
.create()
483-
)
484-
jinja_filter_tester(None, template, '#include <variant>', lctx)
482+
# test c++17
483+
options = {"std": "c++17"}
484+
lctx = (
485+
LanguageContextBuilder(include_experimental_languages=True)
486+
.set_target_language("cpp")
487+
.set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options)
488+
.create()
489+
)
490+
jinja_filter_tester(None, template, '#include <variant>', lctx)
485491
486-
# test c++14
487-
options = {"std": "c++14"}
488-
lctx = (
489-
LanguageContextBuilder(include_experimental_languages=True)
490-
.set_target_language("cpp")
491-
.set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options)
492-
.create()
493-
)
494-
jinja_filter_tester(None, template, '#include "user_variant.h"', lctx)
492+
# test c++14-17 with CETL
493+
options = {"std": "c++14", "std_flavor": "cetl"}
494+
lctx = (
495+
LanguageContextBuilder(include_experimental_languages=True)
496+
.set_target_language("cpp")
497+
.set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options)
498+
.create()
499+
)
500+
jinja_filter_tester(None, template, '#include <variant>', lctx)
501+
502+
# test c++14
503+
options = {"std": "c++14"}
504+
lctx = (
505+
LanguageContextBuilder(include_experimental_languages=True)
506+
.set_target_language("cpp")
507+
.set_target_language_configuration_override(Language.WKCV_LANGUAGE_OPTIONS, options)
508+
.create()
509+
)
510+
jinja_filter_tester(None, template, '#include "user_variant.h"', lctx)
495511
496512
"""
497513
return language.has_variant
@@ -1057,8 +1073,8 @@ def filter_includes(language: Language, t: pydsdl.CompositeType, sort: bool = Tr
10571073
# Listing the includes for a union with only integer types:
10581074
template = "{% for include in my_type | includes -%}{{include}}{%- endfor %}"
10591075
1060-
# cstdint will normally be generated. limits is always generated.
1061-
rendered = "<cstdint><limits>"
1076+
# cstdint will normally be generated. limits and cetlpf are always generated.
1077+
rendered = "<cetl/pf17/cetlpf.hpp><cstdint><limits>"
10621078
10631079
.. invisible-code-block: python
10641080
@@ -1068,7 +1084,7 @@ def filter_includes(language: Language, t: pydsdl.CompositeType, sort: bool = Tr
10681084
10691085
# You can suppress std includes by setting use_standard_types to False under
10701086
# nunavut.lang.cpp
1071-
rendered = "<limits>"
1087+
rendered = "<cetl/pf17/cetlpf.hpp><limits>"
10721088
10731089
.. invisible-code-block: python
10741090
@@ -1160,36 +1176,24 @@ def filter_default_value_initializer(language: Language, instance: pydsdl.Any) -
11601176
return ""
11611177

11621178

1163-
def needs_initializing_value(special_method: SpecialMethod) -> bool:
1164-
"""Helper method used by filter_value_initializer()"""
1165-
return special_method == SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR or needs_rhs(special_method)
1166-
1167-
1168-
def needs_rhs(special_method: SpecialMethod) -> bool:
1169-
"""Helper method used by filter_value_initializer()"""
1170-
return special_method in (
1171-
SpecialMethod.COPY_CONSTRUCTOR_WITH_ALLOCATOR,
1172-
SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR,
1173-
)
1174-
1175-
11761179
def needs_allocator(instance: pydsdl.Any) -> bool:
1177-
"""Helper method used by filter_value_initializer()"""
1178-
return isinstance(instance.data_type, pydsdl.VariableLengthArrayType) or isinstance(
1179-
instance.data_type, pydsdl.CompositeType
1180-
)
1180+
"""
1181+
Return True if the provided instance requires an allocator on initialization, False if not
1182+
"""
11811183

1184+
def type_needs_allocator(type: pydsdl.Any) -> bool:
1185+
return isinstance(type, pydsdl.VariableLengthArrayType) or isinstance(type, pydsdl.CompositeType)
11821186

1183-
def needs_vla_init_args(instance: pydsdl.Any, special_method: SpecialMethod) -> bool:
1184-
"""Helper method used by filter_value_initializer()"""
1185-
return special_method == SpecialMethod.ALLOCATOR_CONSTRUCTOR and isinstance(
1186-
instance.data_type, pydsdl.VariableLengthArrayType
1187-
)
1187+
if hasattr(instance, "data_type"):
1188+
return type_needs_allocator(instance.data_type)
1189+
return type_needs_allocator(instance)
11881190

11891191

1190-
def needs_move(special_method: SpecialMethod) -> bool:
1191-
"""Helper method used by filter_value_initializer()"""
1192-
return special_method == SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR
1192+
def filter_needs_allocator(instance: pydsdl.Any) -> bool:
1193+
"""
1194+
Check if the provided instance requires an allocator instance during initialization
1195+
"""
1196+
return needs_allocator(instance)
11931197

11941198

11951199
def requires_initialization(instance: pydsdl.Any) -> bool:
@@ -1244,6 +1248,37 @@ def filter_value_initializer(
12441248
SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR)
12451249
assert '{foo}' == output
12461250
1251+
output = filter_value_initializer(
1252+
lctx.get_target_language(),
1253+
test_type,
1254+
SpecialMethod.COPY_CONSTRUCTOR_WITH_ALLOCATOR)
1255+
assert '{rhs.foo}' == output
1256+
1257+
output = filter_value_initializer(
1258+
lctx.get_target_language(),
1259+
test_type,
1260+
SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR)
1261+
assert '{std::move(rhs.foo)}' == output
1262+
1263+
test_type.data_type = MagicMock(spec=pydsdl.CompositeType)
1264+
1265+
output = filter_value_initializer(
1266+
lctx.get_target_language(),
1267+
test_type,
1268+
SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR)
1269+
assert '{foo, allocator}' == output
1270+
1271+
output = filter_value_initializer(
1272+
lctx.get_target_language(),
1273+
test_type,
1274+
SpecialMethod.COPY_CONSTRUCTOR_WITH_ALLOCATOR)
1275+
assert '{rhs.foo, allocator}' == output
1276+
1277+
output = filter_value_initializer(
1278+
lctx.get_target_language(),
1279+
test_type,
1280+
SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR)
1281+
assert '{std::move(rhs.foo), allocator}' == output
12471282
"""
12481283

12491284
value_initializer: str = ""
@@ -1253,27 +1288,32 @@ def filter_value_initializer(
12531288
leading_args: typing.List[str] = []
12541289
trailing_args: typing.List[str] = []
12551290

1256-
if needs_initializing_value(special_method):
1257-
instance_id = language.filter_id(instance)
1258-
if needs_rhs(special_method):
1259-
rhs = f"rhs.{instance_id}"
1260-
else:
1261-
rhs = f"{id_prefix}{instance_id}"
1262-
1263-
if needs_vla_init_args(instance, special_method):
1264-
constructor_args = language.get_option("variable_array_type_constructor_args")
1265-
if isinstance(constructor_args, str) and len(constructor_args) > 0:
1266-
trailing_args.append(constructor_args.format(MAX_SIZE=instance.data_type.capacity))
1291+
instance_id = language.filter_id(instance)
1292+
1293+
if special_method == SpecialMethod.ALLOCATOR_CONSTRUCTOR:
1294+
if isinstance(instance.data_type, pydsdl.ArrayType):
1295+
if isinstance(instance.data_type, pydsdl.VariableLengthArrayType):
1296+
constructor_args = language.get_option("variable_array_type_constructor_args")
1297+
if isinstance(constructor_args, str) and len(constructor_args) > 0:
1298+
trailing_args.append(constructor_args.format(MAX_SIZE=instance.data_type.capacity))
1299+
elif needs_allocator(instance.data_type.element_type):
1300+
# Fixed length array with elements that require allocator on construction
1301+
element_init = str(filter_declaration(language, instance.data_type.element_type) + "{allocator}")
1302+
trailing_args.extend([element_init] * instance.data_type.capacity)
1303+
elif special_method == SpecialMethod.INITIALIZING_CONSTRUCTOR_WITH_ALLOCATOR:
1304+
rhs = f"{id_prefix}{instance_id}"
1305+
else:
1306+
# COPY_CONSTRUCTOR_WITH_ALLOCATOR or MOVE_CONSTRUCTOR_WITH_ALLOCATOR
1307+
rhs = f"rhs.{instance_id}"
1308+
if special_method == SpecialMethod.MOVE_CONSTRUCTOR_WITH_ALLOCATOR:
1309+
wrap = "std::move"
12671310

12681311
if needs_allocator(instance):
12691312
if language.get_option("ctor_convention") == ConstructorConvention.USES_LEADING_ALLOCATOR.value:
12701313
leading_args.extend(["std::allocator_arg", "allocator"])
12711314
else:
12721315
trailing_args.append("allocator")
12731316

1274-
if needs_move(special_method):
1275-
wrap = "std::move"
1276-
12771317
value_initializer = assemble_initializer_expression(wrap, rhs, leading_args, trailing_args)
12781318

12791319
return value_initializer

0 commit comments

Comments
 (0)