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