1use super::TypeCheckingVisitor;
18use crate::{VariableSymbol, VariableType};
19
20use leo_ast::{DiGraphError, Type, *};
21use leo_errors::TypeCheckerError;
22use leo_span::{Symbol, sym};
23
24use itertools::Itertools;
25use snarkvm::prelude::{CanaryV0, MainnetV0, TestnetV0};
26use std::collections::{BTreeMap, HashSet};
27
28impl ProgramVisitor for TypeCheckingVisitor<'_> {
29 fn visit_program(&mut self, input: &Program) {
30 input.stubs.iter().for_each(|(symbol, stub)| {
32 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 input.modules.values().for_each(|module| self.visit_module(module));
46
47 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 check_name(&self.state.handler, program_name, "program");
56
57 self.scope_state.program_name = Some(program_name.name);
59
60 let record_info: BTreeMap<String, leo_span::Span> = input
62 .structs
63 .iter()
64 .filter(|(_, c)| c.is_record)
65 .map(|(_, r)| (r.name().to_string(), r.identifier.span))
66 .collect();
67
68 for ((prev_name, _), (curr_name, curr_span)) in record_info.iter().tuple_windows() {
72 if curr_name.starts_with(prev_name) {
73 self.state
74 .handler
75 .emit_err(TypeCheckerError::record_prefixed_by_other_record(curr_name, prev_name, *curr_span));
76 }
77 }
78
79 input.consts.iter().for_each(|(_, c)| self.visit_const(c));
81
82 input.structs.iter().for_each(|(_, function)| self.visit_struct(function));
84
85 if let Err(DiGraphError::CycleDetected(path)) = self.state.struct_graph.post_order() {
87 self.emit_err(TypeCheckerError::cyclic_struct_dependency(
88 path.iter().map(|p| p.iter().format("::")).collect(),
89 ));
90 }
91
92 let mut mapping_count = 0;
94 for (_, mapping) in input.mappings.iter() {
95 self.visit_mapping(mapping);
96 mapping_count += 1;
97 }
98
99 if mapping_count > self.limits.max_mappings {
101 self.emit_err(TypeCheckerError::too_many_mappings(
102 self.limits.max_mappings,
103 input.program_id.name.span + input.program_id.network.span,
104 ));
105 }
106
107 let mut transition_count = 0;
109 for (_, function) in input.functions.iter() {
110 self.visit_function(function);
111 if function.variant.is_transition() {
112 transition_count += 1;
113 }
114 }
115
116 if let Some(constructor) = &input.constructor {
120 self.visit_constructor(constructor);
121 }
122
123 if let Err(DiGraphError::CycleDetected(path)) = self.state.call_graph.post_order() {
125 self.emit_err(TypeCheckerError::cyclic_function_dependency(path));
126 }
127
128 if transition_count > self.limits.max_functions {
131 self.emit_err(TypeCheckerError::too_many_transitions(
132 self.limits.max_functions,
133 input.program_id.name.span + input.program_id.network.span,
134 ));
135 }
136 else if transition_count == 0 {
139 self.emit_err(TypeCheckerError::no_transitions(input.program_id.name.span + input.program_id.network.span));
140 }
141 }
142
143 fn visit_module(&mut self, input: &Module) {
144 let parent_module = self.scope_state.module_name.clone();
145 self.scope_state.program_name = Some(input.program_name);
147 self.scope_state.module_name = input.path.clone();
148
149 input.consts.iter().for_each(|(_, c)| self.visit_const(c));
151
152 input.structs.iter().for_each(|(_, function)| self.visit_struct(function));
154
155 for (_, function) in input.functions.iter() {
156 self.visit_function(function);
157 }
158
159 self.scope_state.module_name = parent_module;
160 }
161
162 fn visit_stub(&mut self, input: &Stub) {
163 self.scope_state.program_name = Some(input.stub_id.name.name);
165 self.scope_state.is_stub = true;
166
167 if !input.consts.is_empty() {
169 self.emit_err(TypeCheckerError::stubs_cannot_have_const_declarations(input.consts.first().unwrap().1.span));
170 }
171
172 input.structs.iter().for_each(|(_, function)| self.visit_struct_stub(function));
174
175 input.functions.iter().for_each(|(_, function)| self.visit_function_stub(function));
177 }
178
179 fn visit_struct(&mut self, input: &Composite) {
180 self.in_conditional_scope(|slf| {
181 slf.in_scope(input.id, |slf| {
182 if input.is_record && !input.const_parameters.is_empty() {
183 slf.emit_err(TypeCheckerError::unexpected_record_const_parameters(input.span));
184 } else {
185 input
186 .const_parameters
187 .iter()
188 .for_each(|const_param| slf.insert_symbol_conditional_scope(const_param.identifier.name));
189
190 for const_param in &input.const_parameters {
191 slf.visit_type(const_param.type_());
192
193 if !matches!(
195 const_param.type_(),
196 Type::Boolean | Type::Integer(_) | Type::Address | Type::Scalar | Type::Group | Type::Field
197 ) {
198 slf.emit_err(TypeCheckerError::bad_const_generic_type(
199 const_param.type_(),
200 const_param.span(),
201 ));
202 }
203
204 if let Err(err) = slf.state.symbol_table.insert_variable(
206 slf.scope_state.program_name.unwrap(),
207 &[const_param.identifier().name],
208 VariableSymbol {
209 type_: const_param.type_().clone(),
210 span: const_param.identifier.span(),
211 declaration: VariableType::ConstParameter,
212 },
213 ) {
214 slf.state.handler.emit_err(err);
215 }
216
217 slf.state.type_table.insert(const_param.identifier().id(), const_param.type_().clone());
219 }
220 }
221
222 input.members.iter().for_each(|member| slf.visit_type(&member.type_));
223 })
224 });
225
226 let mut used = HashSet::new();
228 for Member { identifier, type_, span, .. } in &input.members {
229 self.assert_type_is_valid(type_, *span);
231
232 if !used.insert(identifier.name) {
233 self.emit_err(if input.is_record {
234 TypeCheckerError::duplicate_record_variable(input.name(), *span)
235 } else {
236 TypeCheckerError::duplicate_struct_member(input.name(), *span)
237 });
238 }
239 }
240
241 if input.is_record {
243 check_name(&self.state.handler, input.identifier, "record");
245
246 input.members.iter().for_each(|member| {
249 check_name(&self.state.handler, member.identifier, "record entry");
250 });
251
252 let check_has_field =
253 |need, expected_ty: Type| match input.members.iter().find_map(|Member { identifier, type_, .. }| {
254 (identifier.name == need).then_some((identifier, type_))
255 }) {
256 Some((_, actual_ty)) if expected_ty.eq_flat_relaxed(actual_ty) => {} Some((field, _)) => {
258 self.emit_err(TypeCheckerError::record_var_wrong_type(field, expected_ty, input.span()));
259 }
260 None => {
261 self.emit_err(TypeCheckerError::required_record_variable(need, expected_ty, input.span()));
262 }
263 };
264 check_has_field(sym::owner, Type::Address);
265
266 for Member { identifier, type_, span, .. } in input.members.iter() {
267 if self.contains_optional_type(type_) {
268 self.emit_err(TypeCheckerError::record_field_cannot_be_optional(identifier, type_, *span));
269 }
270 }
271 }
272 else if input.members.is_empty() {
274 self.emit_err(TypeCheckerError::empty_struct(input.span()));
275 }
276
277 if !(input.is_record && self.scope_state.is_stub) {
278 for Member { mode, identifier, type_, span, .. } in input.members.iter() {
279 if matches!(type_, Type::Tuple(_)) {
281 self.emit_err(TypeCheckerError::composite_data_type_cannot_contain_tuple(
282 if input.is_record { "record" } else { "struct" },
283 identifier.span,
284 ));
285 } else if matches!(type_, Type::Future(..)) {
286 self.emit_err(TypeCheckerError::composite_data_type_cannot_contain_future(
287 if input.is_record { "record" } else { "struct" },
288 identifier.span,
289 ));
290 }
291
292 self.assert_member_is_not_record(identifier.span, input.identifier.name, type_);
294 let composite_path = self
297 .scope_state
298 .module_name
299 .iter()
300 .cloned()
301 .chain(std::iter::once(input.identifier.name))
302 .collect::<Vec<Symbol>>();
303 if let Type::Composite(struct_member_type) = type_ {
304 self.state.struct_graph.add_edge(composite_path, struct_member_type.path.absolute_path().to_vec());
306 } else if let Type::Array(array_type) = type_ {
307 let base_element_type = array_type.base_element_type();
309 if let Type::Composite(member_type) = base_element_type {
311 self.state.struct_graph.add_edge(composite_path, member_type.path.absolute_path().to_vec());
312 }
313 }
314
315 if !input.is_record && !matches!(mode, Mode::None) {
317 self.emit_err(TypeCheckerError::struct_cannot_have_member_mode(*span));
318 }
319 }
320 }
321 }
322
323 fn visit_mapping(&mut self, input: &Mapping) {
324 self.visit_type(&input.key_type);
325 self.visit_type(&input.value_type);
326
327 self.assert_type_is_valid(&input.key_type, input.span);
329 match input.key_type.clone() {
331 Type::Future(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "future", input.span)),
332 Type::Tuple(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "tuple", input.span)),
333 Type::Composite(struct_type) => {
334 if let Some(comp) = self.lookup_struct(
335 struct_type.program.or(self.scope_state.program_name),
336 &struct_type.path.absolute_path(),
337 ) {
338 if comp.is_record {
339 self.emit_err(TypeCheckerError::invalid_mapping_type("key", "record", input.span));
340 }
341 } else {
342 self.emit_err(TypeCheckerError::undefined_type(&input.key_type, input.span));
343 }
344 }
345 Type::Mapping(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("key", "mapping", input.span)),
347 _ => {}
348 }
349
350 if self.contains_optional_type(&input.key_type) {
351 self.emit_err(TypeCheckerError::optional_type_not_allowed_in_mapping(
352 input.key_type.clone(),
353 "key",
354 input.span,
355 ))
356 }
357
358 self.assert_type_is_valid(&input.value_type, input.span);
360 match input.value_type.clone() {
362 Type::Future(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "future", input.span)),
363 Type::Tuple(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "tuple", input.span)),
364 Type::Composite(struct_type) => {
365 if let Some(comp) = self.lookup_struct(
366 struct_type.program.or(self.scope_state.program_name),
367 &struct_type.path.absolute_path(),
368 ) {
369 if comp.is_record {
370 self.emit_err(TypeCheckerError::invalid_mapping_type("value", "record", input.span));
371 }
372 } else {
373 self.emit_err(TypeCheckerError::undefined_type(&input.value_type, input.span));
374 }
375 }
376 Type::Mapping(_) => self.emit_err(TypeCheckerError::invalid_mapping_type("value", "mapping", input.span)),
378 _ => {}
379 }
380
381 if self.contains_optional_type(&input.value_type) {
382 self.emit_err(TypeCheckerError::optional_type_not_allowed_in_mapping(
383 input.value_type.clone(),
384 "value",
385 input.span,
386 ))
387 }
388 }
389
390 fn visit_function(&mut self, function: &Function) {
391 self.scope_state.reset();
393
394 self.scope_state.variant = Some(function.variant);
396
397 for annotation in function.annotations.iter() {
399 if !matches!(annotation.identifier.name, sym::test | sym::should_fail) {
400 self.emit_err(TypeCheckerError::unknown_annotation(annotation, annotation.span))
401 }
402 }
403
404 let get = |symbol: Symbol| -> &Annotation {
405 function.annotations.iter().find(|ann| ann.identifier.name == symbol).unwrap()
406 };
407
408 let check_annotation = |symbol: Symbol, allowed_keys: &[Symbol]| -> bool {
409 let count = function.annotations.iter().filter(|ann| ann.identifier.name == symbol).count();
410 if count > 0 {
411 let annotation = get(symbol);
412 for key in annotation.map.keys() {
413 if !allowed_keys.contains(key) {
414 self.emit_err(TypeCheckerError::annotation_error(
415 format_args!("Invalid key `{key}` for annotation @{symbol}"),
416 annotation.span,
417 ));
418 }
419 }
420 if count > 1 {
421 self.emit_err(TypeCheckerError::annotation_error(
422 format_args!("Duplicate annotation @{symbol}"),
423 annotation.span,
424 ));
425 }
426 }
427 count > 0
428 };
429
430 let has_test = check_annotation(sym::test, &[sym::private_key]);
431 let has_should_fail = check_annotation(sym::should_fail, &[]);
432
433 if has_test && !self.state.is_test {
434 self.emit_err(TypeCheckerError::annotation_error(
435 format_args!("Test annotation @test appears outside of tests"),
436 get(sym::test).span,
437 ));
438 }
439
440 if has_should_fail && !self.state.is_test {
441 self.emit_err(TypeCheckerError::annotation_error(
442 format_args!("Test annotation @should_fail appears outside of tests"),
443 get(sym::should_fail).span,
444 ));
445 }
446
447 if has_should_fail && !has_test {
448 self.emit_err(TypeCheckerError::annotation_error(
449 format_args!("Annotation @should_fail appears without @test"),
450 get(sym::should_fail).span,
451 ));
452 }
453
454 if has_test
455 && !self.scope_state.variant.unwrap().is_script()
456 && !self.scope_state.variant.unwrap().is_transition()
457 {
458 self.emit_err(TypeCheckerError::annotation_error(
459 format_args!("Annotation @test may appear only on scripts and transitions"),
460 get(sym::test).span,
461 ));
462 }
463
464 if (has_test) && !function.input.is_empty() {
465 self.emit_err(TypeCheckerError::annotation_error(
466 "A test procedure cannot have inputs",
467 function.input[0].span,
468 ));
469 }
470
471 self.in_conditional_scope(|slf| {
472 slf.in_scope(function.id, |slf| {
473 function
474 .const_parameters
475 .iter()
476 .for_each(|const_param| slf.insert_symbol_conditional_scope(const_param.identifier.name));
477
478 function.input.iter().for_each(|input| slf.insert_symbol_conditional_scope(input.identifier.name));
479
480 slf.scope_state.function = Some(function.name());
482
483 slf.check_function_signature(function, false);
485
486 if function.variant == Variant::Function && function.input.is_empty() {
487 slf.emit_err(TypeCheckerError::empty_function_arglist(function.span));
488 }
489
490 slf.visit_block(&function.block);
491
492 if function.output_type != Type::Unit && !slf.scope_state.has_return {
494 slf.emit_err(TypeCheckerError::missing_return(function.span));
495 }
496 })
497 });
498
499 if self.scope_state.variant == Some(Variant::AsyncTransition)
501 && !self.scope_state.has_called_finalize
502 && !self.scope_state.already_contains_an_async_block
503 {
504 self.emit_err(TypeCheckerError::missing_async_operation_in_async_transition(function.span));
505 }
506
507 self.scope_state.reset();
508 }
509
510 fn visit_constructor(&mut self, constructor: &Constructor) {
511 self.scope_state.reset();
513 self.scope_state.function = Some(sym::constructor);
515 self.scope_state.variant = Some(Variant::AsyncFunction);
517 self.scope_state.is_constructor = true;
518
519 let result = match self.state.network {
522 NetworkName::CanaryV0 => constructor.get_upgrade_variant::<CanaryV0>(),
523 NetworkName::TestnetV0 => constructor.get_upgrade_variant::<TestnetV0>(),
524 NetworkName::MainnetV0 => constructor.get_upgrade_variant::<MainnetV0>(),
525 };
526 let upgrade_variant = match result {
527 Ok(upgrade_variant) => upgrade_variant,
528 Err(e) => {
529 self.emit_err(TypeCheckerError::custom(e, constructor.span));
530 return;
531 }
532 };
533
534 match (&upgrade_variant, constructor.block.statements.is_empty()) {
536 (UpgradeVariant::Custom, true) => {
537 self.emit_err(TypeCheckerError::custom("A 'custom' constructor cannot be empty", constructor.span));
538 }
539 (UpgradeVariant::NoUpgrade | UpgradeVariant::Admin { .. } | UpgradeVariant::Checksum { .. }, false) => {
540 self.emit_err(TypeCheckerError::custom("A 'noupgrade', 'admin', or 'checksum' constructor must be empty. The Leo compiler will insert the appropriate code.", constructor.span));
541 }
542 _ => {}
543 }
544
545 if let UpgradeVariant::Checksum { mapping, key, key_type } = &upgrade_variant {
547 let Some(VariableSymbol { type_: Type::Mapping(mapping_type), .. }) =
549 self.state.symbol_table.lookup_global(mapping)
550 else {
551 self.emit_err(TypeCheckerError::custom(
552 format!("The mapping '{mapping}' does not exist. Please ensure that it is imported or defined in your program."),
553 constructor.annotations[0].span,
554 ));
555 return;
556 };
557 if *mapping_type.key != *key_type {
559 self.emit_err(TypeCheckerError::custom(
560 format!(
561 "The mapping '{}' key type '{}' does not match the key '{}' in the `@checksum` annotation",
562 mapping, mapping_type.key, key
563 ),
564 constructor.annotations[0].span,
565 ));
566 }
567 let check_value_type = |type_: &Type| -> bool {
569 if let Type::Array(array_type) = type_ {
570 if !matches!(array_type.element_type.as_ref(), &Type::Integer(_)) {
571 return false;
572 }
573 if let Some(length) = array_type.length.as_u32() {
574 return length == 32;
575 }
576 return false;
577 }
578 false
579 };
580 if !check_value_type(&mapping_type.value) {
581 self.emit_err(TypeCheckerError::custom(
582 format!("The mapping '{}' value type '{}' must be a '[u8; 32]'", mapping, mapping_type.value),
583 constructor.annotations[0].span,
584 ));
585 }
586 }
587
588 self.in_conditional_scope(|slf| {
590 slf.in_scope(constructor.id, |slf| {
591 slf.visit_block(&constructor.block);
592 })
593 });
594
595 if self.scope_state.has_called_finalize {
597 self.emit_err(TypeCheckerError::custom("The constructor cannot call `finalize`.", constructor.span));
598 }
599
600 if self.scope_state.already_contains_an_async_block {
602 self.emit_err(TypeCheckerError::custom("The constructor cannot have an `async` block.", constructor.span));
603 }
604
605 self.scope_state.reset();
606 }
607
608 fn visit_function_stub(&mut self, input: &FunctionStub) {
609 if input.variant == Variant::Inline {
611 self.emit_err(TypeCheckerError::stub_functions_must_not_be_inlines(input.span));
612 }
613
614 if input.variant == Variant::AsyncFunction {
616 let finalize_input_map = &mut self.async_function_input_types;
617 let resolved_inputs: Vec<Type> = input
618 .input
619 .iter()
620 .map(|input| {
621 match &input.type_ {
622 Type::Future(f) => {
623 Type::Future(FutureType::new(
625 finalize_input_map.get(f.location.as_ref().unwrap()).unwrap().clone(),
626 f.location.clone(),
627 true,
628 ))
629 }
630 _ => input.clone().type_,
631 }
632 })
633 .collect();
634
635 finalize_input_map.insert(
636 Location::new(self.scope_state.program_name.unwrap(), vec![input.identifier.name]),
637 resolved_inputs,
638 );
639 }
640
641 self.check_function_signature(&Function::from(input.clone()), true);
643 }
644
645 fn visit_struct_stub(&mut self, input: &Composite) {
646 self.visit_struct(input);
647 }
648}
649
650fn check_name(handler: &leo_errors::Handler, name: Identifier, item_type: &str) {
651 if name.to_string().contains(&sym::aleo.to_string()) {
652 handler.emit_err(TypeCheckerError::illegal_name(name, item_type, sym::aleo, name.span));
653 }
654}