Skip to content

Commit

Permalink
Lkt lowering: fix lowering order for list node derivation
Browse files Browse the repository at this point in the history
Depending on the order picked by Langkit for node type lowering,
processing the definition of a derived list node could crash. The
problem was that lowering code just assumed so far that the root node
was lowered before list nodes, and that list element nodes were lowered
before the corresponding list nodes as well, whereas this has never been
guaranteed.

To fix this, we now force the lowering of the element node when lowering
a list node, and stop assuming that the root node is lowered first.

For #622
  • Loading branch information
pmderodat committed Jul 26, 2022
1 parent 460af0b commit c174e5c
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 17 deletions.
67 changes: 50 additions & 17 deletions langkit/lkt_lowering.py
Original file line number Diff line number Diff line change
Expand Up @@ -1280,11 +1280,15 @@ def get_field(decl: L.NamedTypeDecl, name: str) -> L.Decl:
assert isinstance(decl.f_decl, L.TypeDecl)
self.lower_type_decl(decl.f_decl)

def resolve_type_decl(self, decl: L.TypeDecl) -> CompiledTypeOrDefer:
def resolve_type_decl(self,
decl: L.TypeDecl,
force_lowering: bool = False) -> CompiledTypeOrDefer:
"""
Fetch the CompiledType instance corresponding to the given type
declaration. If it's not lowered yet, return an appropriate
TypeRepo.Defer instance instead.
declaration.
When ``force_lowering`` is ``False``, if ``decl`` is not lowered yet,
return an appropriate TypeRepo.Defer instance instead.
:param decl: Lkt type declaration to resolve.
"""
Expand All @@ -1295,29 +1299,49 @@ def resolve_type_decl(self, decl: L.TypeDecl) -> CompiledTypeOrDefer:
if result is not None:
return result

# Not found: look now for an existing TypeRepo.Defer instance
result = self.type_refs.get(decl)
if result is not None:
return result
# Not found: unless lowering is forced, look now for an existing
# TypeRepo.Defer instance.
if not force_lowering:
result = self.type_refs.get(decl)
if result is not None:
return result

# Not found neither: create the TypeRepo.Defer instance
# If this is an instantiated generic type, try to build the
# corresponding CompiledType from the type actuals.
if isinstance(decl, L.InstantiatedGenericType):
inner_type = decl.p_get_inner_type
actuals = decl.p_get_actuals

if inner_type == self.array_type:
assert len(actuals) == 1
result = self.resolve_type_decl(actuals[0]).array
result = self.resolve_type_decl(
actuals[0], force_lowering
).array

elif inner_type == self.iterator_trait:
assert len(actuals) == 1
result = self.resolve_type_decl(actuals[0]).iterator
result = self.resolve_type_decl(
actuals[0], force_lowering
).iterator

elif inner_type == self.astlist_type:
assert len(actuals) == 2
root_node = self.resolve_type_decl(actuals[0])
node = self.resolve_type_decl(actuals[1])
assert root_node == T.root_node
root_node = actuals[0]
node = self.resolve_type_decl(
actuals[1], force_lowering=force_lowering
)

# Make sure that "root_node" is indeed a root node (a class
# with no base type). Lkt type checking as already supposed to
# make sure that "node" is a class (i.e. a node), and lowering
# already checks that there is exactly one node types
# hierarchy.
check_source_language(
isinstance(root_node, L.ClassDecl)
and root_node.f_syn_base_type is None,
"In ASTList[N1, N2], N1 must be the root node"
)

assert isinstance(node, (ASTNodeType, TypeRepo.Defer))
result = node.list

Expand All @@ -1330,11 +1354,18 @@ def resolve_type_decl(self, decl: L.TypeDecl) -> CompiledTypeOrDefer:
.format(inner_type, decl)
)

# Otherwise, "decl" is not lowered yet: create a Defer object or lower
# it depending on "force_lowering".
else:
assert isinstance(decl, L.NamedTypeDecl)
result = getattr(T, decl.f_syn_name.text)
result = (
self.lower_type_decl(decl)
if force_lowering else
getattr(T, decl.f_syn_name.text)
)

if isinstance(result, TypeRepo.Defer):
assert not force_lowering
self.type_refs[decl] = result
else:
assert isinstance(result, CompiledType)
Expand Down Expand Up @@ -1376,9 +1407,11 @@ def lower_type_decl(self, decl: L.TypeDecl) -> CompiledType:
if isinstance(decl, L.InstantiatedGenericType):
# At this stage, the only generic types should come from the
# prelude (Array, ASTList), so there is no need to do anything
# special for them: just let the resolution mechanism build the
# CompiledType through CompiledType attribute access magic.
result = resolve_type(self.resolve_type_decl(decl))
# special for them. However the type actuals must be lowered so
# that we can return a compiled type, and not a Defer object.
resolved = self.resolve_type_decl(decl, force_lowering=True)
assert isinstance(resolved, CompiledType)
result = resolved
else:
full_decl = decl.parent
assert isinstance(full_decl, L.FullDecl)
Expand Down
19 changes: 19 additions & 0 deletions testsuite/tests/grammar/list_forward_decl/test.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Check that compiling a list node derivation that occurs in the Lkt source
# before the root node declaration works correctly (it used to crash).

import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- AExampleList*(BExample("example"))
}

class AExampleList : ASTList[CFooNode, BExample] {
}

@has_abstract_list class BExample : CFooNode implements TokenNode {
}

@abstract class CFooNode implements Node[CFooNode] {
}

4 changes: 4 additions & 0 deletions testsuite/tests/grammar/list_forward_decl/test.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
== test.lkt ==
Code generation was successful

Done
1 change: 1 addition & 0 deletions testsuite/tests/grammar/list_forward_decl/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
driver: lkt_compile

0 comments on commit c174e5c

Please sign in to comment.