Skip to main content

leo_parser/
rowan.rs

1// Copyright (C) 2019-2026 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17//! Rowan-based parser implementation.
18//!
19//! This module uses `leo-parser-rowan` (rowan) and converts its output
20//! to the Leo AST via the `ConversionContext`.
21
22// Implementation notes:
23//
24// - All user-reachable errors should be emitted via the error handler (mostly
25//   `ERROR` nodes).
26// - All implementation bugs (e.g. unexpected node structure from
27//   `leo_parser_rowan`) should `panic!` in order to catch logic bugs in the
28//   compiler.
29
30use itertools::Itertools as _;
31use snarkvm::prelude::{Address, Signature, TestnetV0};
32
33use leo_ast::{NetworkName, NodeBuilder};
34use leo_errors::{Handler, ParserError, ParserWarning, Result};
35use leo_parser_rowan::{SyntaxElement, SyntaxKind, SyntaxKind::*, SyntaxNode, SyntaxToken, TextRange};
36use leo_span::{
37    Span,
38    Symbol,
39    source_map::{FileName, SourceFile},
40    sym,
41};
42
43/// Type parameters and const arguments extracted from a `CONST_ARG_LIST` node.
44type ConstArgList = (Vec<(leo_ast::Type, Span)>, Vec<leo_ast::Expression>);
45
46/// Annotated types with visibility modes, used for `_dynamic_call` input/return types.
47type AnnotatedTypes = Vec<(leo_ast::Mode, leo_ast::Type, Span)>;
48
49/// Parent declarations for inheritance
50type Parents = Vec<(Span, leo_ast::Type)>;
51
52// =============================================================================
53// ConversionContext
54// =============================================================================
55
56/// Context for converting rowan syntax nodes to Leo AST nodes.
57struct ConversionContext<'a> {
58    handler: &'a Handler,
59    builder: &'a NodeBuilder,
60    /// The absolute start position to offset spans by.
61    start_pos: u32,
62    /// When true, suppress `unexpected_str` errors during conversion.
63    ///
64    /// These errors are always downstream of parse/lex errors (the conversion
65    /// only fails when the CST contains ERROR nodes from parse recovery), so
66    /// reporting them would be duplicative.
67    suppress_cascade: bool,
68}
69
70impl<'a> ConversionContext<'a> {
71    /// Create a new conversion context.
72    fn new(handler: &'a Handler, builder: &'a NodeBuilder, start_pos: u32, suppress_cascade: bool) -> Self {
73        Self { handler, builder, start_pos, suppress_cascade }
74    }
75
76    /// Emit an `unexpected_str` error, unless cascade suppression is active.
77    fn emit_unexpected_str(&self, expected: &str, found: impl std::fmt::Display, span: Span) {
78        if !self.suppress_cascade {
79            self.handler.emit_err(ParserError::unexpected_str(expected, found, span));
80        }
81    }
82
83    // =========================================================================
84    // Utility Methods
85    // =========================================================================
86
87    /// Convert a rowan TextRange to a leo_span::Span.
88    fn to_span(&self, node: &SyntaxNode) -> Span {
89        let range = node.text_range();
90        Span::new(u32::from(range.start()) + self.start_pos, u32::from(range.end()) + self.start_pos)
91    }
92
93    /// Convert a token's text range to a Span.
94    fn token_span(&self, token: &SyntaxToken) -> Span {
95        let range = token.text_range();
96        Span::new(u32::from(range.start()) + self.start_pos, u32::from(range.end()) + self.start_pos)
97    }
98
99    /// Like `to_span` but starts at the first non-trivia direct token,
100    /// excluding leading whitespace/comments from the span.
101    fn non_trivia_span(&self, node: &SyntaxNode) -> Span {
102        let start = first_non_trivia_token(node).map(|t| t.text_range().start()).unwrap_or(node.text_range().start());
103        let end = node.text_range().end();
104        Span::new(u32::from(start) + self.start_pos, u32::from(end) + self.start_pos)
105    }
106
107    /// Span that excludes both leading and trailing trivia. Suitable for
108    /// leaf-like nodes (type paths, single tokens) where trailing whitespace
109    /// is not significant.
110    fn trimmed_span(&self, node: &SyntaxNode) -> Span {
111        let start = first_non_trivia_token(node).map(|t| t.text_range().start()).unwrap_or(node.text_range().start());
112        let end = last_non_trivia_token(node).map(|t| t.text_range().end()).unwrap_or(node.text_range().end());
113        Span::new(u32::from(start) + self.start_pos, u32::from(end) + self.start_pos)
114    }
115
116    /// Span that excludes leading and trailing trivia by scanning all
117    /// descendant tokens (deep traversal). Use for expression and statement
118    /// nodes whose trailing trivia may be nested inside child nodes.
119    fn content_span(&self, node: &SyntaxNode) -> Span {
120        let mut first = node.text_range().start();
121        let mut last = node.text_range().end();
122        let mut found_first = false;
123        for elem in node.descendants_with_tokens() {
124            if let Some(t) = elem.as_token()
125                && !t.kind().is_trivia()
126            {
127                if !found_first {
128                    first = t.text_range().start();
129                    found_first = true;
130                }
131                last = t.text_range().end();
132            }
133        }
134        Span::new(u32::from(first) + self.start_pos, u32::from(last) + self.start_pos)
135    }
136
137    /// Extend span to include leading annotations, if any.
138    fn span_including_annotations(&self, node: &SyntaxNode, span: Span) -> Span {
139        children(node)
140            .find(|n| n.kind() == ANNOTATION)
141            .map(|ann| Span::new(self.trimmed_span(&ann).lo, span.hi))
142            .unwrap_or(span)
143    }
144
145    /// Convert an IDENT token to a leo_ast::Identifier.
146    fn to_identifier(&self, token: &SyntaxToken) -> leo_ast::Identifier {
147        debug_assert_eq!(token.kind(), IDENT);
148        leo_ast::Identifier {
149            name: Symbol::intern(token.text()),
150            span: self.token_span(token),
151            id: self.builder.next_id(),
152        }
153    }
154
155    /// Create a placeholder identifier for error recovery.
156    fn error_identifier(&self, span: Span) -> leo_ast::Identifier {
157        leo_ast::Identifier { name: Symbol::intern("_error"), span, id: self.builder.next_id() }
158    }
159
160    /// Create a placeholder expression for error recovery.
161    fn error_expression(&self, span: Span) -> leo_ast::Expression {
162        leo_ast::ErrExpression { span, id: self.builder.next_id() }.into()
163    }
164
165    /// Create an `IntrinsicExpression` with no type parameters.
166    fn intrinsic_expression(
167        &self,
168        name: Symbol,
169        arguments: Vec<leo_ast::Expression>,
170        span: Span,
171    ) -> leo_ast::Expression {
172        leo_ast::IntrinsicExpression {
173            name,
174            type_parameters: Vec::new(),
175            input_types: Vec::new(),
176            return_types: Vec::new(),
177            arguments,
178            span,
179            id: self.builder.next_id(),
180        }
181        .into()
182    }
183
184    /// Create an empty block for error recovery.
185    fn error_block(&self, span: Span) -> leo_ast::Block {
186        leo_ast::Block { statements: Vec::new(), span, id: self.builder.next_id() }
187    }
188
189    /// Emit an error if the literal text has a hex, octal, or binary prefix,
190    /// which is not allowed for non-integer types (field, group, scalar).
191    fn validate_hexbin_literal(&self, text: &str, suffix_len: u32, span: Span) {
192        if text.starts_with("0x") || text.starts_with("0o") || text.starts_with("0b") {
193            self.handler.emit_err(ParserError::hexbin_literal_nonintegers(Span::new(span.lo, span.hi - suffix_len)));
194        }
195    }
196
197    /// Find an IDENT token in `node` or emit an error and return a placeholder.
198    fn require_ident(&self, node: &SyntaxNode, label: &str) -> leo_ast::Identifier {
199        let span = self.to_span(node);
200        match tokens(node).find(|t| t.kind() == IDENT) {
201            Some(token) => self.to_identifier(&token),
202            None => {
203                self.emit_unexpected_str(label, node.text(), span);
204                self.error_identifier(span)
205            }
206        }
207    }
208
209    /// Find a type child node or emit an error and return `Type::Err`.
210    fn require_type(&self, node: &SyntaxNode, label: &str) -> Result<leo_ast::Type> {
211        match children(node).find(|n| n.kind().is_type()) {
212            Some(type_node) => self.to_type(&type_node),
213            None => {
214                self.emit_unexpected_str(label, node.text(), self.to_span(node));
215                Ok(leo_ast::Type::Err)
216            }
217        }
218    }
219
220    /// Find an expression child node or emit an error and return `ErrExpression`.
221    fn require_expression(&self, node: &SyntaxNode, label: &str) -> Result<leo_ast::Expression> {
222        match children(node).find(|n| n.kind().is_expression()) {
223            Some(expr_node) => self.to_expression(&expr_node),
224            None => {
225                let span = self.to_span(node);
226                self.emit_unexpected_str(label, node.text(), span);
227                Ok(self.error_expression(span))
228            }
229        }
230    }
231
232    /// Validate an identifier, checking for double underscores and length.
233    fn validate_identifier(&self, ident: &leo_ast::Identifier) {
234        const MAX_IDENTIFIER_LEN: usize = 31;
235        let text = ident.name.to_string();
236        if text.len() > MAX_IDENTIFIER_LEN {
237            self.handler.emit_err(ParserError::identifier_too_long(&text, text.len(), MAX_IDENTIFIER_LEN, ident.span));
238        }
239        if text.contains("__") {
240            self.handler.emit_err(ParserError::identifier_cannot_contain_double_underscore(&text, ident.span));
241        }
242    }
243
244    /// Validate an identifier in a definition position (struct field, variable,
245    /// function name, etc.). In addition to the general identifier checks, this
246    /// rejects identifiers that start with `_` or that are reserved keywords.
247    fn validate_definition_identifier(&self, ident: &leo_ast::Identifier) {
248        // Skip validation for error-recovery placeholders.
249        if ident.name == Symbol::intern("_error") {
250            return;
251        }
252        self.validate_identifier(ident);
253        let text = ident.name.to_string();
254        if text.starts_with('_') {
255            self.handler.emit_err(ParserError::identifier_cannot_start_with_underscore(ident.span));
256        }
257        if symbol_is_keyword(ident.name) {
258            self.emit_unexpected_str("an identifier", &text, ident.span);
259        }
260    }
261
262    // =========================================================================
263    // Type Conversions
264    // =========================================================================
265
266    /// Convert a type syntax node to a Leo AST Type.
267    fn to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
268        let ty = match node.kind() {
269            TYPE_PRIMITIVE => self.type_primitive_to_type(node)?,
270            TYPE_LOCATOR => self.type_locator_to_type(node)?,
271            TYPE_PATH => self.type_path_to_type(node)?,
272            TYPE_ARRAY => self.type_array_to_type(node)?,
273            TYPE_VECTOR => self.type_vector_to_type(node)?,
274            TYPE_TUPLE => self.type_tuple_to_type(node)?,
275            TYPE_OPTIONAL => self.type_optional_to_type(node)?,
276            TYPE_FINAL => self.type_final_to_type(node)?,
277            TYPE_MAPPING => self.type_mapping_to_type(node)?,
278            TYPE_DYN_RECORD => leo_ast::Type::DynRecord,
279            ERROR => {
280                // Parse errors already emitted by emit_parse_errors().
281                leo_ast::Type::Err
282            }
283            kind => panic!("unexpected type node kind: {:?}", kind),
284        };
285        Ok(ty)
286    }
287
288    /// Convert a TYPE_PRIMITIVE node to a Type.
289    fn type_primitive_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
290        debug_assert_eq!(node.kind(), TYPE_PRIMITIVE);
291        let prim = tokens(node)
292            .next()
293            .and_then(|t| keyword_to_primitive_type(t.kind()))
294            .expect("TYPE_PRIMITIVE should contain a type keyword");
295        Ok(prim)
296    }
297
298    /// Convert a TYPE_LOCATOR node to a Type.
299    ///
300    /// TYPE_LOCATOR represents `program.aleo::Type`.
301    fn type_locator_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
302        debug_assert_eq!(node.kind(), TYPE_LOCATOR);
303
304        // Extract IDENT tokens for program and (optional) type name.
305        let mut idents = tokens(node).filter(|t| t.kind() == IDENT);
306        let program_token = idents.next();
307        let name_token = idents.next();
308
309        // Find KW_ALEO in the tokens — always present in a TYPE_LOCATOR node.
310        let kw_aleo_token =
311            tokens(node).find(|t| t.kind() == KW_ALEO).expect("TYPE_LOCATOR should contain `aleo` keyword");
312
313        let network_ident = leo_ast::Identifier {
314            name: Symbol::intern("aleo"),
315            span: self.token_span(&kw_aleo_token),
316            id: self.builder.next_id(),
317        };
318
319        match (program_token, name_token) {
320            (Some(program_token), Some(name_token)) => {
321                // Normal case: program.aleo::Type
322                let program_ident = self.to_identifier(&program_token);
323                let type_ident = self.to_identifier(&name_token);
324                let program_id = leo_ast::ProgramId { name: program_ident, network: network_ident };
325                let path_span = Span::new(program_id.name.span.lo, type_ident.span.hi);
326                let path =
327                    leo_ast::Path::new(Some(program_id), Vec::new(), type_ident, path_span, self.builder.next_id());
328                let (_type_parameters, const_arguments) = self.extract_const_arg_list(node)?;
329                Ok(leo_ast::CompositeType { path, const_arguments }.into())
330            }
331            (Some(program_token), None) => {
332                // program.aleo without ::Type — a program ID used as a type reference.
333                let program_ident = self.to_identifier(&program_token);
334                let program_id = leo_ast::ProgramId { name: program_ident, network: network_ident };
335                let span = self.content_span(node);
336                let path =
337                    leo_ast::Path::new(Some(program_id), Vec::new(), program_ident, span, self.builder.next_id());
338                Ok(leo_ast::CompositeType { path, const_arguments: Vec::new() }.into())
339            }
340            _ => {
341                panic!("TYPE_LOCATOR should contain at least a program IDENT: {:?}", node.text())
342            }
343        }
344    }
345
346    /// Convert a TYPE_PATH node to a Type.
347    ///
348    /// TYPE_PATH represents named/composite types: `Foo`, `Foo::Bar`, `Foo::[N]`.
349    fn type_path_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
350        debug_assert_eq!(node.kind(), TYPE_PATH);
351
352        // Regular path: collect identifiers and const generic args
353        let mut path_components = Vec::new();
354
355        // Collect IDENT tokens that are direct children of TYPE_PATH (not inside CONST_ARG_LIST)
356        for token in tokens(node) {
357            match token.kind() {
358                IDENT => {
359                    path_components.push(self.to_identifier(&token));
360                }
361                // Skip punctuation
362                COLON_COLON | L_BRACKET | R_BRACKET | LT | GT | COMMA | INTEGER => {}
363                kind if kind.is_trivia() => {}
364                kind => panic!("unexpected token in TYPE_PATH: {:?}", kind),
365            }
366        }
367
368        // Extract const arguments from CONST_ARG_LIST child node
369        let (_type_parameters, const_arguments) = self.extract_const_arg_list(node)?;
370
371        // The last component is the type name, rest are path segments.
372        // Path span covers only the identifier tokens, not the const arg list.
373        let name = path_components.pop().expect("TYPE_PATH should have at least one identifier");
374        let path_span =
375            if let Some(first) = path_components.first() { Span::new(first.span.lo, name.span.hi) } else { name.span };
376        let path = leo_ast::Path::new(None, path_components, name, path_span, self.builder.next_id());
377        Ok(leo_ast::CompositeType { path, const_arguments }.into())
378    }
379
380    /// Convert a TYPE_ARRAY node to an ArrayType.
381    fn type_array_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
382        debug_assert_eq!(node.kind(), TYPE_ARRAY);
383
384        let element_node = children(node).find(|n| n.kind().is_type()).expect("array type should have element type");
385        let element_type = self.to_type(&element_node)?;
386        let length_expr = self.array_length_to_expression(node)?;
387        Ok(leo_ast::ArrayType { element_type: Box::new(element_type), length: Box::new(length_expr) }.into())
388    }
389
390    /// Convert a TYPE_VECTOR node to a VectorType.
391    fn type_vector_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
392        debug_assert_eq!(node.kind(), TYPE_VECTOR);
393
394        let element_node = children(node).find(|n| n.kind().is_type()).expect("vector type should have element type");
395        let element_type = self.to_type(&element_node)?;
396        Ok(leo_ast::VectorType { element_type: Box::new(element_type) }.into())
397    }
398
399    /// Extract the array length expression from a TYPE_ARRAY node.
400    fn array_length_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
401        match children(node).find(|n| n.kind() == ARRAY_LENGTH) {
402            Some(length_node) => self.require_expression(&length_node, "array length"),
403            None => {
404                // Error recovery: TYPE_ARRAY without ARRAY_LENGTH (e.g. `[T, N]` typo).
405                let span = self.to_span(node);
406                self.emit_unexpected_str("array length", node.text(), span);
407                Ok(self.error_expression(span))
408            }
409        }
410    }
411
412    /// Convert an INTEGER token to an Expression.
413    fn integer_token_to_expression(&self, token: &SyntaxToken) -> Result<leo_ast::Expression> {
414        debug_assert_eq!(token.kind(), INTEGER);
415        let text = token.text();
416        let span = self.token_span(token);
417        let id = self.builder.next_id();
418
419        // Check for integer type suffix
420        let suffixes = [
421            ("u128", leo_ast::IntegerType::U128),
422            ("u64", leo_ast::IntegerType::U64),
423            ("u32", leo_ast::IntegerType::U32),
424            ("u16", leo_ast::IntegerType::U16),
425            ("u8", leo_ast::IntegerType::U8),
426            ("i128", leo_ast::IntegerType::I128),
427            ("i64", leo_ast::IntegerType::I64),
428            ("i32", leo_ast::IntegerType::I32),
429            ("i16", leo_ast::IntegerType::I16),
430            ("i8", leo_ast::IntegerType::I8),
431        ];
432
433        for (suffix, int_type) in suffixes {
434            if text.ends_with(suffix) {
435                // Suffixed integer - preserve underscores in value
436                let value = text.strip_suffix(suffix).unwrap().to_string();
437                return Ok(leo_ast::Literal::integer(int_type, value, span, id).into());
438            }
439        }
440
441        // No suffix - use Unsuffixed variant (preserving underscores)
442        Ok(leo_ast::Literal::unsuffixed(text.to_string(), span, id).into())
443    }
444
445    /// Convert a TYPE_TUPLE node to a TupleType or Unit.
446    fn type_tuple_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
447        debug_assert_eq!(node.kind(), TYPE_TUPLE);
448        let span = self.to_span(node);
449
450        let type_nodes: Vec<_> = children(node).filter(|n| n.kind().is_type()).collect();
451
452        if type_nodes.is_empty() {
453            // Unit type: ()
454            return Ok(leo_ast::Type::Unit);
455        }
456
457        let elements = type_nodes.iter().map(|n| self.to_type(n)).collect::<Result<Vec<_>>>()?;
458
459        if elements.len() == 1 {
460            // Single-element tuple type is invalid - emit error
461            self.handler.emit_err(ParserError::tuple_must_have_at_least_two_elements("type", span));
462            // Return the single element for error recovery
463            return Ok(elements.into_iter().next().unwrap());
464        }
465
466        Ok(leo_ast::TupleType::new(elements).into())
467    }
468
469    /// Convert a TYPE_OPTIONAL node to an OptionalType.
470    fn type_optional_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
471        debug_assert_eq!(node.kind(), TYPE_OPTIONAL);
472
473        let inner_node = children(node).find(|n| n.kind().is_type()).expect("optional type should have inner type");
474
475        let inner = self.to_type(&inner_node)?;
476        Ok(leo_ast::Type::Optional(leo_ast::OptionalType { inner: Box::new(inner) }))
477    }
478
479    /// Convert a TYPE_FINAL node to a FutureType.
480    fn type_final_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
481        debug_assert_eq!(node.kind(), TYPE_FINAL);
482
483        // Collect any type children (for Future<fn(T) -> R> syntax)
484        let type_nodes: Vec<_> = children(node).filter(|n| n.kind().is_type()).collect();
485
486        if type_nodes.is_empty() {
487            // Simple Future with no explicit signature
488            return Ok(leo_ast::FutureType::default().into());
489        }
490
491        // Future with explicit signature: Future<fn(T1, T2) -> R>
492        let types = type_nodes.iter().map(|n| self.to_type(n)).collect::<Result<Vec<_>>>()?;
493
494        Ok(leo_ast::FutureType::new(types, None, true).into())
495    }
496
497    fn type_mapping_to_type(&self, node: &SyntaxNode) -> Result<leo_ast::Type> {
498        debug_assert_eq!(node.kind(), TYPE_MAPPING);
499        let mut type_nodes = children(node).filter(|n| n.kind().is_type());
500        let key = type_nodes.next().map(|n| self.to_type(&n)).transpose()?.unwrap_or(leo_ast::Type::Err);
501        let value = type_nodes.next().map(|n| self.to_type(&n)).transpose()?.unwrap_or(leo_ast::Type::Err);
502        Ok(leo_ast::Type::Mapping(leo_ast::MappingType { key: Box::new(key), value: Box::new(value) }))
503    }
504
505    // =========================================================================
506    // Expression Conversions
507    // =========================================================================
508
509    /// Convert a syntax node to an expression.
510    fn to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
511        let span = self.content_span(node);
512
513        let expr = match node.kind() {
514            LITERAL_FIELD => self.suffixed_literal_to_expression(node, "field", leo_ast::Literal::field)?,
515            LITERAL_GROUP => self.suffixed_literal_to_expression(node, "group", leo_ast::Literal::group)?,
516            LITERAL_SCALAR => self.suffixed_literal_to_expression(node, "scalar", leo_ast::Literal::scalar)?,
517            LITERAL_INT => self.int_literal_to_expression(node)?,
518            LITERAL_STRING => self.string_literal_to_expression(node)?,
519            LITERAL_ADDRESS => self.address_literal_to_expression(node)?,
520            LITERAL_BOOL => self.bool_literal_to_expression(node)?,
521            LITERAL_NONE => leo_ast::Literal::none(span, self.builder.next_id()).into(),
522            LITERAL_IDENT => self.identifier_literal_to_expression(node)?,
523            BINARY_EXPR => self.binary_expr_to_expression(node)?,
524            UNARY_EXPR => self.unary_expr_to_expression(node)?,
525            CALL_EXPR => self.call_expr_to_expression(node)?,
526            DYNAMIC_CALL_EXPR => self.dynamic_call_expr_to_expression(node)?,
527            METHOD_CALL_EXPR => self.method_call_expr_to_expression(node)?,
528            FIELD_EXPR => self.field_expr_to_expression(node)?,
529            TUPLE_ACCESS_EXPR => self.tuple_access_expr_to_expression(node)?,
530            INDEX_EXPR => self.index_expr_to_expression(node)?,
531            CAST_EXPR => self.cast_expr_to_expression(node)?,
532            TERNARY_EXPR => self.ternary_expr_to_expression(node)?,
533            ARRAY_EXPR => self.array_expr_to_expression(node)?,
534            REPEAT_EXPR => self.repeat_expr_to_expression(node)?,
535            TUPLE_EXPR => self.tuple_expr_to_expression(node)?,
536            STRUCT_EXPR => self.struct_expr_to_expression(node)?,
537            STRUCT_LOCATOR_EXPR => self.struct_locator_expr_to_expression(node)?,
538            PATH_EXPR => self.path_expr_to_expression(node)?,
539            PATH_LOCATOR_EXPR => self.path_locator_expr_to_expression(node)?,
540            PROGRAM_REF_EXPR => self.program_ref_expr_to_expression(node)?,
541            SELF_EXPR => self.keyword_expr_to_path(node, sym::SelfLower)?,
542            BLOCK_KW_EXPR => self.keyword_expr_to_path(node, sym::block)?,
543            NETWORK_KW_EXPR => self.keyword_expr_to_path(node, sym::network)?,
544            PAREN_EXPR => {
545                // Parenthesized expression - just unwrap
546                if let Some(inner) = children(node).find(|n| n.kind().is_expression()) {
547                    self.to_expression(&inner)?
548                } else {
549                    // No inner expression found - likely parse error
550                    self.emit_unexpected_str("expression in parentheses", node.text(), span);
551                    self.error_expression(span)
552                }
553            }
554            // Final expression block
555            FINAL_EXPR => self.final_expr_to_expression(node)?,
556            // For ROOT nodes that wrap an expression (from parse_expression_entry)
557            ROOT => {
558                if let Some(inner) = children(node).find(|n| n.kind().is_expression()) {
559                    self.to_expression(&inner)?
560                } else {
561                    // Parse errors already emitted by emit_parse_errors().
562                    self.error_expression(span)
563                }
564            }
565            // Error recovery: return ErrExpression for ERROR nodes.
566            // Parse errors already emitted by emit_parse_errors().
567            ERROR => self.error_expression(span),
568            kind => panic!("unexpected expression kind: {:?}", kind),
569        };
570
571        Ok(expr)
572    }
573
574    /// Convert a suffixed literal node (field, group, scalar) to an expression.
575    fn suffixed_literal_to_expression(
576        &self,
577        node: &SyntaxNode,
578        suffix: &str,
579        ctor: fn(String, Span, leo_ast::NodeID) -> leo_ast::Literal,
580    ) -> Result<leo_ast::Expression> {
581        let span = self.content_span(node);
582        let id = self.builder.next_id();
583        let token = tokens(node).next().expect("literal node should have a token");
584        let text = token.text();
585        self.validate_hexbin_literal(text, suffix.len() as u32, span);
586        let value = text.strip_suffix(suffix).unwrap();
587        Ok(ctor(value.to_string(), span, id).into())
588    }
589
590    /// Convert a LITERAL_INT node to an expression.
591    fn int_literal_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
592        let token = tokens(node).next().expect("LITERAL_INT should have a token");
593        self.integer_token_to_expression(&token)
594    }
595
596    /// Convert a LITERAL_STRING node to an expression.
597    fn string_literal_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
598        let span = self.content_span(node);
599        let id = self.builder.next_id();
600        let token = tokens(node).next().expect("LITERAL_STRING should have a token");
601        Ok(leo_ast::Literal::string(token.text().to_string(), span, id).into())
602    }
603
604    /// Convert a LITERAL_IDENT node to an expression.
605    fn identifier_literal_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
606        let span = self.content_span(node);
607        let id = self.builder.next_id();
608        let token = tokens(node).next().expect("LITERAL_IDENT should have a token");
609        // Strip the surrounding single quotes.
610        let text = token.text();
611        let content = &text[1..text.len() - 1];
612        Ok(leo_ast::Literal::identifier(content.to_string(), span, id).into())
613    }
614
615    /// Convert a LITERAL_ADDRESS node to an expression.
616    fn address_literal_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
617        let span = self.content_span(node);
618        let id = self.builder.next_id();
619        let token = tokens(node).next().expect("LITERAL_ADDRESS should have a token");
620        let text = token.text();
621        // Validate address literal (skip program addresses like "program.aleo")
622        if !text.contains(".aleo") && text.parse::<Address<TestnetV0>>().is_err() {
623            self.handler.emit_err(ParserError::invalid_address_lit(text, span));
624        }
625        Ok(leo_ast::Literal::address(text.to_string(), span, id).into())
626    }
627
628    /// Convert a LITERAL_BOOL node to an expression.
629    fn bool_literal_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
630        let span = self.content_span(node);
631        let id = self.builder.next_id();
632        let token = tokens(node).next().expect("LITERAL_BOOL should have a token");
633        let value = token.kind() == KW_TRUE;
634        Ok(leo_ast::Literal::boolean(value, span, id).into())
635    }
636
637    /// Convert a BINARY_EXPR node to a BinaryExpression.
638    fn binary_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
639        debug_assert_eq!(node.kind(), BINARY_EXPR);
640        let span = self.content_span(node);
641        let id = self.builder.next_id();
642
643        let mut operands = children(node).filter(|n| n.kind().is_expression() || n.kind().is_type());
644
645        // Find the operator token
646        let op_token = match tokens(node).find(|t| t.kind().is_operator() || t.kind() == KW_AS) {
647            Some(token) => token,
648            None => {
649                self.emit_unexpected_str("operator in binary expression", node.text(), span);
650                return Ok(self.error_expression(span));
651            }
652        };
653
654        let op = token_to_binary_op(op_token.kind());
655
656        // Get left operand
657        let left = match operands.next() {
658            Some(left_node) => self.to_expression(&left_node)?,
659            None => {
660                self.emit_unexpected_str("left operand in binary expression", node.text(), span);
661                return Ok(self.error_expression(span));
662            }
663        };
664
665        // KW_AS should be CAST_EXPR, not binary.
666        if op_token.kind() == KW_AS {
667            self.emit_unexpected_str("cast expression", "binary AS expression", span);
668            return Ok(self.error_expression(span));
669        }
670
671        // Get right operand
672        let right = match operands.next() {
673            Some(right_node) => self.to_expression(&right_node)?,
674            None => {
675                self.emit_unexpected_str("right operand in binary expression", node.text(), span);
676                return Ok(self.error_expression(span));
677            }
678        };
679
680        Ok(leo_ast::BinaryExpression { left, right, op, span, id }.into())
681    }
682
683    /// Convert a UNARY_EXPR node to a UnaryExpression.
684    fn unary_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
685        debug_assert_eq!(node.kind(), UNARY_EXPR);
686        let span = self.content_span(node);
687        let id = self.builder.next_id();
688
689        // Get the operator
690        let Some(op_token) = tokens(node).find(|t| matches!(t.kind(), BANG | MINUS)) else {
691            self.emit_unexpected_str("operator in unary expression", node.text(), span);
692            return Ok(self.error_expression(span));
693        };
694
695        let op = if op_token.kind() == BANG { leo_ast::UnaryOperation::Not } else { leo_ast::UnaryOperation::Negate };
696
697        // Get the operand
698        let Some(operand) = children(node).find(|n| n.kind().is_expression()) else {
699            self.emit_unexpected_str("operand in unary expression", node.text(), span);
700            return Ok(self.error_expression(span));
701        };
702
703        let mut receiver = self.to_expression(&operand)?;
704
705        // Fold negation into numeric literals
706        if op == leo_ast::UnaryOperation::Negate
707            && let leo_ast::Expression::Literal(leo_ast::Literal {
708                variant:
709                    leo_ast::LiteralVariant::Integer(_, ref mut string)
710                    | leo_ast::LiteralVariant::Field(ref mut string)
711                    | leo_ast::LiteralVariant::Group(ref mut string)
712                    | leo_ast::LiteralVariant::Scalar(ref mut string),
713                span: ref mut lit_span,
714                ..
715            }) = receiver
716            && !string.starts_with('-')
717        {
718            string.insert(0, '-');
719            *lit_span = span;
720            return Ok(receiver);
721        }
722
723        Ok(leo_ast::UnaryExpression { receiver, op, span, id }.into())
724    }
725
726    /// Extract type parameters and const arguments from a CONST_ARG_LIST child, if present.
727    ///
728    /// In the rowan CST, `CONST_ARG_LIST` children are either type nodes (for
729    /// intrinsic type parameters like `Deserialize::[u32]`), `DYNAMIC_CALL_RETURN_TYPE`
730    /// nodes (for visibility-annotated types like `public u64`), or expression nodes
731    /// (for const generic arguments like `Foo::[N]`).
732    fn extract_const_arg_list(&self, node: &SyntaxNode) -> Result<ConstArgList> {
733        let mut type_parameters = Vec::new();
734        let mut const_arguments = Vec::new();
735        if let Some(arg_list) = children(node).find(|n| n.kind() == CONST_ARG_LIST) {
736            for child in children(&arg_list) {
737                if child.kind() == DYNAMIC_CALL_RETURN_TYPE {
738                    // Visibility-annotated type: extract the inner type (visibility
739                    // is extracted separately when building return_types).
740                    if let Some(type_node) = children(&child).find(|n| n.kind().is_type()) {
741                        let span = self.content_span(&child);
742                        let ty = self.to_type(&type_node)?;
743                        type_parameters.push((ty, span));
744                    }
745                } else if child.kind().is_type() {
746                    let span = self.content_span(&child);
747                    let ty = self.to_type(&child)?;
748                    type_parameters.push((ty, span));
749                } else if child.kind().is_expression() {
750                    let expr = self.to_expression(&child)?;
751                    const_arguments.push(expr);
752                }
753            }
754        }
755        Ok((type_parameters, const_arguments))
756    }
757
758    /// Extract input and return types with visibility for `_dynamic_call` from the CST.
759    ///
760    /// Rule: the last type parameter is the return type, all preceding are input types.
761    /// - `_dynamic_call::[u64](...)` — return u64, no input annotations
762    /// - `_dynamic_call::[public u32, u64](...)` — input: public u32, return: u64
763    /// - `_dynamic_call::[public u32, public u32, (u32, u32)](...)` — two inputs, tuple return
764    ///
765    /// Tuple return types are unpacked into individual elements.
766    /// Visibility prefixes (public/private/constant) are extracted from `DYNAMIC_CALL_RETURN_TYPE` nodes.
767    fn extract_dynamic_call_types(
768        &self,
769        callee_node: &SyntaxNode,
770        type_parameters: &[(leo_ast::Type, Span)],
771    ) -> Result<(AnnotatedTypes, AnnotatedTypes)> {
772        let Some(arg_list) = children(callee_node).find(|n| n.kind() == CONST_ARG_LIST) else {
773            return Ok((Vec::new(), Vec::new()));
774        };
775
776        // Collect all annotated type entries with their modes.
777        let mut all_entries = Vec::new();
778
779        for child in children(&arg_list) {
780            if child.kind() == DYNAMIC_CALL_RETURN_TYPE {
781                let mode = tokens(&child).find_map(|tok| token_kind_to_mode(tok.kind())).unwrap_or(leo_ast::Mode::None);
782                all_entries.push(mode);
783            } else if child.kind().is_type() {
784                all_entries.push(leo_ast::Mode::None);
785            }
786        }
787
788        // Split: everything except the last is input types, the last is return type.
789        if type_parameters.is_empty() {
790            return Ok((Vec::new(), Vec::new()));
791        }
792
793        let mut input_types = Vec::new();
794        let mut return_types = Vec::new();
795
796        let last_idx = type_parameters.len() - 1;
797        for (i, ((ty, sp), mode)) in type_parameters.iter().zip(all_entries.iter()).enumerate() {
798            if i < last_idx {
799                // Input type entry.
800                input_types.push((*mode, ty.clone(), *sp));
801            } else {
802                // Last entry: return type.
803                // - Unit `()` means void return (empty return_types).
804                // - Tuple `(T1, T2)` is unpacked into individual elements.
805                // - Anything else is a single return type.
806                match ty {
807                    leo_ast::Type::Unit => {}
808                    leo_ast::Type::Tuple(tuple) => {
809                        for elem in tuple.elements() {
810                            return_types.push((*mode, elem.clone(), *sp));
811                        }
812                    }
813                    _ => {
814                        return_types.push((*mode, ty.clone(), *sp));
815                    }
816                }
817            }
818        }
819
820        Ok((input_types, return_types))
821    }
822
823    /// Convert a CALL_EXPR node to a CallExpression.
824    fn call_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
825        debug_assert_eq!(node.kind(), CALL_EXPR);
826        let span = self.content_span(node);
827        let id = self.builder.next_id();
828
829        // The first child should be the function being called (PATH_EXPR or PATH_LOCATOR_EXPR).
830        let mut child_iter = children(node);
831        let callee_node = child_iter.next().expect("call expr should have callee");
832
833        let function = match callee_node.kind() {
834            PATH_LOCATOR_EXPR => self.locator_tokens_to_path(&callee_node)?,
835            _ => self.path_expr_to_path(&callee_node)?,
836        };
837
838        // Collect arguments (remaining expression children)
839        let arguments = children(node)
840            .skip(1)  // Skip the callee
841            .filter(|n| n.kind().is_expression())
842            .map(|n| self.to_expression(&n))
843            .collect::<Result<Vec<_>>>()?;
844
845        // Extract type parameters and const arguments from CONST_ARG_LIST.
846        // In the rowan CST, CONST_ARG_LIST is a child of the PATH_EXPR callee node.
847        let (type_parameters, const_arguments) = self.extract_const_arg_list(&callee_node)?;
848
849        // If the path has exactly one qualifier (e.g. `group::to_x_coordinate`),
850        // try to canonicalize to an intrinsic. Non-intrinsic qualified calls
851        // fall through to the normal CallExpression below.
852        if function.user_program().is_none() && function.qualifier().len() == 1 {
853            let module = function.qualifier()[0].name;
854            let name = function.identifier().name;
855            if let Some(intrinsic_name) = leo_ast::Intrinsic::convert_path_symbols(module, name) {
856                return Ok(leo_ast::IntrinsicExpression {
857                    name: intrinsic_name,
858                    type_parameters,
859                    input_types: Vec::new(),
860                    return_types: Vec::new(),
861                    arguments,
862                    span,
863                    id,
864                }
865                .into());
866            }
867        }
868
869        // Bare intrinsic calls (e.g. `_dynamic_call::[u64](args)`).
870        // These have no qualifier and the identifier starts with `_`.
871        if function.user_program().is_none() && function.qualifier().is_empty() {
872            let name = function.identifier().name;
873            if leo_ast::Intrinsic::from_symbol(name, &type_parameters).is_some() {
874                // For _dynamic_call, split type parameters into input_types and return_types.
875                // Rule: last type param is the return type, all preceding are input types.
876                let (input_types, return_types) = if name == leo_span::sym::_dynamic_call {
877                    self.extract_dynamic_call_types(&callee_node, &type_parameters)?
878                } else {
879                    (Vec::new(), Vec::new())
880                };
881                return Ok(leo_ast::IntrinsicExpression {
882                    name,
883                    type_parameters,
884                    input_types,
885                    return_types,
886                    arguments,
887                    span,
888                    id,
889                }
890                .into());
891            }
892        }
893
894        Ok(leo_ast::CallExpression { function, const_arguments, arguments, span, id }.into())
895    }
896
897    /// Convert a DYNAMIC_CALL_EXPR node to an expression.
898    fn dynamic_call_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
899        debug_assert_eq!(node.kind(), DYNAMIC_CALL_EXPR);
900        let span = self.content_span(node);
901        let id = self.builder.next_id();
902
903        let interface = children(node)
904            .filter(|n| n.kind().is_type())
905            .map(|n| self.to_type(&n))
906            .next()
907            .expect("Parser guarantees a type")?;
908
909        let function = if let Some(token) = tokens(node).find(|t| t.kind() == IDENT) {
910            self.to_identifier(&token)
911        } else {
912            self.error_identifier(span)
913        };
914
915        // Collect expression children: first is target, optional second is network
916        // (if inside the @(...) before the ::), rest are arguments.
917        // We detect which expressions are "inside the parens" vs "arguments" by
918        // looking at the :: token position after the @(...). Expressions before ::
919        // are target/network, expressions after are arguments.
920        let separator_offset = tokens(node).filter(|t| t.kind() == COLON_COLON).last().map(|t| t.text_range().start());
921
922        let expr_children: Vec<_> = children(node).filter(|n| n.kind().is_expression()).collect();
923
924        let mut pre_sep = Vec::new();
925        let mut post_sep = Vec::new();
926        for child in &expr_children {
927            if let Some(sep_off) = separator_offset {
928                if child.text_range().start() < sep_off {
929                    pre_sep.push(child);
930                } else {
931                    post_sep.push(child);
932                }
933            } else {
934                // No separator found (error recovery), treat all as pre
935                pre_sep.push(child);
936            }
937        }
938
939        let target = if let Some(target_node) = pre_sep.first() {
940            self.to_expression(target_node)?
941        } else {
942            self.error_expression(span)
943        };
944
945        let network =
946            if let Some(network_node) = pre_sep.get(1) { Some(self.to_expression(network_node)?) } else { None };
947
948        let arguments = post_sep.iter().map(|n| self.to_expression(n)).collect::<Result<Vec<_>>>()?;
949
950        Ok(leo_ast::DynamicCallExpression { interface, target_program: target, network, function, arguments, span, id }
951            .into())
952    }
953
954    /// Convert a METHOD_CALL_EXPR node to the appropriate expression.
955    ///
956    /// Structure: `receiver DOT method_name L_PAREN args R_PAREN`
957    fn method_call_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
958        debug_assert_eq!(node.kind(), METHOD_CALL_EXPR);
959        let span = self.content_span(node);
960        let id = self.builder.next_id();
961
962        // First expression child is the receiver.
963        let mut expr_children = children(node).filter(|n| n.kind().is_expression());
964        let receiver = match expr_children.next() {
965            Some(receiver_node) => self.to_expression(&receiver_node)?,
966            None => {
967                self.emit_unexpected_str("receiver in method call", node.text(), span);
968                return Ok(self.error_expression(span));
969            }
970        };
971
972        // Get the method name (IDENT or keyword token after DOT).
973        let method_name = match find_name_after_dot(node) {
974            Some(method_token) => self.to_identifier(&method_token),
975            None => {
976                self.emit_unexpected_str("method name in method call", node.text(), span);
977                return Ok(self.error_expression(span));
978            }
979        };
980
981        // Remaining expression children are the arguments.
982        let mut args: Vec<_> = expr_children.map(|n| self.to_expression(&n)).collect::<Result<Vec<_>>>()?;
983
984        // Check for known methods that map to unary/binary operations or intrinsics
985        if args.is_empty() {
986            if let Some(op) = leo_ast::UnaryOperation::from_symbol(method_name.name) {
987                return Ok(leo_ast::UnaryExpression { span, op, receiver, id }.into());
988            }
989        } else if args.len() == 1
990            && let Some(op) = leo_ast::BinaryOperation::from_symbol(method_name.name)
991        {
992            return Ok(leo_ast::BinaryExpression { span, op, left: receiver, right: args.pop().unwrap(), id }.into());
993        }
994
995        // Check for known intrinsic method calls.
996        // Ordering follows the lossless parser (conversions.rs):
997        // 1. Specific intrinsics (signature, Future, Optional)
998        // 2. Unresolved `.get()`/`.set()` (deferred to type checker)
999        // 3. Vector/Mapping methods
1000        let method = method_name.name;
1001        let all_args = || std::iter::once(receiver.clone()).chain(args.clone()).collect::<Vec<_>>();
1002
1003        // Known module-specific intrinsics matched by name and arg count.
1004        let intrinsic_name = match args.len() {
1005            2 => leo_ast::Intrinsic::convert_path_symbols(sym::signature, method),
1006            0 => leo_ast::Intrinsic::convert_path_symbols(sym::Final, method)
1007                .or_else(|| leo_ast::Intrinsic::convert_path_symbols(sym::Optional, method)),
1008            1 => leo_ast::Intrinsic::convert_path_symbols(sym::Optional, method),
1009            _ => None,
1010        };
1011        if let Some(intrinsic_name) = intrinsic_name {
1012            return Ok(self.intrinsic_expression(intrinsic_name, all_args(), span));
1013        }
1014
1015        // Unresolved `.get()` / `.set()` — the receiver type is unknown at
1016        // parse time, so defer resolution to the type checker.
1017        if method == sym::get && args.len() == 1 {
1018            return Ok(self.intrinsic_expression(Symbol::intern("__unresolved_get"), all_args(), span));
1019        }
1020        if method == sym::set && args.len() == 2 {
1021            return Ok(self.intrinsic_expression(Symbol::intern("__unresolved_set"), all_args(), span));
1022        }
1023
1024        // Remaining Vector/Mapping method intrinsics.
1025        for module in [sym::Vector, sym::Mapping] {
1026            if let Some(intrinsic_name) = leo_ast::Intrinsic::convert_path_symbols(module, method) {
1027                return Ok(self.intrinsic_expression(intrinsic_name, all_args(), span));
1028            }
1029        }
1030
1031        // Unknown method call - emit error
1032        self.handler.emit_err(ParserError::invalid_method_call(receiver, method_name, args.len(), span));
1033        Ok(self.error_expression(span))
1034    }
1035
1036    /// Convert a TUPLE_ACCESS_EXPR node to a TupleAccess expression.
1037    fn tuple_access_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1038        debug_assert_eq!(node.kind(), TUPLE_ACCESS_EXPR);
1039        let span = self.content_span(node);
1040        let id = self.builder.next_id();
1041
1042        let inner = if let Some(inner_node) = children(node).find(|n| n.kind().is_expression()) {
1043            self.to_expression(&inner_node)?
1044        } else {
1045            self.emit_unexpected_str("expression in tuple access", node.text(), span);
1046            return Ok(self.error_expression(span));
1047        };
1048
1049        let index_token = match tokens(node).find(|t| t.kind() == INTEGER) {
1050            Some(token) => token,
1051            None => {
1052                self.emit_unexpected_str("tuple index", node.text(), span);
1053                return Ok(self.error_expression(span));
1054            }
1055        };
1056
1057        let index_text = index_token.text().replace('_', "");
1058        let index: usize = match index_text.parse() {
1059            Ok(idx) => idx,
1060            Err(_) => {
1061                self.emit_unexpected_str("valid tuple index", index_text, span);
1062                return Ok(self.error_expression(span));
1063            }
1064        };
1065        Ok(leo_ast::TupleAccess { tuple: inner, index: index.into(), span, id }.into())
1066    }
1067
1068    /// Convert a FIELD_EXPR node to a MemberAccess expression.
1069    fn field_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1070        debug_assert_eq!(node.kind(), FIELD_EXPR);
1071        let span = self.content_span(node);
1072        let id = self.builder.next_id();
1073
1074        // Get the inner expression and its CST kind (used for special-access dispatch).
1075        let (inner, first_child_kind) = match children(node).find(|n| n.kind().is_expression()) {
1076            Some(n) => {
1077                let kind = n.kind();
1078                (self.to_expression(&n)?, kind)
1079            }
1080            None => {
1081                self.emit_unexpected_str("expression in field access", node.text(), span);
1082                return Ok(self.error_expression(span));
1083            }
1084        };
1085
1086        // Get the field name (token after DOT).
1087        // Field names can be identifiers or keywords (e.g. `self.address`).
1088        let field_token = match find_name_after_dot(node) {
1089            Some(token) => token,
1090            None => {
1091                self.emit_unexpected_str("field name in field access", node.text(), span);
1092                return Ok(self.error_expression(span));
1093            }
1094        };
1095
1096        // Check for `name.aleo` program ID references. The rowan parser
1097        // creates FIELD_EXPR for these, but the reference parser treats
1098        // them as address literals (program addresses).
1099        if field_token.kind() == KW_ALEO
1100            && let leo_ast::Expression::Path(ref path) = inner
1101            && path.user_program().is_none()
1102            && path.qualifier().is_empty()
1103        {
1104            let full_name = format!("{}.aleo", path.identifier().name);
1105            return Ok(leo_ast::Literal::address(full_name, span, id).into());
1106        }
1107
1108        // Check for special accesses: self.caller, block.height, etc.
1109        let field_name = Symbol::intern(field_token.text());
1110
1111        let special = match (first_child_kind, field_name) {
1112            (SELF_EXPR, sym::address) => Some(sym::_self_address),
1113            (SELF_EXPR, sym::caller) => Some(sym::_self_caller),
1114            (SELF_EXPR, sym::checksum) => Some(sym::_self_checksum),
1115            (SELF_EXPR, sym::edition) => Some(sym::_self_edition),
1116            (SELF_EXPR, sym::id) => Some(sym::_self_id),
1117            (SELF_EXPR, sym::program_owner) => Some(sym::_self_program_owner),
1118            (SELF_EXPR, sym::signer) => Some(sym::_self_signer),
1119            (BLOCK_KW_EXPR, sym::height) => Some(sym::_block_height),
1120            (BLOCK_KW_EXPR, sym::timestamp) => Some(sym::_block_timestamp),
1121            (NETWORK_KW_EXPR, sym::id) => Some(sym::_network_id),
1122            (SELF_EXPR | BLOCK_KW_EXPR | NETWORK_KW_EXPR, _) => {
1123                self.handler.emit_err(ParserError::custom("Unsupported special access", span));
1124                return Ok(self.error_expression(span));
1125            }
1126            _ => None,
1127        };
1128
1129        if let Some(intrinsic_name) = special {
1130            return Ok(self.intrinsic_expression(intrinsic_name, Vec::new(), span));
1131        }
1132
1133        // Field token may be an identifier or keyword (e.g. `self.address`).
1134        let name = leo_ast::Identifier {
1135            name: Symbol::intern(field_token.text()),
1136            span: self.token_span(&field_token),
1137            id: self.builder.next_id(),
1138        };
1139        Ok(leo_ast::MemberAccess { inner, name, span, id }.into())
1140    }
1141
1142    /// Convert an INDEX_EXPR node to an ArrayAccess expression.
1143    fn index_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1144        debug_assert_eq!(node.kind(), INDEX_EXPR);
1145        let span = self.content_span(node);
1146        let id = self.builder.next_id();
1147
1148        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1149
1150        let array = match exprs.next() {
1151            Some(n) => self.to_expression(&n)?,
1152            None => {
1153                self.emit_unexpected_str("array in index expression", node.text(), span);
1154                return Ok(self.error_expression(span));
1155            }
1156        };
1157
1158        let index = match exprs.next() {
1159            Some(n) => self.to_expression(&n)?,
1160            None => {
1161                self.emit_unexpected_str("index in index expression", node.text(), span);
1162                return Ok(self.error_expression(span));
1163            }
1164        };
1165
1166        Ok(leo_ast::ArrayAccess { array, index, span, id }.into())
1167    }
1168
1169    /// Convert a CAST_EXPR node to a CastExpression.
1170    fn cast_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1171        debug_assert_eq!(node.kind(), CAST_EXPR);
1172        let span = self.content_span(node);
1173        let id = self.builder.next_id();
1174
1175        // Get the expression being cast
1176        let Some(expr_node) = children(node).find(|n| n.kind().is_expression()) else {
1177            self.emit_unexpected_str("expression in cast", node.text(), span);
1178            return Ok(self.error_expression(span));
1179        };
1180        let expression = self.to_expression(&expr_node)?;
1181
1182        // Get the target type
1183        let Some(type_node) = children(node).find(|n| n.kind().is_type()) else {
1184            self.emit_unexpected_str("type in cast expression", node.text(), span);
1185            return Ok(self.error_expression(span));
1186        };
1187        let type_ = self.to_type(&type_node)?;
1188
1189        Ok(leo_ast::CastExpression { expression, type_, span, id }.into())
1190    }
1191
1192    /// Convert a TERNARY_EXPR node to a TernaryExpression.
1193    fn ternary_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1194        debug_assert_eq!(node.kind(), TERNARY_EXPR);
1195        let span = self.content_span(node);
1196        let id = self.builder.next_id();
1197
1198        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1199
1200        let condition = match exprs.next() {
1201            Some(n) => self.to_expression(&n)?,
1202            None => {
1203                self.emit_unexpected_str("condition in ternary expression", node.text(), span);
1204                return Ok(self.error_expression(span));
1205            }
1206        };
1207
1208        let if_true = match exprs.next() {
1209            Some(n) => self.to_expression(&n)?,
1210            None => {
1211                self.emit_unexpected_str("true branch in ternary expression", node.text(), span);
1212                return Ok(self.error_expression(span));
1213            }
1214        };
1215
1216        let if_false = match exprs.next() {
1217            Some(n) => self.to_expression(&n)?,
1218            None => {
1219                self.emit_unexpected_str("false branch in ternary expression", node.text(), span);
1220                return Ok(self.error_expression(span));
1221            }
1222        };
1223
1224        Ok(leo_ast::TernaryExpression { condition, if_true, if_false, span, id }.into())
1225    }
1226
1227    /// Convert an ARRAY_EXPR node to an ArrayExpression.
1228    fn array_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1229        debug_assert_eq!(node.kind(), ARRAY_EXPR);
1230        let span = self.content_span(node);
1231        let id = self.builder.next_id();
1232
1233        let elements = children(node)
1234            .filter(|n| n.kind().is_expression())
1235            .map(|n| self.to_expression(&n))
1236            .collect::<Result<Vec<_>>>()?;
1237
1238        Ok(leo_ast::ArrayExpression { elements, span, id }.into())
1239    }
1240
1241    /// Convert a REPEAT_EXPR node to a RepeatExpression.
1242    fn repeat_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1243        debug_assert_eq!(node.kind(), REPEAT_EXPR);
1244        let span = self.content_span(node);
1245        let id = self.builder.next_id();
1246
1247        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1248        let expr = match exprs.next() {
1249            Some(n) => self.to_expression(&n)?,
1250            None => {
1251                self.emit_unexpected_str("expression in repeat", node.text(), span);
1252                return Ok(self.error_expression(span));
1253            }
1254        };
1255        let count = match exprs.next() {
1256            Some(n) => self.to_expression(&n)?,
1257            None => {
1258                self.emit_unexpected_str("repeat count", node.text(), span);
1259                return Ok(self.error_expression(span));
1260            }
1261        };
1262
1263        Ok(leo_ast::RepeatExpression { expr, count, span, id }.into())
1264    }
1265
1266    /// Convert a TUPLE_EXPR node to a TupleExpression or UnitExpression.
1267    fn tuple_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1268        debug_assert_eq!(node.kind(), TUPLE_EXPR);
1269        let span = self.content_span(node);
1270        let id = self.builder.next_id();
1271
1272        let elements: Vec<_> = children(node)
1273            .filter(|n| n.kind().is_expression())
1274            .map(|n| self.to_expression(&n))
1275            .collect::<Result<Vec<_>>>()?;
1276
1277        match elements.len() {
1278            0 => {
1279                // Empty tuple is invalid - emit error
1280                self.handler.emit_err(ParserError::tuple_must_have_at_least_two_elements("expression", span));
1281                // Return unit expression for error recovery
1282                Ok(leo_ast::UnitExpression { span, id }.into())
1283            }
1284            1 => {
1285                // Single-element tuple is invalid - emit error
1286                self.handler.emit_err(ParserError::tuple_must_have_at_least_two_elements("expression", span));
1287                // Return the single element for error recovery
1288                Ok(elements.into_iter().next().unwrap())
1289            }
1290            _ => Ok(leo_ast::TupleExpression { elements, span, id }.into()),
1291        }
1292    }
1293
1294    /// Build a `CompositeExpression` from a path, collecting field initializers
1295    /// and const arguments from the node.
1296    fn composite_expression_from_path(
1297        &self,
1298        node: &SyntaxNode,
1299        path: leo_ast::Path,
1300        span: Span,
1301        id: leo_ast::NodeID,
1302    ) -> Result<leo_ast::Expression> {
1303        let members = children(node)
1304            .filter(|n| matches!(n.kind(), STRUCT_FIELD_INIT | STRUCT_FIELD_SHORTHAND))
1305            .map(|n| self.struct_field_init_to_member(&n))
1306            .collect::<Result<Vec<_>>>()?;
1307        let (_type_parameters, const_arguments) = self.extract_const_arg_list(node)?;
1308        Ok(leo_ast::CompositeExpression { path, const_arguments, members, span, id }.into())
1309    }
1310
1311    /// Convert a STRUCT_EXPR node to a CompositeExpression.
1312    fn struct_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1313        debug_assert_eq!(node.kind(), STRUCT_EXPR);
1314        let span = self.content_span(node);
1315        let id = self.builder.next_id();
1316        let path = self.struct_expr_to_path(node)?;
1317        self.composite_expression_from_path(node, path, span, id)
1318    }
1319
1320    /// Extract a Path from a STRUCT_EXPR node's name tokens.
1321    fn struct_expr_to_path(&self, node: &SyntaxNode) -> Result<leo_ast::Path> {
1322        let fallback_span = self.content_span(node);
1323
1324        // Collect IDENT tokens before L_BRACE, deriving span from identifiers.
1325        let mut path_components = Vec::new();
1326        for token in tokens(node) {
1327            if token.kind() == L_BRACE {
1328                break;
1329            }
1330            if token.kind() == IDENT {
1331                path_components.push(self.to_identifier(&token));
1332            }
1333        }
1334
1335        let path_span = match (path_components.first(), path_components.last()) {
1336            (Some(first), Some(last)) => Span::new(first.span.lo, last.span.hi),
1337            _ => fallback_span,
1338        };
1339
1340        let name = match path_components.pop() {
1341            Some(name) => name,
1342            None => {
1343                self.emit_unexpected_str("type name in struct expression", node.text(), fallback_span);
1344                self.error_identifier(fallback_span)
1345            }
1346        };
1347        Ok(leo_ast::Path::new(None, path_components, name, path_span, self.builder.next_id()))
1348    }
1349
1350    /// Convert a STRUCT_LOCATOR_EXPR node to an Expression.
1351    fn struct_locator_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1352        let span = self.content_span(node);
1353        let id = self.builder.next_id();
1354        let path = self.locator_tokens_to_path(node)?;
1355        self.composite_expression_from_path(node, path, span, id)
1356    }
1357
1358    /// Convert a PATH_LOCATOR_EXPR node to an Expression.
1359    fn path_locator_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1360        let path = self.locator_tokens_to_path(node)?;
1361        Ok(leo_ast::Expression::Path(path))
1362    }
1363
1364    /// Extract program and type name from a locator node's IDENT tokens.
1365    ///
1366    /// Locator nodes have the structure: `IDENT DOT KW_ALEO COLON_COLON IDENT [COLON_COLON CONST_ARG_LIST]`.
1367    /// The first IDENT is the program name, the second (after SLASH) is the type/function name.
1368    fn locator_tokens_to_path(&self, node: &SyntaxNode) -> Result<leo_ast::Path> {
1369        let mut idents = tokens(node).filter(|t| t.kind() == IDENT);
1370        let program_token = idents.next().expect("locator should have program IDENT");
1371        let name_token = idents.next().expect("locator should have name IDENT");
1372
1373        // Convert program token into Identifier
1374        let program_ident = self.to_identifier(&program_token);
1375
1376        // Detect `KW_ALEO` — required
1377        let kw_aleo_token = tokens(node).find(|t| t.kind() == KW_ALEO).expect("locator should have `aleo` keyword");
1378
1379        let network_ident = leo_ast::Identifier {
1380            name: Symbol::intern("aleo"),
1381            span: self.token_span(&kw_aleo_token),
1382            id: self.builder.next_id(),
1383        };
1384
1385        // Wrap into ProgramId — network is never None
1386        let program = leo_ast::ProgramId { name: program_ident, network: network_ident };
1387
1388        // Name identifier (type name)
1389        let name = self.to_identifier(&name_token);
1390        let path_span = Span::new(program.name.span.lo, name.span.hi);
1391
1392        Ok(leo_ast::Path::new(Some(program), Vec::new(), name, path_span, self.builder.next_id()))
1393    }
1394
1395    /// Convert a STRUCT_FIELD_INIT or STRUCT_FIELD_SHORTHAND node to a CompositeFieldInitializer.
1396    fn struct_field_init_to_member(&self, node: &SyntaxNode) -> Result<leo_ast::CompositeFieldInitializer> {
1397        debug_assert!(matches!(node.kind(), STRUCT_FIELD_INIT | STRUCT_FIELD_SHORTHAND));
1398        let span = self.content_span(node);
1399        let id = self.builder.next_id();
1400
1401        let Some(ident_token) = tokens(node).find(|t| t.kind() == IDENT) else {
1402            self.emit_unexpected_str("identifier in struct field", node.text(), span);
1403            return Ok(leo_ast::CompositeFieldInitializer {
1404                identifier: self.error_identifier(span),
1405                expression: None,
1406                span,
1407                id,
1408            });
1409        };
1410        let identifier = self.to_identifier(&ident_token);
1411
1412        let expression = if node.kind() == STRUCT_FIELD_INIT {
1413            children(node).find(|n| n.kind().is_expression()).map(|n| self.to_expression(&n)).transpose()?
1414        } else {
1415            None
1416        };
1417
1418        Ok(leo_ast::CompositeFieldInitializer { identifier, expression, span, id })
1419    }
1420
1421    /// Convert a PROGRAM_REF_EXPR node (`name.aleo`) to an address literal.
1422    fn program_ref_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1423        debug_assert_eq!(node.kind(), PROGRAM_REF_EXPR);
1424        let span = self.content_span(node);
1425        let id = self.builder.next_id();
1426        let text: String = tokens(node).map(|t| t.text().to_string()).collect();
1427        Ok(leo_ast::Literal::address(text, span, id).into())
1428    }
1429
1430    /// Convert a PATH_EXPR node to an Expression.
1431    fn path_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1432        debug_assert_eq!(node.kind(), PATH_EXPR);
1433
1434        let path = self.path_expr_to_path(node)?;
1435        let span = self.trimmed_span(node);
1436        let id = self.builder.next_id();
1437
1438        // Detect `group::GEN` → IntrinsicExpression.
1439        if path.user_program().is_none()
1440            && path.qualifier().len() == 1
1441            && path.qualifier()[0].name == sym::group
1442            && path.identifier().name == sym::GEN
1443        {
1444            return Ok(self.intrinsic_expression(sym::_group_gen, Vec::new(), span));
1445        }
1446
1447        // Detect signature literals (identifiers starting with `sign1` that
1448        // parse as valid `Signature<TestnetV0>`).
1449        if path.user_program().is_none() && path.qualifier().is_empty() {
1450            let name_text = path.identifier().name.to_string();
1451            if name_text.starts_with("sign1") && name_text.parse::<Signature<TestnetV0>>().is_ok() {
1452                return Ok(leo_ast::Literal::signature(name_text, span, id).into());
1453            }
1454            // Reject standalone `_ident` in expression context -- these are only
1455            // valid as the start of intrinsic calls (e.g. `_self_caller()`).
1456            if name_text.starts_with('_') {
1457                self.handler.emit_err(ParserError::identifier_cannot_start_with_underscore(span));
1458                return Ok(self.error_expression(span));
1459            }
1460        }
1461
1462        Ok(leo_ast::Expression::Path(path))
1463    }
1464
1465    /// Convert a keyword expression (SELF_EXPR, BLOCK_KW_EXPR, NETWORK_KW_EXPR) to a Path.
1466    fn keyword_expr_to_path(&self, node: &SyntaxNode, name: Symbol) -> Result<leo_ast::Expression> {
1467        let span = self.trimmed_span(node);
1468        let ident = leo_ast::Identifier { name, span, id: self.builder.next_id() };
1469        let path = leo_ast::Path::new(None, Vec::new(), ident, span, self.builder.next_id());
1470        Ok(leo_ast::Expression::Path(path))
1471    }
1472
1473    /// Convert a FINAL_EXPR node to an Expression.
1474    fn final_expr_to_expression(&self, node: &SyntaxNode) -> Result<leo_ast::Expression> {
1475        debug_assert_eq!(node.kind(), FINAL_EXPR);
1476        let span = self.content_span(node);
1477        let id = self.builder.next_id();
1478
1479        // Find the block inside the final expression
1480        if let Some(block_node) = children(node).find(|n| n.kind() == BLOCK) {
1481            let block = self.to_block(&block_node)?;
1482            Ok(leo_ast::AsyncExpression { block, span, id }.into())
1483        } else {
1484            // No block found - emit error
1485            self.emit_unexpected_str("block in final expression", node.text(), span);
1486            Ok(self.error_expression(span))
1487        }
1488    }
1489
1490    /// Convert a PATH_EXPR node to a Path.
1491    ///
1492    /// Note: The lexer produces single IDENT tokens for associated function paths
1493    /// like `group::to_x_coordinate` or `signature::verify` (via the `PathSpecial`
1494    /// regex). These coalesced tokens must be split on `::` to build correct path
1495    /// components. This is a fundamental lexer constraint — `group` and `signature`
1496    /// are type keywords that must also work as associated function path prefixes.
1497    fn path_expr_to_path(&self, node: &SyntaxNode) -> Result<leo_ast::Path> {
1498        let span = self.trimmed_span(node);
1499
1500        // Regular path: collect identifiers
1501        let mut path_components = Vec::new();
1502        for token in tokens(node) {
1503            match token.kind() {
1504                IDENT => {
1505                    let text = token.text();
1506                    // The lexer produces single IDENT tokens for associated function
1507                    // paths like "group::to_x_coordinate" or "signature::verify".
1508                    // Split these on "::" to build the correct path components.
1509                    if text.contains("::") {
1510                        let token_span = self.token_span(&token);
1511                        let mut offset = token_span.lo;
1512                        for (i, segment) in text.split("::").enumerate() {
1513                            if i > 0 {
1514                                offset += 2; // skip "::"
1515                            }
1516                            let seg_span = Span::new(offset, offset + segment.len() as u32);
1517                            path_components.push(leo_ast::Identifier {
1518                                name: Symbol::intern(segment),
1519                                span: seg_span,
1520                                id: self.builder.next_id(),
1521                            });
1522                            offset += segment.len() as u32;
1523                        }
1524                    } else {
1525                        path_components.push(self.to_identifier(&token));
1526                    }
1527                }
1528                kind => {
1529                    if let Some(name) = keyword_to_path_symbol(kind) {
1530                        path_components.push(leo_ast::Identifier {
1531                            name,
1532                            span: self.token_span(&token),
1533                            id: self.builder.next_id(),
1534                        });
1535                    }
1536                }
1537            }
1538        }
1539
1540        let name = match path_components.pop() {
1541            Some(name) => name,
1542            None => {
1543                self.emit_unexpected_str("identifier in path", node.text(), span);
1544                self.error_identifier(span)
1545            }
1546        };
1547        Ok(leo_ast::Path::new(None, path_components, name, span, self.builder.next_id()))
1548    }
1549
1550    // =========================================================================
1551    // Statement Conversions
1552    // =========================================================================
1553
1554    /// Convert a syntax node to a statement.
1555    fn to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1556        let span = self.to_span(node);
1557        let id = self.builder.next_id();
1558
1559        let stmt = match node.kind() {
1560            LET_STMT => self.let_stmt_to_statement(node)?,
1561            CONST_STMT => self.const_stmt_to_statement(node)?,
1562            RETURN_STMT => self.return_stmt_to_statement(node)?,
1563            EXPR_STMT => self.expr_stmt_to_statement(node)?,
1564            ASSIGN_STMT => self.simple_assign_to_statement(node)?,
1565            COMPOUND_ASSIGN_STMT => self.compound_assign_to_statement(node)?,
1566            IF_STMT => self.if_stmt_to_statement(node)?,
1567            FOR_STMT | FOR_INCLUSIVE_STMT => self.for_stmt_to_statement(node)?,
1568            BLOCK => self.to_block(node)?.into(),
1569            ASSERT_STMT => {
1570                let expression = self.require_expression(node, "expression in assert")?;
1571                leo_ast::AssertStatement { variant: leo_ast::AssertVariant::Assert(expression), span, id }.into()
1572            }
1573            ASSERT_EQ_STMT => {
1574                self.assert_binary_to_statement(node, "assert_eq", span, id, leo_ast::AssertVariant::AssertEq)?
1575            }
1576            ASSERT_NEQ_STMT => {
1577                self.assert_binary_to_statement(node, "assert_neq", span, id, leo_ast::AssertVariant::AssertNeq)?
1578            }
1579            // For ROOT nodes that wrap a statement (from parse_statement_entry)
1580            ROOT => {
1581                if let Some(inner) = children(node).find(|n| n.kind().is_statement()) {
1582                    self.to_statement(&inner)?
1583                } else {
1584                    // Parse errors already emitted by emit_parse_errors().
1585                    leo_ast::ExpressionStatement { expression: self.error_expression(span), span, id }.into()
1586                }
1587            }
1588            // Error recovery for ERROR nodes.
1589            // Parse errors already emitted by emit_parse_errors().
1590            ERROR => leo_ast::ExpressionStatement { expression: self.error_expression(span), span, id }.into(),
1591            kind => panic!("unexpected statement kind: {:?}", kind),
1592        };
1593
1594        Ok(stmt)
1595    }
1596
1597    /// Convert an ASSERT_EQ_STMT or ASSERT_NEQ_STMT node to an AssertStatement.
1598    fn assert_binary_to_statement(
1599        &self,
1600        node: &SyntaxNode,
1601        label: &str,
1602        span: Span,
1603        id: leo_ast::NodeID,
1604        make_variant: fn(leo_ast::Expression, leo_ast::Expression) -> leo_ast::AssertVariant,
1605    ) -> Result<leo_ast::Statement> {
1606        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1607        let e0 = match exprs.next() {
1608            Some(expr) => self.to_expression(&expr)?,
1609            None => {
1610                self.emit_unexpected_str(&format!("first expression in {label}"), node.text(), span);
1611                self.error_expression(span)
1612            }
1613        };
1614        let e1 = match exprs.next() {
1615            Some(expr) => self.to_expression(&expr)?,
1616            None => {
1617                self.emit_unexpected_str(&format!("second expression in {label}"), node.text(), span);
1618                self.error_expression(span)
1619            }
1620        };
1621        Ok(leo_ast::AssertStatement { variant: make_variant(e0, e1), span, id }.into())
1622    }
1623
1624    /// Convert a BLOCK node to a Block.
1625    fn to_block(&self, node: &SyntaxNode) -> Result<leo_ast::Block> {
1626        debug_assert_eq!(node.kind(), BLOCK);
1627        let span = self.to_span(node);
1628        let id = self.builder.next_id();
1629
1630        let statements = children(node)
1631            .filter(|n| n.kind().is_statement())
1632            .map(|n| self.to_statement(&n))
1633            .collect::<Result<Vec<_>>>()?;
1634
1635        Ok(leo_ast::Block { statements, span, id })
1636    }
1637
1638    /// Convert a LET_STMT node to a DefinitionStatement.
1639    fn let_stmt_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1640        debug_assert_eq!(node.kind(), LET_STMT);
1641        let span = self.to_span(node);
1642        let id = self.builder.next_id();
1643
1644        // Find the pattern
1645        let place = match children(node).find(|n| matches!(n.kind(), IDENT_PATTERN | TUPLE_PATTERN | WILDCARD_PATTERN))
1646        {
1647            Some(pattern_node) => self.pattern_to_definition_place(&pattern_node)?,
1648            None => {
1649                self.emit_unexpected_str("pattern in let statement", node.text(), span);
1650                leo_ast::DefinitionPlace::Single(self.error_identifier(span))
1651            }
1652        };
1653
1654        // Find type annotation if present
1655        let type_ = children(node).find(|n| n.kind().is_type()).map(|n| self.to_type(&n)).transpose()?;
1656
1657        let value = self.require_expression(node, "value in let statement")?;
1658
1659        Ok(leo_ast::DefinitionStatement { place, type_, value, span, id }.into())
1660    }
1661
1662    /// Convert a pattern node to a DefinitionPlace.
1663    fn pattern_to_definition_place(&self, node: &SyntaxNode) -> Result<leo_ast::DefinitionPlace> {
1664        let span = self.to_span(node);
1665        match node.kind() {
1666            IDENT_PATTERN => {
1667                let ident = self.require_ident(node, "identifier in pattern");
1668                self.validate_definition_identifier(&ident);
1669                Ok(leo_ast::DefinitionPlace::Single(ident))
1670            }
1671            TUPLE_PATTERN => {
1672                let names = children(node)
1673                    .filter(|n| matches!(n.kind(), IDENT_PATTERN | WILDCARD_PATTERN))
1674                    .map(|n| {
1675                        if n.kind() == WILDCARD_PATTERN {
1676                            // Use a placeholder identifier for wildcard
1677                            let span = self.to_span(&n);
1678                            leo_ast::Identifier { name: Symbol::intern("_"), span, id: self.builder.next_id() }
1679                        } else {
1680                            let ident = self.require_ident(&n, "identifier in pattern");
1681                            self.validate_definition_identifier(&ident);
1682                            ident
1683                        }
1684                    })
1685                    .collect();
1686                Ok(leo_ast::DefinitionPlace::Multiple(names))
1687            }
1688            WILDCARD_PATTERN => {
1689                let ident = leo_ast::Identifier { name: Symbol::intern("_"), span, id: self.builder.next_id() };
1690                Ok(leo_ast::DefinitionPlace::Single(ident))
1691            }
1692            _ => {
1693                self.emit_unexpected_str("valid pattern", node.text(), span);
1694                let ident = self.error_identifier(span);
1695                Ok(leo_ast::DefinitionPlace::Single(ident))
1696            }
1697        }
1698    }
1699
1700    /// Convert a CONST_STMT node to a ConstDeclaration.
1701    fn const_stmt_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1702        debug_assert_eq!(node.kind(), CONST_STMT);
1703        let span = self.to_span(node);
1704        let id = self.builder.next_id();
1705
1706        let place = self.require_ident(node, "name in const declaration");
1707
1708        let type_ = self.require_type(node, "type in const declaration")?;
1709
1710        let value = self.require_expression(node, "value in const declaration")?;
1711
1712        Ok(leo_ast::ConstDeclaration { place, type_, value, span, id }.into())
1713    }
1714
1715    /// Convert a RETURN_STMT node to a ReturnStatement.
1716    fn return_stmt_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1717        debug_assert_eq!(node.kind(), RETURN_STMT);
1718        let span = self.to_span(node);
1719        let id = self.builder.next_id();
1720
1721        // Get optional expression
1722        let expression = children(node)
1723            .find(|n| n.kind().is_expression())
1724            .map(|n| self.to_expression(&n))
1725            .transpose()?
1726            .unwrap_or_else(|| leo_ast::UnitExpression { span, id: self.builder.next_id() }.into());
1727
1728        Ok(leo_ast::ReturnStatement { expression, span, id }.into())
1729    }
1730
1731    /// Convert an EXPR_STMT node to an ExpressionStatement.
1732    fn expr_stmt_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1733        debug_assert_eq!(node.kind(), EXPR_STMT);
1734        let span = self.to_span(node);
1735        let id = self.builder.next_id();
1736
1737        let expression = self.require_expression(node, "expression in expression statement")?;
1738
1739        Ok(leo_ast::ExpressionStatement { expression, span, id }.into())
1740    }
1741
1742    /// Convert a simple ASSIGN_STMT (`x = expr;`) to a Statement.
1743    fn simple_assign_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1744        debug_assert_eq!(node.kind(), ASSIGN_STMT);
1745        let span = self.to_span(node);
1746        let id = self.builder.next_id();
1747
1748        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1749
1750        let place = match exprs.next() {
1751            Some(n) => self.to_expression(&n)?,
1752            None => {
1753                self.emit_unexpected_str("left side in assignment", node.text(), span);
1754                return Ok(leo_ast::ExpressionStatement { expression: self.error_expression(span), span, id }.into());
1755            }
1756        };
1757
1758        let value = match exprs.next() {
1759            Some(n) => self.to_expression(&n)?,
1760            None => {
1761                self.emit_unexpected_str("right side in assignment", node.text(), span);
1762                self.error_expression(span)
1763            }
1764        };
1765
1766        Ok(leo_ast::AssignStatement { place, value, span, id }.into())
1767    }
1768
1769    /// Convert a COMPOUND_ASSIGN_STMT (`x += expr;`) to a Statement.
1770    ///
1771    /// Desugars `x op= rhs` into `x = x op rhs`.
1772    fn compound_assign_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1773        debug_assert_eq!(node.kind(), COMPOUND_ASSIGN_STMT);
1774        let span = self.to_span(node);
1775        let id = self.builder.next_id();
1776
1777        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1778
1779        let left = match exprs.next() {
1780            Some(n) => self.to_expression(&n)?,
1781            None => {
1782                self.emit_unexpected_str("left side in compound assignment", node.text(), span);
1783                return Ok(leo_ast::ExpressionStatement { expression: self.error_expression(span), span, id }.into());
1784            }
1785        };
1786
1787        let right = match exprs.next() {
1788            Some(n) => self.to_expression(&n)?,
1789            None => {
1790                self.emit_unexpected_str("right side in compound assignment", node.text(), span);
1791                self.error_expression(span)
1792            }
1793        };
1794
1795        let op_token =
1796            tokens(node).find(|t| is_assign_op(t.kind())).expect("COMPOUND_ASSIGN_STMT should have operator");
1797
1798        let binary_op = match op_token.kind() {
1799            PLUS_EQ => leo_ast::BinaryOperation::Add,
1800            MINUS_EQ => leo_ast::BinaryOperation::Sub,
1801            STAR_EQ => leo_ast::BinaryOperation::Mul,
1802            SLASH_EQ => leo_ast::BinaryOperation::Div,
1803            PERCENT_EQ => leo_ast::BinaryOperation::Rem,
1804            STAR2_EQ => leo_ast::BinaryOperation::Pow,
1805            AMP_EQ => leo_ast::BinaryOperation::BitwiseAnd,
1806            PIPE_EQ => leo_ast::BinaryOperation::BitwiseOr,
1807            CARET_EQ => leo_ast::BinaryOperation::Xor,
1808            SHL_EQ => leo_ast::BinaryOperation::Shl,
1809            SHR_EQ => leo_ast::BinaryOperation::Shr,
1810            AMP2_EQ => leo_ast::BinaryOperation::And,
1811            PIPE2_EQ => leo_ast::BinaryOperation::Or,
1812            k => panic!("unexpected compound assignment operator: {k:?}"),
1813        };
1814
1815        let value =
1816            leo_ast::BinaryExpression { left: left.clone(), right, op: binary_op, span, id: self.builder.next_id() }
1817                .into();
1818
1819        Ok(leo_ast::AssignStatement { place: left, value, span, id }.into())
1820    }
1821
1822    /// Convert an IF_STMT node to a ConditionalStatement.
1823    fn if_stmt_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1824        debug_assert_eq!(node.kind(), IF_STMT);
1825        let span = self.to_span(node);
1826        let id = self.builder.next_id();
1827
1828        let condition = self.require_expression(node, "condition in if statement")?;
1829
1830        // Single-pass: first BLOCK or IF_STMT child is the then-block,
1831        // second (if any) is the else clause.
1832        let mut block_or_if = children(node).filter(|n| n.kind() == BLOCK || n.kind() == IF_STMT);
1833
1834        let then = match block_or_if.next() {
1835            Some(n) if n.kind() == BLOCK => self.to_block(&n)?,
1836            _ => {
1837                self.emit_unexpected_str("then block in if statement", node.text(), span);
1838                self.error_block(span)
1839            }
1840        };
1841
1842        let otherwise = block_or_if.next().map(|n| self.to_statement(&n)).transpose()?.map(Box::new);
1843
1844        Ok(leo_ast::ConditionalStatement { condition, then, otherwise, span, id }.into())
1845    }
1846
1847    /// Convert a FOR_STMT or FOR_INCLUSIVE_STMT node to an IterationStatement.
1848    fn for_stmt_to_statement(&self, node: &SyntaxNode) -> Result<leo_ast::Statement> {
1849        debug_assert!(matches!(node.kind(), FOR_STMT | FOR_INCLUSIVE_STMT));
1850        let span = self.to_span(node);
1851        let id = self.builder.next_id();
1852
1853        let variable = self.require_ident(node, "variable in for statement");
1854
1855        // Get optional type annotation
1856        let type_ = children(node).find(|n| n.kind().is_type()).map(|n| self.to_type(&n)).transpose()?;
1857
1858        // Get range expressions (before and after ..)
1859        let mut exprs = children(node).filter(|n| n.kind().is_expression());
1860
1861        let start = match exprs.next() {
1862            Some(n) => self.to_expression(&n)?,
1863            None => {
1864                self.emit_unexpected_str("start expression in for statement", node.text(), span);
1865                self.error_expression(span)
1866            }
1867        };
1868
1869        let stop = match exprs.next() {
1870            Some(n) => self.to_expression(&n)?,
1871            None => {
1872                self.emit_unexpected_str("stop expression in for statement", node.text(), span);
1873                self.error_expression(span)
1874            }
1875        };
1876
1877        // Get body block
1878        let block = match children(node).find(|n| n.kind() == BLOCK) {
1879            Some(block_node) => self.to_block(&block_node)?,
1880            None => {
1881                self.emit_unexpected_str("block in for statement", node.text(), span);
1882                self.error_block(span)
1883            }
1884        };
1885
1886        let inclusive = node.kind() == FOR_INCLUSIVE_STMT;
1887
1888        Ok(leo_ast::IterationStatement { variable, type_, start, stop, inclusive, block, span, id }.into())
1889    }
1890
1891    // =========================================================================
1892    // Item/Program Conversions
1893    // =========================================================================
1894
1895    /// Collect a single program item (function, struct/record, const, interface) into the given vectors.
1896    fn collect_program_item(
1897        &self,
1898        item: &SyntaxNode,
1899        is_in_program_block: bool,
1900        functions: &mut Vec<(Symbol, leo_ast::Function)>,
1901        composites: &mut Vec<(Symbol, leo_ast::Composite)>,
1902        consts: &mut Vec<(Symbol, leo_ast::ConstDeclaration)>,
1903        interfaces: &mut Vec<(Symbol, leo_ast::Interface)>,
1904    ) -> Result<()> {
1905        match item.kind() {
1906            FUNCTION_DEF | FINAL_FN_DEF => {
1907                let func = self.to_function(item, is_in_program_block)?;
1908                functions.push((func.identifier.name, func));
1909            }
1910            STRUCT_DEF | RECORD_DEF => {
1911                let composite = self.to_composite(item)?;
1912                composites.push((composite.identifier.name, composite));
1913            }
1914            GLOBAL_CONST => {
1915                let global_const = self.to_global_const(item)?;
1916                consts.push((global_const.place.name, global_const));
1917            }
1918            INTERFACE_DEF => {
1919                let interface = self.to_interface(item)?;
1920                interfaces.push((interface.identifier.name, interface));
1921            }
1922            _ => {}
1923        }
1924        Ok(())
1925    }
1926
1927    /// Collect a single library item into the given vectors.
1928    fn collect_library_item(
1929        &self,
1930        item: &SyntaxNode,
1931        consts: &mut Vec<(Symbol, leo_ast::ConstDeclaration)>,
1932        structs: &mut Vec<(Symbol, leo_ast::Composite)>,
1933        functions: &mut Vec<(Symbol, leo_ast::Function)>,
1934    ) -> Result<()> {
1935        if is_library_item(item.kind()) {
1936            match item.kind() {
1937                GLOBAL_CONST => {
1938                    let global_const = self.to_global_const(item)?;
1939                    consts.push((global_const.place.name, global_const));
1940                }
1941                STRUCT_DEF => {
1942                    let composite = self.to_composite(item)?;
1943                    structs.push((composite.identifier.name, composite));
1944                }
1945                FUNCTION_DEF => {
1946                    // `is_in_program_block = false` so the variant is always `Fn` (not EntryPoint).
1947                    let func = self.to_function(item, false)?;
1948                    functions.push((func.identifier.name, func));
1949                }
1950                _ => {}
1951            }
1952        } else if is_program_item(item.kind()) {
1953            // A recognized program-only item appeared in a library file.
1954            let span = self.to_span(item);
1955            self.handler.emit_err(ParserError::custom(
1956                "Only `const` declarations, `struct` definitions, and `fn` functions are allowed in a library.",
1957                span,
1958            ));
1959        }
1960        // Other node kinds (e.g. ERROR nodes from CST-level parse failures) are
1961        // already reported by the rowan parser; nothing to do here.
1962        Ok(())
1963    }
1964
1965    /// Convert a syntax node to a module.
1966    fn to_module(&self, node: &SyntaxNode, program_name: Symbol, path: Vec<Symbol>) -> Result<leo_ast::Module> {
1967        // Module nodes are ROOT nodes containing items (functions, structs, consts, interfaces)
1968        let mut functions = Vec::new();
1969        let mut composites = Vec::new();
1970        let mut consts = Vec::new();
1971        let mut interfaces = Vec::new();
1972
1973        for child in children(node) {
1974            if child.kind() == PROGRAM_DECL {
1975                for item in children(&child) {
1976                    self.collect_program_item(
1977                        &item,
1978                        true,
1979                        &mut functions,
1980                        &mut composites,
1981                        &mut consts,
1982                        &mut interfaces,
1983                    )?;
1984                }
1985            } else {
1986                self.collect_program_item(
1987                    &child,
1988                    false,
1989                    &mut functions,
1990                    &mut composites,
1991                    &mut consts,
1992                    &mut interfaces,
1993                )?;
1994            }
1995        }
1996
1997        // Sort functions: entry points first
1998        functions.sort_by_key(|func| if func.1.variant.is_entry() { 0u8 } else { 1u8 });
1999
2000        Ok(leo_ast::Module { program_name, path, consts, composites, functions, interfaces })
2001    }
2002
2003    /// Convert a syntax node to a program (main file).
2004    fn to_main(&self, node: &SyntaxNode) -> Result<leo_ast::Program> {
2005        // The main file contains imports and a program declaration
2006        let mut imports = indexmap::IndexMap::new();
2007        let mut functions = Vec::new();
2008        let mut composites = Vec::new();
2009        let mut consts = Vec::new();
2010        let mut mappings = Vec::new();
2011        let mut storage_variables = Vec::new();
2012        let mut constructors = Vec::new();
2013        let mut interfaces = Vec::new();
2014        let mut program_name = None;
2015        let mut network = None;
2016        let mut parents = Vec::new();
2017        let mut span = None;
2018
2019        for child in children(node) {
2020            match child.kind() {
2021                IMPORT => {
2022                    let program_id = self.import_to_program_id(&child)?;
2023                    imports.insert(program_id.as_symbol(), program_id);
2024                }
2025                PROGRAM_DECL => {
2026                    if program_name.is_some() {
2027                        self.handler.emit_err(ParserError::multiple_program_declarations(self.non_trivia_span(&child)));
2028                        continue;
2029                    }
2030                    // Extract program name, network, and optional parent interface
2031                    let (pname, pnetwork, pparents) = self.program_decl_to_name_with_parent(&child)?;
2032                    program_name = Some(pname);
2033                    network = Some(pnetwork);
2034                    parents = pparents;
2035                    span = Some(self.to_span(&child));
2036
2037                    // Process items inside program decl
2038                    for item in children(&child) {
2039                        self.collect_program_item(
2040                            &item,
2041                            true,
2042                            &mut functions,
2043                            &mut composites,
2044                            &mut consts,
2045                            &mut interfaces,
2046                        )?;
2047                        match item.kind() {
2048                            MAPPING_DEF => {
2049                                let mapping = self.to_mapping(&item)?;
2050                                mappings.push((mapping.identifier.name, mapping));
2051                            }
2052                            STORAGE_DEF => {
2053                                let storage = self.to_storage(&item)?;
2054                                storage_variables.push((storage.identifier.name, storage));
2055                            }
2056                            CONSTRUCTOR_DEF => {
2057                                constructors.push(self.to_constructor(&item)?);
2058                            }
2059                            _ => {}
2060                        }
2061                    }
2062                }
2063                _ => {
2064                    self.collect_program_item(
2065                        &child,
2066                        false,
2067                        &mut functions,
2068                        &mut composites,
2069                        &mut consts,
2070                        &mut interfaces,
2071                    )?;
2072                }
2073            }
2074        }
2075
2076        if let Some(extra) = constructors.get(1) {
2077            return Err(ParserError::custom("A program can only have one constructor.", extra.span).into());
2078        }
2079
2080        let (Some(program_name), Some(network), Some(span)) = (program_name, network, span) else {
2081            return Err(ParserError::missing_program_scope(self.to_span(node)).into());
2082        };
2083
2084        // Sort functions: entry points first
2085        functions.sort_by_key(|func| if func.1.variant.is_entry() { 0u8 } else { 1u8 });
2086
2087        let program_id = leo_ast::ProgramId { name: program_name, network };
2088        let program_id_as_symbol = program_id.as_symbol();
2089        let program_scope = leo_ast::ProgramScope {
2090            program_id,
2091            parents,
2092            consts,
2093            composites,
2094            mappings,
2095            storage_variables,
2096            functions,
2097            interfaces,
2098            constructor: constructors.pop(),
2099            span,
2100        };
2101
2102        Ok(leo_ast::Program {
2103            imports,
2104            modules: indexmap::IndexMap::new(),
2105            stubs: indexmap::IndexMap::new(),
2106            program_scopes: vec![(program_id_as_symbol, program_scope)].into_iter().collect(),
2107        })
2108    }
2109
2110    /// Convert a syntax node to a library (`lib.leo` file).
2111    fn to_library(&self, name: Symbol, node: &SyntaxNode) -> Result<leo_ast::Library> {
2112        let mut consts = Vec::new();
2113        let mut structs = Vec::new();
2114        let mut functions = Vec::new();
2115
2116        for child in children(node) {
2117            self.collect_library_item(&child, &mut consts, &mut structs, &mut functions)?;
2118        }
2119
2120        Ok(leo_ast::Library { name, modules: indexmap::IndexMap::new(), consts, structs, functions })
2121    }
2122
2123    /// Extract a ProgramId from an IMPORT node. Guarantees `network` is always present.
2124    fn import_to_program_id(&self, node: &SyntaxNode) -> Result<leo_ast::ProgramId> {
2125        debug_assert_eq!(node.kind(), IMPORT);
2126        let span = self.to_span(node);
2127
2128        // Extract the first IDENT as the program name
2129        let program_name_text = match tokens(node).find(|t| t.kind() == IDENT) {
2130            Some(name_token) => name_token.text().to_string(),
2131            None => {
2132                self.emit_unexpected_str("import name", node.text(), span);
2133                "_error".to_string()
2134            }
2135        };
2136
2137        // Check if KW_ALEO exists in tokens
2138        let network_span = tokens(node).find(|t| t.kind() == KW_ALEO).map(|t| self.token_span(&t)).unwrap_or(span); // fallback span if missing
2139
2140        let program_id = leo_ast::ProgramId {
2141            name: leo_ast::Identifier { name: Symbol::intern(&program_name_text), span, id: self.builder.next_id() },
2142            network: leo_ast::Identifier {
2143                name: Symbol::intern("aleo"),
2144                span: network_span,
2145                id: self.builder.next_id(),
2146            },
2147        };
2148
2149        // Extra validation: if no KW_ALEO token but there is some invalid network, emit error
2150        if tokens(node).all(|t| t.kind() != KW_ALEO)
2151            && let Some(net_token) = find_invalid_network(node)
2152        {
2153            self.handler.emit_err(ParserError::invalid_network(self.token_span(&net_token)));
2154        }
2155
2156        Ok(program_id)
2157    }
2158
2159    /// Extract program name and network from a PROGRAM_DECL node.
2160    fn program_decl_to_name(&self, node: &SyntaxNode) -> Result<(leo_ast::Identifier, leo_ast::Identifier)> {
2161        debug_assert_eq!(node.kind(), PROGRAM_DECL);
2162        let span = self.to_span(node);
2163
2164        // Program format: program name.aleo { ... }
2165        let program_name = self.require_ident(node, "program name");
2166
2167        let network = match tokens(node).find(|t| t.kind() == KW_ALEO) {
2168            Some(aleo_token) => leo_ast::Identifier {
2169                name: Symbol::intern("aleo"),
2170                span: self.token_span(&aleo_token),
2171                id: self.builder.next_id(),
2172            },
2173            None => {
2174                // Check for an invalid network identifier (e.g. `program test.eth`).
2175                if let Some(net_token) = find_invalid_network(node) {
2176                    self.handler.emit_err(ParserError::invalid_network(self.token_span(&net_token)));
2177                } else {
2178                    self.emit_unexpected_str(".aleo network", node.text(), span);
2179                }
2180                leo_ast::Identifier { name: Symbol::intern("aleo"), span, id: self.builder.next_id() }
2181            }
2182        };
2183
2184        Ok((program_name, network))
2185    }
2186
2187    /// Extract program name, network, and optional parent interface from a PROGRAM_DECL node.
2188    fn program_decl_to_name_with_parent(
2189        &self,
2190        node: &SyntaxNode,
2191    ) -> Result<(leo_ast::Identifier, leo_ast::Identifier, Parents)> {
2192        debug_assert_eq!(node.kind(), PROGRAM_DECL);
2193        let (program_name, network) = self.program_decl_to_name(node)?;
2194
2195        let parents = if let Some(parent_list) = children(node).find(|n| n.kind() == PARENT_LIST) {
2196            self.collect_parent_list(&parent_list)?
2197        } else {
2198            vec![]
2199        };
2200
2201        Ok((program_name, network, parents))
2202    }
2203
2204    fn collect_parent_list(&self, node: &SyntaxNode) -> Result<Parents> {
2205        debug_assert_eq!(node.kind(), PARENT_LIST);
2206        children(node)
2207            .filter(|n| n.kind().is_type())
2208            .map(|n| self.to_type(&n).map(|t| (self.to_span(&n), t)))
2209            .collect::<Result<Vec<_>>>()
2210    }
2211
2212    /// Collect all ANNOTATION children from a node.
2213    fn collect_annotations(&self, node: &SyntaxNode) -> Result<Vec<leo_ast::Annotation>> {
2214        children(node).filter(|n| n.kind() == ANNOTATION).map(|n| self.to_annotation(&n)).collect()
2215    }
2216
2217    /// Find a BLOCK child or produce an error block for recovery.
2218    fn require_block(&self, node: &SyntaxNode, span: Span) -> Result<leo_ast::Block> {
2219        Ok(children(node)
2220            .find(|n| n.kind() == BLOCK)
2221            .map(|n| self.to_block(&n))
2222            .transpose()?
2223            .unwrap_or_else(|| self.error_block(span)))
2224    }
2225
2226    /// Convert a FUNCTION_DEF / FINAL_FN_DEF node to a Function.
2227    fn to_function(&self, node: &SyntaxNode, is_in_program_block: bool) -> Result<leo_ast::Function> {
2228        debug_assert!(matches!(node.kind(), FUNCTION_DEF | FINAL_FN_DEF | CONSTRUCTOR_DEF));
2229        let span = self.span_including_annotations(node, self.non_trivia_span(node));
2230        let id = self.builder.next_id();
2231
2232        let annotations = self.collect_annotations(node)?;
2233
2234        // Determine variant
2235        let variant = if is_in_program_block {
2236            leo_ast::Variant::EntryPoint
2237        } else {
2238            match node.kind() {
2239                FINAL_FN_DEF => leo_ast::Variant::FinalFn,
2240                _ => leo_ast::Variant::Fn,
2241            }
2242        };
2243
2244        let identifier = self.require_ident(node, "function name");
2245        self.validate_identifier(&identifier);
2246
2247        let const_parameters = self.extract_const_parameters(node)?;
2248
2249        // Get input parameters
2250        let input = children(node)
2251            .find(|n| n.kind() == PARAM_LIST)
2252            .map(|n| self.param_list_to_inputs(&n))
2253            .transpose()?
2254            .unwrap_or_default();
2255
2256        // Get return type and build output declarations.
2257        //
2258        // Two structures are possible:
2259        // - Single return: FUNCTION_DEF > ... ARROW [KW_PUBLIC|KW_PRIVATE|KW_CONSTANT]? TYPE_* BLOCK
2260        // - Tuple return:  FUNCTION_DEF > ... ARROW RETURN_TYPE(L_PAREN [vis TYPE_*]+ R_PAREN) BLOCK
2261        let (output, output_type) = if let Some(return_type_node) = children(node).find(|n| n.kind() == RETURN_TYPE) {
2262            // Tuple return type.
2263            self.return_type_to_outputs(&return_type_node)?
2264        } else if let Some(type_node) = children(node).find(|n| n.kind().is_type()) {
2265            // Single return type (direct child of FUNCTION_DEF).
2266            let type_ = self.to_type(&type_node)?;
2267            // Check for visibility keyword before the type node.
2268            let (mode, mode_start) = self.return_mode_before(node, &type_node);
2269            let type_span = self.content_span(&type_node);
2270            let output_span = match mode_start {
2271                Some(start) => Span::new(start, type_span.hi),
2272                None => type_span,
2273            };
2274            let output =
2275                vec![leo_ast::Output { mode, type_: type_.clone(), span: output_span, id: self.builder.next_id() }];
2276            (output, type_)
2277        } else {
2278            (Vec::new(), leo_ast::Type::Unit)
2279        };
2280
2281        let block = self.require_block(node, span)?;
2282
2283        Ok(leo_ast::Function {
2284            annotations,
2285            variant,
2286            identifier,
2287            const_parameters,
2288            input,
2289            output,
2290            output_type,
2291            block,
2292            span,
2293            id,
2294        })
2295    }
2296
2297    /// Extract the visibility mode keyword that precedes a type node within a parent.
2298    ///
2299    /// Scans tokens of the parent, looking for a visibility keyword that
2300    /// appears immediately before the type node's text range.
2301    /// Returns the mode and optionally the mode token's offset-adjusted span start.
2302    fn return_mode_before(&self, parent: &SyntaxNode, type_node: &SyntaxNode) -> (leo_ast::Mode, Option<u32>) {
2303        let type_start = type_node.text_range().start();
2304        let mut mode = leo_ast::Mode::None;
2305        let mut mode_start = None;
2306        for token in tokens(parent) {
2307            let token_end = token.text_range().end();
2308            if token_end > type_start {
2309                break;
2310            }
2311            if let Some(m) = token_kind_to_mode(token.kind()) {
2312                mode = m;
2313                mode_start = Some(u32::from(token.text_range().start()) + self.start_pos);
2314            }
2315        }
2316        (mode, mode_start)
2317    }
2318
2319    /// Convert a RETURN_TYPE node (tuple return) to (Vec<Output>, Type).
2320    fn return_type_to_outputs(&self, node: &SyntaxNode) -> Result<(Vec<leo_ast::Output>, leo_ast::Type)> {
2321        debug_assert_eq!(node.kind(), RETURN_TYPE);
2322
2323        // RETURN_TYPE contains: L_PAREN [vis? TYPE_*]+ R_PAREN
2324        // Iterate children, tracking the last-seen visibility keyword.
2325        let mut outputs = Vec::new();
2326        let mut current_mode = leo_ast::Mode::None;
2327        let mut current_mode_start: Option<u32> = None;
2328
2329        for child in node.children_with_tokens() {
2330            match &child {
2331                SyntaxElement::Token(token) if !token.kind().is_trivia() => {
2332                    if let Some(m) = token_kind_to_mode(token.kind()) {
2333                        current_mode = m;
2334                        current_mode_start = Some(u32::from(token.text_range().start()) + self.start_pos);
2335                    }
2336                }
2337                SyntaxElement::Node(child_node) if child_node.kind().is_type() => {
2338                    let type_ = self.to_type(child_node)?;
2339                    let type_span = self.content_span(child_node);
2340                    let output_span = match current_mode_start.take() {
2341                        Some(start) => Span::new(start, type_span.hi),
2342                        None => type_span,
2343                    };
2344                    outputs.push(leo_ast::Output {
2345                        mode: current_mode,
2346                        type_,
2347                        span: output_span,
2348                        id: self.builder.next_id(),
2349                    });
2350                    current_mode = leo_ast::Mode::None;
2351                }
2352                _ => {}
2353            }
2354        }
2355
2356        let output_type = match outputs.len() {
2357            0 => leo_ast::Type::Unit,
2358            1 => outputs[0].type_.clone(),
2359            _ => leo_ast::TupleType::new(outputs.iter().map(|o| o.type_.clone()).collect()).into(),
2360        };
2361
2362        Ok((outputs, output_type))
2363    }
2364
2365    /// Convert an ANNOTATION node to an Annotation.
2366    fn to_annotation(&self, node: &SyntaxNode) -> Result<leo_ast::Annotation> {
2367        debug_assert_eq!(node.kind(), ANNOTATION);
2368        let span = self.trimmed_span(node);
2369        let id = self.builder.next_id();
2370
2371        // Annotation names can be identifiers or keywords (e.g. @program, @test).
2372        // The name is the first IDENT or keyword token after `@`.
2373        let name_token =
2374            tokens(node).find(|t| t.kind() == IDENT || t.kind().is_keyword()).expect("annotation should have name");
2375        let name = Symbol::intern(name_token.text());
2376        let name_span = self.token_span(&name_token);
2377        let identifier = leo_ast::Identifier { name, span: name_span, id: self.builder.next_id() };
2378
2379        // Parse annotation key-value pairs from ANNOTATION_PAIR child nodes.
2380        let map = children(node)
2381            .filter(|n| n.kind() == ANNOTATION_PAIR)
2382            .filter_map(|pair| {
2383                let key =
2384                    tokens(&pair).find(|t| t.kind() == IDENT || t.kind() == KW_ADDRESS || t.kind() == KW_MAPPING)?;
2385                let val = tokens(&pair).find(|t| t.kind() == STRING)?;
2386                let text = val.text();
2387                Some((Symbol::intern(key.text()), text[1..text.len() - 1].to_string()))
2388            })
2389            .collect();
2390
2391        Ok(leo_ast::Annotation { identifier, map, span, id })
2392    }
2393
2394    /// Convert a PARAM_LIST node to function inputs.
2395    fn param_list_to_inputs(&self, node: &SyntaxNode) -> Result<Vec<leo_ast::Input>> {
2396        debug_assert_eq!(node.kind(), PARAM_LIST);
2397
2398        children(node)
2399            .filter(|n| matches!(n.kind(), PARAM | PARAM_PUBLIC | PARAM_PRIVATE | PARAM_CONSTANT))
2400            .map(|n| self.param_to_input(&n))
2401            .collect()
2402    }
2403
2404    /// Convert a PARAM node to an Input.
2405    fn param_to_input(&self, node: &SyntaxNode) -> Result<leo_ast::Input> {
2406        debug_assert!(matches!(node.kind(), PARAM | PARAM_PUBLIC | PARAM_PRIVATE | PARAM_CONSTANT));
2407        let span = self.non_trivia_span(node);
2408        let id = self.builder.next_id();
2409
2410        let mode = node_kind_to_mode(node.kind());
2411
2412        let identifier = self.require_ident(node, "parameter name");
2413        self.validate_identifier(&identifier);
2414
2415        let type_ = self.require_type(node, "parameter type")?;
2416
2417        Ok(leo_ast::Input { identifier, mode, type_, span, id })
2418    }
2419
2420    /// Convert a const parameter list.
2421    fn to_const_parameters(&self, node: &SyntaxNode) -> Result<Vec<leo_ast::ConstParameter>> {
2422        debug_assert_eq!(node.kind(), CONST_PARAM_LIST);
2423
2424        children(node)
2425            .filter(|n| n.kind() == CONST_PARAM)
2426            .map(|n| {
2427                let span = self.non_trivia_span(&n);
2428                let id = self.builder.next_id();
2429
2430                let identifier = self.require_ident(&n, "const parameter name");
2431
2432                let type_ = self.require_type(&n, "const parameter type")?;
2433
2434                Ok(leo_ast::ConstParameter { identifier, type_, span, id })
2435            })
2436            .collect()
2437    }
2438
2439    /// Extract optional const parameters from a node with a CONST_PARAM_LIST child.
2440    fn extract_const_parameters(&self, node: &SyntaxNode) -> Result<Vec<leo_ast::ConstParameter>> {
2441        children(node)
2442            .find(|n| n.kind() == CONST_PARAM_LIST)
2443            .map(|n| self.to_const_parameters(&n))
2444            .transpose()
2445            .map(|opt| opt.unwrap_or_default())
2446    }
2447
2448    /// Convert a STRUCT_DEF or RECORD_DEF node to a Composite.
2449    fn to_composite(&self, node: &SyntaxNode) -> Result<leo_ast::Composite> {
2450        debug_assert!(matches!(node.kind(), STRUCT_DEF | RECORD_DEF));
2451        let span = self.non_trivia_span(node);
2452        let id = self.builder.next_id();
2453
2454        let is_record = node.kind() == RECORD_DEF;
2455
2456        let identifier = self.require_ident(node, "struct/record name");
2457        self.validate_identifier(&identifier);
2458
2459        let const_parameters = self.extract_const_parameters(node)?;
2460
2461        // Get members
2462        let members = children(node)
2463            .filter(|n| {
2464                matches!(
2465                    n.kind(),
2466                    STRUCT_MEMBER | STRUCT_MEMBER_PUBLIC | STRUCT_MEMBER_PRIVATE | STRUCT_MEMBER_CONSTANT
2467                )
2468            })
2469            .map(|n| self.struct_member_to_member(&n))
2470            .collect::<Result<Vec<_>>>()?;
2471
2472        Ok(leo_ast::Composite { identifier, const_parameters, members, is_record, span, id })
2473    }
2474
2475    /// Convert a STRUCT_MEMBER node to a Member.
2476    fn struct_member_to_member(&self, node: &SyntaxNode) -> Result<leo_ast::Member> {
2477        debug_assert!(matches!(
2478            node.kind(),
2479            STRUCT_MEMBER | STRUCT_MEMBER_PUBLIC | STRUCT_MEMBER_PRIVATE | STRUCT_MEMBER_CONSTANT
2480        ));
2481        let span = self.non_trivia_span(node);
2482        let id = self.builder.next_id();
2483
2484        let mode = node_kind_to_mode(node.kind());
2485
2486        let identifier = self.require_ident(node, "member name");
2487        self.validate_identifier(&identifier);
2488
2489        let type_ = self.require_type(node, "member type")?;
2490
2491        Ok(leo_ast::Member { mode, identifier, type_, span, id })
2492    }
2493
2494    /// Convert a GLOBAL_CONST node to a ConstDeclaration.
2495    fn to_global_const(&self, node: &SyntaxNode) -> Result<leo_ast::ConstDeclaration> {
2496        debug_assert_eq!(node.kind(), GLOBAL_CONST);
2497        let span = self.non_trivia_span(node);
2498        let id = self.builder.next_id();
2499
2500        let place = self.require_ident(node, "const name");
2501        self.validate_definition_identifier(&place);
2502
2503        let type_ = self.require_type(node, "const type")?;
2504
2505        let value = self.require_expression(node, "const value")?;
2506
2507        Ok(leo_ast::ConstDeclaration { place, type_, value, span, id })
2508    }
2509
2510    /// Convert a MAPPING_DEF node to a Mapping.
2511    fn to_mapping(&self, node: &SyntaxNode) -> Result<leo_ast::Mapping> {
2512        debug_assert_eq!(node.kind(), MAPPING_DEF);
2513        let span = self.non_trivia_span(node);
2514        let id = self.builder.next_id();
2515
2516        let identifier = self.require_ident(node, "name in mapping");
2517
2518        // Get key and value types
2519        let mut type_nodes = children(node).filter(|n| n.kind().is_type());
2520
2521        let key_type = match type_nodes.next() {
2522            Some(key_node) => self.to_type(&key_node)?,
2523            None => {
2524                self.emit_unexpected_str("key type in mapping", node.text(), span);
2525                leo_ast::Type::Err
2526            }
2527        };
2528
2529        let value_type = match type_nodes.next() {
2530            Some(value_node) => self.to_type(&value_node)?,
2531            None => {
2532                self.emit_unexpected_str("value type in mapping", node.text(), span);
2533                leo_ast::Type::Err
2534            }
2535        };
2536
2537        Ok(leo_ast::Mapping { identifier, key_type, value_type, span, id })
2538    }
2539
2540    /// Convert a STORAGE_DEF node to a StorageVariable.
2541    fn to_storage(&self, node: &SyntaxNode) -> Result<leo_ast::StorageVariable> {
2542        debug_assert_eq!(node.kind(), STORAGE_DEF);
2543        let span = self.non_trivia_span(node);
2544        let id = self.builder.next_id();
2545
2546        let name = self.require_ident(node, "name in storage");
2547
2548        let type_ = self.require_type(node, "type in storage")?;
2549
2550        Ok(leo_ast::StorageVariable { identifier: name, type_, span, id })
2551    }
2552
2553    /// Convert a CONSTRUCTOR_DEF node to a Constructor.
2554    fn to_constructor(&self, node: &SyntaxNode) -> Result<leo_ast::Constructor> {
2555        debug_assert_eq!(node.kind(), CONSTRUCTOR_DEF);
2556        let span = self.span_including_annotations(node, self.non_trivia_span(node));
2557        let id = self.builder.next_id();
2558
2559        let annotations = self.collect_annotations(node)?;
2560        let block = self.require_block(node, span)?;
2561
2562        Ok(leo_ast::Constructor { annotations, block, span, id })
2563    }
2564
2565    // =========================================================================
2566    // Interface Conversions
2567    // =========================================================================
2568
2569    /// Convert an INTERFACE_DEF node to an Interface.
2570    fn to_interface(&self, node: &SyntaxNode) -> Result<leo_ast::Interface> {
2571        debug_assert_eq!(node.kind(), INTERFACE_DEF);
2572        let span = self.to_span(node);
2573
2574        // Get interface name (first IDENT)
2575        let identifier = self.require_ident(node, "interface name");
2576
2577        // Check for parent interface after COLON
2578        // Format: `interface Name : ParentName { ... }`
2579        let parents = if let Some(parent_list) = children(node).find(|n| n.kind() == PARENT_LIST) {
2580            self.collect_parent_list(&parent_list)?
2581        } else {
2582            vec![]
2583        };
2584
2585        let mut functions = Vec::new();
2586        let mut records = Vec::new();
2587        let mut mappings = Vec::new();
2588        let mut storages = Vec::new();
2589
2590        for child in children(node) {
2591            match child.kind() {
2592                FN_PROTOTYPE_DEF => {
2593                    let proto = self.to_function_prototype(&child)?;
2594                    functions.push((proto.identifier.name, proto));
2595                }
2596                RECORD_PROTOTYPE_DEF => {
2597                    let proto = self.to_record_prototype(&child)?;
2598                    records.push((proto.identifier.name, proto));
2599                }
2600                MAPPING_DEF => {
2601                    let mapping = self.to_mapping(&child)?;
2602                    mappings.push(mapping);
2603                }
2604                STORAGE_DEF => {
2605                    let storage = self.to_storage(&child)?;
2606                    storages.push(storage);
2607                }
2608                _ => {}
2609            }
2610        }
2611
2612        Ok(leo_ast::Interface {
2613            identifier,
2614            parents,
2615            span,
2616            id: self.builder.next_id(),
2617            functions,
2618            records,
2619            mappings,
2620            storages,
2621        })
2622    }
2623
2624    /// Convert an FN_PROTOTYPE_DEF node to a FunctionPrototype.
2625    fn to_function_prototype(&self, node: &SyntaxNode) -> Result<leo_ast::FunctionPrototype> {
2626        debug_assert_eq!(node.kind(), FN_PROTOTYPE_DEF);
2627        let span = self.to_span(node);
2628
2629        let identifier = self.require_ident(node, "function name");
2630
2631        // Collect const generic parameters
2632        let const_parameters = self.extract_const_parameters(node)?;
2633
2634        // Collect input parameters
2635        let input = children(node)
2636            .find(|n| n.kind() == PARAM_LIST)
2637            .map(|n| self.param_list_to_inputs(&n))
2638            .transpose()?
2639            .unwrap_or_default();
2640
2641        // Get return type and build output declarations.
2642        // Same logic as to_function but for prototypes (no block)
2643        let output = if let Some(return_type_node) = children(node).find(|n| n.kind() == RETURN_TYPE) {
2644            // Tuple return type.
2645            self.return_type_to_outputs(&return_type_node)?.0
2646        } else if let Some(type_node) = children(node).find(|n| n.kind().is_type()) {
2647            // Single return type (direct child of FN_PROTOTYPE_DEF).
2648            let type_ = self.to_type(&type_node)?;
2649            // Check for visibility keyword before the type node.
2650            let (mode, mode_start) = self.return_mode_before(node, &type_node);
2651            let type_span = self.content_span(&type_node);
2652            let output_span = match mode_start {
2653                Some(start) => Span::new(start, type_span.hi),
2654                None => type_span,
2655            };
2656            vec![leo_ast::Output { mode, type_, span: output_span, id: self.builder.next_id() }]
2657        } else {
2658            Vec::new()
2659        };
2660
2661        Ok(leo_ast::FunctionPrototype::new(
2662            vec![], // annotations (not supported in prototypes)
2663            identifier,
2664            const_parameters,
2665            input,
2666            output,
2667            span,
2668            self.builder.next_id(),
2669        ))
2670    }
2671
2672    /// Convert a RECORD_PROTOTYPE_DEF node to a RecordPrototype.
2673    fn to_record_prototype(&self, node: &SyntaxNode) -> Result<leo_ast::RecordPrototype> {
2674        debug_assert_eq!(node.kind(), RECORD_PROTOTYPE_DEF);
2675
2676        let span = self.to_span(node);
2677        let identifier = self.require_ident(node, "record name");
2678        let members = children(node)
2679            .filter(|n| {
2680                matches!(
2681                    n.kind(),
2682                    STRUCT_MEMBER | STRUCT_MEMBER_PUBLIC | STRUCT_MEMBER_PRIVATE | STRUCT_MEMBER_CONSTANT
2683                )
2684            })
2685            .map(|n| self.struct_member_to_member(&n))
2686            .collect::<Result<Vec<_>>>()?;
2687
2688        // Check for redundant prototypes: `{ .. }` or `{ owner: address, .. }`.
2689        let is_redundant = members.is_empty()
2690            || members.iter().all(|m| {
2691                m.identifier.name == sym::owner && m.type_ == leo_ast::Type::Address && m.mode == leo_ast::Mode::None
2692            });
2693        if is_redundant && !members.is_empty() {
2694            // Strip the owner-only members since they are implicit.
2695            self.handler.emit_warning(ParserWarning::record_prototype_redundant(identifier.name, span));
2696            return Ok(leo_ast::RecordPrototype { identifier, span, members: Vec::new(), id: self.builder.next_id() });
2697        } else if is_redundant {
2698            // members is empty but we had braces — check if the node actually had braces.
2699            // If there's a L_BRACE token child, it means `{ .. }` was used.
2700            let had_braces = node.children_with_tokens().any(|c| c.kind() == L_BRACE);
2701            if had_braces {
2702                self.handler.emit_warning(ParserWarning::record_prototype_redundant(identifier.name, span));
2703            }
2704        }
2705
2706        Ok(leo_ast::RecordPrototype { identifier, span, members, id: self.builder.next_id() })
2707    }
2708}
2709
2710// =============================================================================
2711// Public Parse Functions
2712// =============================================================================
2713
2714/// Create a span from a rowan `TextRange`, clamping to source bounds and ensuring `hi >= lo`.
2715fn clamped_span(range: TextRange, start_pos: u32, source_len: u32) -> Span {
2716    let end = start_pos + source_len;
2717    let lo = (u32::from(range.start()) + start_pos).min(end);
2718    let hi = (u32::from(range.end()) + start_pos).min(end).max(lo);
2719    Span::new(lo, hi)
2720}
2721
2722/// Emit lexer errors to the handler with appropriate error types.
2723fn emit_lex_errors(handler: &Handler, lex_errors: &[leo_parser_rowan::LexError], start_pos: u32, source_len: u32) {
2724    use leo_parser_rowan::LexErrorKind;
2725    for error in lex_errors {
2726        let span = clamped_span(error.range, start_pos, source_len);
2727
2728        match &error.kind {
2729            LexErrorKind::InvalidDigit { digit, radix, token } => {
2730                handler.emit_err(ParserError::wrong_digit_for_radix_span(*digit, *radix, token, span));
2731            }
2732            LexErrorKind::CouldNotLex { content } => {
2733                handler.emit_err(ParserError::could_not_lex_span(content, span));
2734            }
2735            LexErrorKind::BidiOverride => {
2736                handler.emit_err(ParserError::lexer_bidi_override_span(span));
2737            }
2738        }
2739    }
2740}
2741
2742/// Emit parse errors to the handler, using structured error types when available.
2743/// Duplicate errors at the same location are filtered out to prevent cascading errors.
2744fn emit_parse_errors(
2745    handler: &Handler,
2746    errors: &[leo_parser_rowan::ParseError],
2747    start_pos: u32,
2748    source_len: u32,
2749    lex_errors: &[leo_parser_rowan::LexError],
2750) {
2751    use std::collections::HashSet;
2752
2753    let has_lex_errors = !lex_errors.is_empty();
2754
2755    // Collect lex error byte ranges so we can skip overlapping parse errors.
2756    let lex_ranges: Vec<(u32, u32)> = lex_errors
2757        .iter()
2758        .map(|e| {
2759            let lo = u32::from(e.range.start()).saturating_add(start_pos);
2760            let hi = u32::from(e.range.end()).saturating_add(start_pos);
2761            (lo, hi)
2762        })
2763        .collect();
2764
2765    // Track emitted error ranges to prevent duplicate errors at the same location
2766    let mut emitted_ranges: HashSet<(u32, u32)> = HashSet::new();
2767    let mut count = 0;
2768    let max_errors = 10;
2769
2770    for error in errors {
2771        if count >= max_errors {
2772            break;
2773        }
2774
2775        let span = clamped_span(error.range, start_pos, source_len);
2776        let range_key = (span.lo, span.hi);
2777
2778        // Skip if we already emitted an error at this exact range
2779        if emitted_ranges.contains(&range_key) {
2780            continue;
2781        }
2782
2783        // When there are lex errors, skip parse errors at EOF since
2784        // they are secondary effects of the lex failure.
2785        if has_lex_errors && span.lo == span.hi && span.hi == start_pos + source_len {
2786            continue;
2787        }
2788
2789        // Skip parse errors that overlap with lex error ranges — these
2790        // are secondary effects of the lex failure already reported.
2791        if lex_ranges.iter().any(|&(lo, hi)| span.lo < hi && span.hi > lo) {
2792            continue;
2793        }
2794
2795        emitted_ranges.insert(range_key);
2796
2797        // Detect EOF errors: the found token is empty or "end of file", or the
2798        // span sits at/past the end of source.
2799        let is_eof_error = match &error.found {
2800            Some(f) => f.is_empty() || f == "end of file",
2801            None => false,
2802        } || (span.lo == span.hi && span.hi >= start_pos + source_len);
2803
2804        if is_eof_error {
2805            handler.emit_err(ParserError::unexpected_eof(span));
2806            count += 1;
2807            continue;
2808        }
2809
2810        // Use ParserError::unexpected if we have structured found/expected info
2811        if let Some(found) = &error.found {
2812            if error.expected.is_empty() {
2813                // No structured expected tokens — use the message as a custom error.
2814                // This covers errors from `error()` like "expected field name".
2815                handler.emit_err(ParserError::custom(&error.message, span));
2816            } else {
2817                let expected_str = error.expected.join(", ");
2818                handler.emit_err(ParserError::unexpected(found, expected_str, span));
2819            }
2820            count += 1;
2821            continue;
2822        }
2823
2824        // Fall back to custom error for unstructured errors
2825        handler.emit_err(ParserError::custom(&error.message, span));
2826        count += 1;
2827    }
2828}
2829
2830/// Emit lex and parse errors, then create a `ConversionContext`.
2831fn conversion_context<'a>(
2832    handler: &'a Handler,
2833    node_builder: &'a NodeBuilder,
2834    lex_errors: &[leo_parser_rowan::LexError],
2835    parse_errors: &[leo_parser_rowan::ParseError],
2836    start_pos: u32,
2837    source_len: u32,
2838) -> ConversionContext<'a> {
2839    emit_lex_errors(handler, lex_errors, start_pos, source_len);
2840    emit_parse_errors(handler, parse_errors, start_pos, source_len, lex_errors);
2841    let has_errors = !parse_errors.is_empty() || !lex_errors.is_empty();
2842    ConversionContext::new(handler, node_builder, start_pos, has_errors)
2843}
2844
2845/// Parses a single expression from source code.
2846pub fn parse_expression(
2847    handler: Handler,
2848    node_builder: &NodeBuilder,
2849    source: &str,
2850    start_pos: u32,
2851    _network: NetworkName,
2852) -> Result<leo_ast::Expression> {
2853    let parse = leo_parser_rowan::parse_expression_entry(source);
2854    let ctx =
2855        conversion_context(&handler, node_builder, parse.lex_errors(), parse.errors(), start_pos, source.len() as u32);
2856    ctx.to_expression(&parse.syntax())
2857}
2858
2859/// Parses a single statement from source code.
2860pub fn parse_statement(
2861    handler: Handler,
2862    node_builder: &NodeBuilder,
2863    source: &str,
2864    start_pos: u32,
2865    _network: NetworkName,
2866) -> Result<leo_ast::Statement> {
2867    let parse = leo_parser_rowan::parse_statement_entry(source);
2868    let ctx =
2869        conversion_context(&handler, node_builder, parse.lex_errors(), parse.errors(), start_pos, source.len() as u32);
2870    ctx.to_statement(&parse.syntax())
2871}
2872
2873/// Parses a module (non-main source file) into a Module AST.
2874pub fn parse_module(
2875    handler: Handler,
2876    node_builder: &NodeBuilder,
2877    source: &str,
2878    start_pos: u32,
2879    program_name: Symbol,
2880    path: Vec<Symbol>,
2881    _network: NetworkName,
2882) -> Result<leo_ast::Module> {
2883    let parse = leo_parser_rowan::parse_module_entry(source);
2884    let ctx =
2885        conversion_context(&handler, node_builder, parse.lex_errors(), parse.errors(), start_pos, source.len() as u32);
2886    ctx.to_module(&parse.syntax(), program_name, path)
2887}
2888
2889/// Parses a complete program with its modules into a Program AST.
2890pub fn parse_program(
2891    handler: Handler,
2892    node_builder: &NodeBuilder,
2893    source: &SourceFile,
2894    modules: &[std::rc::Rc<SourceFile>],
2895    _network: NetworkName,
2896) -> Result<leo_ast::Program> {
2897    // Parse main program file
2898    let parse = leo_parser_rowan::parse_file(&source.src);
2899    let main_context = conversion_context(
2900        &handler,
2901        node_builder,
2902        parse.lex_errors(),
2903        parse.errors(),
2904        source.absolute_start,
2905        source.src.len() as u32,
2906    );
2907    let mut program = main_context.to_main(&parse.syntax())?;
2908    let program_name = *program.program_scopes.first().unwrap().0;
2909
2910    // Determine the root directory of the main file (for module resolution)
2911    let root_dir = match &source.name {
2912        FileName::Real(path) => path.parent().map(|p| p.to_path_buf()),
2913        _ => None,
2914    };
2915
2916    for module in modules {
2917        let module_parse = leo_parser_rowan::parse_module_entry(&module.src);
2918        let module_context = conversion_context(
2919            &handler,
2920            node_builder,
2921            module_parse.lex_errors(),
2922            module_parse.errors(),
2923            module.absolute_start,
2924            module.src.len() as u32,
2925        );
2926
2927        if let Some(key) = compute_module_key(&module.name, root_dir.as_deref()) {
2928            for segment in &key {
2929                if symbol_is_keyword(*segment) {
2930                    return Err(ParserError::keyword_used_as_module_name(key.iter().format("::"), segment).into());
2931                }
2932            }
2933            let module_ast = module_context.to_module(&module_parse.syntax(), program_name, key.clone())?;
2934            program.modules.insert(key, module_ast);
2935        }
2936    }
2937
2938    Ok(program)
2939}
2940
2941/// Parses a complete library with its submodules into a Library AST.
2942pub fn parse_library(
2943    handler: Handler,
2944    node_builder: &NodeBuilder,
2945    library_name: Symbol,
2946    source: &SourceFile,
2947    modules: &[std::rc::Rc<SourceFile>],
2948    _network: NetworkName,
2949) -> Result<leo_ast::Library> {
2950    // Parse `lib.leo` library file.
2951    let parse = leo_parser_rowan::parse_file(&source.src);
2952    let main_context = conversion_context(
2953        &handler,
2954        node_builder,
2955        parse.lex_errors(),
2956        parse.errors(),
2957        source.absolute_start,
2958        source.src.len() as u32,
2959    );
2960
2961    let mut library = main_context.to_library(library_name, &parse.syntax())?;
2962
2963    // Determine the root directory of `lib.leo` for module key computation.
2964    let root_dir = match &source.name {
2965        FileName::Real(path) => path.parent().map(|p| p.to_path_buf()),
2966        _ => None,
2967    };
2968
2969    // Parse each submodule source file and insert it into the library.
2970    for module_sf in modules {
2971        let module_parse = leo_parser_rowan::parse_module_entry(&module_sf.src);
2972        let module_context = conversion_context(
2973            &handler,
2974            node_builder,
2975            module_parse.lex_errors(),
2976            module_parse.errors(),
2977            module_sf.absolute_start,
2978            module_sf.src.len() as u32,
2979        );
2980
2981        if let Some(key) = compute_module_key(&module_sf.name, root_dir.as_deref()) {
2982            for segment in &key {
2983                if symbol_is_keyword(*segment) {
2984                    return Err(ParserError::keyword_used_as_module_name(key.iter().format("::"), segment).into());
2985                }
2986            }
2987            // Library modules use library_name as the owner (analogous to program_name in
2988            // program modules). Items inside are registered under Location::new(library_name, path).
2989            let module_ast = module_context.to_module(&module_parse.syntax(), library_name, key.clone())?;
2990            library.modules.insert(key, module_ast);
2991        }
2992    }
2993
2994    Ok(library)
2995}
2996
2997// =============================================================================
2998// Helper Functions
2999// =============================================================================
3000
3001/// Get non-trivia children of a node.
3002fn children(node: &SyntaxNode) -> impl Iterator<Item = SyntaxNode> + '_ {
3003    node.children().filter(|n| !n.kind().is_trivia())
3004}
3005
3006/// Get non-trivia tokens from a node.
3007fn tokens(node: &SyntaxNode) -> impl Iterator<Item = SyntaxToken> + '_ {
3008    node.children_with_tokens().filter_map(|elem| elem.into_token()).filter(|t| !t.kind().is_trivia())
3009}
3010
3011/// Find the first IDENT or keyword token after the DOT in a node.
3012fn find_name_after_dot(node: &SyntaxNode) -> Option<SyntaxToken> {
3013    let dot_end = tokens(node).find(|t| t.kind() == DOT)?.text_range().end();
3014    tokens(node).filter(|t| t.text_range().start() >= dot_end).find(|t| t.kind() == IDENT || t.kind().is_keyword())
3015}
3016
3017/// First non-trivia direct token of a node.
3018fn first_non_trivia_token(node: &SyntaxNode) -> Option<SyntaxToken> {
3019    node.children_with_tokens().find_map(|e| e.into_token().filter(|t| !t.kind().is_trivia()))
3020}
3021
3022/// Last non-trivia direct token of a node.
3023fn last_non_trivia_token(node: &SyntaxNode) -> Option<SyntaxToken> {
3024    node.children_with_tokens().filter_map(|e| e.into_token().filter(|t| !t.kind().is_trivia())).last()
3025}
3026
3027/// Find an invalid network identifier (IDENT after DOT) in a node's tokens.
3028fn find_invalid_network(node: &SyntaxNode) -> Option<SyntaxToken> {
3029    let mut saw_dot = false;
3030    tokens(node).find(|t| {
3031        if t.kind() == DOT {
3032            saw_dot = true;
3033            return false;
3034        }
3035        saw_dot && t.kind() == IDENT
3036    })
3037}
3038
3039/// Convert a visibility keyword token kind to a `Mode`.
3040fn token_kind_to_mode(kind: SyntaxKind) -> Option<leo_ast::Mode> {
3041    match kind {
3042        KW_PUBLIC => Some(leo_ast::Mode::Public),
3043        KW_PRIVATE => Some(leo_ast::Mode::Private),
3044        KW_CONSTANT => Some(leo_ast::Mode::Constant),
3045        _ => None,
3046    }
3047}
3048
3049/// Convert a parameter or struct member node kind to a `Mode`.
3050fn node_kind_to_mode(kind: SyntaxKind) -> leo_ast::Mode {
3051    match kind {
3052        PARAM_PUBLIC | STRUCT_MEMBER_PUBLIC => leo_ast::Mode::Public,
3053        PARAM_PRIVATE | STRUCT_MEMBER_PRIVATE => leo_ast::Mode::Private,
3054        PARAM_CONSTANT | STRUCT_MEMBER_CONSTANT => leo_ast::Mode::Constant,
3055        _ => leo_ast::Mode::None,
3056    }
3057}
3058
3059/// Convert a keyword token kind to the corresponding path symbol, if applicable.
3060fn keyword_to_path_symbol(kind: SyntaxKind) -> Option<Symbol> {
3061    match kind {
3062        KW_SELF => Some(sym::SelfLower),
3063        KW_BLOCK => Some(sym::block),
3064        KW_NETWORK => Some(sym::network),
3065        KW_FINAL_UPPER => Some(sym::Final),
3066        _ => None,
3067    }
3068}
3069
3070/// Check if a SyntaxKind is an assignment operator.
3071fn is_assign_op(kind: SyntaxKind) -> bool {
3072    matches!(
3073        kind,
3074        EQ | PLUS_EQ
3075            | MINUS_EQ
3076            | STAR_EQ
3077            | SLASH_EQ
3078            | PERCENT_EQ
3079            | STAR2_EQ
3080            | AMP_EQ
3081            | PIPE_EQ
3082            | CARET_EQ
3083            | SHL_EQ
3084            | SHR_EQ
3085            | AMP2_EQ
3086            | PIPE2_EQ
3087    )
3088}
3089
3090/// Convert a type keyword to a primitive Type, if applicable.
3091fn keyword_to_primitive_type(kind: SyntaxKind) -> Option<leo_ast::Type> {
3092    let ty = match kind {
3093        KW_ADDRESS => leo_ast::Type::Address,
3094        KW_BOOL => leo_ast::Type::Boolean,
3095        KW_FIELD => leo_ast::Type::Field,
3096        KW_GROUP => leo_ast::Type::Group,
3097        KW_SCALAR => leo_ast::Type::Scalar,
3098        KW_SIGNATURE => leo_ast::Type::Signature,
3099        KW_STRING => leo_ast::Type::String,
3100        KW_DYN => leo_ast::Type::DynRecord,
3101        KW_IDENTIFIER => leo_ast::Type::Identifier,
3102        KW_U8 => leo_ast::Type::Integer(leo_ast::IntegerType::U8),
3103        KW_U16 => leo_ast::Type::Integer(leo_ast::IntegerType::U16),
3104        KW_U32 => leo_ast::Type::Integer(leo_ast::IntegerType::U32),
3105        KW_U64 => leo_ast::Type::Integer(leo_ast::IntegerType::U64),
3106        KW_U128 => leo_ast::Type::Integer(leo_ast::IntegerType::U128),
3107        KW_I8 => leo_ast::Type::Integer(leo_ast::IntegerType::I8),
3108        KW_I16 => leo_ast::Type::Integer(leo_ast::IntegerType::I16),
3109        KW_I32 => leo_ast::Type::Integer(leo_ast::IntegerType::I32),
3110        KW_I64 => leo_ast::Type::Integer(leo_ast::IntegerType::I64),
3111        KW_I128 => leo_ast::Type::Integer(leo_ast::IntegerType::I128),
3112        _ => return None,
3113    };
3114    Some(ty)
3115}
3116
3117/// Convert a SyntaxKind operator to BinaryOperation.
3118fn token_to_binary_op(kind: SyntaxKind) -> leo_ast::BinaryOperation {
3119    match kind {
3120        EQ2 => leo_ast::BinaryOperation::Eq,
3121        BANG_EQ => leo_ast::BinaryOperation::Neq,
3122        LT => leo_ast::BinaryOperation::Lt,
3123        LT_EQ => leo_ast::BinaryOperation::Lte,
3124        GT => leo_ast::BinaryOperation::Gt,
3125        GT_EQ => leo_ast::BinaryOperation::Gte,
3126        PLUS => leo_ast::BinaryOperation::Add,
3127        MINUS => leo_ast::BinaryOperation::Sub,
3128        STAR => leo_ast::BinaryOperation::Mul,
3129        SLASH => leo_ast::BinaryOperation::Div,
3130        PERCENT => leo_ast::BinaryOperation::Rem,
3131        PIPE2 => leo_ast::BinaryOperation::Or,
3132        AMP2 => leo_ast::BinaryOperation::And,
3133        PIPE => leo_ast::BinaryOperation::BitwiseOr,
3134        AMP => leo_ast::BinaryOperation::BitwiseAnd,
3135        STAR2 => leo_ast::BinaryOperation::Pow,
3136        SHL => leo_ast::BinaryOperation::Shl,
3137        SHR => leo_ast::BinaryOperation::Shr,
3138        CARET => leo_ast::BinaryOperation::Xor,
3139        _ => panic!("unexpected binary operator: {:?}", kind),
3140    }
3141}
3142
3143fn symbol_is_keyword(symbol: Symbol) -> bool {
3144    matches!(
3145        symbol,
3146        sym::address
3147            | sym::aleo
3148            | sym::As
3149            | sym::assert
3150            | sym::assert_eq
3151            | sym::assert_neq
3152            | sym::block
3153            | sym::bool
3154            | sym::Const
3155            | sym::constant
3156            | sym::constructor
3157            | sym::Else
3158            | sym::False
3159            | sym::field
3160            | sym::FnUpper
3161            | sym::Fn
3162            | sym::For
3163            | sym::Final
3164            | sym::group
3165            | sym::i8
3166            | sym::i16
3167            | sym::i32
3168            | sym::i64
3169            | sym::i128
3170            | sym::If
3171            | sym::import
3172            | sym::In
3173            | sym::inline
3174            | sym::Let
3175            | sym::leo
3176            | sym::mapping
3177            | sym::storage
3178            | sym::network
3179            | sym::private
3180            | sym::program
3181            | sym::public
3182            | sym::record
3183            | sym::Return
3184            | sym::scalar
3185            | sym::script
3186            | sym::SelfLower
3187            | sym::signature
3188            | sym::string
3189            | sym::Struct
3190            | sym::True
3191            | sym::u8
3192            | sym::u16
3193            | sym::u32
3194            | sym::u64
3195            | sym::u128
3196    )
3197}
3198
3199/// Computes a module key from a `FileName`, optionally relative to a root directory.
3200fn compute_module_key(name: &FileName, root_dir: Option<&std::path::Path>) -> Option<Vec<Symbol>> {
3201    let path = match name {
3202        FileName::Custom(name) => std::path::Path::new(name).to_path_buf(),
3203        FileName::Real(path) => {
3204            let root = root_dir?;
3205            path.strip_prefix(root).ok()?.to_path_buf()
3206        }
3207    };
3208
3209    let mut key: Vec<Symbol> =
3210        path.components().map(|comp| Symbol::intern(&comp.as_os_str().to_string_lossy())).collect();
3211
3212    if let Some(last) = path.file_name()
3213        && let Some(stem) = std::path::Path::new(last).file_stem()
3214    {
3215        key.pop();
3216        key.push(Symbol::intern(&stem.to_string_lossy()));
3217    }
3218
3219    Some(key)
3220}
3221
3222/// Returns `true` for syntax node kinds that are valid inside a library (`lib.leo`).
3223///
3224/// Currently `const` declarations and `struct` definitions are allowed; this list will grow
3225/// as library support expands to include functions and other items.
3226fn is_library_item(kind: SyntaxKind) -> bool {
3227    matches!(kind, GLOBAL_CONST | STRUCT_DEF | FUNCTION_DEF)
3228}
3229
3230/// Returns `true` for syntax node kinds that are valid inside a program (`main.leo`).
3231fn is_program_item(kind: SyntaxKind) -> bool {
3232    matches!(
3233        kind,
3234        GLOBAL_CONST
3235            | FUNCTION_DEF
3236            | FINAL_FN_DEF
3237            | STRUCT_DEF
3238            | RECORD_DEF
3239            | INTERFACE_DEF
3240            | MAPPING_DEF
3241            | STORAGE_DEF
3242            | CONSTRUCTOR_DEF
3243            | PROGRAM_DECL
3244            | IMPORT
3245    )
3246}