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