Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
730 changes: 492 additions & 238 deletions aas_core_codegen/cpp/lib/_generate_verification.py

Large diffs are not rendered by default.

159 changes: 154 additions & 5 deletions aas_core_codegen/cpp/transpilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,27 @@ def __init__(
# While this class does not directly use it, the descendants of this class do!
self._variable_name_set = set() # type: Set[Identifier]

def _type_parameter_of(
self, type_annotation: intermediate_type_inference.TypeAnnotationUnion
) -> Optional[Identifier]:
"""
Determine the template parameter which stands for the ``type_annotation``.

Only the verification functions are templates, namely over the items of
the generic sequences, see :py:func:`intermediate.generic_sequence_item_class`.
The transpiler of the verification functions overrides this method.

We declare the loop and the generator variables over a generic sequence with
the template parameter so that the items are not converted to
temporary shared pointers of the parent class.

:param type_annotation: inferred type of a value
:return:
the template parameter, if the value is an item of a generic sequence,
and None otherwise
"""
return None

def _transform_enumeration_literal(
self, enumeration_name: Identifier, literal_name: Identifier
) -> Stripped:
Expand Down Expand Up @@ -1207,6 +1228,52 @@ def transform_is_in(
if isinstance(container_type, intermediate_type_inference.ListTypeAnnotation):
member = self._as_int64_position(node.member, member)

# NOTE (mristin):
# The ``std::find`` compares the shared pointers by their raw pointers, so
# the classes of the items and of the member need to be related. This holds
# for the declared classes, as the type inference checks that one is
# a subclass of the other. However, the template parameter of a generic
# sequence, see :py:func:`intermediate.generic_sequence_item_class`, can be
# any descendant, *e.g.*, a sibling of the class of the member. Hence, we
# convert the member to the more general of the two declared classes.
member_type = self.type_map[node.member]
if (
isinstance(container_type, intermediate_type_inference.ListTypeAnnotation)
and isinstance(
container_type.items, intermediate_type_inference.OurTypeAnnotation
)
and isinstance(container_type.items.our_type, intermediate.Class)
and isinstance(member_type, intermediate_type_inference.OurTypeAnnotation)
and isinstance(member_type.our_type, intermediate.Class)
and (
self._type_parameter_of(container_type.items) is not None
or self._type_parameter_of(member_type) is not None
)
):
general_type = (
container_type.items
if member_type.our_type.is_subclass_of(container_type.items.our_type)
else member_type
)

general_type_cpp, error_msg = generate_type(
type_annotation=intermediate_type_inference.OurTypeAnnotation(
our_type=general_type.our_type
),
types_namespace=self._types_namespace,
)
if error_msg is not None:
return None, Error(node.member.original_node, error_msg)

assert general_type_cpp is not None

member = Stripped(
f"""\
{general_type_cpp}(
{I}{indent_but_first_line(member, I)}
)"""
)

contains_function = cpp_naming.function_name(Identifier("contains"))

return (
Expand Down Expand Up @@ -1694,6 +1761,49 @@ def transform_function_call(

assert function_name is not None

# NOTE (mristin):
# A transpiled verification function is a template over the items of
# its generic sequences, see
# :py:func:`intermediate.generic_sequence_item_class`. C++ deduces
# the template parameters without any implicit conversions, so we pass
# ``None`` to an optional generic sequence as a typed empty optional,
# and a required vector through ``common::make_optional``.
if isinstance(func_type.func, intermediate.TranspilableVerification):
for i, (arg_node, argument) in enumerate(
zip(node.args, func_type.func.arguments)
):
if intermediate.generic_sequence_item_class(argument) is None:
continue

if not isinstance(
argument.type_annotation, intermediate.OptionalTypeAnnotation
):
continue

if (
isinstance(arg_node, parse_tree.Constant)
and arg_node.value is None
):
optional_type = cpp_common.generate_type(
type_annotation=argument.type_annotation,
types_namespace=self._types_namespace,
)
args[i] = Stripped(f"{optional_type}()")

elif not isinstance(
self.type_map[arg_node],
intermediate_type_inference.OptionalTypeAnnotation,
):
args[i] = Stripped(
f"""\
common::make_optional(
{I}{indent_but_first_line(args[i], I)}
)"""
)

else:
pass

if len(args) == 0:
return Stripped(f"{function_name}()"), None

Expand Down Expand Up @@ -2425,8 +2535,18 @@ def _transform_any_or_all(
cpp_aliasing.Declaration.MUT_REF
)

type_parameter = self._type_parameter_of(variable_type_annotation)

variable_type_cpp: Optional[Stripped]
if self._declaration(node.generator) is cpp_aliasing.Declaration.COPY:
error_msg: Optional[str]
if type_parameter is not None:
variable_type_cpp = Stripped(f"std::shared_ptr<{type_parameter}>")
error_msg = None

if self._declaration(node.generator) is not cpp_aliasing.Declaration.COPY:
variable_type_cpp = Stripped(f"const {variable_type_cpp}&")

elif self._declaration(node.generator) is cpp_aliasing.Declaration.COPY:
variable_type_cpp, error_msg = generate_type(
type_annotation=variable_type_annotation,
types_namespace=self._types_namespace,
Expand Down Expand Up @@ -2749,6 +2869,27 @@ def transform_assignment(

assert declared_type is not None

# NOTE (mristin):
# An immutable alias of a generic sequence shares its type annotation,
# see :py:meth:`intermediate_type_inference._Inferrer.transform_assignment`,
# so we declare the alias with the template parameter as well.
target_type_beneath = intermediate_type_inference.beneath_optional(
target_type
)
if isinstance(
target_type_beneath, intermediate_type_inference.ListTypeAnnotation
):
type_parameter = self._type_parameter_of(target_type_beneath.items)
if type_parameter is not None:
declared_type = Stripped(
f"std::vector<std::shared_ptr<{type_parameter}> >"
)

if isinstance(
target_type, intermediate_type_inference.OptionalTypeAnnotation
):
declared_type = Stripped(f"common::optional<{declared_type} >")

# NOTE (mristin):
# A variable declared as ``Final[...]`` is neither re-assigned nor
# mutated, so we can declare it as ``const``.
Expand Down Expand Up @@ -3104,10 +3245,18 @@ def transform_for(

header: Optional[str] = None
if isinstance(node.generator, parse_tree.ForEach):
variable_type_cpp, error_msg = generate_type(
type_annotation=variable_type_annotation,
types_namespace=self._types_namespace,
)
type_parameter = self._type_parameter_of(variable_type_annotation)

variable_type_cpp: Optional[Stripped]
error_msg: Optional[str]
if type_parameter is not None:
variable_type_cpp = Stripped(f"std::shared_ptr<{type_parameter}>")
error_msg = None
else:
variable_type_cpp, error_msg = generate_type(
type_annotation=variable_type_annotation,
types_namespace=self._types_namespace,
)
if error_msg is not None:
errors.append(Error(node.generator.variable.original_node, error_msg))

Expand Down
48 changes: 41 additions & 7 deletions aas_core_codegen/csharp/lib/_generate_verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -472,10 +472,41 @@ def _transpile_transpilable_verification(
assert stmt is not None
body.append(stmt)

# NOTE (mristin):
# The ``List<T>`` is invariant in C#, so we declare the generic sequences,
# see :py:func:`intermediate.generic_sequence_item_class`, as the covariant
# ``IReadOnlyList<T>``. This way, the lists of the descendants can be passed
# without a copy, as ``List<T>`` implements ``IReadOnlyList<T>``.
arg_defs = [] # type: List[Stripped]
remarks_on_generic_sequences = [] # type: List[str]
for arg in verification.arguments:
arg_name = csharp_naming.argument_name(arg.name)

if intermediate.generic_sequence_item_class(arg) is None:
arg_type = csharp_common.generate_type(arg.type_annotation)
else:
list_type_anno = intermediate.beneath_optional(arg.type_annotation)
assert isinstance(list_type_anno, intermediate.ListTypeAnnotation)

item_type = csharp_common.generate_type(list_type_anno.items)

arg_type = Stripped(f"IReadOnlyList<{item_type}>")
if isinstance(arg.type_annotation, intermediate.OptionalTypeAnnotation):
arg_type = Stripped(f"{arg_type}?")

remarks_on_generic_sequences.append(
f"The argument {arg_name} is declared as the covariant "
f"IReadOnlyList<{item_type}> instead of the invariant "
f"List<{item_type}> so that the lists of the descendants of "
f"{item_type} can be passed without a copy."
)

arg_defs.append(Stripped(f"{arg_type} {arg_name}"))

writer = io.StringIO()
if verification.description is not None:
comment, comment_errors = csharp_description.generate_comment_for_signature(
verification.description
verification.description, extra_remarks=remarks_on_generic_sequences
)
if comment_errors is not None:
return None, Error(
Expand All @@ -488,6 +519,15 @@ def _transpile_transpilable_verification(

writer.write(comment)
writer.write("\n")
elif len(remarks_on_generic_sequences) > 0:
writer.write(
csharp_description.generate_comment_for_remarks(
remarks_on_generic_sequences
)
)
writer.write("\n")
else:
pass

method_name = csharp_naming.method_name(verification.name)

Expand All @@ -502,12 +542,6 @@ def _transpile_transpilable_verification(
else:
return_type = csharp_common.generate_type(type_annotation=verification.returns)

arg_defs = [] # type: List[Stripped]
for arg in verification.arguments:
arg_type = csharp_common.generate_type(arg.type_annotation)
arg_name = csharp_naming.argument_name(arg.name)
arg_defs.append(Stripped(f"{arg_type} {arg_name}"))

if len(arg_defs) == 0:
writer.write(
f"""\
Expand Down
13 changes: 13 additions & 0 deletions aas_core_codegen/csharp/transpilation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1860,6 +1860,19 @@ def transform_assignment(
target, error = self.transform_name(node=node.target)
if error is not None:
errors.append(error)
elif (
node.annotation is not None
and isinstance(node.value, parse_tree.Name)
and self.type_map[node.target] is self.type_map[node.value]
):
# NOTE (mristin):
# An immutable alias shares the type annotation of the aliased
# variable, see
# :py:meth:`intermediate_type_inference._Inferrer.transform_assignment`,
# so we infer its type with ``var``. This way, the alias of a generic
# sequence is also declared as ``IReadOnlyList<T>``, see
# :py:func:`intermediate.generic_sequence_item_class`.
target = Stripped(f"var {target}")
elif node.annotation is not None:
# NOTE (mristin):
# We spell out the declared type, as it might differ from the type
Expand Down
18 changes: 18 additions & 0 deletions aas_core_codegen/golang/lib/_generate_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,24 @@ def generate(symbol_table: intermediate.SymbolTable) -> str:
{II}}}
{I}}}
{I}return false
}}"""
),
Stripped(
f"""\
// Check if the slice contains the given instance of a class.
//
// In contrast to [SliceContains], the instance can be of a different type than
// the items of the slice. This is necessary for the items of the generic sequences,
// which are of a type parameter, and for the instances of the descendants of
// the class of the items. We compare the instances as ``any``'s, *i.e.*, by their
// references.
func SliceContainsInstance[T any](s []T, v any) bool {{
{I}for _, item := range s {{
{II}if any(item) == v {{
{III}return true
{II}}}
{I}}}
{I}return false
}}"""
),
Stripped(
Expand Down
Loading
Loading