Liblktlang: fix semantic analysis for user-provided Metadata types

This commit is contained in:
Pierre-Marie de Rodat
2026-01-28 13:04:59 +00:00
parent 11c587138e
commit 1256e7a69f
7 changed files with 154 additions and 101 deletions

View File

@@ -1076,77 +1076,72 @@ class Decl: LktNode implements DeclarationInterface {
env_spec {
add_all_to_env(
# If the type explicitly implements the ``Node`` trait, or it
# is annotated with "@root_node", consider it the root node of
# the language specification.
if
(
self.full_decl()?.has_annotation(s"root_node")
or self.implements_node()
)
and self.name() != s"RootNode__"
then
[
EnvAssoc(
key=s"RootNode__",
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.direct_env,
env_name=null[Symbol],
direct_env=node.root_env()
),
metadata=null[Metadata]
{
# Regular env entry, so that this declaration is available
# using its name in this module.
val regular_entry = EnvAssoc(
key=self.name(),
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.current_env,
env_name=null[Symbol],
direct_env=null[LexicalEnv]
),
EnvAssoc(
key=self.name(),
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.current_env,
env_name=null[Symbol],
direct_env=null[LexicalEnv]
),
metadata=null[Metadata]
)
]
elif
self.full_decl()?.has_annotation(s"metadata")
and self.name() != s"Metadata"
then
[
EnvAssoc(
key=s"Metadata",
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.direct_env,
env_name=null[Symbol],
direct_env=node.root_env()
),
metadata=null[Metadata]
),
EnvAssoc(
key=self.name(),
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.current_env,
env_name=null[Symbol],
direct_env=null[LexicalEnv]
),
metadata=null[Metadata]
)
]
else
[
EnvAssoc(
key=self.name(),
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.current_env,
env_name=null[Symbol],
direct_env=null[LexicalEnv]
),
metadata=null[Metadata]
)
]
metadata=null[Metadata]
);
# If the type explicitly implements the ``Node`` trait, or it
# is annotated with "@root_node", consider it the root node of
# the language specification.
val root_node_entry =
if
(
self.full_decl()?.has_annotation(s"root_node")
or self.implements_node()
)
and self.name() != s"RootNode__"
then
[
EnvAssoc(
key=s"RootNode__",
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.direct_env,
env_name=null[Symbol],
direct_env=node.root_env()
),
metadata=null[Metadata]
)
]
else
[]: EnvAssoc;
# If the type is a metadata struct declaration, register it in
# the root scope under the appropriate symbol: see the doc for
# __default_metadata in the prelude unit.
val metadata_entry =
if self.full_decl()?.has_annotation(s"metadata") then
[
EnvAssoc(
key=(
if node.is_from_prelude()
then s"__default_metadata"
else s"__user_metadata"
),
value=node,
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.direct_env,
env_name=null[Symbol],
direct_env=node.root_env()
),
metadata=null[Metadata]
)
]
else
[]: EnvAssoc;
[regular_entry] & root_node_entry & metadata_entry
}
)
}
}
@@ -5377,19 +5372,16 @@ class LangkitRoot: Decl {
fun defined_scope(): LexicalEnv = node.children_env
|" Get the hidden environment in the prelude containing a default
|" declaration of the Metadata type, for when it is not defined by the
|" specification.
fun internal_env(): LexicalEnv = {
bind origin = null[Entity[LktNode]];
node.children_env.get_first(s"__internal").as[Decl].defined_scope()
}
|" Return the name of this Lkt module, or the empty symbol for the prelude.
@external()
fun module_name(): Symbol
|" Return the Metadata type: see the doc for __default_metadata in the
|" prelude unit.
fun resolve_metadata(): Entity[LktNode] =
node.root_env().get_first(s"__user_metadata")
or? node.root_env().get_first(s"__default_metadata")
|" An empty synthetic TypeRef list node. Used to generate synthetic type
|" declarations.
|"
@@ -5417,14 +5409,21 @@ class LangkitRoot: Decl {
cond=not node.is_from_prelude()
)
handle_children()
# Make default declarations (Metadata) available in the prelude
reference(
[node.as[LktNode]],
LangkitRoot.internal_env,
cond=node.is_from_prelude()
# Add a dynamic entry to resolve to the right Metadata type
add_to_env_kv(
key=s"Metadata",
value=(
if node.is_from_prelude() then node else null[LktNode]
),
dest_env=DesignatedEnv(
kind=DesignatedEnvKind.direct_env,
env_name=null[Symbol],
direct_env=node.root_env()
),
resolver=LangkitRoot.resolve_metadata
)
handle_children()
}
}

View File

@@ -831,15 +831,21 @@ struct Token {
struct SourceLocation {
}
struct __internal {
|" Placeholder type when the Langkit specification does not have a type
|" annotated with ``@metadata``.
struct __default_metadata {
|" Default Metadata type, to be used when the Langkit specification does
|" not have a type annotated with ``@metadata``.
|"
|" This is hidden in its own scope and later has a reference in the
|" global lexical environemnt so that if no type is designated as metadata,
|" this types serves as fallback.
|" 1. This is hidden in a scope so that by default it is unreachable from
|" user code. It is still registered in the root scope as
|" ``__default_metadata`` (for convenient access to it).
|"
|" 2. If user code defines a Metadata struct, it is registered in the root
|" scope as ``__user_metadata``.
|"
|" 3. A dynamic entity resolver inserted in the root scope will resolve to
|" the user Metadata (if available) or the default one (if not).
@metadata
struct __EmptyMetadata {
struct Metadata {
}
}

View File

@@ -84,6 +84,8 @@ Completing <RefId "Stru" test.lkt:8:9-8:13> ('Stru')
- LogicVar: struct_kind
- LookupKind: enum_kind
- LspNodeInterface: interface_kind
- Metadata: struct_kind
- Metadata: struct_kind
- NodeInterface: interface_kind
- PreconditionFailure: struct_kind
- PropertyError: struct_kind
@@ -104,8 +106,7 @@ Completing <RefId "Stru" test.lkt:8:9-8:13> ('Stru')
- TokenNode: interface_kind
- TypableNodeInterface: interface_kind
- TypeInterface: interface_kind
- __EmptyMetadata: struct_kind
- __internal: struct_kind
- __default_metadata: struct_kind
Completing <RefId "v" test.lkt:8:16-8:17> ('v')
- Address: struct_kind
- AnalysisUnit: interface_kind
@@ -138,6 +139,8 @@ Completing <RefId "v" test.lkt:8:16-8:17> ('v')
- LogicVar: struct_kind
- LookupKind: enum_kind
- LspNodeInterface: interface_kind
- Metadata: struct_kind
- Metadata: struct_kind
- NodeInterface: interface_kind
- PreconditionFailure: struct_kind
- PropertyError: struct_kind
@@ -160,8 +163,7 @@ Completing <RefId "v" test.lkt:8:16-8:17> ('v')
- TypeInterface: interface_kind
- _: variable_kind
- _: variable_kind
- __EmptyMetadata: struct_kind
- __internal: struct_kind
- __default_metadata: struct_kind
- add_all_to_env: function_kind
- add_env: function_kind
- add_single_to_env: function_kind

View File

@@ -234,7 +234,7 @@ Expr <CallExpr test.lkt:28:9-28:36>
has_type <StructDecl prelude: "EnvAction">
Id <RefId "add_to_env_kv" test.lkt:28:9-28:22>
has_type <FunctionType prelude: "(Symbol, FooNode, DesignatedEnv, __EmptyMetadata, () -> Entity[FooNode]) -> EnvAction">
has_type <FunctionType prelude: "(Symbol, FooNode, DesignatedEnv, Metadata, () -> Entity[FooNode]) -> EnvAction">
references <FunDecl prelude: "add_to_env_kv">
Expr <PatternSingleLineStringLit test.lkt:28:23-28:29>
@@ -248,7 +248,7 @@ Expr <CallExpr test.lkt:29:9-35:10>
has_type <StructDecl prelude: "EnvAction">
Id <RefId "add_to_env_kv" test.lkt:29:9-29:22>
has_type <FunctionType prelude: "(Symbol, FooNode, DesignatedEnv, __EmptyMetadata, () -> Entity[FooNode]) -> EnvAction">
has_type <FunctionType prelude: "(Symbol, FooNode, DesignatedEnv, Metadata, () -> Entity[FooNode]) -> EnvAction">
references <FunDecl prelude: "add_to_env_kv">
Id <RefId "key" test.lkt:30:13-30:16>
@@ -282,11 +282,11 @@ Expr <CallExpr test.lkt:29:9-35:10>
references <FunParamDecl prelude: "metadata">
Expr <NullLit test.lkt:33:22-33:36>
has_type <StructDecl prelude: "__EmptyMetadata">
has_type <StructDecl prelude: "Metadata">
Id <RefId "Metadata" test.lkt:33:27-33:35>
has_type None
references <StructDecl prelude: "__EmptyMetadata">
references <StructDecl prelude: "Metadata">
Id <RefId "resolver" test.lkt:34:13-34:21>
has_type None

View File

@@ -0,0 +1,7 @@
@metadata
struct Metadata {
}
class FooNode implements Node[FooNode] {
fun get_md(): Metadata = self.info.md
}

View File

@@ -0,0 +1,38 @@
Resolving test.lkt
==================
Expr <Id "metadata" test.lkt:1:2-1:10>
has_type None
Id <RefId "Node" test.lkt:5:26-5:30>
has_type None
references <TraitDecl prelude: "Node[T]">
Id <RefId "FooNode" test.lkt:5:31-5:38>
has_type None
references <ClassDecl "FooNode" test.lkt:5:1-7:2>
Decl <FunDecl "get_md" test.lkt:6:5-6:42>
has_type <FunctionType "() -> Metadata" test.lkt>
Id <RefId "Metadata" test.lkt:6:19-6:27>
has_type None
references <StructDecl "Metadata" test.lkt:2:1-3:2>
Expr <DotExpr test.lkt:6:30-6:42>
has_type <StructDecl "Metadata" test.lkt:2:1-3:2>
Expr <DotExpr test.lkt:6:30-6:39>
has_type <StructDecl prelude: "EntityInfo">
Id <RefId "self" test.lkt:6:30-6:34>
has_type <StructDecl prelude: "Entity[FooNode]">
references <SelfDecl "self" test.lkt:5:1-7:2>
Id <RefId "info" test.lkt:6:35-6:39>
has_type <StructDecl prelude: "EntityInfo">
references <FieldDecl prelude: "info">
Id <RefId "md" test.lkt:6:40-6:42>
has_type <StructDecl "Metadata" test.lkt:2:1-3:2>
references <FieldDecl prelude: "md">

View File

@@ -0,0 +1 @@
driver: lkt