Skip to content

Commit

Permalink
Lkt lowering: robustify traits checking for nodes
Browse files Browse the repository at this point in the history
Reject invalid uses of traits, make sure that the Node trait is
instantiated with the root node, etc.

For #622
  • Loading branch information
pmderodat committed Jul 26, 2022
1 parent 7d9cbe0 commit 2cb44f1
Show file tree
Hide file tree
Showing 10 changed files with 201 additions and 20 deletions.
19 changes: 17 additions & 2 deletions contrib/lkt/language/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,9 +165,24 @@ def get_builtin_type(entity_name=T.Symbol):
def get_builtin_gen_decl(entity_name=T.Symbol):
return Self.root_get(entity_name).cast_or_raise(T.GenericDecl)

node_type = Property(
node_gen_trait = Property(
Self.get_builtin_gen_decl('Node'), public=True,
doc="Unit method. Return the Node base class."
doc="Unit method. Return the ``Node`` builtin generic trait."
)

node_trait = Property(
Self.node_gen_trait.decl.cast(T.TraitDecl), public=True,
doc="Unit method. Return the ``Node`` builtin trait."
)

token_node_trait = Property(
Self.get_builtin_type('TokenNode'), public=True,
doc="Unit method. Return the ``TokenNode`` builtin trait."
)

error_node_trait = Property(
Self.get_builtin_type('ErrorNode'), public=True,
doc="Unit method. Return the ``ErrorNode`` builtin trait."
)

char_type = Property(
Expand Down
93 changes: 75 additions & 18 deletions langkit/lkt_lowering.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,6 @@
ANNOTATIONS_WHITELIST = ['builtin']


def get_trait(decl: L.TypeDecl, trait_name: str) -> Optional[L.TypeDecl]:
"""
Return the trait named ``trait_name`` on declaration ``decl``.
"""
for trait in decl.f_traits:
trait_decl: L.TypeDecl = trait.p_designated_type
if trait_decl.p_name == trait_name:
return trait_decl
return None


def check_referenced_decl(expr: L.Expr) -> L.Decl:
"""
Wrapper around ``Expr.p_check_referenced_decl``.
Expand Down Expand Up @@ -1246,9 +1235,12 @@ def get_field(decl: L.NamedTypeDecl, name: str) -> L.Decl:
self.analysis_unit_trait = root.p_analysis_unit_trait
self.array_type = root.p_array_type
self.astlist_type = root.p_astlist_type
self.error_node_trait = root.p_error_node_trait
self.iterator_trait = root.p_iterator_trait
self.string_type = root.p_string_type
self.node_trait = root.p_node_trait
self.property_error_type = root.p_property_error_type
self.string_type = root.p_string_type
self.token_node_trait = root.p_token_node_trait

self.find_method = get_field(self.iterator_trait, 'find')
self.map_method = get_field(self.iterator_trait, 'map')
Expand Down Expand Up @@ -2068,7 +2060,7 @@ def lower_fields(self,
"""
result = []
for full_decl in decls:
with diagnostic_context(Location.from_lkt_node(full_decl)):
with self.ctx.lkt_context(full_decl):
decl = full_decl.f_decl

# Check field name conformity
Expand Down Expand Up @@ -2117,11 +2109,70 @@ def create_node(self,
# Resolve the base node (if any)
base_type: Optional[ASTNodeType]

# Check the set of traits that this node implements
node_trait_ref: Optional[L.LktNode] = None
token_node_trait_ref: Optional[L.LktNode] = None
error_node_trait_ref: Optional[L.LktNode] = None
for trait_ref in decl.f_traits:
trait_decl: L.TypeDecl = trait_ref.p_designated_type
if (
isinstance(trait_decl, L.InstantiatedGenericType)
and trait_decl.p_get_inner_type == self.node_trait
):
# If this trait is an instantiation of the Node trait, make
# sure it is instantiated on the root node itself (i.e.
# "decl").
actuals = trait_decl.p_get_actuals
assert len(actuals) == 1
with self.ctx.lkt_context(trait_ref):
check_source_language(
actuals[0] == decl,
"The Node generic trait must be instantiated with the"
f" root node ({decl.f_syn_name.text})"
)
node_trait_ref = trait_ref

elif trait_decl == self.token_node_trait:
token_node_trait_ref = trait_ref

elif trait_decl == self.error_node_trait:
error_node_trait_ref = trait_ref

else:
with self.ctx.lkt_context(trait_ref):
error("Nodes cannot implement this trait")

def check_trait(trait_ref: Optional[L.LktNode],
expected: bool,
message: str) -> None:
"""
If ``expected`` is ``True``, emit an error if ``trait_ref`` is
``None``. If ``expected`` is ``False``, emit an error if
``trait_ref`` is not ``None``. In both cases, use ``message`` as
the error message.
"""
if expected:
check_source_language(trait_ref is not None, message)
elif trait_ref is not None:
with self.ctx.lkt_context(trait_ref):
error(message)

# Root node case
if decl.p_base_type is None:
check_source_language(
get_trait(decl, "Node") is not None,
'The root node must implement the Node trait'
check_trait(
node_trait_ref,
True,
"The root node must implement the Node trait"
)
check_trait(
token_node_trait_ref,
False,
"The root node cannot be a token node"
)
check_trait(
error_node_trait_ref,
False,
"The root node cannot be an error node"
)

if CompiledTypeRepo.root_grammar_class is not None:
Expand All @@ -2139,11 +2190,17 @@ def create_node(self,
base_type = cast(ASTNodeType,
self.lower_type_decl(base_type_decl))

check_trait(
node_trait_ref,
False,
"Only the root node can implement the Node trait"
)

# This is a token node if either the TokenNode trait is implemented
# or if the base node is a token node itself. Likewise for
# ErrorNode.
is_token_node = get_trait(decl, "TokenNode") is not None
is_error_node = get_trait(decl, "ErrorNode") is not None
is_token_node = token_node_trait_ref is not None
is_error_node = error_node_trait_ref is not None

check_source_language(
base_type is not base_type.is_enum_node,
Expand Down
12 changes: 12 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/derived_node.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- Example(@example)
}

@abstract class FooNode implements Node[FooNode] {
}

class Example : FooNode implements Node[Example] {
}
12 changes: 12 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/non_node.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- Example(@example)
}

@abstract class FooNode implements Node[Int] {
}

class Example : FooNode {
}
12 changes: 12 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/root_error.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- skip(Example)
}

@abstract class FooNode implements Node[FooNode], ErrorNode {
}

class Example : FooNode {
}
12 changes: 12 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/root_no_node.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- Example(@example)
}

@abstract class FooNode {
}

class Example : FooNode implements TokenNode {
}
12 changes: 12 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/root_token.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- Example(@example)
}

@abstract class FooNode implements Node[FooNode], TokenNode {
}

class Example : FooNode {
}
36 changes: 36 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/test.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
== derived_node.lkt ==
derived_node.lkt:11:36: error: Only the root node can implement the Node trait
11 | class Example : FooNode implements Node[Example] {
| ^^^^^^^^^^^^^


== non_node.lkt ==
non_node.lkt:8:36: error: The Node generic trait must be instantiated with the root node (FooNode)
8 | @abstract class FooNode implements Node[Int] {
| ^^^^^^^^^


== root_error.lkt ==
root_error.lkt:8:51: error: The root node cannot be an error node
8 | @abstract class FooNode implements Node[FooNode], ErrorNode {
| ^^^^^^^^^


== root_no_node.lkt ==
root_no_node.lkt:8:11: error: The root node must implement the Node trait
8 | @abstract class FooNode {


== root_token.lkt ==
root_token.lkt:8:51: error: The root node cannot be a token node
8 | @abstract class FooNode implements Node[FooNode], TokenNode {
| ^^^^^^^^^


== unknown_trait.lkt ==
unknown_trait.lkt:11:36: error: Nodes cannot implement this trait
11 | class Example : FooNode implements AnalysisUnit[FooNode] {
| ^^^^^^^^^^^^^^^^^^^^^


Done
1 change: 1 addition & 0 deletions testsuite/tests/grammar/invalid_node_traits/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
driver: lkt_compile
12 changes: 12 additions & 0 deletions testsuite/tests/grammar/invalid_node_traits/unknown_trait.lkt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import lexer_example

@with_lexer(foo_lexer)
grammar foo_grammar {
@main_rule main_rule <- Example(@example)
}

@abstract class FooNode implements Node[FooNode] {
}

class Example : FooNode implements AnalysisUnit[FooNode] {
}

0 comments on commit 2cb44f1

Please sign in to comment.