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