diff --git a/compiler/compiler/tests/utilities/check_unique_node_ids.rs b/compiler/compiler/tests/utilities/check_unique_node_ids.rs new file mode 100644 index 0000000000..0debeb7554 --- /dev/null +++ b/compiler/compiler/tests/utilities/check_unique_node_ids.rs @@ -0,0 +1,352 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use leo_ast::*; + +use std::{collections::HashSet, marker::PhantomData}; + +/// A utility that checks that each node in the AST has a unique `NodeID`. +pub struct CheckUniqueNodeIds<'a> { + /// The set of `NodeID`s that have been seen. + seen: HashSet, + _phantom: PhantomData<&'a ()>, +} + +impl<'a> CheckUniqueNodeIds<'a> { + /// Creates a new `CheckUniqueNodeId`. + pub fn new() -> Self { + Self { seen: HashSet::new(), _phantom: PhantomData } + } + + /// Checks that the given `NodeID` has not been seen before. + pub fn check(&mut self, id: NodeID) { + if !self.seen.insert(id) { + panic!("Duplicate NodeID found in the AST: {}", id); + } + } + + /// Checks that the given `Type` has a unique `NodeID`. + pub fn check_ty(&mut self, ty: &'a Type) { + match ty { + Type::Identifier(identifier) => self.visit_identifier(identifier, &Default::default()), + Type::Mapping(mapping) => { + self.check_ty(&mapping.key); + self.check_ty(&mapping.value); + } + Type::Tuple(tuple) => { + for ty in &tuple.0 { + self.check_ty(ty); + } + } + _ => {} + } + } +} + +impl<'a> ExpressionVisitor<'a> for CheckUniqueNodeIds<'a> { + type AdditionalInput = (); + type Output = (); + + fn visit_access(&mut self, input: &'a AccessExpression, _: &Self::AdditionalInput) -> Self::Output { + match input { + AccessExpression::AssociatedConstant(AssociatedConstant { ty, name, id, .. }) => { + self.check_ty(ty); + self.visit_identifier(name, &Default::default()); + self.check(*id); + } + AccessExpression::AssociatedFunction(AssociatedFunction { ty, name, arguments, id, .. }) => { + self.check_ty(ty); + self.visit_identifier(name, &Default::default()); + for argument in arguments { + self.visit_expression(argument, &Default::default()); + } + self.check(*id); + } + AccessExpression::Member(MemberAccess { inner, name, id, .. }) => { + self.visit_expression(inner, &Default::default()); + self.visit_identifier(name, &Default::default()); + self.check(*id); + } + AccessExpression::Tuple(TupleAccess { tuple, id, .. }) => { + self.visit_expression(tuple, &Default::default()); + self.check(*id); + } + } + } + + fn visit_binary(&mut self, input: &'a BinaryExpression, _: &Self::AdditionalInput) -> Self::Output { + let BinaryExpression { left, right, id, .. } = input; + self.visit_expression(left, &Default::default()); + self.visit_expression(right, &Default::default()); + self.check(*id); + } + + fn visit_call(&mut self, input: &'a CallExpression, _: &Self::AdditionalInput) -> Self::Output { + let CallExpression { function, arguments, external, id, .. } = input; + self.visit_expression(function, &Default::default()); + for argument in arguments { + self.visit_expression(argument, &Default::default()); + } + if let Some(external) = external { + self.visit_expression(external, &Default::default()); + } + self.check(*id); + } + + fn visit_cast(&mut self, input: &'a CastExpression, _: &Self::AdditionalInput) -> Self::Output { + let CastExpression { expression, type_, id, .. } = input; + self.visit_expression(expression, &Default::default()); + self.check_ty(type_); + self.check(*id); + } + + fn visit_struct_init(&mut self, input: &'a StructExpression, _: &Self::AdditionalInput) -> Self::Output { + let StructExpression { name, members, id, .. } = input; + self.visit_identifier(name, &Default::default()); + for StructVariableInitializer { identifier, expression, id, .. } in members { + self.visit_identifier(identifier, &Default::default()); + if let Some(expression) = expression { + self.visit_expression(expression, &Default::default()); + } + self.check(*id); + } + self.check(*id); + } + + fn visit_err(&mut self, input: &'a ErrExpression, _: &Self::AdditionalInput) -> Self::Output { + self.check(input.id); + } + + fn visit_identifier(&mut self, input: &'a Identifier, _: &Self::AdditionalInput) -> Self::Output { + self.check(input.id) + } + + fn visit_literal(&mut self, input: &'a Literal, _: &Self::AdditionalInput) -> Self::Output { + self.check(input.id()) + } + + fn visit_ternary(&mut self, input: &'a TernaryExpression, _: &Self::AdditionalInput) -> Self::Output { + let TernaryExpression { condition, if_true, if_false, id, .. } = input; + self.visit_expression(condition, &Default::default()); + self.visit_expression(if_true, &Default::default()); + self.visit_expression(if_false, &Default::default()); + self.check(*id); + } + + fn visit_tuple(&mut self, input: &'a TupleExpression, _: &Self::AdditionalInput) -> Self::Output { + let TupleExpression { elements, id, .. } = input; + for element in elements { + self.visit_expression(element, &Default::default()); + } + self.check(*id); + } + + fn visit_unary(&mut self, input: &'a UnaryExpression, _: &Self::AdditionalInput) -> Self::Output { + let UnaryExpression { receiver, id, .. } = input; + self.visit_expression(receiver, &Default::default()); + self.check(*id); + } + + fn visit_unit(&mut self, input: &'a UnitExpression, _: &Self::AdditionalInput) -> Self::Output { + self.check(input.id) + } +} + +impl<'a> StatementVisitor<'a> for CheckUniqueNodeIds<'a> { + fn visit_assert(&mut self, input: &'a AssertStatement) { + match &input.variant { + AssertVariant::Assert(expr) => self.visit_expression(expr, &Default::default()), + AssertVariant::AssertEq(left, right) | AssertVariant::AssertNeq(left, right) => { + self.visit_expression(left, &Default::default()); + self.visit_expression(right, &Default::default()) + } + }; + self.check(input.id) + } + + fn visit_assign(&mut self, input: &'a AssignStatement) { + self.visit_expression(&input.place, &Default::default()); + self.visit_expression(&input.value, &Default::default()); + self.check(input.id) + } + + fn visit_block(&mut self, input: &'a Block) { + input.statements.iter().for_each(|stmt| self.visit_statement(stmt)); + self.check(input.id) + } + + fn visit_conditional(&mut self, input: &'a ConditionalStatement) { + self.visit_expression(&input.condition, &Default::default()); + self.visit_block(&input.then); + if let Some(stmt) = input.otherwise.as_ref() { + self.visit_statement(stmt); + } + self.check(input.id) + } + + fn visit_console(&mut self, input: &'a ConsoleStatement) { + match &input.function { + ConsoleFunction::Assert(expr) => { + self.visit_expression(expr, &Default::default()); + } + ConsoleFunction::AssertEq(left, right) => { + self.visit_expression(left, &Default::default()); + self.visit_expression(right, &Default::default()); + } + ConsoleFunction::AssertNeq(left, right) => { + self.visit_expression(left, &Default::default()); + self.visit_expression(right, &Default::default()); + } + }; + self.check(input.id) + } + + fn visit_definition(&mut self, input: &'a DefinitionStatement) { + self.visit_expression(&input.place, &Default::default()); + self.check_ty(&input.type_); + self.visit_expression(&input.value, &Default::default()); + self.check(input.id) + } + + fn visit_expression_statement(&mut self, input: &'a ExpressionStatement) { + self.visit_expression(&input.expression, &Default::default()); + self.check(input.id) + } + + fn visit_iteration(&mut self, input: &'a IterationStatement) { + self.visit_identifier(&input.variable, &Default::default()); + self.check_ty(&input.type_); + self.visit_expression(&input.start, &Default::default()); + self.visit_expression(&input.stop, &Default::default()); + self.visit_block(&input.block); + self.check(input.id) + } + + fn visit_return(&mut self, input: &'a ReturnStatement) { + self.visit_expression(&input.expression, &Default::default()); + if let Some(arguments) = &input.finalize_arguments { + arguments.iter().for_each(|argument| { + self.visit_expression(argument, &Default::default()); + }) + } + self.check(input.id) + } +} + +impl<'a> ProgramVisitor<'a> for CheckUniqueNodeIds<'a> { + fn visit_struct(&mut self, input: &'a Struct) { + let Struct { identifier, members, id, .. } = input; + self.visit_identifier(identifier, &Default::default()); + for Member { identifier, type_, id, .. } in members { + self.visit_identifier(identifier, &Default::default()); + self.check_ty(type_); + self.check(*id); + } + self.check(*id); + } + + fn visit_mapping(&mut self, input: &'a Mapping) { + let Mapping { identifier, key_type, value_type, id, .. } = input; + self.visit_identifier(identifier, &Default::default()); + self.check_ty(key_type); + self.check_ty(value_type); + self.check(*id); + } + + fn visit_function(&mut self, input: &'a Function) { + let Function { annotations, identifier, input, output, block, finalize, id, .. } = input; + // Check the annotations. + for Annotation { identifier, id, .. } in annotations { + self.visit_identifier(identifier, &Default::default()); + self.check(*id); + } + // Check the function name. + self.visit_identifier(identifier, &Default::default()); + // Check the inputs. + for in_ in input { + match in_ { + Input::Internal(FunctionInput { identifier, type_, id, .. }) => { + self.visit_identifier(identifier, &Default::default()); + self.check_ty(type_); + self.check(*id); + } + Input::External(External { identifier, program_name, record, id, .. }) => { + self.visit_identifier(identifier, &Default::default()); + self.visit_identifier(program_name, &Default::default()); + self.visit_identifier(record, &Default::default()); + self.check(*id); + } + } + } + // Check the outputs. + for out in output { + match out { + Output::Internal(FunctionOutput { type_, id, .. }) => { + self.check_ty(type_); + self.check(*id); + } + Output::External(External { identifier, program_name, record, id, .. }) => { + self.visit_identifier(identifier, &Default::default()); + self.visit_identifier(program_name, &Default::default()); + self.visit_identifier(record, &Default::default()); + self.check(*id); + } + } + } + // Check the function body. + self.visit_block(block); + // Check the finalize block. + if let Some(Finalize { identifier, input, output, block, id, .. }) = finalize { + // Check the finalize name. + self.visit_identifier(identifier, &Default::default()); + // Check the inputs. + for in_ in input { + match in_ { + Input::Internal(FunctionInput { identifier, type_, id, .. }) => { + self.visit_identifier(identifier, &Default::default()); + self.check_ty(type_); + self.check(*id); + } + Input::External(External { identifier, program_name, record, id, .. }) => { + self.visit_identifier(identifier, &Default::default()); + self.visit_identifier(program_name, &Default::default()); + self.visit_identifier(record, &Default::default()); + self.check(*id); + } + } + } + // Check the outputs. + for out in output { + match out { + Output::Internal(FunctionOutput { type_, id, .. }) => { + self.check_ty(type_); + self.check(*id); + } + Output::External(External { identifier, program_name, record, id, .. }) => { + self.visit_identifier(identifier, &Default::default()); + self.visit_identifier(program_name, &Default::default()); + self.visit_identifier(record, &Default::default()); + self.check(*id); + } + } + } + // Check the function body. + self.visit_block(block); + self.check(*id); + } + self.check(*id); + } +}