Skip to content

Commit ca2c4bd

Browse files
committed
Lkt lowering: add checks for error nodes
1 parent e3410b2 commit ca2c4bd

File tree

12 files changed

+131
-106
lines changed

12 files changed

+131
-106
lines changed

langkit/compile_context.py

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@
1818
import importlib
1919
import os
2020
from os import path
21-
from typing import (Any, Callable, Dict, List, Optional, Set, TYPE_CHECKING,
22-
Tuple, Union, cast)
21+
from typing import (Any, Callable, Dict, Iterator, List, Optional, Set,
22+
TYPE_CHECKING, Tuple, Union, cast)
2323

2424
from funcy import lzip
2525

@@ -853,19 +853,29 @@ def actual_build_date(self) -> str:
853853
return self.build_date or "undefined"
854854

855855
@staticmethod
856-
def lkt_context(lkt_node: L.LktNode) -> AbstractContextManager[None]:
856+
def lkt_context(
857+
lkt_node: L.LktNode | None
858+
) -> AbstractContextManager[None]:
857859
"""
858860
Context manager to set the diagnostic context to the given node.
859861
860862
:param lkt_node: Node to use as a reference for this diagnostic
861-
context.
863+
context. If it is ``None``, leave the diagnostic context unchanged.
862864
"""
863-
# Invalid type passed here will fail much later and only if a
864-
# check_source_language call fails. To ease debugging, check that
865-
# "lkt_node" has the right type here.
866-
assert isinstance(lkt_node, L.LktNode)
865+
if lkt_node is None:
866+
@contextmanager
867+
def null_ctx_mgr() -> Iterator[None]:
868+
yield
867869

868-
return diagnostic_context(Location.from_lkt_node(lkt_node))
870+
return null_ctx_mgr()
871+
872+
else:
873+
# Invalid type passed here will fail much later and only if a
874+
# check_source_language call fails. To ease debugging, check that
875+
# "lkt_node" has the right type here.
876+
assert isinstance(lkt_node, L.LktNode)
877+
878+
return diagnostic_context(Location.from_lkt_node(lkt_node))
869879

870880
@staticmethod
871881
def lkt_doc(full_decl):

langkit/lkt_lowering.py

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2241,6 +2241,28 @@ def check_trait(trait_ref: Optional[L.LktNode],
22412241
'Inheritting from an enum node is forbidden'
22422242
)
22432243

2244+
with self.ctx.lkt_context(error_node_trait_ref):
2245+
# Determine whether this node is abstract. Remember that base enum
2246+
# node types are abstract (it is their derivations that are
2247+
# concrete).
2248+
is_abstract = (
2249+
not isinstance(annotations, NodeAnnotations)
2250+
or annotations.abstract
2251+
)
2252+
if is_abstract and is_error_node:
2253+
error("Error nodes cannot be abstract")
2254+
2255+
# Determine whether this node is synthetic
2256+
is_synthetic = annotations.synthetic
2257+
if is_synthetic and is_error_node:
2258+
error("Error nodes cannot be synthetic")
2259+
2260+
if base_type and base_type.is_list and is_error_node:
2261+
error("Error nodes cannot be lists")
2262+
2263+
if is_token_node and is_error_node:
2264+
error("Error nodes cannot be token nodes")
2265+
22442266
# Lower fields. Regular nodes can hold all types of fields, but token
22452267
# nodes and enum nodes can hold only user field and properties.
22462268
allowed_field_types = (
@@ -2271,11 +2293,10 @@ def check_trait(trait_ref: Optional[L.LktNode],
22712293
doc=self.ctx.lkt_doc(decl.parent),
22722294
base=base_type,
22732295
fields=fields,
2274-
is_abstract=(not isinstance(annotations, NodeAnnotations)
2275-
or annotations.abstract),
2296+
is_abstract=is_abstract,
22762297
is_token_node=is_token_node,
22772298
is_error_node=is_error_node,
2278-
is_synthetic=annotations.synthetic,
2299+
is_synthetic=is_synthetic,
22792300
has_abstract_list=annotations.has_abstract_list,
22802301
is_enum_node=is_enum_node,
22812302
is_bool_node=is_bool_node,
@@ -2292,6 +2313,18 @@ def check_trait(trait_ref: Optional[L.LktNode],
22922313
qualifier=annotations.qualifier
22932314
)
22942315

2316+
# Reject non-null fields for error nodes. Non-null fields can come from
2317+
# this node's own declaration, or they can come from inheritance.
2318+
if is_error_node:
2319+
error_msg = "Error nodes can only have null fields"
2320+
for f in result.get_parse_fields(include_inherited=True):
2321+
if not (f.null or f.abstract):
2322+
if f.struct != result:
2323+
error(f"{error_msg}: {f.qualname} is not null")
2324+
else:
2325+
with f.diagnostic_context:
2326+
error(error_msg)
2327+
22952328
return result
22962329

22972330
def create_enum_node_alternatives(
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import lexer_example
2+
3+
@with_lexer(foo_lexer)
4+
grammar foo_grammar {
5+
@main_rule main_rule <- or(Example(@example) | skip(ErrorDecl))
6+
}
7+
8+
@abstract class FooNode implements Node[FooNode] {
9+
}
10+
11+
@has_abstract_list class Example : FooNode implements TokenNode {
12+
}
Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,37 @@
1-
== test_abstract ==
2-
test.py:XXX: error: Error nodes cannot be abstract
1+
== test_abstract.lkt ==
2+
test_abstract.lkt:3:52: error: Error nodes cannot be abstract
3+
3 | @abstract class BaseErrorDecl : FooNode implements ErrorNode {
4+
| ^^^^^^^^^
35

4-
== test_bad_type ==
5-
test.py:XXX: error: The "error_node" field, when present, must contain a boolean
66

7-
== test_derived_not_error ==
8-
test.py:XXX: error: "error_node" annotation inconsistent with inherited node
7+
== test_error_and_list.lkt ==
8+
test_error_and_list.lkt:3:56: error: Error nodes cannot be lists
9+
3 | class ErrorDecl : ASTList[FooNode, Example] implements ErrorNode {
10+
| ^^^^^^^^^
911

10-
== test_error_and_list ==
11-
test.py:XXX: error: Error nodes cannot also be lists
1212

13-
== test_error_and_token ==
14-
test.py:XXX: error: Error nodes cannot also be token nodes
13+
== test_error_and_token.lkt ==
14+
test_error_and_token.lkt:3:38: error: Error nodes cannot be token nodes
15+
3 | class ErrorDecl : FooNode implements ErrorNode, TokenNode {
16+
| ^^^^^^^^^
1517

16-
== test_nonnull_field ==
17-
test.py:XXX: error: Only null fields allowed here
1818

19-
== test_synthetic ==
20-
test.py:XXX: error: Error nodes cannot be synthetic
19+
== test_nonnull_field.lkt ==
20+
test_nonnull_field.lkt:4:18: error: Error nodes can only have null fields
21+
4 | @parse_field f: Example
22+
| ^^^^^^^^^^
23+
24+
25+
== test_nonnull_inherited_field.lkt ==
26+
test_nonnull_inherited_field.lkt:7:1: error: Error nodes can only have null fields: BaseErrorDecl.f is not null
27+
7 | class ErrorDecl : BaseErrorDecl implements ErrorNode {
28+
| ^
29+
30+
31+
== test_synthetic.lkt ==
32+
test_synthetic.lkt:3:49: error: Error nodes cannot be synthetic
33+
3 | @synthetic class ErrorDecl : FooNode implements ErrorNode {
34+
| ^^^^^^^^^
35+
2136

2237
Done

testsuite/tests/grammar/invalid_error_node/test.py

Lines changed: 0 additions & 79 deletions
This file was deleted.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
driver: python
1+
driver: lkt_compile
2+
description: |
3+
Check that invalid error nodes are properly reported.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import common
2+
3+
@abstract class BaseErrorDecl : FooNode implements ErrorNode {
4+
}
5+
6+
class ErrorDecl : BaseErrorDecl {
7+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import common
2+
3+
class ErrorDecl : ASTList[FooNode, Example] implements ErrorNode {
4+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import common
2+
3+
class ErrorDecl : FooNode implements ErrorNode, TokenNode {
4+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import common
2+
3+
class ErrorDecl : FooNode implements ErrorNode {
4+
@parse_field f: Example
5+
}

0 commit comments

Comments
 (0)