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    /// Recovery set for return type parsing.
34    const RETURN_TYPE_RECOVERY: &'static [SyntaxKind] = &[COMMA, R_PAREN, L_BRACE];
35
36    /// Parse a complete file.
37    ///
38    /// A file may contain one or more program sections, each consisting of
39    /// optional imports followed by a `program` declaration. Multi-program
40    /// files appear in Leo test suites where programs are separated by
41    /// `// --- Next Program --- //` comments (the comment itself is trivia
42    /// and doesn't affect parsing).
43    ///
44    /// Module-level items (`const`, `struct`, `inline`) are also accepted
45    /// at the top level to support multi-section test files that combine
46    /// program declarations with module content separated by
47    /// `// --- Next Module: path --- //` comments.
48    pub fn parse_file_items(&mut self) {
49        loop {
50            self.skip_trivia();
51            if self.at_eof() {
52                break;
53            }
54
55            match self.current() {
56                KW_IMPORT => {
57                    self.parse_import();
58                }
59                KW_PROGRAM => {
60                    self.parse_program_decl();
61                }
62                // Module-level items at top level (for module files and
63                // multi-section test files with `// --- Next Module:` separators).
64                KW_CONST | KW_STRUCT | KW_INLINE | AT => {
65                    if self.parse_module_item().is_none() {
66                        self.error_and_bump("expected module item");
67                    }
68                }
69                _ => {
70                    self.error_and_bump("expected `import`, `program`, or module item at top level");
71                }
72            }
73        }
74    }
75
76    /// Parse module-level items.
77    ///
78    /// Module files contain only `const`, `struct`, and `inline` declarations
79    /// (with optional annotations). No `import` or `program` blocks.
80    pub fn parse_module_items(&mut self) {
81        loop {
82            self.skip_trivia();
83            if self.at_eof() {
84                break;
85            }
86
87            if self.parse_module_item().is_none() {
88                self.error_and_bump("expected `const`, `struct`, or `inline` in module");
89            }
90        }
91    }
92
93    /// Parse a single module-level item: `const`, `struct`, or `inline fn`.
94    fn parse_module_item(&mut self) -> Option<CompletedMarker> {
95        // Handle annotations
96        while self.at(AT) {
97            self.parse_annotation();
98        }
99
100        match self.current() {
101            KW_CONST => self.parse_global_const(),
102            KW_STRUCT => self.parse_struct_def(),
103            KW_INLINE => self.parse_function_or_constructor(),
104            _ => None,
105        }
106    }
107
108    /// Parse an import declaration: `import program.aleo;`
109    fn parse_import(&mut self) -> Option<CompletedMarker> {
110        let m = self.start();
111        self.bump_any(); // import
112
113        // Parse program ID: name.aleo
114        self.parse_program_id();
115
116        self.expect(SEMICOLON);
117        Some(m.complete(self, IMPORT))
118    }
119
120    /// Parse a program declaration: `program name.aleo { ... }`
121    fn parse_program_decl(&mut self) -> Option<CompletedMarker> {
122        let m = self.start();
123        self.bump_any(); // program
124
125        // Parse program ID: name.aleo
126        self.parse_program_id();
127
128        self.expect(L_BRACE);
129
130        // Parse program items
131        while !self.at(R_BRACE) && !self.at_eof() {
132            if self.parse_program_item().is_none() {
133                // Error recovery
134                self.error_recover("expected program item", ITEM_RECOVERY);
135            }
136        }
137
138        self.expect(R_BRACE);
139        Some(m.complete(self, PROGRAM_DECL))
140    }
141
142    /// Parse a program ID: `name.aleo`
143    fn parse_program_id(&mut self) {
144        self.skip_trivia();
145        if self.at(IDENT) {
146            self.bump_any(); // name
147            self.expect(DOT);
148            self.expect(KW_ALEO);
149        } else {
150            self.error("expected program name".to_string());
151        }
152    }
153
154    /// Parse a single program item (struct, record, mapping, function, etc.)
155    fn parse_program_item(&mut self) -> Option<CompletedMarker> {
156        // Note: Don't skip trivia here - let item parsers include leading whitespace
157
158        // Check for annotations first
159        let has_annotations = self.at(AT);
160        if has_annotations {
161            // Parse annotations
162            while self.at(AT) {
163                self.parse_annotation();
164            }
165        }
166
167        // Parse the item
168        match self.current() {
169            KW_STRUCT => self.parse_struct_def(),
170            KW_RECORD => self.parse_record_def(),
171            KW_MAPPING => self.parse_mapping_def(),
172            KW_STORAGE => self.parse_storage_def(),
173            KW_CONST => self.parse_global_const(),
174            KW_FUNCTION | KW_TRANSITION | KW_INLINE | KW_SCRIPT | KW_ASYNC => self.parse_function_or_constructor(),
175            _ => {
176                self.error(format!("expected program item, found {:?}", self.current()));
177                None
178            }
179        }
180    }
181
182    /// Parse an annotation: `@program` or `@foo(args)`
183    fn parse_annotation(&mut self) {
184        let m = self.start();
185        self.bump_any(); // @
186
187        self.skip_trivia();
188        // Accept identifiers and keywords as annotation names
189        // (e.g. `@program`, `@test`, `@noupgrade`).
190        if self.at(IDENT) || self.current().is_keyword() {
191            self.bump_any();
192        } else {
193            self.error("expected annotation name".to_string());
194        }
195
196        // Optional parenthesized arguments.
197        // Consume all tokens between parens, supporting arbitrary content
198        // like `@checksum(mapping = "...", key = "...")`.
199        if self.eat(L_PAREN) {
200            let mut depth: u32 = 1;
201            while !self.at_eof() && depth > 0 {
202                if self.at(L_PAREN) {
203                    depth += 1;
204                } else if self.at(R_PAREN) {
205                    depth -= 1;
206                    if depth == 0 {
207                        break;
208                    }
209                }
210                self.bump_any();
211            }
212            self.expect(R_PAREN);
213        }
214
215        m.complete(self, ANNOTATION);
216    }
217
218    /// Parse a struct definition: `struct Name { field: Type, ... }`
219    fn parse_struct_def(&mut self) -> Option<CompletedMarker> {
220        let m = self.start();
221        self.bump_any(); // struct
222
223        // Name
224        self.skip_trivia();
225        if self.at(IDENT) {
226            self.bump_any();
227        } else {
228            self.error("expected struct name".to_string());
229        }
230
231        // Optional const generic parameters: ::[N: u32]
232        if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
233            self.bump_any(); // ::
234            self.parse_const_param_list();
235        }
236
237        // Fields
238        self.expect(L_BRACE);
239        self.parse_struct_fields();
240        self.expect(R_BRACE);
241
242        Some(m.complete(self, STRUCT_DEF))
243    }
244
245    /// Parse a record definition: `record Name { field: Type, ... }`
246    fn parse_record_def(&mut self) -> Option<CompletedMarker> {
247        let m = self.start();
248        self.bump_any(); // record
249
250        // Name
251        self.skip_trivia();
252        if self.at(IDENT) {
253            self.bump_any();
254        } else {
255            self.error("expected record name".to_string());
256        }
257
258        // Optional const generic parameters: ::[N: u32]
259        if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
260            self.bump_any(); // ::
261            self.parse_const_param_list();
262        }
263
264        // Fields
265        self.expect(L_BRACE);
266        self.parse_struct_fields();
267        self.expect(R_BRACE);
268
269        Some(m.complete(self, RECORD_DEF))
270    }
271
272    /// Parse struct/record fields.
273    fn parse_struct_fields(&mut self) {
274        while !self.at(R_BRACE) && !self.at_eof() {
275            // Start marker first so leading whitespace is inside the field node
276            let m = self.start();
277
278            // Check for visibility modifier
279            let _ = self.eat(KW_PUBLIC) || self.eat(KW_PRIVATE) || self.eat(KW_CONSTANT);
280
281            // Field name
282            self.skip_trivia();
283            if self.at(IDENT) {
284                self.bump_any();
285            } else {
286                m.abandon(self);
287                // Try to recover to next field or end of struct
288                if !self.at(R_BRACE) {
289                    self.error_recover("expected field name", Self::FIELD_RECOVERY);
290                }
291                // If we recovered to a comma, consume it and continue
292                if self.eat(COMMA) {
293                    continue;
294                }
295                break;
296            }
297
298            // Colon and type
299            self.expect(COLON);
300            if self.parse_type().is_none() {
301                self.error_recover("expected type", Self::FIELD_RECOVERY);
302            }
303
304            m.complete(self, STRUCT_MEMBER);
305
306            // Optional comma
307            if !self.eat(COMMA) {
308                break;
309            }
310        }
311    }
312
313    /// Parse a mapping definition: `mapping name: Key => Value;`
314    fn parse_mapping_def(&mut self) -> Option<CompletedMarker> {
315        let m = self.start();
316        self.bump_any(); // mapping
317
318        // Name
319        self.skip_trivia();
320        if self.at(IDENT) {
321            self.bump_any();
322        } else {
323            self.error("expected mapping name".to_string());
324        }
325
326        // Key and value types
327        self.expect(COLON);
328        if self.parse_type().is_none() {
329            self.error_recover("expected key type", TYPE_RECOVERY);
330        }
331        self.expect(FAT_ARROW);
332        if self.parse_type().is_none() {
333            self.error_recover("expected value type", TYPE_RECOVERY);
334        }
335
336        self.expect(SEMICOLON);
337        Some(m.complete(self, MAPPING_DEF))
338    }
339
340    /// Parse a storage definition: `storage name: Type;`
341    fn parse_storage_def(&mut self) -> Option<CompletedMarker> {
342        let m = self.start();
343        self.bump_any(); // storage
344
345        // Name
346        self.skip_trivia();
347        if self.at(IDENT) {
348            self.bump_any();
349        } else {
350            self.error("expected storage name".to_string());
351        }
352
353        // Type
354        self.expect(COLON);
355        if self.parse_type().is_none() {
356            self.error_recover("expected type", TYPE_RECOVERY);
357        }
358
359        self.expect(SEMICOLON);
360        Some(m.complete(self, STORAGE_DEF))
361    }
362
363    /// Parse a global constant: `const NAME: Type = expr;`
364    fn parse_global_const(&mut self) -> Option<CompletedMarker> {
365        let m = self.start();
366        self.bump_any(); // const
367
368        // Name
369        self.skip_trivia();
370        if self.at(IDENT) {
371            self.bump_any();
372        } else {
373            self.error("expected constant name".to_string());
374        }
375
376        // Type
377        self.expect(COLON);
378        if self.parse_type().is_none() {
379            self.error_recover("expected type", TYPE_RECOVERY);
380        }
381
382        // Value
383        self.expect(EQ);
384        if self.parse_expr().is_none() {
385            self.error_recover("expected expression", EXPR_RECOVERY);
386        }
387
388        self.expect(SEMICOLON);
389        Some(m.complete(self, GLOBAL_CONST))
390    }
391
392    /// Parse a function definition.
393    /// Parse function, transition, inline, or async variants
394    fn parse_function_or_constructor(&mut self) -> Option<CompletedMarker> {
395        let m = self.start();
396
397        // Optional async keyword
398        self.eat(KW_ASYNC);
399
400        // Dispatch based on what follows
401        match self.current() {
402            KW_FUNCTION | KW_TRANSITION | KW_INLINE | KW_SCRIPT => {
403                self.parse_function_body();
404                Some(m.complete(self, FUNCTION_DEF))
405            }
406            KW_CONSTRUCTOR => {
407                self.parse_constructor_body();
408                Some(m.complete(self, CONSTRUCTOR_DEF))
409            }
410            _ => {
411                self.error("expected function, transition, inline, or constructor".to_string());
412                m.abandon(self);
413                None
414            }
415        }
416    }
417
418    /// Parse function body (after async/function keyword marker started)
419    fn parse_function_body(&mut self) {
420        // Function keyword (function, transition, or inline)
421        if !self.eat(KW_FUNCTION) && !self.eat(KW_TRANSITION) && !self.eat(KW_INLINE) && !self.eat(KW_SCRIPT) {
422            self.error("expected function, transition, inline, or script".to_string());
423        }
424
425        // Function name
426        self.skip_trivia();
427        if self.at(IDENT) {
428            self.bump_any();
429        } else {
430            self.error("expected function name".to_string());
431        }
432
433        // Optional const generic parameters: ::[N: u32, M: u32]
434        if self.at(COLON_COLON) && self.nth(1) == L_BRACKET {
435            self.bump_any(); // ::
436            self.parse_const_param_list();
437        }
438
439        // Parameters
440        self.parse_param_list();
441
442        // Return type: `-> [visibility] Type` or `-> (vis Type, vis Type)`
443        if self.eat(ARROW) {
444            self.parse_return_type();
445        }
446
447        // Body
448        self.parse_block();
449    }
450
451    /// Parse a function return type.
452    ///
453    /// Handles both single return types with optional visibility modifiers
454    /// (`-> public u32`) and tuple return types
455    /// (`-> (public u32, private u32)`).
456    fn parse_return_type(&mut self) {
457        self.skip_trivia();
458        if self.at(L_PAREN) {
459            // Tuple return type: (vis Type, vis Type, ...)
460            let m = self.start();
461            self.bump_any(); // (
462            if !self.at(R_PAREN) {
463                // First output
464                let _ = self.eat(KW_PUBLIC) || self.eat(KW_PRIVATE) || self.eat(KW_CONSTANT);
465                if self.parse_type().is_none() {
466                    self.error_recover("expected return type", Self::RETURN_TYPE_RECOVERY);
467                }
468                while self.eat(COMMA) {
469                    if self.at(R_PAREN) {
470                        break;
471                    }
472                    let _ = self.eat(KW_PUBLIC) || self.eat(KW_PRIVATE) || self.eat(KW_CONSTANT);
473                    if self.parse_type().is_none() {
474                        self.error_recover("expected return type", Self::RETURN_TYPE_RECOVERY);
475                    }
476                }
477            }
478            self.expect(R_PAREN);
479            m.complete(self, RETURN_TYPE);
480        } else {
481            // Single return type with optional visibility
482            let _ = self.eat(KW_PUBLIC) || self.eat(KW_PRIVATE) || self.eat(KW_CONSTANT);
483            if self.parse_type().is_none() {
484                self.error_recover("expected return type", Self::RETURN_TYPE_RECOVERY);
485            }
486        }
487    }
488
489    /// Parse constructor body: `constructor() { }`
490    fn parse_constructor_body(&mut self) {
491        self.bump_any(); // constructor
492
493        // Parameters
494        self.parse_param_list();
495
496        // Body
497        self.parse_block();
498    }
499
500    /// Parse a parameter list: `(a: Type, b: Type)`
501    fn parse_param_list(&mut self) {
502        let m = self.start();
503        self.expect(L_PAREN);
504
505        while !self.at(R_PAREN) && !self.at_eof() {
506            self.parse_param();
507            if !self.eat(COMMA) {
508                break;
509            }
510        }
511
512        self.expect(R_PAREN);
513        m.complete(self, PARAM_LIST);
514    }
515
516    /// Parse a single parameter: `[visibility] name: Type`
517    fn parse_param(&mut self) {
518        let m = self.start();
519        self.skip_trivia();
520
521        // Optional visibility
522        let _ = self.eat(KW_PUBLIC) || self.eat(KW_PRIVATE) || self.eat(KW_CONSTANT);
523
524        // Name
525        self.skip_trivia();
526        if self.at(IDENT) {
527            self.bump_any();
528        } else {
529            self.error("expected parameter name".to_string());
530        }
531
532        // Type
533        self.expect(COLON);
534        if self.parse_type().is_none() {
535            self.error_recover("expected parameter type", PARAM_RECOVERY);
536        }
537
538        m.complete(self, PARAM);
539    }
540}
541
542#[cfg(test)]
543mod tests {
544    use super::*;
545    use crate::{lexer::lex, parser::Parse};
546    use expect_test::{Expect, expect};
547
548    fn check_file(input: &str, expect: Expect) {
549        let (tokens, _) = lex(input);
550        let mut parser = Parser::new(input, &tokens);
551        let root = parser.start();
552        parser.parse_file_items();
553        root.complete(&mut parser, ROOT);
554        let parse: Parse = parser.finish();
555        let output = format!("{:#?}", parse.syntax());
556        expect.assert_eq(&output);
557    }
558
559    fn check_file_no_errors(input: &str) {
560        let (tokens, _) = lex(input);
561        let mut parser = Parser::new(input, &tokens);
562        let root = parser.start();
563        parser.parse_file_items();
564        root.complete(&mut parser, ROOT);
565        let parse: Parse = parser.finish();
566        if !parse.errors().is_empty() {
567            for err in parse.errors() {
568                eprintln!("error at {:?}: {}", err.range, err.message);
569            }
570            eprintln!("tree:\n{:#?}", parse.syntax());
571            panic!("parse had {} error(s)", parse.errors().len());
572        }
573    }
574
575    // =========================================================================
576    // Imports
577    // =========================================================================
578
579    #[test]
580    fn parse_import() {
581        check_file("import credits.aleo;", expect![[r#"
582                ROOT@0..20
583                  IMPORT@0..20
584                    KW_IMPORT@0..6 "import"
585                    WHITESPACE@6..7 " "
586                    IDENT@7..14 "credits"
587                    DOT@14..15 "."
588                    KW_ALEO@15..19 "aleo"
589                    SEMICOLON@19..20 ";"
590            "#]]);
591    }
592
593    // =========================================================================
594    // Program Declaration
595    // =========================================================================
596
597    #[test]
598    fn parse_program_empty() {
599        check_file("program test.aleo { }", expect![[r#"
600                ROOT@0..21
601                  PROGRAM_DECL@0..21
602                    KW_PROGRAM@0..7 "program"
603                    WHITESPACE@7..8 " "
604                    IDENT@8..12 "test"
605                    DOT@12..13 "."
606                    KW_ALEO@13..17 "aleo"
607                    WHITESPACE@17..18 " "
608                    L_BRACE@18..19 "{"
609                    WHITESPACE@19..20 " "
610                    R_BRACE@20..21 "}"
611            "#]]);
612    }
613
614    // =========================================================================
615    // Structs
616    // =========================================================================
617
618    #[test]
619    fn parse_struct() {
620        check_file("program test.aleo { struct Point { x: u32, y: u32 } }", expect![[r#"
621                ROOT@0..53
622                  PROGRAM_DECL@0..53
623                    KW_PROGRAM@0..7 "program"
624                    WHITESPACE@7..8 " "
625                    IDENT@8..12 "test"
626                    DOT@12..13 "."
627                    KW_ALEO@13..17 "aleo"
628                    WHITESPACE@17..18 " "
629                    L_BRACE@18..19 "{"
630                    STRUCT_DEF@19..51
631                      WHITESPACE@19..20 " "
632                      KW_STRUCT@20..26 "struct"
633                      WHITESPACE@26..27 " "
634                      IDENT@27..32 "Point"
635                      WHITESPACE@32..33 " "
636                      L_BRACE@33..34 "{"
637                      STRUCT_MEMBER@34..41
638                        WHITESPACE@34..35 " "
639                        IDENT@35..36 "x"
640                        COLON@36..37 ":"
641                        WHITESPACE@37..38 " "
642                        TYPE_PATH@38..41
643                          KW_U32@38..41 "u32"
644                      COMMA@41..42 ","
645                      STRUCT_MEMBER@42..49
646                        WHITESPACE@42..43 " "
647                        IDENT@43..44 "y"
648                        COLON@44..45 ":"
649                        WHITESPACE@45..46 " "
650                        TYPE_PATH@46..49
651                          KW_U32@46..49 "u32"
652                      WHITESPACE@49..50 " "
653                      R_BRACE@50..51 "}"
654                    WHITESPACE@51..52 " "
655                    R_BRACE@52..53 "}"
656            "#]]);
657    }
658
659    // =========================================================================
660    // Records
661    // =========================================================================
662
663    #[test]
664    fn parse_record() {
665        check_file("program test.aleo { record Token { owner: address, amount: u64 } }", expect![[r#"
666                ROOT@0..66
667                  PROGRAM_DECL@0..66
668                    KW_PROGRAM@0..7 "program"
669                    WHITESPACE@7..8 " "
670                    IDENT@8..12 "test"
671                    DOT@12..13 "."
672                    KW_ALEO@13..17 "aleo"
673                    WHITESPACE@17..18 " "
674                    L_BRACE@18..19 "{"
675                    RECORD_DEF@19..64
676                      WHITESPACE@19..20 " "
677                      KW_RECORD@20..26 "record"
678                      WHITESPACE@26..27 " "
679                      IDENT@27..32 "Token"
680                      WHITESPACE@32..33 " "
681                      L_BRACE@33..34 "{"
682                      STRUCT_MEMBER@34..49
683                        WHITESPACE@34..35 " "
684                        IDENT@35..40 "owner"
685                        COLON@40..41 ":"
686                        WHITESPACE@41..42 " "
687                        TYPE_PATH@42..49
688                          KW_ADDRESS@42..49 "address"
689                      COMMA@49..50 ","
690                      STRUCT_MEMBER@50..62
691                        WHITESPACE@50..51 " "
692                        IDENT@51..57 "amount"
693                        COLON@57..58 ":"
694                        WHITESPACE@58..59 " "
695                        TYPE_PATH@59..62
696                          KW_U64@59..62 "u64"
697                      WHITESPACE@62..63 " "
698                      R_BRACE@63..64 "}"
699                    WHITESPACE@64..65 " "
700                    R_BRACE@65..66 "}"
701            "#]]);
702    }
703
704    // =========================================================================
705    // Mappings
706    // =========================================================================
707
708    #[test]
709    fn parse_mapping() {
710        check_file("program test.aleo { mapping balances: address => u64; }", expect![[r#"
711                ROOT@0..55
712                  PROGRAM_DECL@0..55
713                    KW_PROGRAM@0..7 "program"
714                    WHITESPACE@7..8 " "
715                    IDENT@8..12 "test"
716                    DOT@12..13 "."
717                    KW_ALEO@13..17 "aleo"
718                    WHITESPACE@17..18 " "
719                    L_BRACE@18..19 "{"
720                    MAPPING_DEF@19..53
721                      WHITESPACE@19..20 " "
722                      KW_MAPPING@20..27 "mapping"
723                      WHITESPACE@27..28 " "
724                      IDENT@28..36 "balances"
725                      COLON@36..37 ":"
726                      WHITESPACE@37..38 " "
727                      TYPE_PATH@38..45
728                        KW_ADDRESS@38..45 "address"
729                      WHITESPACE@45..46 " "
730                      FAT_ARROW@46..48 "=>"
731                      WHITESPACE@48..49 " "
732                      TYPE_PATH@49..52
733                        KW_U64@49..52 "u64"
734                      SEMICOLON@52..53 ";"
735                    WHITESPACE@53..54 " "
736                    R_BRACE@54..55 "}"
737            "#]]);
738    }
739
740    // =========================================================================
741    // Functions
742    // =========================================================================
743
744    #[test]
745    fn parse_function() {
746        check_file("program test.aleo { function add(a: u32, b: u32) -> u32 { return a + b; } }", expect![[r#"
747                ROOT@0..75
748                  PROGRAM_DECL@0..75
749                    KW_PROGRAM@0..7 "program"
750                    WHITESPACE@7..8 " "
751                    IDENT@8..12 "test"
752                    DOT@12..13 "."
753                    KW_ALEO@13..17 "aleo"
754                    WHITESPACE@17..18 " "
755                    L_BRACE@18..19 "{"
756                    FUNCTION_DEF@19..73
757                      WHITESPACE@19..20 " "
758                      KW_FUNCTION@20..28 "function"
759                      WHITESPACE@28..29 " "
760                      IDENT@29..32 "add"
761                      PARAM_LIST@32..48
762                        L_PAREN@32..33 "("
763                        PARAM@33..39
764                          IDENT@33..34 "a"
765                          COLON@34..35 ":"
766                          WHITESPACE@35..36 " "
767                          TYPE_PATH@36..39
768                            KW_U32@36..39 "u32"
769                        COMMA@39..40 ","
770                        PARAM@40..47
771                          WHITESPACE@40..41 " "
772                          IDENT@41..42 "b"
773                          COLON@42..43 ":"
774                          WHITESPACE@43..44 " "
775                          TYPE_PATH@44..47
776                            KW_U32@44..47 "u32"
777                        R_PAREN@47..48 ")"
778                      WHITESPACE@48..49 " "
779                      ARROW@49..51 "->"
780                      WHITESPACE@51..52 " "
781                      TYPE_PATH@52..55
782                        KW_U32@52..55 "u32"
783                      BLOCK@55..73
784                        WHITESPACE@55..56 " "
785                        L_BRACE@56..57 "{"
786                        WHITESPACE@57..58 " "
787                        RETURN_STMT@58..71
788                          KW_RETURN@58..64 "return"
789                          WHITESPACE@64..65 " "
790                          BINARY_EXPR@65..70
791                            PATH_EXPR@65..67
792                              IDENT@65..66 "a"
793                              WHITESPACE@66..67 " "
794                            PLUS@67..68 "+"
795                            WHITESPACE@68..69 " "
796                            PATH_EXPR@69..70
797                              IDENT@69..70 "b"
798                          SEMICOLON@70..71 ";"
799                        WHITESPACE@71..72 " "
800                        R_BRACE@72..73 "}"
801                    WHITESPACE@73..74 " "
802                    R_BRACE@74..75 "}"
803            "#]]);
804    }
805
806    // =========================================================================
807    // Const Generic Parameters (Declarations)
808    // =========================================================================
809
810    #[test]
811    fn parse_function_const_generic_single() {
812        check_file_no_errors("program test.aleo { function foo::[N: u32]() {} }");
813    }
814
815    #[test]
816    fn parse_function_const_generic_multi() {
817        check_file_no_errors("program test.aleo { inline bar::[N: u32, M: u32](arr: u32) -> u32 { return 0u32; } }");
818    }
819
820    #[test]
821    fn parse_function_const_generic_empty() {
822        check_file_no_errors("program test.aleo { inline baz::[]() {} }");
823    }
824
825    #[test]
826    fn parse_async_transition_const_generic() {
827        check_file_no_errors("program test.aleo { async transition t::[N: u32]() -> Future { return async {}; } }");
828    }
829
830    #[test]
831    fn parse_struct_const_generic() {
832        check_file_no_errors("program test.aleo { struct Foo::[N: u32] { arr: u32, } }");
833    }
834
835    #[test]
836    fn parse_struct_const_generic_multi() {
837        check_file_no_errors("program test.aleo { struct Matrix::[M: u32, N: u32] { data: u32, } }");
838    }
839
840    #[test]
841    fn parse_record_const_generic() {
842        // Syntactically valid, semantically rejected later.
843        check_file_no_errors("program test.aleo { record Bar::[N: u32] { owner: address, } }");
844    }
845
846    #[test]
847    fn parse_transition() {
848        check_file("program test.aleo { transition main(public x: u32) { } }", expect![[r#"
849                ROOT@0..56
850                  PROGRAM_DECL@0..56
851                    KW_PROGRAM@0..7 "program"
852                    WHITESPACE@7..8 " "
853                    IDENT@8..12 "test"
854                    DOT@12..13 "."
855                    KW_ALEO@13..17 "aleo"
856                    WHITESPACE@17..18 " "
857                    L_BRACE@18..19 "{"
858                    FUNCTION_DEF@19..54
859                      WHITESPACE@19..20 " "
860                      KW_TRANSITION@20..30 "transition"
861                      WHITESPACE@30..31 " "
862                      IDENT@31..35 "main"
863                      PARAM_LIST@35..50
864                        L_PAREN@35..36 "("
865                        PARAM@36..49
866                          KW_PUBLIC@36..42 "public"
867                          WHITESPACE@42..43 " "
868                          IDENT@43..44 "x"
869                          COLON@44..45 ":"
870                          WHITESPACE@45..46 " "
871                          TYPE_PATH@46..49
872                            KW_U32@46..49 "u32"
873                        R_PAREN@49..50 ")"
874                      WHITESPACE@50..51 " "
875                      BLOCK@51..54
876                        L_BRACE@51..52 "{"
877                        WHITESPACE@52..53 " "
878                        R_BRACE@53..54 "}"
879                    WHITESPACE@54..55 " "
880                    R_BRACE@55..56 "}"
881            "#]]);
882    }
883}