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