From 2cb44f1bc8cb12f7c418b9a1b034b0881ecba312 Mon Sep 17 00:00:00 2001 From: Pierre-Marie de Rodat Date: Thu, 23 Jun 2022 14:04:51 +0000 Subject: [PATCH] Lkt lowering: robustify traits checking for nodes Reject invalid uses of traits, make sure that the Node trait is instantiated with the root node, etc. For #622 --- contrib/lkt/language/parser.py | 19 +++- langkit/lkt_lowering.py | 93 +++++++++++++++---- .../invalid_node_traits/derived_node.lkt | 12 +++ .../grammar/invalid_node_traits/non_node.lkt | 12 +++ .../invalid_node_traits/root_error.lkt | 12 +++ .../invalid_node_traits/root_no_node.lkt | 12 +++ .../invalid_node_traits/root_token.lkt | 12 +++ .../grammar/invalid_node_traits/test.out | 36 +++++++ .../grammar/invalid_node_traits/test.yaml | 1 + .../invalid_node_traits/unknown_trait.lkt | 12 +++ 10 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 testsuite/tests/grammar/invalid_node_traits/derived_node.lkt create mode 100644 testsuite/tests/grammar/invalid_node_traits/non_node.lkt create mode 100644 testsuite/tests/grammar/invalid_node_traits/root_error.lkt create mode 100644 testsuite/tests/grammar/invalid_node_traits/root_no_node.lkt create mode 100644 testsuite/tests/grammar/invalid_node_traits/root_token.lkt create mode 100644 testsuite/tests/grammar/invalid_node_traits/test.out create mode 100644 testsuite/tests/grammar/invalid_node_traits/test.yaml create mode 100644 testsuite/tests/grammar/invalid_node_traits/unknown_trait.lkt diff --git a/contrib/lkt/language/parser.py b/contrib/lkt/language/parser.py index 8c6cde6ae..2e151b159 100644 --- a/contrib/lkt/language/parser.py +++ b/contrib/lkt/language/parser.py @@ -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( diff --git a/langkit/lkt_lowering.py b/langkit/lkt_lowering.py index 5a2c86b42..db1cd9b16 100644 --- a/langkit/lkt_lowering.py +++ b/langkit/lkt_lowering.py @@ -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``. @@ -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') @@ -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 @@ -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: @@ -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, diff --git a/testsuite/tests/grammar/invalid_node_traits/derived_node.lkt b/testsuite/tests/grammar/invalid_node_traits/derived_node.lkt new file mode 100644 index 000000000..166ad84f5 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/derived_node.lkt @@ -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] { +} diff --git a/testsuite/tests/grammar/invalid_node_traits/non_node.lkt b/testsuite/tests/grammar/invalid_node_traits/non_node.lkt new file mode 100644 index 000000000..c302289f1 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/non_node.lkt @@ -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 { +} diff --git a/testsuite/tests/grammar/invalid_node_traits/root_error.lkt b/testsuite/tests/grammar/invalid_node_traits/root_error.lkt new file mode 100644 index 000000000..ebcfa6720 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/root_error.lkt @@ -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 { +} diff --git a/testsuite/tests/grammar/invalid_node_traits/root_no_node.lkt b/testsuite/tests/grammar/invalid_node_traits/root_no_node.lkt new file mode 100644 index 000000000..caa777f33 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/root_no_node.lkt @@ -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 { +} diff --git a/testsuite/tests/grammar/invalid_node_traits/root_token.lkt b/testsuite/tests/grammar/invalid_node_traits/root_token.lkt new file mode 100644 index 000000000..88a7b9f04 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/root_token.lkt @@ -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 { +} diff --git a/testsuite/tests/grammar/invalid_node_traits/test.out b/testsuite/tests/grammar/invalid_node_traits/test.out new file mode 100644 index 000000000..fea4259f3 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/test.out @@ -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 diff --git a/testsuite/tests/grammar/invalid_node_traits/test.yaml b/testsuite/tests/grammar/invalid_node_traits/test.yaml new file mode 100644 index 000000000..4a499fbd3 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/test.yaml @@ -0,0 +1 @@ +driver: lkt_compile diff --git a/testsuite/tests/grammar/invalid_node_traits/unknown_trait.lkt b/testsuite/tests/grammar/invalid_node_traits/unknown_trait.lkt new file mode 100644 index 000000000..88caef6f8 --- /dev/null +++ b/testsuite/tests/grammar/invalid_node_traits/unknown_trait.lkt @@ -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] { +}