Skip to main content

leo_parser_rowan/parser/
items.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//! Top-level item parsing for the Leo language.
18//!
19//! This module implements parsing for all Leo top-level declarations:
20//! - Imports
21//! - Program declarations
22//! - Functions, transitions, and inline functions
23//! - Structs and records
24//! - Mappings and storage
25//! - Global constants
26
27use super::{CompletedMarker, EXPR_RECOVERY, ITEM_RECOVERY, PARAM_RECOVERY, Parser, TYPE_RECOVERY};
28use crate::syntax_kind::SyntaxKind::{self, *};
29
30impl Parser<'_, '_> {
31    /// Recovery set for struct/record fields.
32    const FIELD_RECOVERY: &'static [SyntaxKind] = &[COMMA, R_BRACE, KW_PUBLIC, KW_PRIVATE, KW_CONSTANT];
33    /// Tokens that can start a module-level item (for error recovery).
34    const MODULE_ITEM_RECOVERY: &'static [SyntaxKind] = &[KW_EXPORT, KW_CONST, KW_STRUCT, KW_FN, KW_FINAL, AT];
35    /// Expected items within a `program { ... }` block.
36    const PROGRAM_ITEM_EXPECTED: &'static [SyntaxKind] = &[
37        R_BRACE,
38        AT,
39        KW_RECORD,
40        KW_STRUCT,
41        KW_FN,
42        KW_FINAL,
43        KW_CONST,
44        KW_MAPPING,
45        KW_STORAGE,
46        KW_SCRIPT,
47        KW_INTERFACE,
48    ];
49    /// Recovery set for return type parsing.
50    const RETURN_TYPE_RECOVERY: &'static [SyntaxKind] = &[COMMA, R_PAREN, L_BRACE];
51    /// Recovery set for struct/record name errors — skip to the next item or block boundary.
52    const STRUCT_NAME_RECOVERY: &'static [SyntaxKind] = &[
53        L_BRACE,
54        R_BRACE,
55        SEMICOLON,
56        KW_IMPORT,
57        KW_PROGRAM,
58        KW_CONST,
59        KW_STRUCT,
60        KW_RECORD,
61        KW_FN,
62        KW_FINAL,
63        KW_MAPPING,
64        KW_STORAGE,
65        KW_SCRIPT,
66        KW_INTERFACE,
67        AT,
68    ];
69
70    /// Consume an optional visibility modifier keyword (`public`, `private`, or `constant`).
71    /// Returns the keyword kind that was consumed, or `None`.
72    fn eat_visibility(&mut self) -> Option<SyntaxKind> {
73        if self.eat(KW_PUBLIC) {
74            Some(KW_PUBLIC)
75        } else if self.eat(KW_PRIVATE) {
76            Some(KW_PRIVATE)
77        } else if self.eat(KW_CONSTANT) {
78            Some(KW_CONSTANT)
79        } else {
80            None
81        }
82    }
83
84    /// Parse a complete file.
85    ///
86    /// A file may contain one or more program sections, each consisting of
87    /// optional imports followed by a `program` declaration. Multi-program
88    /// files appear in Leo test suites where programs are separated by
89    /// `// --- Next Program --- //` comments (the comment itself is trivia
90    /// and doesn't affect parsing).
91    ///
92    /// Module-level items (`const`, `struct`, `fn`) are also accepted
93    /// at the top level to support multi-section test files that combine
94    /// program declarations with module content separated by
95    /// `// --- Next Module: path --- //` comments.
96    pub fn parse_file_items(&mut self) {
97        loop {
98            self.skip_trivia();
99            if self.at_eof() {
100                break;
101            }
102
103            // Clear error state so each top-level item gets fresh error reporting.
104            self.erroring = false;
105
106            match self.current() {
107                KW_IMPORT => {
108                    self.parse_import();
109                }
110                KW_PROGRAM => {
111                    self.parse_program_decl();
112                }
113                // Module-level items at top level (for module files and
114                // multi-section test files with `// --- Next Module:` separators).
115                KW_EXPORT | KW_CONST | KW_STRUCT | KW_FN | KW_FINAL | AT | KW_INTERFACE | KW_VIEW => {
116                    if self.parse_module_item().is_none() {
117                        self.error_and_bump("expected module item");
118                    }
119                }
120                _ => {
121                    self.error("expected `import`, `program`, or module item at top level");
122                    self.recover(&[
123                        KW_IMPORT,
124                        KW_PROGRAM,
125                        KW_EXPORT,
126                        KW_CONST,
127                        KW_STRUCT,
128                        KW_FN,
129                        KW_FINAL,
130                        KW_VIEW,
131                        KW_INTERFACE,
132                        AT,
133                    ]);
134                }
135            }
136        }
137    }
138
139    /// Parse module-level items.
140    ///
141    /// Module files contain only `const`, `struct`, and `fn` declarations
142    /// (with optional annotations). No `import` or `program` blocks.
143    pub fn parse_module_items(&mut self) {
144        loop {
145            self.skip_trivia();
146            if self.at_eof() {
147                break;
148            }
149
150            // Clear error state so each module item gets fresh error reporting.
151            self.erroring = false;
152
153            if self.parse_module_item().is_none() {
154                self.error("expected `const`, `struct`, or `fn` in module");
155                self.recover(Self::MODULE_ITEM_RECOVERY);
156            }
157        }
158    }
159
160    /// Parse a single module-level item: `const`, `struct`, `interface` or `fn`.
161    ///
162    /// The leading `export` becomes a child token of the resulting item.
163    /// Annotations are handled inside each item parser.
164    fn parse_module_item(&mut self) -> Option<CompletedMarker> {
165        // Dispatch based on the token following an optional `export`.
166        let head = if self.at(KW_EXPORT) { self.nth(1) } else { self.current() };
167        match head {
168            KW_CONST => self.parse_global_const(),
169            KW_STRUCT => self.parse_composite_def(STRUCT_DEF),
170            KW_INTERFACE => self.parse_interface_def(),
171            AT | KW_FN | KW_FINAL | KW_VIEW => self.parse_function_or_constructor(false),
172            _ => {
173                if self.at(KW_EXPORT) {
174                    self.error("expected `fn`, `struct`, `const`, or `interface` after `export`");
175                    self.bump_any();
176                }
177                None
178            }
179        }
180    }
181
182    /// Parse an import declaration: `import program.aleo;`
183    fn parse_import(&mut self) -> Option<CompletedMarker> {
184        let m = self.start();
185        self.bump_any(); // import
186
187        // Parse program ID: name.aleo
188        self.parse_program_id();
189
190        self.expect(SEMICOLON);
191        Some(m.complete(self, IMPORT))
192    }
193
194    /// Parse a program declaration: `program name.aleo [: InterfaceName] { ... }`
195    fn parse_program_decl(&mut self) -> Option<CompletedMarker> {
196        let m = self.start();
197        self.bump_any(); // program
198
199        // Parse program ID: name.aleo
200        self.parse_program_id();
201
202        self.skip_trivia();
203        // Optional parent interface: `: InterfaceName`
204        if self.eat(COLON) {
205            self.skip_trivia();
206            // Clear error state so each item gets fresh error reporting.
207            self.erroring = false;
208            self.parse_parent_list();
209        }
210
211        self.expect(L_BRACE);
212
213        // Parse program items
214        while !self.at(R_BRACE) && !self.at_eof() {
215            // Clear error state so each item gets fresh error reporting.
216            self.erroring = false;
217            if self.parse_program_item().is_none() {
218                // Error was already reported by parse_program_item; just recover.
219                self.recover(ITEM_RECOVERY);
220            }
221        }
222
223        self.expect(R_BRACE);
224        Some(m.complete(self, PROGRAM_DECL))
225    }
226
227    /// Parse a program ID: `name.aleo`
228    fn parse_program_id(&mut self) {
229        self.skip_trivia();
230        if self.at(IDENT) {
231            self.bump_any(); // name
232            self.expect(DOT);
233            if !self.eat(KW_ALEO) {
234                if self.at(IDENT) {
235                    // Consume the invalid network identifier — the AST converter
236                    // will emit a specific `invalid_network` error (EPAR0370028).
237                    self.bump_any();
238                } else {
239                    self.error("expected 'aleo'");
240                }
241            }
242        } else {
243            self.error("expected program name");
244        }
245    }
246
247    /// Parse a single program item (struct, record, mapping, function, etc.)
248    ///
249    /// Annotations (`@foo`) are parsed as the first step and become children
250    /// of the resulting item node rather than siblings.
251    fn parse_program_item(&mut self) -> Option<CompletedMarker> {
252        if self.at(KW_EXPORT) {
253            self.error("`export` is not allowed inside a `program` block; items here are implicitly public");
254            self.bump_any();
255        }
256        // For annotated items, dispatch to function_or_constructor since
257        // annotations are only valid on functions/transitions in program blocks.
258        // The function parser handles annotations internally.
259        match self.current() {
260            AT => self.parse_function_or_constructor(true),
261            KW_STRUCT => self.parse_composite_def(STRUCT_DEF),
262            KW_RECORD => self.parse_composite_def(RECORD_DEF),
263            KW_MAPPING => self.parse_mapping_def(),
264            KW_STORAGE => self.parse_storage_def(),
265            KW_CONST => self.parse_global_const(),
266            KW_FN | KW_FINAL | KW_VIEW | KW_CONSTRUCTOR => self.parse_function_or_constructor(true),
267            KW_SCRIPT => {
268                self.error("'script' functions are no longer supported; use @test on entry point functions instead");
269                self.bump_any();
270                None
271            }
272            KW_INTERFACE => self.parse_interface_def(),
273            _ => {
274                let expected: Vec<&str> = Self::PROGRAM_ITEM_EXPECTED.iter().map(|k| k.user_friendly_name()).collect();
275                self.error_unexpected(self.current(), &expected);
276                None
277            }
278        }
279    }
280
281    /// Parse an annotation: `@program` or `@foo(args)`
282    fn parse_annotation(&mut self) {
283        let m = self.start();
284        self.bump_any(); // @
285
286        // Reject space between `@` and the annotation name (e.g. `@ test`).
287        if self.current_including_trivia().is_trivia() {
288            self.error_unexpected(self.current(), &["an identifier", "'program'"]);
289            self.skip_trivia();
290            // Still consume the name for recovery.
291            if self.at(IDENT) || self.current().is_keyword() {
292                self.bump_any();
293            }
294        } else if self.at(IDENT) || self.current().is_keyword() {
295            // Accept identifiers and keywords as annotation names
296            // (e.g. `@program`, `@test`, `@noupgrade`).
297            self.bump_any();
298        } else {
299            self.error("expected annotation name");
300        }
301
302        // Optional parenthesized arguments: `(key = "value", ...)`.
303        // Annotation members must be `identifier = "string"` separated by commas.
304        if self.eat(L_PAREN) {
305            if !self.at(R_PAREN) {
306                self.parse_annotation_member();
307                while self.eat(COMMA) {
308                    if self.at(R_PAREN) {
309                        break;
310                    }
311                    // Clear error state so each member gets fresh error reporting.
312                    self.erroring = false;
313                    self.parse_annotation_member();
314                }
315            }
316            // Reject trailing content before `)` (e.g. `oops` in `@foo(k = "v" oops)`).
317            if !self.at(R_PAREN) && !self.at_eof() {
318                self.error_unexpected(self.current(), &["')'", "','"]);
319                // Recovery: skip to closing paren.
320                while !self.at(R_PAREN) && !self.at_eof() {
321                    self.bump_any();
322                }
323            }
324            self.expect(R_PAREN);
325        }
326
327        m.complete(self, ANNOTATION);
328    }
329
330    /// Parse a single annotation member: `key = "value"`.
331    fn parse_annotation_member(&mut self) {
332        let m = self.start();
333        // Key must be an identifier, `address`, or `mapping`.
334        if self.at(IDENT) || self.at(KW_ADDRESS) || self.at(KW_MAPPING) {
335            self.bump_any();
336        } else {
337            self.error_unexpected(self.current(), &["an identifier", "')'", "'address", "'mapping'"]);
338            // Recovery: skip to `,` or `)`.
339            while !self.at(COMMA) && !self.at(R_PAREN) && !self.at_eof() {
340                self.bump_any();
341            }
342            m.abandon(self);
343            return;
344        }
345        self.expect(EQ);
346        if self.at(STRING) {
347            self.bump_any();
348        } else {
349            self.error("expected string literal for annotation value");
350        }
351        m.complete(self, ANNOTATION_PAIR);
352    }
353
354    /// Parse a struct or record definition: `[export] struct|record Name { field: TypeKind, ... }`.
355    fn parse_composite_def(&mut self, kind: SyntaxKind) -> Option<CompletedMarker> {
356        let m = self.start();
357        let _ = self.eat(KW_EXPORT);
358        let label = if kind == STRUCT_DEF { "struct" } else { "record" };
359        self.bump_any(); // struct | record
360
361        // Name
362        self.skip_trivia();
363        if self.at(IDENT) {
364            self.bump_any();
365        } else {
366            self.error(format!("expected {label} name"));
367            self.recover(Self::STRUCT_NAME_RECOVERY);
368            return Some(m.complete(self, ERROR));
369        }
370
371        // Optional const generic parameters: ::[N: u32]
372        if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
373            self.bump_any(); // ::
374            self.parse_const_param_list();
375        }
376
377        // Fields
378        self.expect(L_BRACE);
379        self.parse_struct_fields_until(&[R_BRACE]);
380        self.expect(R_BRACE);
381
382        Some(m.complete(self, kind))
383    }
384
385    /// Parse struct/record fields.
386    fn parse_struct_fields_until(&mut self, closing_symbols: &[SyntaxKind]) {
387        while !closing_symbols.iter().any(|closing| self.at(*closing)) && !self.at_eof() {
388            // Skip trivia before starting marker so member span starts at identifier
389            self.skip_trivia();
390            let m = self.start();
391
392            // Check for visibility modifier
393            let vis = self.eat_visibility();
394
395            // Field name
396            self.skip_trivia();
397            if self.at(IDENT) {
398                self.bump_any();
399            } else {
400                m.abandon(self);
401                // Try to recover to next field or end of struct
402                if !closing_symbols.iter().any(|closing| self.at(*closing)) {
403                    self.error_recover("expected field name", Self::FIELD_RECOVERY);
404                }
405                // If we recovered to a comma, consume it and continue
406                if self.eat(COMMA) {
407                    continue;
408                }
409                break;
410            }
411
412            // Colon and type
413            self.expect(COLON);
414            if self.parse_type().is_none() {
415                self.error_recover("expected type", Self::FIELD_RECOVERY);
416            }
417
418            let kind = match vis {
419                Some(KW_PUBLIC) => STRUCT_MEMBER_PUBLIC,
420                Some(KW_PRIVATE) => STRUCT_MEMBER_PRIVATE,
421                Some(KW_CONSTANT) => STRUCT_MEMBER_CONSTANT,
422                _ => STRUCT_MEMBER,
423            };
424            m.complete(self, kind);
425
426            // Comma or end of fields.
427            if !self.eat(COMMA) {
428                // If we're at `}` or EOF, the field list is done.
429                // Otherwise, there's a missing comma — report it and continue
430                // so we can parse more fields rather than cascading.
431                if !closing_symbols.iter().any(|closing| self.at(*closing)) && !self.at_eof() {
432                    self.error("expected ','");
433                    // Clear erroring so the next field can report its own errors.
434                    self.erroring = false;
435                }
436            }
437        }
438    }
439
440    /// Parse a mapping definition: `mapping name: Key => Value;`
441    fn parse_mapping_def(&mut self) -> Option<CompletedMarker> {
442        let m = self.start();
443        self.bump_any(); // mapping
444
445        // Name
446        self.skip_trivia();
447        if self.at(IDENT) {
448            self.bump_any();
449        } else {
450            self.error("expected mapping name");
451        }
452
453        // Key and value types
454        self.expect(COLON);
455        if self.parse_type().is_none() {
456            self.error_recover("expected key type", TYPE_RECOVERY);
457        }
458        self.expect(FAT_ARROW);
459        if self.parse_type().is_none() {
460            self.error_recover("expected value type", TYPE_RECOVERY);
461        }
462
463        self.expect(SEMICOLON);
464        Some(m.complete(self, MAPPING_DEF))
465    }
466
467    /// Parse a storage definition: `storage name: TypeKind;`
468    fn parse_storage_def(&mut self) -> Option<CompletedMarker> {
469        let m = self.start();
470        self.bump_any(); // storage
471
472        // Name
473        self.skip_trivia();
474        if self.at(IDENT) {
475            self.bump_any();
476        } else {
477            self.error("expected storage name");
478        }
479
480        // TypeKind
481        self.expect(COLON);
482        if self.parse_type().is_none() {
483            self.error_recover("expected type", TYPE_RECOVERY);
484        }
485
486        self.expect(SEMICOLON);
487        Some(m.complete(self, STORAGE_DEF))
488    }
489
490    /// Parse a global constant: `[export] const NAME: TypeKind = expr;`
491    fn parse_global_const(&mut self) -> Option<CompletedMarker> {
492        let m = self.start();
493        let _ = self.eat(KW_EXPORT);
494        self.bump_any(); // const
495
496        // Name
497        self.skip_trivia();
498        if self.at(IDENT) {
499            self.bump_any();
500        } else {
501            self.error("expected constant name");
502            self.recover(&[SEMICOLON]);
503            self.eat(SEMICOLON);
504            return Some(m.complete(self, GLOBAL_CONST));
505        }
506
507        // TypeKind
508        self.expect(COLON);
509        if self.parse_type().is_none() {
510            self.error_recover("expected type", TYPE_RECOVERY);
511        }
512
513        // Value
514        self.expect(EQ);
515        if self.parse_expr().is_none() {
516            self.error_recover("expected expression", EXPR_RECOVERY);
517        }
518
519        self.expect(SEMICOLON);
520        Some(m.complete(self, GLOBAL_CONST))
521    }
522
523    /// Parse a function definition.
524    /// Parse fn, final fn, view fn, or constructor variants.
525    /// Annotations are parsed as children of the function node.
526    ///
527    /// `in_program_block` is `true` when parsing inside a `program` block; an `export` token
528    /// that follows annotations is rejected with the same diagnostic used for the leading-`export`
529    /// case in `parse_program_item`, instead of being silently consumed.
530    fn parse_function_or_constructor(&mut self, in_program_block: bool) -> Option<CompletedMarker> {
531        let m = self.start();
532
533        // Parse leading annotations as children of this function node.
534        while self.at(AT) {
535            self.parse_annotation();
536        }
537
538        if self.at(KW_EXPORT) {
539            if in_program_block {
540                self.error("`export` is not allowed inside a `program` block; items here are implicitly public");
541            }
542            self.bump_any();
543        }
544
545        // `final` and `view` are mutually exclusive function modifiers. Eat the first one we
546        // see; if the other follows, emit a targeted error but still consume it so we recover
547        // and parse the body.
548        let ate_final = self.eat(KW_FINAL);
549        let ate_view = if ate_final && self.at(KW_VIEW) {
550            self.error("`final` and `view` cannot be combined");
551            self.bump_any();
552            false
553        } else {
554            self.eat(KW_VIEW)
555        };
556
557        // Dispatch based on what follows.
558        match self.current() {
559            KW_FN => {
560                self.parse_function_body();
561                let kind = match (ate_final, ate_view) {
562                    (true, _) => FINAL_FN_DEF,
563                    (false, true) => VIEW_FN_DEF,
564                    (false, false) => FUNCTION_DEF,
565                };
566                Some(m.complete(self, kind))
567            }
568            KW_CONSTRUCTOR => {
569                self.parse_constructor_body();
570                Some(m.complete(self, CONSTRUCTOR_DEF))
571            }
572            _ => {
573                self.error("expected 'fn' or 'constructor'");
574                m.abandon(self);
575                None
576            }
577        }
578    }
579
580    /// Parse function body (after final/fn keyword marker started)
581    fn parse_function_body(&mut self) {
582        // Function keyword
583        if !self.eat(KW_FN) {
584            self.error("expected 'fn'");
585        }
586
587        // Function name
588        self.skip_trivia();
589        if self.at(IDENT) {
590            self.bump_any();
591        } else {
592            self.error("expected function name");
593        }
594
595        // Optional const generic parameters: ::[N: u32, M: u32]
596        if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
597            self.bump_any(); // ::
598            self.parse_const_param_list();
599        }
600
601        // Parameters
602        self.parse_param_list();
603
604        // Return type: `-> [visibility] TypeKind` or `-> (vis TypeKind, vis TypeKind)`
605        if self.eat(ARROW) {
606            self.parse_return_type();
607        }
608
609        // Body
610        self.parse_block();
611    }
612
613    /// Parse a function return type.
614    ///
615    /// Handles both single return types with optional visibility modifiers
616    /// (`-> public u32`) and tuple return types
617    /// (`-> (public u32, private u32)`).
618    fn parse_return_type(&mut self) {
619        self.skip_trivia();
620        if self.at(L_PAREN) {
621            // Tuple return type: (vis TypeKind, vis TypeKind, ...)
622            let m = self.start();
623            self.bump_any(); // (
624            if !self.at(R_PAREN) {
625                // First output
626                self.eat_visibility();
627                if self.parse_type().is_none() {
628                    self.error_recover("expected return type", Self::RETURN_TYPE_RECOVERY);
629                }
630                while self.eat(COMMA) {
631                    if self.at(R_PAREN) {
632                        break;
633                    }
634                    self.eat_visibility();
635                    if self.parse_type().is_none() {
636                        self.error_recover("expected return type", Self::RETURN_TYPE_RECOVERY);
637                    }
638                }
639            }
640            self.expect(R_PAREN);
641            m.complete(self, RETURN_TYPE);
642        } else {
643            // Single return type with optional visibility
644            self.eat_visibility();
645            if self.parse_type().is_none() {
646                self.error_recover("expected return type", Self::RETURN_TYPE_RECOVERY);
647            }
648        }
649    }
650
651    /// Parse constructor body: `constructor() { }`
652    fn parse_constructor_body(&mut self) {
653        self.bump_any(); // constructor
654
655        // Parameters
656        self.parse_param_list();
657
658        // Body
659        self.parse_block();
660    }
661
662    /// Parse a parameter list: `(a: TypeKind, b: TypeKind)`
663    fn parse_param_list(&mut self) {
664        let m = self.start();
665        self.expect(L_PAREN);
666
667        while !self.at(R_PAREN) && !self.at_eof() {
668            // Clear error state so each parameter gets fresh error reporting.
669            self.erroring = false;
670            self.parse_param();
671            if !self.eat(COMMA) {
672                break;
673            }
674        }
675
676        self.expect(R_PAREN);
677        m.complete(self, PARAM_LIST);
678    }
679
680    /// Parse a parent list: `TypeKind + TypeKind`
681    fn parse_parent_list(&mut self) {
682        let m = self.start();
683        self.parse_type();
684        self.skip_trivia();
685
686        while self.at(PLUS) && !self.at_eof() {
687            self.bump_any(); // PLUS
688            self.skip_trivia();
689            self.parse_type();
690            self.skip_trivia();
691        }
692
693        m.complete(self, PARENT_LIST);
694    }
695
696    /// Parse a single parameter: `[visibility] name: TypeKind`
697    fn parse_param(&mut self) {
698        let m = self.start();
699        self.skip_trivia();
700
701        // Optional visibility
702        let vis = self.eat_visibility();
703
704        // Name
705        self.skip_trivia();
706        if self.at(IDENT) {
707            self.bump_any();
708        } else {
709            self.error("expected parameter name");
710        }
711
712        // TypeKind
713        self.expect(COLON);
714        if self.parse_type().is_none() {
715            self.error_recover("expected parameter type", PARAM_RECOVERY);
716        }
717
718        let kind = match vis {
719            Some(KW_PUBLIC) => PARAM_PUBLIC,
720            Some(KW_PRIVATE) => PARAM_PRIVATE,
721            Some(KW_CONSTANT) => PARAM_CONSTANT,
722            _ => PARAM,
723        };
724        m.complete(self, kind);
725    }
726
727    // =========================================================================
728    // Interface Parsing
729    // =========================================================================
730
731    /// Parse an interface definition: `[export] interface Name [: Parent] { fn_prototypes... }`
732    fn parse_interface_def(&mut self) -> Option<CompletedMarker> {
733        let m = self.start();
734        let _ = self.eat(KW_EXPORT);
735        self.bump_any(); // interface
736
737        // Name
738        self.skip_trivia();
739        if self.at(IDENT) {
740            self.bump_any();
741        } else {
742            self.error("expected parameter name");
743        }
744
745        // Optional parent: `: ParentName`
746        self.skip_trivia();
747        if self.eat(COLON) {
748            self.skip_trivia();
749            self.parse_parent_list();
750        }
751
752        // Interface body
753        self.expect(L_BRACE);
754        while !self.at(R_BRACE) && !self.at_eof() {
755            self.erroring = false;
756            if !self.parse_interface_item() {
757                self.error_and_bump("expected function or record prototype");
758            }
759        }
760        self.expect(R_BRACE);
761
762        Some(m.complete(self, INTERFACE_DEF))
763    }
764
765    /// Parse an interface item: function prototype, view fn prototype, or record/mapping/storage prototype
766    fn parse_interface_item(&mut self) -> bool {
767        match self.current() {
768            KW_FN | KW_VIEW => {
769                self.parse_fn_prototype();
770                true
771            }
772            KW_RECORD => {
773                self.parse_record_prototype();
774                true
775            }
776            KW_MAPPING => {
777                self.parse_mapping_def();
778                true
779            }
780            KW_STORAGE => {
781                self.parse_storage_def();
782                true
783            }
784            _ => false,
785        }
786    }
787
788    /// Parse function prototype: `[view] fn name(...) [-> TypeKind];`
789    ///
790    /// When the leading `view` keyword is present, the prototype is recorded as a view fn
791    /// (see `to_function_prototype`).
792    fn parse_fn_prototype(&mut self) -> Option<CompletedMarker> {
793        let m = self.start();
794        let _ = self.eat(KW_VIEW);
795        self.bump_any(); // fn
796
797        // Name
798        self.skip_trivia();
799        if self.at(IDENT) {
800            self.bump_any();
801        } else {
802            self.error("expected function name");
803        }
804
805        // Optional const generic parameters: ::[N: u32]
806        if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
807            self.bump_any(); // ::
808            self.parse_const_param_list();
809        }
810
811        // Parameters
812        self.parse_param_list();
813
814        // Return type
815        if self.eat(ARROW) {
816            self.parse_return_type();
817        }
818
819        self.expect(SEMICOLON);
820        Some(m.complete(self, FN_PROTOTYPE_DEF))
821    }
822
823    /// Parse record prototype: `record Name;`
824    fn parse_record_prototype(&mut self) -> Option<CompletedMarker> {
825        let m = self.start();
826        self.bump_any(); // record
827
828        self.skip_trivia();
829        if self.at(IDENT) {
830            self.bump_any();
831        } else {
832            self.error("expected record name");
833        }
834
835        if self.at(SEMICOLON) {
836            // record something;
837            self.bump_any();
838        } else {
839            // record something { /* fields */ .. }
840            self.expect(L_BRACE);
841            self.parse_struct_fields_until(&[DOT_DOT, R_BRACE]);
842            if self.eat(DOT_DOT) {
843                self.expect(R_BRACE);
844            } else {
845                self.error("expected `..`; fully constraining record fields in interfaces is not yet supported");
846                self.expect(R_BRACE);
847            }
848        }
849
850        Some(m.complete(self, RECORD_PROTOTYPE_DEF))
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857    use crate::{lexer::lex, parser::Parse};
858    use expect_test::{Expect, expect};
859
860    fn check_file(input: &str, expect: Expect) {
861        let (tokens, _) = lex(input);
862        let mut parser = Parser::new(input, &tokens);
863        let root = parser.start();
864        parser.parse_file_items();
865        root.complete(&mut parser, ROOT);
866        let parse: Parse = parser.finish(vec![]);
867        let output = format!("{:#?}", parse.syntax());
868        expect.assert_eq(&output);
869    }
870
871    fn check_file_no_errors(input: &str) {
872        let (tokens, _) = lex(input);
873        let mut parser = Parser::new(input, &tokens);
874        let root = parser.start();
875        parser.parse_file_items();
876        root.complete(&mut parser, ROOT);
877        let parse: Parse = parser.finish(vec![]);
878        if !parse.errors().is_empty() {
879            for err in parse.errors() {
880                eprintln!("error at {:?}: {}", err.range, err.message);
881            }
882            eprintln!("tree:\n{:#?}", parse.syntax());
883            panic!("parse had {} error(s)", parse.errors().len());
884        }
885    }
886
887    // =========================================================================
888    // Imports
889    // =========================================================================
890
891    #[test]
892    fn parse_import() {
893        check_file("import credits.aleo;", expect![[r#"
894                ROOT@0..20
895                  IMPORT@0..20
896                    KW_IMPORT@0..6 "import"
897                    WHITESPACE@6..7 " "
898                    IDENT@7..14 "credits"
899                    DOT@14..15 "."
900                    KW_ALEO@15..19 "aleo"
901                    SEMICOLON@19..20 ";"
902            "#]]);
903    }
904
905    // =========================================================================
906    // Program Declaration
907    // =========================================================================
908
909    #[test]
910    fn parse_program_empty() {
911        check_file("program test.aleo { }", expect![[r#"
912                ROOT@0..21
913                  PROGRAM_DECL@0..21
914                    KW_PROGRAM@0..7 "program"
915                    WHITESPACE@7..8 " "
916                    IDENT@8..12 "test"
917                    DOT@12..13 "."
918                    KW_ALEO@13..17 "aleo"
919                    WHITESPACE@17..18 " "
920                    L_BRACE@18..19 "{"
921                    WHITESPACE@19..20 " "
922                    R_BRACE@20..21 "}"
923            "#]]);
924    }
925
926    // =========================================================================
927    // Structs
928    // =========================================================================
929
930    #[test]
931    fn parse_struct() {
932        check_file("program test.aleo { struct Point { x: u32, y: u32 } }", expect![[r#"
933            ROOT@0..53
934              PROGRAM_DECL@0..53
935                KW_PROGRAM@0..7 "program"
936                WHITESPACE@7..8 " "
937                IDENT@8..12 "test"
938                DOT@12..13 "."
939                KW_ALEO@13..17 "aleo"
940                WHITESPACE@17..18 " "
941                L_BRACE@18..19 "{"
942                STRUCT_DEF@19..51
943                  WHITESPACE@19..20 " "
944                  KW_STRUCT@20..26 "struct"
945                  WHITESPACE@26..27 " "
946                  IDENT@27..32 "Point"
947                  WHITESPACE@32..33 " "
948                  L_BRACE@33..34 "{"
949                  WHITESPACE@34..35 " "
950                  STRUCT_MEMBER@35..41
951                    IDENT@35..36 "x"
952                    COLON@36..37 ":"
953                    WHITESPACE@37..38 " "
954                    TYPE_PRIMITIVE@38..41
955                      KW_U32@38..41 "u32"
956                  COMMA@41..42 ","
957                  WHITESPACE@42..43 " "
958                  STRUCT_MEMBER@43..49
959                    IDENT@43..44 "y"
960                    COLON@44..45 ":"
961                    WHITESPACE@45..46 " "
962                    TYPE_PRIMITIVE@46..49
963                      KW_U32@46..49 "u32"
964                  WHITESPACE@49..50 " "
965                  R_BRACE@50..51 "}"
966                WHITESPACE@51..52 " "
967                R_BRACE@52..53 "}"
968        "#]]);
969    }
970
971    // =========================================================================
972    // Records
973    // =========================================================================
974
975    #[test]
976    fn parse_record() {
977        check_file("program test.aleo { record Token { owner: address, amount: u64 } }", expect![[r#"
978            ROOT@0..66
979              PROGRAM_DECL@0..66
980                KW_PROGRAM@0..7 "program"
981                WHITESPACE@7..8 " "
982                IDENT@8..12 "test"
983                DOT@12..13 "."
984                KW_ALEO@13..17 "aleo"
985                WHITESPACE@17..18 " "
986                L_BRACE@18..19 "{"
987                RECORD_DEF@19..64
988                  WHITESPACE@19..20 " "
989                  KW_RECORD@20..26 "record"
990                  WHITESPACE@26..27 " "
991                  IDENT@27..32 "Token"
992                  WHITESPACE@32..33 " "
993                  L_BRACE@33..34 "{"
994                  WHITESPACE@34..35 " "
995                  STRUCT_MEMBER@35..49
996                    IDENT@35..40 "owner"
997                    COLON@40..41 ":"
998                    WHITESPACE@41..42 " "
999                    TYPE_PRIMITIVE@42..49
1000                      KW_ADDRESS@42..49 "address"
1001                  COMMA@49..50 ","
1002                  WHITESPACE@50..51 " "
1003                  STRUCT_MEMBER@51..62
1004                    IDENT@51..57 "amount"
1005                    COLON@57..58 ":"
1006                    WHITESPACE@58..59 " "
1007                    TYPE_PRIMITIVE@59..62
1008                      KW_U64@59..62 "u64"
1009                  WHITESPACE@62..63 " "
1010                  R_BRACE@63..64 "}"
1011                WHITESPACE@64..65 " "
1012                R_BRACE@65..66 "}"
1013        "#]]);
1014    }
1015
1016    // =========================================================================
1017    // Mappings
1018    // =========================================================================
1019
1020    #[test]
1021    fn parse_mapping() {
1022        check_file("program test.aleo { mapping balances: address => u64; }", expect![[r#"
1023            ROOT@0..55
1024              PROGRAM_DECL@0..55
1025                KW_PROGRAM@0..7 "program"
1026                WHITESPACE@7..8 " "
1027                IDENT@8..12 "test"
1028                DOT@12..13 "."
1029                KW_ALEO@13..17 "aleo"
1030                WHITESPACE@17..18 " "
1031                L_BRACE@18..19 "{"
1032                MAPPING_DEF@19..53
1033                  WHITESPACE@19..20 " "
1034                  KW_MAPPING@20..27 "mapping"
1035                  WHITESPACE@27..28 " "
1036                  IDENT@28..36 "balances"
1037                  COLON@36..37 ":"
1038                  WHITESPACE@37..38 " "
1039                  TYPE_PRIMITIVE@38..45
1040                    KW_ADDRESS@38..45 "address"
1041                  WHITESPACE@45..46 " "
1042                  FAT_ARROW@46..48 "=>"
1043                  WHITESPACE@48..49 " "
1044                  TYPE_PRIMITIVE@49..52
1045                    KW_U64@49..52 "u64"
1046                  SEMICOLON@52..53 ";"
1047                WHITESPACE@53..54 " "
1048                R_BRACE@54..55 "}"
1049        "#]]);
1050    }
1051
1052    // =========================================================================
1053    // Functions
1054    // =========================================================================
1055
1056    #[test]
1057    fn parse_function() {
1058        check_file("program test.aleo { fn add(a: u32, b: u32) -> u32 { return a + b; } }", expect![[r#"
1059            ROOT@0..69
1060              PROGRAM_DECL@0..69
1061                KW_PROGRAM@0..7 "program"
1062                WHITESPACE@7..8 " "
1063                IDENT@8..12 "test"
1064                DOT@12..13 "."
1065                KW_ALEO@13..17 "aleo"
1066                WHITESPACE@17..18 " "
1067                L_BRACE@18..19 "{"
1068                FUNCTION_DEF@19..67
1069                  WHITESPACE@19..20 " "
1070                  KW_FN@20..22 "fn"
1071                  WHITESPACE@22..23 " "
1072                  IDENT@23..26 "add"
1073                  PARAM_LIST@26..42
1074                    L_PAREN@26..27 "("
1075                    PARAM@27..33
1076                      IDENT@27..28 "a"
1077                      COLON@28..29 ":"
1078                      WHITESPACE@29..30 " "
1079                      TYPE_PRIMITIVE@30..33
1080                        KW_U32@30..33 "u32"
1081                    COMMA@33..34 ","
1082                    PARAM@34..41
1083                      WHITESPACE@34..35 " "
1084                      IDENT@35..36 "b"
1085                      COLON@36..37 ":"
1086                      WHITESPACE@37..38 " "
1087                      TYPE_PRIMITIVE@38..41
1088                        KW_U32@38..41 "u32"
1089                    R_PAREN@41..42 ")"
1090                  WHITESPACE@42..43 " "
1091                  ARROW@43..45 "->"
1092                  WHITESPACE@45..46 " "
1093                  TYPE_PRIMITIVE@46..49
1094                    KW_U32@46..49 "u32"
1095                  BLOCK@49..67
1096                    WHITESPACE@49..50 " "
1097                    L_BRACE@50..51 "{"
1098                    WHITESPACE@51..52 " "
1099                    RETURN_STMT@52..65
1100                      KW_RETURN@52..58 "return"
1101                      WHITESPACE@58..59 " "
1102                      BINARY_EXPR@59..64
1103                        PATH_EXPR@59..61
1104                          IDENT@59..60 "a"
1105                          WHITESPACE@60..61 " "
1106                        PLUS@61..62 "+"
1107                        WHITESPACE@62..63 " "
1108                        PATH_EXPR@63..64
1109                          IDENT@63..64 "b"
1110                      SEMICOLON@64..65 ";"
1111                    WHITESPACE@65..66 " "
1112                    R_BRACE@66..67 "}"
1113                WHITESPACE@67..68 " "
1114                R_BRACE@68..69 "}"
1115        "#]]);
1116    }
1117
1118    #[test]
1119    fn parse_final_function() {
1120        check_file("program test.aleo { } final fn foo() { assert_eq(1u64, 1u64); }", expect![[r#"
1121            ROOT@0..63
1122              PROGRAM_DECL@0..21
1123                KW_PROGRAM@0..7 "program"
1124                WHITESPACE@7..8 " "
1125                IDENT@8..12 "test"
1126                DOT@12..13 "."
1127                KW_ALEO@13..17 "aleo"
1128                WHITESPACE@17..18 " "
1129                L_BRACE@18..19 "{"
1130                WHITESPACE@19..20 " "
1131                R_BRACE@20..21 "}"
1132              WHITESPACE@21..22 " "
1133              FINAL_FN_DEF@22..63
1134                KW_FINAL@22..27 "final"
1135                WHITESPACE@27..28 " "
1136                KW_FN@28..30 "fn"
1137                WHITESPACE@30..31 " "
1138                IDENT@31..34 "foo"
1139                PARAM_LIST@34..36
1140                  L_PAREN@34..35 "("
1141                  R_PAREN@35..36 ")"
1142                WHITESPACE@36..37 " "
1143                BLOCK@37..63
1144                  L_BRACE@37..38 "{"
1145                  WHITESPACE@38..39 " "
1146                  ASSERT_EQ_STMT@39..61
1147                    KW_ASSERT_EQ@39..48 "assert_eq"
1148                    L_PAREN@48..49 "("
1149                    LITERAL_INT@49..53
1150                      INTEGER@49..53 "1u64"
1151                    COMMA@53..54 ","
1152                    WHITESPACE@54..55 " "
1153                    LITERAL_INT@55..59
1154                      INTEGER@55..59 "1u64"
1155                    R_PAREN@59..60 ")"
1156                    SEMICOLON@60..61 ";"
1157                  WHITESPACE@61..62 " "
1158                  R_BRACE@62..63 "}"
1159        "#]]);
1160    }
1161    // =========================================================================
1162    // Query Functions
1163    // =========================================================================
1164
1165    #[test]
1166    fn parse_view_function() {
1167        check_file_no_errors(
1168            "program test.aleo { mapping balances: address => u64; view fn balance_of(addr: address) -> u64 { return 0u64; } }",
1169        );
1170    }
1171
1172    #[test]
1173    fn parse_view_uses_view_node_kind() {
1174        check_file("program test.aleo { view fn q() -> u64 { return 0u64; } }", expect![[r#"
1175            ROOT@0..57
1176              PROGRAM_DECL@0..57
1177                KW_PROGRAM@0..7 "program"
1178                WHITESPACE@7..8 " "
1179                IDENT@8..12 "test"
1180                DOT@12..13 "."
1181                KW_ALEO@13..17 "aleo"
1182                WHITESPACE@17..18 " "
1183                L_BRACE@18..19 "{"
1184                VIEW_FN_DEF@19..55
1185                  WHITESPACE@19..20 " "
1186                  KW_VIEW@20..24 "view"
1187                  WHITESPACE@24..25 " "
1188                  KW_FN@25..27 "fn"
1189                  WHITESPACE@27..28 " "
1190                  IDENT@28..29 "q"
1191                  PARAM_LIST@29..31
1192                    L_PAREN@29..30 "("
1193                    R_PAREN@30..31 ")"
1194                  WHITESPACE@31..32 " "
1195                  ARROW@32..34 "->"
1196                  WHITESPACE@34..35 " "
1197                  TYPE_PRIMITIVE@35..38
1198                    KW_U64@35..38 "u64"
1199                  BLOCK@38..55
1200                    WHITESPACE@38..39 " "
1201                    L_BRACE@39..40 "{"
1202                    WHITESPACE@40..41 " "
1203                    RETURN_STMT@41..53
1204                      KW_RETURN@41..47 "return"
1205                      WHITESPACE@47..48 " "
1206                      LITERAL_INT@48..52
1207                        INTEGER@48..52 "0u64"
1208                      SEMICOLON@52..53 ";"
1209                    WHITESPACE@53..54 " "
1210                    R_BRACE@54..55 "}"
1211                WHITESPACE@55..56 " "
1212                R_BRACE@56..57 "}"
1213        "#]]);
1214    }
1215
1216    #[test]
1217    fn parse_view_with_input() {
1218        check_file_no_errors("program test.aleo { view fn q(addr: address, k: u32) -> u64 { return 1u64; } }");
1219    }
1220
1221    #[test]
1222    fn parse_final_view_rejected() {
1223        let input = "program test.aleo { final view fn q() {} }";
1224        let (tokens, _) = lex(input);
1225        let mut parser = Parser::new(input, &tokens);
1226        let root = parser.start();
1227        parser.parse_file_items();
1228        root.complete(&mut parser, ROOT);
1229        let parse: Parse = parser.finish(vec![]);
1230        let errors = parse.errors();
1231        assert!(
1232            errors.iter().any(|e| e.message.contains("`final` and `view` cannot be combined")),
1233            "expected `final view` rejection, got: {errors:?}"
1234        );
1235    }
1236
1237    // =========================================================================
1238    // Const Generic Parameters (Declarations)
1239    // =========================================================================
1240
1241    #[test]
1242    fn parse_function_const_generic_single() {
1243        check_file_no_errors("program test.aleo { fn foo::[N: u32]() {} }");
1244    }
1245
1246    #[test]
1247    fn parse_function_const_generic_multi() {
1248        check_file_no_errors("program test.aleo { fn bar::[N: u32, M: u32](arr: u32) -> u32 { return 0u32; } }");
1249    }
1250
1251    #[test]
1252    fn parse_function_const_generic_empty() {
1253        check_file_no_errors("program test.aleo { fn baz::[]() {} }");
1254    }
1255
1256    #[test]
1257    fn parse_final_entry_const_generic() {
1258        check_file_no_errors("program test.aleo { fn t::[N: u32]() -> Final { return final {}; } }");
1259    }
1260
1261    #[test]
1262    fn parse_struct_const_generic() {
1263        check_file_no_errors("program test.aleo { struct Foo::[N: u32] { arr: u32, } }");
1264    }
1265
1266    #[test]
1267    fn parse_struct_const_generic_multi() {
1268        check_file_no_errors("program test.aleo { struct Matrix::[M: u32, N: u32] { data: u32, } }");
1269    }
1270
1271    #[test]
1272    fn parse_record_const_generic() {
1273        // Syntactically valid, semantically rejected later.
1274        check_file_no_errors("program test.aleo { record Bar::[N: u32] { owner: address, } }");
1275    }
1276
1277    #[test]
1278    fn parse_transition() {
1279        check_file("program test.aleo { fn main(public x: u32) { } }", expect![[r#"
1280            ROOT@0..48
1281              PROGRAM_DECL@0..48
1282                KW_PROGRAM@0..7 "program"
1283                WHITESPACE@7..8 " "
1284                IDENT@8..12 "test"
1285                DOT@12..13 "."
1286                KW_ALEO@13..17 "aleo"
1287                WHITESPACE@17..18 " "
1288                L_BRACE@18..19 "{"
1289                FUNCTION_DEF@19..46
1290                  WHITESPACE@19..20 " "
1291                  KW_FN@20..22 "fn"
1292                  WHITESPACE@22..23 " "
1293                  IDENT@23..27 "main"
1294                  PARAM_LIST@27..42
1295                    L_PAREN@27..28 "("
1296                    PARAM_PUBLIC@28..41
1297                      KW_PUBLIC@28..34 "public"
1298                      WHITESPACE@34..35 " "
1299                      IDENT@35..36 "x"
1300                      COLON@36..37 ":"
1301                      WHITESPACE@37..38 " "
1302                      TYPE_PRIMITIVE@38..41
1303                        KW_U32@38..41 "u32"
1304                    R_PAREN@41..42 ")"
1305                  WHITESPACE@42..43 " "
1306                  BLOCK@43..46
1307                    L_BRACE@43..44 "{"
1308                    WHITESPACE@44..45 " "
1309                    R_BRACE@45..46 "}"
1310                WHITESPACE@46..47 " "
1311                R_BRACE@47..48 "}"
1312        "#]]);
1313    }
1314
1315    // =========================================================================
1316    // Record with Visibility Modifiers (3a)
1317    // =========================================================================
1318
1319    #[test]
1320    fn parse_record_visibility() {
1321        check_file("program test.aleo { record Token { public owner: address, private amount: u64, } }", expect![[
1322            r#"
1323            ROOT@0..82
1324              PROGRAM_DECL@0..82
1325                KW_PROGRAM@0..7 "program"
1326                WHITESPACE@7..8 " "
1327                IDENT@8..12 "test"
1328                DOT@12..13 "."
1329                KW_ALEO@13..17 "aleo"
1330                WHITESPACE@17..18 " "
1331                L_BRACE@18..19 "{"
1332                RECORD_DEF@19..80
1333                  WHITESPACE@19..20 " "
1334                  KW_RECORD@20..26 "record"
1335                  WHITESPACE@26..27 " "
1336                  IDENT@27..32 "Token"
1337                  WHITESPACE@32..33 " "
1338                  L_BRACE@33..34 "{"
1339                  WHITESPACE@34..35 " "
1340                  STRUCT_MEMBER_PUBLIC@35..56
1341                    KW_PUBLIC@35..41 "public"
1342                    WHITESPACE@41..42 " "
1343                    IDENT@42..47 "owner"
1344                    COLON@47..48 ":"
1345                    WHITESPACE@48..49 " "
1346                    TYPE_PRIMITIVE@49..56
1347                      KW_ADDRESS@49..56 "address"
1348                  COMMA@56..57 ","
1349                  WHITESPACE@57..58 " "
1350                  STRUCT_MEMBER_PRIVATE@58..77
1351                    KW_PRIVATE@58..65 "private"
1352                    WHITESPACE@65..66 " "
1353                    IDENT@66..72 "amount"
1354                    COLON@72..73 ":"
1355                    WHITESPACE@73..74 " "
1356                    TYPE_PRIMITIVE@74..77
1357                      KW_U64@74..77 "u64"
1358                  COMMA@77..78 ","
1359                  WHITESPACE@78..79 " "
1360                  R_BRACE@79..80 "}"
1361                WHITESPACE@80..81 " "
1362                R_BRACE@81..82 "}"
1363        "#
1364        ]]);
1365    }
1366
1367    // =========================================================================
1368    // Final Fn (3b)
1369    // =========================================================================
1370
1371    #[test]
1372    fn parse_final_fn_with_return() {
1373        check_file("program test.aleo { final fn main(public x: u32) -> Final { return final {}; } }", expect![[r#"
1374            ROOT@0..80
1375              PROGRAM_DECL@0..80
1376                KW_PROGRAM@0..7 "program"
1377                WHITESPACE@7..8 " "
1378                IDENT@8..12 "test"
1379                DOT@12..13 "."
1380                KW_ALEO@13..17 "aleo"
1381                WHITESPACE@17..18 " "
1382                L_BRACE@18..19 "{"
1383                FINAL_FN_DEF@19..78
1384                  WHITESPACE@19..20 " "
1385                  KW_FINAL@20..25 "final"
1386                  WHITESPACE@25..26 " "
1387                  KW_FN@26..28 "fn"
1388                  WHITESPACE@28..29 " "
1389                  IDENT@29..33 "main"
1390                  PARAM_LIST@33..48
1391                    L_PAREN@33..34 "("
1392                    PARAM_PUBLIC@34..47
1393                      KW_PUBLIC@34..40 "public"
1394                      WHITESPACE@40..41 " "
1395                      IDENT@41..42 "x"
1396                      COLON@42..43 ":"
1397                      WHITESPACE@43..44 " "
1398                      TYPE_PRIMITIVE@44..47
1399                        KW_U32@44..47 "u32"
1400                    R_PAREN@47..48 ")"
1401                  WHITESPACE@48..49 " "
1402                  ARROW@49..51 "->"
1403                  WHITESPACE@51..52 " "
1404                  TYPE_FINAL@52..58
1405                    KW_FINAL_UPPER@52..57 "Final"
1406                    WHITESPACE@57..58 " "
1407                  BLOCK@58..78
1408                    L_BRACE@58..59 "{"
1409                    WHITESPACE@59..60 " "
1410                    RETURN_STMT@60..76
1411                      KW_RETURN@60..66 "return"
1412                      WHITESPACE@66..67 " "
1413                      FINAL_EXPR@67..75
1414                        KW_FINAL@67..72 "final"
1415                        WHITESPACE@72..73 " "
1416                        BLOCK@73..75
1417                          L_BRACE@73..74 "{"
1418                          R_BRACE@74..75 "}"
1419                      SEMICOLON@75..76 ";"
1420                    WHITESPACE@76..77 " "
1421                    R_BRACE@77..78 "}"
1422                WHITESPACE@78..79 " "
1423                R_BRACE@79..80 "}"
1424        "#]]);
1425    }
1426
1427    // =========================================================================
1428    // Return Types with Visibility (3c)
1429    // =========================================================================
1430
1431    #[test]
1432    fn parse_function_return_tuple() {
1433        check_file(
1434            "program test.aleo { fn foo() -> (public u32, private field) { return (1u32, 2field); } }",
1435            expect![[r#"
1436                ROOT@0..88
1437                  PROGRAM_DECL@0..88
1438                    KW_PROGRAM@0..7 "program"
1439                    WHITESPACE@7..8 " "
1440                    IDENT@8..12 "test"
1441                    DOT@12..13 "."
1442                    KW_ALEO@13..17 "aleo"
1443                    WHITESPACE@17..18 " "
1444                    L_BRACE@18..19 "{"
1445                    FUNCTION_DEF@19..86
1446                      WHITESPACE@19..20 " "
1447                      KW_FN@20..22 "fn"
1448                      WHITESPACE@22..23 " "
1449                      IDENT@23..26 "foo"
1450                      PARAM_LIST@26..28
1451                        L_PAREN@26..27 "("
1452                        R_PAREN@27..28 ")"
1453                      WHITESPACE@28..29 " "
1454                      ARROW@29..31 "->"
1455                      WHITESPACE@31..32 " "
1456                      RETURN_TYPE@32..59
1457                        L_PAREN@32..33 "("
1458                        KW_PUBLIC@33..39 "public"
1459                        WHITESPACE@39..40 " "
1460                        TYPE_PRIMITIVE@40..43
1461                          KW_U32@40..43 "u32"
1462                        COMMA@43..44 ","
1463                        WHITESPACE@44..45 " "
1464                        KW_PRIVATE@45..52 "private"
1465                        WHITESPACE@52..53 " "
1466                        TYPE_PRIMITIVE@53..58
1467                          KW_FIELD@53..58 "field"
1468                        R_PAREN@58..59 ")"
1469                      BLOCK@59..86
1470                        WHITESPACE@59..60 " "
1471                        L_BRACE@60..61 "{"
1472                        WHITESPACE@61..62 " "
1473                        RETURN_STMT@62..84
1474                          KW_RETURN@62..68 "return"
1475                          WHITESPACE@68..69 " "
1476                          TUPLE_EXPR@69..83
1477                            L_PAREN@69..70 "("
1478                            LITERAL_INT@70..74
1479                              INTEGER@70..74 "1u32"
1480                            COMMA@74..75 ","
1481                            WHITESPACE@75..76 " "
1482                            LITERAL_FIELD@76..82
1483                              INTEGER@76..82 "2field"
1484                            R_PAREN@82..83 ")"
1485                          SEMICOLON@83..84 ";"
1486                        WHITESPACE@84..85 " "
1487                        R_BRACE@85..86 "}"
1488                    WHITESPACE@86..87 " "
1489                    R_BRACE@87..88 "}"
1490            "#]],
1491        );
1492    }
1493
1494    // =========================================================================
1495    // Multiple Annotations (3d)
1496    // =========================================================================
1497
1498    #[test]
1499    fn parse_function_multi_annotation() {
1500        check_file("program test.aleo { @test @foo(k = \"v\") fn bar() { } }", expect![[r#"
1501            ROOT@0..54
1502              PROGRAM_DECL@0..54
1503                KW_PROGRAM@0..7 "program"
1504                WHITESPACE@7..8 " "
1505                IDENT@8..12 "test"
1506                DOT@12..13 "."
1507                KW_ALEO@13..17 "aleo"
1508                WHITESPACE@17..18 " "
1509                L_BRACE@18..19 "{"
1510                FUNCTION_DEF@19..52
1511                  ANNOTATION@19..26
1512                    WHITESPACE@19..20 " "
1513                    AT@20..21 "@"
1514                    IDENT@21..25 "test"
1515                    WHITESPACE@25..26 " "
1516                  ANNOTATION@26..39
1517                    AT@26..27 "@"
1518                    IDENT@27..30 "foo"
1519                    L_PAREN@30..31 "("
1520                    ANNOTATION_PAIR@31..38
1521                      IDENT@31..32 "k"
1522                      WHITESPACE@32..33 " "
1523                      EQ@33..34 "="
1524                      WHITESPACE@34..35 " "
1525                      STRING@35..38 "\"v\""
1526                    R_PAREN@38..39 ")"
1527                  WHITESPACE@39..40 " "
1528                  KW_FN@40..42 "fn"
1529                  WHITESPACE@42..43 " "
1530                  IDENT@43..46 "bar"
1531                  PARAM_LIST@46..48
1532                    L_PAREN@46..47 "("
1533                    R_PAREN@47..48 ")"
1534                  WHITESPACE@48..49 " "
1535                  BLOCK@49..52
1536                    L_BRACE@49..50 "{"
1537                    WHITESPACE@50..51 " "
1538                    R_BRACE@51..52 "}"
1539                WHITESPACE@52..53 " "
1540                R_BRACE@53..54 "}"
1541        "#]]);
1542    }
1543
1544    // =========================================================================
1545    // Storage Declaration (3e)
1546    // =========================================================================
1547
1548    #[test]
1549    fn parse_storage() {
1550        check_file("program test.aleo { storage state: u64; }", expect![[r#"
1551            ROOT@0..41
1552              PROGRAM_DECL@0..41
1553                KW_PROGRAM@0..7 "program"
1554                WHITESPACE@7..8 " "
1555                IDENT@8..12 "test"
1556                DOT@12..13 "."
1557                KW_ALEO@13..17 "aleo"
1558                WHITESPACE@17..18 " "
1559                L_BRACE@18..19 "{"
1560                STORAGE_DEF@19..39
1561                  WHITESPACE@19..20 " "
1562                  KW_STORAGE@20..27 "storage"
1563                  WHITESPACE@27..28 " "
1564                  IDENT@28..33 "state"
1565                  COLON@33..34 ":"
1566                  WHITESPACE@34..35 " "
1567                  TYPE_PRIMITIVE@35..38
1568                    KW_U64@35..38 "u64"
1569                  SEMICOLON@38..39 ";"
1570                WHITESPACE@39..40 " "
1571                R_BRACE@40..41 "}"
1572        "#]]);
1573    }
1574
1575    // =========================================================================
1576    // Script Variant Rejected (3f)
1577    // =========================================================================
1578
1579    #[test]
1580    fn parse_script_rejected() {
1581        check_file("program test.aleo { script main() { } }", expect![[r#"
1582            ROOT@0..39
1583              PROGRAM_DECL@0..39
1584                KW_PROGRAM@0..7 "program"
1585                WHITESPACE@7..8 " "
1586                IDENT@8..12 "test"
1587                DOT@12..13 "."
1588                KW_ALEO@13..17 "aleo"
1589                WHITESPACE@17..18 " "
1590                L_BRACE@18..19 "{"
1591                WHITESPACE@19..20 " "
1592                KW_SCRIPT@20..26 "script"
1593                ERROR@26..37
1594                  WHITESPACE@26..27 " "
1595                  IDENT@27..31 "main"
1596                  L_PAREN@31..32 "("
1597                  R_PAREN@32..33 ")"
1598                  WHITESPACE@33..34 " "
1599                  L_BRACE@34..35 "{"
1600                  WHITESPACE@35..36 " "
1601                  R_BRACE@36..37 "}"
1602                WHITESPACE@37..38 " "
1603                R_BRACE@38..39 "}"
1604        "#]]);
1605    }
1606
1607    // =========================================================================
1608    // Program with Nested Items (3g)
1609    // =========================================================================
1610
1611    #[test]
1612    fn parse_program_with_items() {
1613        check_file("program test.aleo { struct Foo { x: u32, } fn bar() -> u32 { return 1u32; } }", expect![[r#"
1614            ROOT@0..77
1615              PROGRAM_DECL@0..77
1616                KW_PROGRAM@0..7 "program"
1617                WHITESPACE@7..8 " "
1618                IDENT@8..12 "test"
1619                DOT@12..13 "."
1620                KW_ALEO@13..17 "aleo"
1621                WHITESPACE@17..18 " "
1622                L_BRACE@18..19 "{"
1623                STRUCT_DEF@19..42
1624                  WHITESPACE@19..20 " "
1625                  KW_STRUCT@20..26 "struct"
1626                  WHITESPACE@26..27 " "
1627                  IDENT@27..30 "Foo"
1628                  WHITESPACE@30..31 " "
1629                  L_BRACE@31..32 "{"
1630                  WHITESPACE@32..33 " "
1631                  STRUCT_MEMBER@33..39
1632                    IDENT@33..34 "x"
1633                    COLON@34..35 ":"
1634                    WHITESPACE@35..36 " "
1635                    TYPE_PRIMITIVE@36..39
1636                      KW_U32@36..39 "u32"
1637                  COMMA@39..40 ","
1638                  WHITESPACE@40..41 " "
1639                  R_BRACE@41..42 "}"
1640                FUNCTION_DEF@42..75
1641                  WHITESPACE@42..43 " "
1642                  KW_FN@43..45 "fn"
1643                  WHITESPACE@45..46 " "
1644                  IDENT@46..49 "bar"
1645                  PARAM_LIST@49..51
1646                    L_PAREN@49..50 "("
1647                    R_PAREN@50..51 ")"
1648                  WHITESPACE@51..52 " "
1649                  ARROW@52..54 "->"
1650                  WHITESPACE@54..55 " "
1651                  TYPE_PRIMITIVE@55..58
1652                    KW_U32@55..58 "u32"
1653                  BLOCK@58..75
1654                    WHITESPACE@58..59 " "
1655                    L_BRACE@59..60 "{"
1656                    WHITESPACE@60..61 " "
1657                    RETURN_STMT@61..73
1658                      KW_RETURN@61..67 "return"
1659                      WHITESPACE@67..68 " "
1660                      LITERAL_INT@68..72
1661                        INTEGER@68..72 "1u32"
1662                      SEMICOLON@72..73 ";"
1663                    WHITESPACE@73..74 " "
1664                    R_BRACE@74..75 "}"
1665                WHITESPACE@75..76 " "
1666                R_BRACE@76..77 "}"
1667        "#]]);
1668    }
1669
1670    // =========================================================================
1671    // Import Error: Invalid Network (3h)
1672    // =========================================================================
1673
1674    #[test]
1675    fn parse_import_invalid_network() {
1676        // `foo.bar` — the parser consumes the invalid network identifier for
1677        // recovery; the AST converter emits the semantic error. The CST parse
1678        // itself should succeed without errors (the parser is lenient here).
1679        check_file("import foo.bar;", expect![[r#"
1680            ROOT@0..15
1681              IMPORT@0..15
1682                KW_IMPORT@0..6 "import"
1683                WHITESPACE@6..7 " "
1684                IDENT@7..10 "foo"
1685                DOT@10..11 "."
1686                IDENT@11..14 "bar"
1687                SEMICOLON@14..15 ";"
1688        "#]]);
1689    }
1690
1691    // =========================================================================
1692    // Annotation Error Cases (3i)
1693    // =========================================================================
1694
1695    #[test]
1696    fn parse_annotation_space_after_at() {
1697        // Space between @ and name should produce an error.
1698        let (tokens, _) = lex("program test.aleo { @ test fn foo() { } }");
1699        let mut parser = Parser::new("program test.aleo { @ test fn foo() { } }", &tokens);
1700        let root = parser.start();
1701        parser.parse_file_items();
1702        root.complete(&mut parser, ROOT);
1703        let parse: Parse = parser.finish(vec![]);
1704        assert!(!parse.errors().is_empty(), "expected error for space after @, got none");
1705    }
1706}