leo_passes/type_checking/
program.rs

1// Copyright (C) 2019-2025 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
17use super::TypeCheckingVisitor;
18use crate::{VariableSymbol, VariableType};
19
20use leo_ast::{DiGraphError, Type, *};
21use leo_errors::{Label, TypeCheckerError};
22use leo_span::{Symbol, sym};
23
24use itertools::Itertools;
25use snarkvm::prelude::{CanaryV0, MainnetV0, TestnetV0};
26use std::collections::{BTreeMap, HashMap};
27
28impl ProgramVisitor for TypeCheckingVisitor<'_> {
29    fn visit_program(&mut self, input: &Program) {
30        // Typecheck the program's stubs.
31        input.stubs.iter().for_each(|(symbol, stub)| {
32            // Check that naming and ordering is consistent.
33            if symbol != &stub.stub_id.name.name {
34                self.emit_err(TypeCheckerError::stub_name_mismatch(
35                    symbol,
36                    stub.stub_id.name,
37                    stub.stub_id.network.span,
38                ));
39            }
40            self.visit_stub(stub)
41        });
42        self.scope_state.is_stub = false;
43
44        // Typecheck the modules.
45        input.modules.values().for_each(|module| self.visit_module(module));
46
47        // Typecheck the program scopes.
48        input.program_scopes.values().for_each(|scope| self.visit_program_scope(scope));
49    }
50
51    fn visit_program_scope(&mut self, input: &ProgramScope) {
52        let program_name = input.program_id.name;
53
54        // Set the current program name.
55        self.scope_state.program_name = Some(program_name.name);
56
57        // Collect a map from record names to their spans
58        let record_info: BTreeMap<String, leo_span::Span> = input
59            .structs
60            .iter()
61            .filter(|(_, c)| c.is_record)
62            .map(|(_, r)| (r.name().to_string(), r.identifier.span))
63            .collect();
64
65        // Check if any record name is a prefix for another record name. We don't really collect all possible prefixes
66        // here but only adjacent ones. That is, if we have records `Foo`, `FooBar`, and `FooBarBaz`, we only emit
67        // errors for `Foo/FooBar` and for `FooBar/FooBarBaz` but not for `Foo/FooBarBaz`.
68        for ((prev_name, _), (curr_name, curr_span)) in record_info.iter().tuple_windows() {
69            if curr_name.starts_with(prev_name) {
70                self.state
71                    .handler
72                    .emit_err(TypeCheckerError::record_prefixed_by_other_record(curr_name, prev_name, *curr_span));
73            }
74        }
75
76        // Typecheck each const definition, and append to symbol table.
77        input.consts.iter().for_each(|(_, c)| self.visit_const(c));
78
79        // Typecheck each struct definition.
80        input.structs.iter().for_each(|(_, function)| self.visit_struct(function));
81
82        // Check that the struct dependency graph does not have any cycles.
83        if let Err(DiGraphError::CycleDetected(path)) = self.state.struct_graph.post_order() {
84            self.emit_err(TypeCheckerError::cyclic_struct_dependency(
85                path.iter().map(|p| p.iter().format("::")).collect(),
86            ));
87        }
88
89        // Typecheck each mapping definition.
90        let mut mapping_count = 0;
91        for (_, mapping) in input.mappings.iter() {
92            self.visit_mapping(mapping);
93            mapping_count += 1;
94        }
95
96        // Typecheck each storage variable definition.
97        for (_, storage_variable) in input.storage_variables.iter() {
98            self.visit_storage_variable(storage_variable);
99        }
100
101        // Check that the number of mappings does not exceed the maximum.
102        if mapping_count > self.limits.max_mappings {
103            self.emit_err(TypeCheckerError::too_many_mappings(
104                self.limits.max_mappings,
105                input.program_id.name.span + input.program_id.network.span,
106            ));
107        }
108
109        // Typecheck each function definitions.
110        let mut transition_count = 0;
111        for (_, function) in input.functions.iter() {
112            self.visit_function(function);
113            if function.variant.is_transition() {
114                transition_count += 1;
115            }
116        }
117
118        // Typecheck the constructor.
119        // Note: Constructors are required for all **new** programs once they are supported in the AVM.
120        //  However, we do not require them to exist to ensure backwards compatibility with existing programs.
121        if let Some(constructor) = &input.constructor {
122            self.visit_constructor(constructor);
123        }
124
125        // Check that the call graph does not have any cycles.
126        if let Err(DiGraphError::CycleDetected(path)) = self.state.call_graph.post_order() {
127            self.emit_err(TypeCheckerError::cyclic_function_dependency(path));
128        }
129
130        // TODO: Need similar checks for structs (all in separate PR)
131        // Check that the number of transitions does not exceed the maximum.
132        if transition_count > self.limits.max_functions {
133            self.emit_err(TypeCheckerError::too_many_transitions(
134                self.limits.max_functions,
135                input.program_id.name.span + input.program_id.network.span,
136            ));
137        }
138        // Check that each program has at least one transition function.
139        // This is a snarkvm requirement.
140        else if transition_count == 0 {
141            self.emit_err(TypeCheckerError::no_transitions(input.program_id.name.span + input.program_id.network.span));
142        }
143    }
144
145    fn visit_module(&mut self, input: &Module) {
146        let parent_module = self.scope_state.module_name.clone();
147        // Set the current program name.
148        self.scope_state.program_name = Some(input.program_name);
149        self.scope_state.module_name = input.path.clone();
150
151        // Typecheck each const definition, and append to symbol table.
152        input.consts.iter().for_each(|(_, c)| self.visit_const(c));
153
154        // Typecheck each struct definition.
155        input.structs.iter().for_each(|(_, function)| self.visit_struct(function));
156
157        for (_, function) in input.functions.iter() {
158            self.visit_function(function);
159        }
160
161        self.scope_state.module_name = parent_module;
162    }
163
164    fn visit_stub(&mut self, input: &Stub) {
165        // Set the scope state.
166        self.scope_state.program_name = Some(input.stub_id.name.name);
167        self.scope_state.is_stub = true;
168
169        // Cannot have constant declarations in stubs.
170        if !input.consts.is_empty() {
171            self.emit_err(TypeCheckerError::stubs_cannot_have_const_declarations(input.consts.first().unwrap().1.span));
172        }
173
174        // Typecheck the program's structs.
175        input.structs.iter().for_each(|(_, function)| self.visit_struct_stub(function));
176
177        // Typecheck the program's functions.
178        input.functions.iter().for_each(|(_, function)| self.visit_function_stub(function));
179    }
180
181    fn visit_struct(&mut self, input: &Composite) {
182        self.in_conditional_scope(|slf| {
183            slf.in_scope(input.id, |slf| {
184                if input.is_record && !input.const_parameters.is_empty() {
185                    slf.emit_err(TypeCheckerError::unexpected_record_const_parameters(input.span));
186                } else {
187                    input
188                        .const_parameters
189                        .iter()
190                        .for_each(|const_param| slf.insert_symbol_conditional_scope(const_param.identifier.name));
191
192                    for const_param in &input.const_parameters {
193                        slf.visit_type(const_param.type_());
194
195                        // Restrictions for const parameters
196                        if !matches!(
197                            const_param.type_(),
198                            Type::Boolean | Type::Integer(_) | Type::Address | Type::Scalar | Type::Group | Type::Field
199                        ) {
200                            slf.emit_err(TypeCheckerError::bad_const_generic_type(
201                                const_param.type_(),
202                                const_param.span(),
203                            ));
204                        }
205
206                        // Add the input to the symbol table.
207                        if let Err(err) = slf.state.symbol_table.insert_variable(
208                            slf.scope_state.program_name.unwrap(),
209                            &[const_param.identifier().name],
210                            VariableSymbol {
211                                type_: const_param.type_().clone(),
212                                span: const_param.identifier.span(),
213                                declaration: VariableType::ConstParameter,
214                            },
215                        ) {
216                            slf.state.handler.emit_err(err);
217                        }
218
219                        // Add the input to the type table.
220                        slf.state.type_table.insert(const_param.identifier().id(), const_param.type_().clone());
221                    }
222                }
223
224                input.members.iter().for_each(|member| slf.visit_type(&member.type_));
225            })
226        });
227
228        // Check for conflicting struct/record member names.
229        let mut used = HashMap::new();
230        for Member { identifier, type_, span, .. } in &input.members {
231            // Check that the member types are defined.
232            self.assert_type_is_valid(type_, *span);
233
234            if let Some(first_span) = used.get(&identifier.name) {
235                self.emit_err(if input.is_record {
236                    TypeCheckerError::duplicate_record_variable(identifier.name, *span).with_labels(vec![
237                        Label::new(format!("`{}` first declared here", identifier.name), *first_span)
238                            .with_color(leo_errors::Color::Blue),
239                        Label::new("record variable already declared", *span),
240                    ])
241                } else {
242                    TypeCheckerError::duplicate_struct_member(identifier.name, *span).with_labels(vec![
243                        Label::new(format!("`{}` first declared here", identifier.name), *first_span)
244                            .with_color(leo_errors::Color::Blue),
245                        Label::new("struct field already declared", *span),
246                    ])
247                });
248            } else {
249                used.insert(identifier.name, *span);
250            }
251        }
252
253        // For records, enforce presence of the `owner: Address` member.
254        if input.is_record {
255            let check_has_field =
256                |need, expected_ty: Type| match input.members.iter().find_map(|Member { identifier, type_, .. }| {
257                    (identifier.name == need).then_some((identifier, type_))
258                }) {
259                    Some((_, actual_ty)) if expected_ty.eq_flat_relaxed(actual_ty) => {} // All good, found + right type!
260                    Some((field, _)) => {
261                        self.emit_err(TypeCheckerError::record_var_wrong_type(field, expected_ty, input.span()));
262                    }
263                    None => {
264                        self.emit_err(TypeCheckerError::required_record_variable(need, expected_ty, input.span()));
265                    }
266                };
267            check_has_field(sym::owner, Type::Address);
268
269            for Member { identifier, type_, span, .. } in input.members.iter() {
270                if self.contains_optional_type(type_) {
271                    self.emit_err(TypeCheckerError::record_field_cannot_be_optional(identifier, type_, *span));
272                }
273            }
274        }
275        // For structs, check that there is at least one member.
276        else if input.members.is_empty() {
277            self.emit_err(TypeCheckerError::empty_struct(input.span()));
278        } else if input.members.iter().all(|m| m.type_.is_empty()) {
279            self.emit_err(TypeCheckerError::zero_size_struct(input.span()));
280        }
281
282        if !(input.is_record && self.scope_state.is_stub) {
283            for Member { mode, identifier, type_, span, .. } in input.members.iter() {
284                // Check that the member type is not a tuple.
285                if matches!(type_, Type::Tuple(_)) {
286                    self.emit_err(TypeCheckerError::composite_data_type_cannot_contain_tuple(
287                        if input.is_record { "record" } else { "struct" },
288                        identifier.span,
289                    ));
290                } else if matches!(type_, Type::Future(..)) {
291                    self.emit_err(TypeCheckerError::composite_data_type_cannot_contain_future(
292                        if input.is_record { "record" } else { "struct" },
293                        identifier.span,
294                    ));
295                }
296
297                // Ensure that there are no record members.
298                self.assert_member_is_not_record(identifier.span, input.identifier.name, type_);
299                // If the member is a struct, add it to the struct dependency graph.
300                // Note that we have already checked that each member is defined and valid.
301                let composite_path = self
302                    .scope_state
303                    .module_name
304                    .iter()
305                    .cloned()
306                    .chain(std::iter::once(input.identifier.name))
307                    .collect::<Vec<Symbol>>();
308                if let Type::Composite(struct_member_type) = type_ {
309                    // Note that since there are no cycles in the program dependency graph, there are no cycles in the struct dependency graph caused by external structs.
310                    self.state.struct_graph.add_edge(composite_path, struct_member_type.path.absolute_path().to_vec());
311                } else if let Type::Array(array_type) = type_ {
312                    // Get the base element type.
313                    let base_element_type = array_type.base_element_type();
314                    // If the base element type is a struct, then add it to the struct dependency graph.
315                    if let Type::Composite(member_type) = base_element_type {
316                        self.state.struct_graph.add_edge(composite_path, member_type.path.absolute_path().to_vec());
317                    }
318                }
319
320                // If the input is a struct, then check that the member does not have a mode.
321                if !input.is_record && !matches!(mode, Mode::None) {
322                    self.emit_err(TypeCheckerError::struct_cannot_have_member_mode(*span));
323                }
324            }
325        }
326    }
327
328    fn visit_mapping(&mut self, input: &Mapping) {
329        self.visit_type(&input.key_type);
330        self.visit_type(&input.value_type);
331
332        // Check that a mapping's key type is valid.
333        self.assert_type_is_valid(&input.key_type, input.span);
334        // Check that a mapping's key type is not a future, tuple, record, or mapping.
335        match input.key_type.clone() {
336            Type::Future(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "future", input.span)),
337            Type::Tuple(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "tuple", input.span)),
338            Type::Composite(struct_type) => {
339                if let Some(comp) = self.lookup_struct(
340                    struct_type.program.or(self.scope_state.program_name),
341                    &struct_type.path.absolute_path(),
342                ) {
343                    if comp.is_record {
344                        self.emit_err(TypeCheckerError::invalid_mapping_type("key", "record", input.span));
345                    }
346                } else {
347                    self.emit_err(TypeCheckerError::undefined_type(&input.key_type, input.span));
348                }
349            }
350            // Note that this is not possible since the parser does not currently accept mapping types.
351            Type::Mapping(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "mapping", input.span)),
352            _ => {}
353        }
354
355        if input.key_type.is_empty() {
356            self.emit_err(TypeCheckerError::invalid_mapping_type("key", "zero sized type", input.span));
357        }
358
359        if self.contains_optional_type(&input.key_type) {
360            self.emit_err(TypeCheckerError::optional_type_not_allowed_in_mapping(
361                input.key_type.clone(),
362                "key",
363                input.span,
364            ))
365        }
366
367        // Check that a mapping's value type is valid.
368        self.assert_type_is_valid(&input.value_type, input.span);
369        // Check that a mapping's value type is not a future, tuple, record or mapping.
370        match input.value_type.clone() {
371            Type::Future(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "future", input.span)),
372            Type::Tuple(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "tuple", input.span)),
373            Type::Composite(struct_type) => {
374                if let Some(comp) = self.lookup_struct(
375                    struct_type.program.or(self.scope_state.program_name),
376                    &struct_type.path.absolute_path(),
377                ) {
378                    if comp.is_record {
379                        self.emit_err(TypeCheckerError::invalid_mapping_type("value", "record", input.span));
380                    }
381                } else {
382                    self.emit_err(TypeCheckerError::undefined_type(&input.value_type, input.span));
383                }
384            }
385            // Note that this is not possible since the parser does not currently accept mapping types.
386            Type::Mapping(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "mapping", input.span)),
387            _ => {}
388        }
389
390        if input.value_type.is_empty() {
391            self.emit_err(TypeCheckerError::invalid_mapping_type("value", "zero sized type", input.span));
392        }
393
394        if self.contains_optional_type(&input.value_type) {
395            self.emit_err(TypeCheckerError::optional_type_not_allowed_in_mapping(
396                input.value_type.clone(),
397                "value",
398                input.span,
399            ))
400        }
401    }
402
403    fn visit_storage_variable(&mut self, input: &StorageVariable) {
404        self.visit_type(&input.type_);
405
406        let storage_type = if let Type::Vector(VectorType { element_type }) = &input.type_ {
407            *element_type.clone()
408        } else {
409            input.type_.clone()
410        };
411
412        self.assert_storage_type_is_valid(&storage_type, input.span);
413    }
414
415    fn visit_function(&mut self, function: &Function) {
416        // Reset the scope state.
417        self.scope_state.reset();
418
419        // Set the scope state before traversing the function.
420        self.scope_state.variant = Some(function.variant);
421
422        // Check that the function's annotations are valid.
423        for annotation in function.annotations.iter() {
424            if !matches!(annotation.identifier.name, sym::test | sym::should_fail) {
425                self.emit_err(TypeCheckerError::unknown_annotation(annotation, annotation.span))
426            }
427        }
428
429        let get = |symbol: Symbol| -> &Annotation {
430            function.annotations.iter().find(|ann| ann.identifier.name == symbol).unwrap()
431        };
432
433        let check_annotation = |symbol: Symbol, allowed_keys: &[Symbol]| -> bool {
434            let count = function.annotations.iter().filter(|ann| ann.identifier.name == symbol).count();
435            if count > 0 {
436                let annotation = get(symbol);
437                for key in annotation.map.keys() {
438                    if !allowed_keys.contains(key) {
439                        self.emit_err(TypeCheckerError::annotation_error(
440                            format_args!("Invalid key `{key}` for annotation @{symbol}"),
441                            annotation.span,
442                        ));
443                    }
444                }
445                if count > 1 {
446                    self.emit_err(TypeCheckerError::annotation_error(
447                        format_args!("Duplicate annotation @{symbol}"),
448                        annotation.span,
449                    ));
450                }
451            }
452            count > 0
453        };
454
455        let has_test = check_annotation(sym::test, &[sym::private_key]);
456        let has_should_fail = check_annotation(sym::should_fail, &[]);
457
458        if has_test && !self.state.is_test {
459            self.emit_err(TypeCheckerError::annotation_error(
460                format_args!("Test annotation @test appears outside of tests"),
461                get(sym::test).span,
462            ));
463        }
464
465        if has_should_fail && !self.state.is_test {
466            self.emit_err(TypeCheckerError::annotation_error(
467                format_args!("Test annotation @should_fail appears outside of tests"),
468                get(sym::should_fail).span,
469            ));
470        }
471
472        if has_should_fail && !has_test {
473            self.emit_err(TypeCheckerError::annotation_error(
474                format_args!("Annotation @should_fail appears without @test"),
475                get(sym::should_fail).span,
476            ));
477        }
478
479        if has_test
480            && !self.scope_state.variant.unwrap().is_script()
481            && !self.scope_state.variant.unwrap().is_transition()
482        {
483            self.emit_err(TypeCheckerError::annotation_error(
484                format_args!("Annotation @test may appear only on scripts and transitions"),
485                get(sym::test).span,
486            ));
487        }
488
489        if (has_test) && !function.input.is_empty() {
490            self.emit_err(TypeCheckerError::annotation_error(
491                "A test procedure cannot have inputs",
492                function.input[0].span,
493            ));
494        }
495
496        self.in_conditional_scope(|slf| {
497            slf.in_scope(function.id, |slf| {
498                function
499                    .const_parameters
500                    .iter()
501                    .for_each(|const_param| slf.insert_symbol_conditional_scope(const_param.identifier.name));
502
503                function.input.iter().for_each(|input| slf.insert_symbol_conditional_scope(input.identifier.name));
504
505                // Store the name of the function.
506                slf.scope_state.function = Some(function.name());
507
508                // Query helper function to type check function parameters and outputs.
509                slf.check_function_signature(function, false);
510
511                if function.variant == Variant::Function && function.input.is_empty() {
512                    slf.emit_err(TypeCheckerError::empty_function_arglist(function.span));
513                } else if function.variant == Variant::Function && function.input.iter().all(|i| i.type_.is_empty()) {
514                    slf.emit_err(TypeCheckerError::empty_function_args(function.span));
515                }
516
517                slf.visit_block(&function.block);
518
519                // If the function has a return type, then check that it has a return.
520                if function.output_type != Type::Unit && !slf.scope_state.has_return {
521                    slf.emit_err(TypeCheckerError::missing_return(function.span));
522                }
523            })
524        });
525
526        // Make sure that async transitions call finalize.
527        if self.scope_state.variant == Some(Variant::AsyncTransition)
528            && !self.scope_state.has_called_finalize
529            && !self.scope_state.already_contains_an_async_block
530        {
531            self.emit_err(TypeCheckerError::missing_async_operation_in_async_transition(function.span));
532        }
533
534        self.scope_state.reset();
535    }
536
537    fn visit_constructor(&mut self, constructor: &Constructor) {
538        // Reset the scope state.
539        self.scope_state.reset();
540        // Set the scope state before traversing the constructor.
541        self.scope_state.function = Some(sym::constructor);
542        // Note: We set the variant to `AsyncFunction` since constructors have similar semantics.
543        self.scope_state.variant = Some(Variant::AsyncFunction);
544        self.scope_state.is_constructor = true;
545
546        // Get the upgrade variant.
547        // Note, `get_upgrade_variant` will return an error if the constructor is not well-formed.
548        let result = match self.state.network {
549            NetworkName::CanaryV0 => constructor.get_upgrade_variant::<CanaryV0>(),
550            NetworkName::TestnetV0 => constructor.get_upgrade_variant::<TestnetV0>(),
551            NetworkName::MainnetV0 => constructor.get_upgrade_variant::<MainnetV0>(),
552        };
553        let upgrade_variant = match result {
554            Ok(upgrade_variant) => upgrade_variant,
555            Err(e) => {
556                self.emit_err(TypeCheckerError::custom(e, constructor.span));
557                return;
558            }
559        };
560
561        // Validate the number of statements.
562        match (&upgrade_variant, constructor.block.statements.is_empty()) {
563            (UpgradeVariant::Custom, true) => {
564                self.emit_err(TypeCheckerError::custom("A 'custom' constructor cannot be empty", constructor.span));
565            }
566            (UpgradeVariant::NoUpgrade | UpgradeVariant::Admin { .. } | UpgradeVariant::Checksum { .. }, false) => {
567                self.emit_err(TypeCheckerError::custom("A 'noupgrade', 'admin', or 'checksum' constructor must be empty. The Leo compiler will insert the appropriate code.", constructor.span));
568            }
569            _ => {}
570        }
571
572        // For the checksum variant, check that the mapping exists and that the type matches.
573        if let UpgradeVariant::Checksum { mapping, key, key_type } = &upgrade_variant {
574            // Look up the mapping type.
575            let Some(VariableSymbol { type_: Type::Mapping(mapping_type), .. }) =
576                self.state.symbol_table.lookup_global(mapping)
577            else {
578                self.emit_err(TypeCheckerError::custom(
579                    format!("The mapping '{mapping}' does not exist. Please ensure that it is imported or defined in your program."),
580                    constructor.annotations[0].span,
581                ));
582                return;
583            };
584            // Check that the mapping key type matches the expected key type.
585            if *mapping_type.key != *key_type {
586                self.emit_err(TypeCheckerError::custom(
587                    format!(
588                        "The mapping '{}' key type '{}' does not match the key '{}' in the `@checksum` annotation",
589                        mapping, mapping_type.key, key
590                    ),
591                    constructor.annotations[0].span,
592                ));
593            }
594            // Check that the value type is a `[u8; 32]`.
595            let check_value_type = |type_: &Type| -> bool {
596                if let Type::Array(array_type) = type_ {
597                    if !matches!(array_type.element_type.as_ref(), &Type::Integer(_)) {
598                        return false;
599                    }
600                    if let Some(length) = array_type.length.as_u32() {
601                        return length == 32;
602                    }
603                    return false;
604                }
605                false
606            };
607            if !check_value_type(&mapping_type.value) {
608                self.emit_err(TypeCheckerError::custom(
609                    format!("The mapping '{}' value type '{}' must be a '[u8; 32]'", mapping, mapping_type.value),
610                    constructor.annotations[0].span,
611                ));
612            }
613        }
614
615        // Traverse the constructor.
616        self.in_conditional_scope(|slf| {
617            slf.in_scope(constructor.id, |slf| {
618                slf.visit_block(&constructor.block);
619            })
620        });
621
622        // Check that the constructor does not call `finalize`.
623        if self.scope_state.has_called_finalize {
624            self.emit_err(TypeCheckerError::custom("The constructor cannot call `finalize`.", constructor.span));
625        }
626
627        // Check that the constructor does not have an `async` block.
628        if self.scope_state.already_contains_an_async_block {
629            self.emit_err(TypeCheckerError::custom("The constructor cannot have an `async` block.", constructor.span));
630        }
631
632        self.scope_state.reset();
633    }
634
635    fn visit_function_stub(&mut self, input: &FunctionStub) {
636        // Must not be an inline function
637        if input.variant == Variant::Inline {
638            self.emit_err(TypeCheckerError::stub_functions_must_not_be_inlines(input.span));
639        }
640
641        // Create future stubs.
642        if input.variant == Variant::AsyncFunction {
643            let finalize_input_map = &mut self.async_function_input_types;
644            let resolved_inputs: Vec<Type> = input
645                .input
646                .iter()
647                .map(|input| {
648                    match &input.type_ {
649                        Type::Future(f) => {
650                            // Since we traverse stubs in post-order, we can assume that the corresponding finalize stub has already been traversed.
651                            Type::Future(FutureType::new(
652                                finalize_input_map.get(f.location.as_ref().unwrap()).unwrap().clone(),
653                                f.location.clone(),
654                                true,
655                            ))
656                        }
657                        _ => input.clone().type_,
658                    }
659                })
660                .collect();
661
662            finalize_input_map.insert(
663                Location::new(self.scope_state.program_name.unwrap(), vec![input.identifier.name]),
664                resolved_inputs,
665            );
666        }
667
668        // Query helper function to type check function parameters and outputs.
669        self.check_function_signature(&Function::from(input.clone()), /* is_stub */ true);
670    }
671
672    fn visit_struct_stub(&mut self, input: &Composite) {
673        self.visit_struct(input);
674    }
675}