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