Skip to main content

harn_modules/
declarations.rs

1use harn_parser::{BindingPattern, Node, SNode};
2
3/// Kind of symbol that can be exported by a module.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
5pub enum DefKind {
6    Function,
7    Pipeline,
8    Tool,
9    Skill,
10    EvalPack,
11    Struct,
12    Enum,
13    Interface,
14    Type,
15    Variable,
16    Parameter,
17}
18
19impl DefKind {
20    /// Whether an exported declaration has a runtime binding that can be
21    /// projected into an importing module. Type and interface declarations
22    /// remain valid imports, but carry only static information.
23    pub const fn has_runtime_value(self) -> bool {
24        !matches!(self, Self::Type | Self::Interface | Self::Parameter)
25    }
26}
27
28/// One public name introduced by a single top-level declaration.
29///
30/// This is the language-level export contract shared by the module graph and
31/// the VM artifact compiler. Consumers must not re-derive declaration kinds
32/// with independent AST matches.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct PublicDeclaration {
35    pub name: String,
36    pub kind: DefKind,
37}
38
39/// Return every public name introduced by one declaration.
40///
41/// Interfaces are public by language design: unlike the other declaration
42/// forms, the grammar does not accept a `pub` modifier for them. Attributes
43/// preserve the visibility of their wrapped declaration.
44pub fn public_declarations(snode: &SNode) -> Vec<PublicDeclaration> {
45    match &snode.node {
46        Node::AttributedDecl { inner, .. } => public_declarations(inner),
47        Node::FnDecl {
48            name, is_pub: true, ..
49        } => declaration(name, DefKind::Function),
50        Node::Pipeline {
51            name, is_pub: true, ..
52        } => declaration(name, DefKind::Pipeline),
53        Node::ToolDecl {
54            name, is_pub: true, ..
55        } => declaration(name, DefKind::Tool),
56        Node::SkillDecl {
57            name, is_pub: true, ..
58        } => declaration(name, DefKind::Skill),
59        Node::EvalPackDecl {
60            binding_name,
61            is_pub: true,
62            ..
63        } => declaration(binding_name, DefKind::EvalPack),
64        Node::StructDecl {
65            name, is_pub: true, ..
66        } => declaration(name, DefKind::Struct),
67        Node::EnumDecl {
68            name, is_pub: true, ..
69        } => declaration(name, DefKind::Enum),
70        Node::InterfaceDecl { name, .. } => declaration(name, DefKind::Interface),
71        Node::TypeDecl {
72            name, is_pub: true, ..
73        } => declaration(name, DefKind::Type),
74        Node::LetBinding {
75            pattern,
76            is_pub: true,
77            ..
78        }
79        | Node::ConstBinding {
80            pattern,
81            is_pub: true,
82            ..
83        } => pattern_names(pattern)
84            .into_iter()
85            .map(|name| PublicDeclaration {
86                name,
87                kind: DefKind::Variable,
88            })
89            .collect(),
90        _ => Vec::new(),
91    }
92}
93
94fn declaration(name: &str, kind: DefKind) -> Vec<PublicDeclaration> {
95    vec![PublicDeclaration {
96        name: name.to_string(),
97        kind,
98    }]
99}
100
101pub(crate) fn pattern_names(pattern: &BindingPattern) -> Vec<String> {
102    match pattern {
103        BindingPattern::Identifier(name) => vec![name.clone()],
104        BindingPattern::Dict(fields) => fields
105            .iter()
106            .filter_map(|field| field.alias.as_ref().or(Some(&field.key)).cloned())
107            .collect(),
108        BindingPattern::List(elements) => elements
109            .iter()
110            .map(|element| element.name.clone())
111            .collect(),
112        BindingPattern::Pair(a, b) => vec![a.clone(), b.clone()],
113    }
114}
115
116// ---------------------------------------------------------------------------
117// Graph-building declaration walks.
118//
119// The exports above answer "what is public here?" for callers outside the
120// crate. These answer "what does this file declare, and where?" for the module
121// graph itself. Both read the same AST, so they share an owner rather than
122// letting `lib.rs` grow a second syntax walk beside the graph builder.
123// ---------------------------------------------------------------------------
124
125use std::path::Path;
126
127use harn_lexer::Span;
128
129use crate::{import_recording, DefSite, ModuleInfo, PackageSnapshot};
130
131pub(crate) fn collect_module_info(
132    file: &Path,
133    snode: &SNode,
134    module: &mut ModuleInfo,
135    package_snapshots: &[PackageSnapshot],
136) {
137    if let Node::AttributedDecl { inner, .. } = &snode.node {
138        collect_module_info(file, inner, module, package_snapshots);
139        return;
140    }
141
142    for public in public_declarations(snode) {
143        module.own_exports.insert(public.name);
144    }
145
146    match &snode.node {
147        Node::FnDecl { name, params, .. } => {
148            module.declarations.insert(
149                name.clone(),
150                decl_site(file, snode.span, name, DefKind::Function),
151            );
152            for param_name in params.iter().map(|param| param.name.clone()) {
153                module.declarations.insert(
154                    param_name.clone(),
155                    decl_site(file, snode.span, &param_name, DefKind::Parameter),
156                );
157            }
158        }
159        Node::Pipeline { name, .. } => {
160            module.declarations.insert(
161                name.clone(),
162                decl_site(file, snode.span, name, DefKind::Pipeline),
163            );
164        }
165        Node::ToolDecl { name, .. } => {
166            module.declarations.insert(
167                name.clone(),
168                decl_site(file, snode.span, name, DefKind::Tool),
169            );
170        }
171        Node::SkillDecl { name, .. } => {
172            module.declarations.insert(
173                name.clone(),
174                decl_site(file, snode.span, name, DefKind::Skill),
175            );
176        }
177        Node::EvalPackDecl { binding_name, .. } => {
178            module.declarations.insert(
179                binding_name.clone(),
180                decl_site(file, snode.span, binding_name, DefKind::EvalPack),
181            );
182        }
183        Node::StructDecl { name, .. } => {
184            module.declarations.insert(
185                name.clone(),
186                decl_site(file, snode.span, name, DefKind::Struct),
187            );
188        }
189        Node::EnumDecl { name, .. } => {
190            module.declarations.insert(
191                name.clone(),
192                decl_site(file, snode.span, name, DefKind::Enum),
193            );
194        }
195        Node::InterfaceDecl { name, .. } => {
196            module.declarations.insert(
197                name.clone(),
198                decl_site(file, snode.span, name, DefKind::Interface),
199            );
200        }
201        Node::TypeDecl { name, .. } => {
202            module.declarations.insert(
203                name.clone(),
204                decl_site(file, snode.span, name, DefKind::Type),
205            );
206        }
207        Node::LetBinding { pattern, .. } | Node::ConstBinding { pattern, .. } => {
208            for name in pattern_names(pattern) {
209                module.declarations.insert(
210                    name.clone(),
211                    decl_site(file, snode.span, &name, DefKind::Variable),
212                );
213            }
214        }
215        _ if import_recording::record_import_node(module, file, snode, package_snapshots) => {}
216        _ => {}
217    }
218}
219
220pub(crate) fn collect_type_declarations(snode: &SNode, decls: &mut Vec<SNode>) {
221    match &snode.node {
222        Node::TypeDecl { .. }
223        | Node::StructDecl { .. }
224        | Node::EnumDecl { .. }
225        | Node::InterfaceDecl { .. } => decls.push(snode.clone()),
226        Node::AttributedDecl { inner, .. } => collect_type_declarations(inner, decls),
227        _ => {}
228    }
229}
230
231pub(crate) fn collect_callable_declarations(snode: &SNode, decls: &mut Vec<SNode>) {
232    match &snode.node {
233        Node::FnDecl { .. } | Node::Pipeline { .. } | Node::ToolDecl { .. } => {
234            decls.push(snode.clone());
235        }
236        Node::AttributedDecl { inner, .. } => collect_callable_declarations(inner, decls),
237        _ => {}
238    }
239}
240
241pub(crate) fn type_decl_name(snode: &SNode) -> Option<&str> {
242    match &snode.node {
243        Node::TypeDecl { name, .. }
244        | Node::StructDecl { name, .. }
245        | Node::EnumDecl { name, .. }
246        | Node::InterfaceDecl { name, .. } => Some(name.as_str()),
247        _ => None,
248    }
249}
250
251pub(crate) fn callable_decl_name(snode: &SNode) -> Option<&str> {
252    match &snode.node {
253        Node::FnDecl { name, .. } | Node::Pipeline { name, .. } | Node::ToolDecl { name, .. } => {
254            Some(name.as_str())
255        }
256        Node::AttributedDecl { inner, .. } => callable_decl_name(inner),
257        _ => None,
258    }
259}
260
261pub(crate) fn decl_site(file: &Path, span: Span, name: &str, kind: DefKind) -> DefSite {
262    DefSite {
263        name: name.to_string(),
264        file: file.to_path_buf(),
265        kind,
266        span,
267    }
268}