// SPDX-License-Identifier: Apache-2.0
// SPDX-FileCopyrightText: 2024 The Gleam contributors

//! AST traversal routines, referenced from [`syn::visit`](https://docs.rs/syn/latest/syn/visit/index.html)
//!
//! Each method of the [`Visit`] trait can be overridden to customize the
//! behaviour when visiting the corresponding type of AST node. By default,
//! every method recursively visits the substructure of the node by using the
//! right visitor method.
//!
//! # Example
//!
//! Suppose we would like to collect all function names in a module,
//! we can do the following:
//!
//! ```no_run
//! use gleam_core::ast::{TypedFunction, visit::{self, Visit}};
//!
//! struct FnCollector<'ast> {
//!     functions: Vec<&'ast TypedFunction>
//! }
//!
//! impl<'ast> Visit<'ast> for FnCollector<'ast> {
//!     fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
//!         self.functions.push(fun);
//!
//!         // Use the default behaviour to visit any nested functions
//!         visit::visit_typed_function(self, fun);
//!     }
//! }
//!
//! fn print_all_module_functions() {
//!     let module = todo!("module");
//!     let mut fn_collector = FnCollector { functions: vec![] };
//!
//!     // This will walk the AST and collect all functions
//!     fn_collector.visit_typed_module(module);
//!
//!     // Print the collected functions
//!     println!("{:#?}", fn_collector.functions);
//! }
//! ```

use crate::{
    analyse::Inferred,
    ast::{
        BitArraySize, RecordBeingUpdated, TypeAstConstructorName, TypedBitArraySize,
        TypedConstantBitArraySegment, TypedDefinitions, TypedImport, TypedTailPattern,
        TypedTypeAlias, typed::InvalidExpression,
    },
    exhaustiveness::CompiledCase,
    parse::LiteralFloatValue,
    type_::{
        FieldMap, ModuleValueConstructor, PatternConstructor, TypedCallArg, ValueConstructor,
        error::VariableOrigin,
    },
};
use std::sync::Arc;

use ecow::EcoString;
use num_bigint::BigInt;
use vec1::Vec1;

use crate::type_::Type;

use super::{
    AssignName, BinOp, BitArrayOption, CallArg, Pattern, PipelineAssignmentKind, RecordUpdateArg,
    SrcSpan, Statement, TodoKind, TypeAst, TypedArg, TypedAssert, TypedAssignment, TypedClause,
    TypedClauseGuard, TypedConstant, TypedCustomType, TypedExpr, TypedExprBitArraySegment,
    TypedFunction, TypedModule, TypedModuleConstant, TypedPattern, TypedPatternBitArraySegment,
    TypedPipelineAssignment, TypedStatement, TypedUse, untyped::FunctionLiteralKind,
};

pub trait Visit<'ast> {
    fn visit_typed_module(&mut self, module: &'ast TypedModule) {
        visit_typed_module(self, module);
    }

    fn visit_typed_function(&mut self, fun: &'ast TypedFunction) {
        visit_typed_function(self, fun);
    }

    fn visit_typed_module_constant(&mut self, constant: &'ast TypedModuleConstant) {
        visit_typed_module_constant(self, constant);
    }

    fn visit_typed_custom_type(&mut self, custom_type: &'ast TypedCustomType) {
        visit_typed_custom_type(self, custom_type);
    }

    fn visit_typed_type_alias(&mut self, type_alias: &'ast TypedTypeAlias) {
        visit_typed_type_alias(self, type_alias);
    }

    fn visit_typed_import(&mut self, import: &'ast TypedImport) {
        visit_typed_import(self, import);
    }

    fn visit_typed_expr(&mut self, expr: &'ast TypedExpr) {
        visit_typed_expr(self, expr);
    }

    fn visit_typed_expr_echo(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        expression: &'ast Option<Box<TypedExpr>>,
        message: &'ast Option<Box<TypedExpr>>,
    ) {
        visit_typed_expr_echo(self, location, type_, expression, message);
    }

    fn visit_typed_expr_int(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        string_value: &'ast EcoString,
        int_value: &'ast BigInt,
    ) {
        visit_typed_expr_int(self, location, type_, string_value, int_value);
    }

    fn visit_typed_expr_float(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        value: &'ast EcoString,
    ) {
        visit_typed_expr_float(self, location, type_, value);
    }

    fn visit_typed_expr_string(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        value: &'ast EcoString,
    ) {
        visit_typed_expr_string(self, location, type_, value);
    }

    fn visit_typed_expr_block(
        &mut self,
        location: &'ast SrcSpan,
        statements: &'ast [TypedStatement],
    ) {
        visit_typed_expr_block(self, location, statements);
    }

    fn visit_typed_expr_pipeline(
        &mut self,
        location: &'ast SrcSpan,
        first_value: &'ast TypedPipelineAssignment,
        assignments: &'ast [(TypedPipelineAssignment, PipelineAssignmentKind)],
        finally: &'ast TypedExpr,
        finally_kind: &'ast PipelineAssignmentKind,
    ) {
        visit_typed_expr_pipeline(
            self,
            location,
            first_value,
            assignments,
            finally,
            finally_kind,
        );
    }

    fn visit_typed_expr_var(
        &mut self,
        location: &'ast SrcSpan,
        constructor: &'ast ValueConstructor,
        name: &'ast EcoString,
    ) {
        visit_typed_expr_var(self, location, constructor, name);
    }

    fn visit_typed_expr_fn(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        kind: &'ast FunctionLiteralKind,
        arguments: &'ast [TypedArg],
        body: &'ast Vec1<TypedStatement>,
        return_annotation: &'ast Option<TypeAst>,
    ) {
        visit_typed_expr_fn(
            self,
            location,
            type_,
            kind,
            arguments,
            body,
            return_annotation,
        );
    }

    fn visit_typed_expr_list(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        elements: &'ast [TypedExpr],
        tail: &'ast Option<Box<TypedExpr>>,
    ) {
        visit_typed_expr_list(self, location, type_, elements, tail);
    }

    fn visit_typed_expr_call(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        fun: &'ast TypedExpr,
        arguments: &'ast [TypedCallArg],
        open_parenthesis: &'ast Option<u32>,
    ) {
        visit_typed_expr_call(self, location, type_, fun, arguments, open_parenthesis);
    }

    fn visit_typed_expr_bin_op(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        operator: &'ast BinOp,
        operator_start: &'ast u32,
        left: &'ast TypedExpr,
        right: &'ast TypedExpr,
    ) {
        visit_typed_expr_bin_op(self, location, type_, operator, operator_start, left, right);
    }

    fn visit_typed_expr_case(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        subjects: &'ast [TypedExpr],
        clauses: &'ast [TypedClause],
        compiled_case: &'ast CompiledCase,
    ) {
        visit_typed_expr_case(self, location, type_, subjects, clauses, compiled_case);
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_expr_record_access(
        &mut self,
        location: &'ast SrcSpan,
        field_start: &'ast u32,
        type_: &'ast Arc<Type>,
        label: &'ast EcoString,
        index: &'ast u64,
        record: &'ast TypedExpr,
        documentation: &'ast Option<EcoString>,
    ) {
        visit_typed_expr_record_access(
            self,
            location,
            field_start,
            type_,
            label,
            index,
            record,
            documentation,
        );
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_expr_module_select(
        &mut self,
        location: &'ast SrcSpan,
        field_start: &'ast u32,
        type_: &'ast Arc<Type>,
        label: &'ast EcoString,
        module_name: &'ast EcoString,
        module_alias: &'ast EcoString,
        constructor: &'ast ModuleValueConstructor,
    ) {
        visit_typed_expr_module_select(
            self,
            location,
            field_start,
            type_,
            label,
            module_name,
            module_alias,
            constructor,
        );
    }

    fn visit_typed_expr_tuple(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        elements: &'ast [TypedExpr],
    ) {
        visit_typed_expr_tuple(self, location, type_, elements);
    }

    fn visit_typed_expr_tuple_index(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        index: &'ast u64,
        tuple: &'ast TypedExpr,
    ) {
        visit_typed_expr_tuple_index(self, location, type_, index, tuple);
    }

    fn visit_typed_expr_todo(
        &mut self,
        location: &'ast SrcSpan,
        message: &'ast Option<Box<TypedExpr>>,
        kind: &'ast TodoKind,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_expr_todo(self, location, message, kind, type_);
    }

    fn visit_typed_expr_panic(
        &mut self,
        location: &'ast SrcSpan,
        message: &'ast Option<Box<TypedExpr>>,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_expr_panic(self, location, message, type_);
    }

    fn visit_typed_expr_bit_array(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        segments: &'ast [TypedExprBitArraySegment],
    ) {
        visit_typed_expr_bit_array(self, location, type_, segments);
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_expr_record_update(
        &mut self,
        location: &'ast SrcSpan,
        spread_start: &'ast u32,
        type_: &'ast Arc<Type>,
        updated_record: &'ast TypedExpr,
        updated_record_assigned_name: &'ast Option<EcoString>,
        constructor: &'ast TypedExpr,
        arguments: &'ast [TypedCallArg],
    ) {
        visit_typed_expr_record_update(
            self,
            location,
            spread_start,
            type_,
            updated_record,
            updated_record_assigned_name,
            constructor,
            arguments,
        );
    }

    fn visit_typed_expr_negate_bool(&mut self, location: &'ast SrcSpan, value: &'ast TypedExpr) {
        visit_typed_expr_negate_bool(self, location, value);
    }

    fn visit_typed_expr_negate_int(&mut self, location: &'ast SrcSpan, value: &'ast TypedExpr) {
        visit_typed_expr_negate_int(self, location, value);
    }

    fn visit_typed_expr_invalid(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        extra_information: &'ast Option<InvalidExpression>,
    ) {
        visit_typed_expr_invalid(self, location, type_, extra_information);
    }

    fn visit_typed_statement(&mut self, statement: &'ast TypedStatement) {
        visit_typed_statement(self, statement);
    }

    fn visit_typed_assignment(&mut self, assignment: &'ast TypedAssignment) {
        visit_typed_assignment(self, assignment);
    }

    fn visit_typed_use(&mut self, use_: &'ast TypedUse) {
        visit_typed_use(self, use_);
    }

    fn visit_typed_assert(&mut self, assert: &'ast TypedAssert) {
        visit_typed_assert(self, assert);
    }

    fn visit_typed_pipeline_assignment(&mut self, assignment: &'ast TypedPipelineAssignment) {
        visit_typed_pipeline_assignment(self, assignment);
    }

    fn visit_typed_call_arg(&mut self, arg: &'ast TypedCallArg) {
        visit_typed_call_arg(self, arg);
    }

    fn visit_typed_clause(&mut self, clause: &'ast TypedClause) {
        visit_typed_clause(self, clause);
    }

    fn visit_typed_clause_guard(&mut self, guard: &'ast TypedClauseGuard) {
        visit_typed_clause_guard(self, guard);
    }

    fn visit_typed_clause_guard_bin_op(
        &mut self,
        left: &'ast TypedClauseGuard,
        right: &'ast TypedClauseGuard,
        operator: &'ast BinOp,
        operator_start: &'ast u32,
        location: &'ast SrcSpan,
    ) {
        visit_typed_clause_guard_bin_op(self, left, right, operator, operator_start, location);
    }

    fn visit_typed_clause_guard_var(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast EcoString,
        type_: &'ast Arc<Type>,
        definition_location: &'ast SrcSpan,
        origin: &'ast VariableOrigin,
    ) {
        visit_typed_clause_guard_var(self, location, name, type_, definition_location, origin);
    }

    fn visit_typed_clause_guard_tuple_index(
        &mut self,
        location: &'ast SrcSpan,
        index: &'ast u64,
        type_: &'ast Arc<Type>,
        tuple: &'ast TypedClauseGuard,
    ) {
        visit_typed_clause_guard_tuple_index(self, location, index, type_, tuple);
    }

    fn visit_typed_clause_guard_field_access(
        &mut self,
        label_location: &'ast SrcSpan,
        index: &'ast Option<u64>,
        label: &'ast EcoString,
        type_: &'ast Arc<Type>,
        container: &'ast TypedClauseGuard,
    ) {
        visit_typed_clause_guard_field_access(self, label_location, index, label, type_, container);
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_clause_guard_module_select(
        &mut self,
        location: &'ast SrcSpan,
        field_start: &'ast u32,
        definition_location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        label: &'ast EcoString,
        module_name: &'ast EcoString,
        module_alias: &'ast EcoString,
        literal: &'ast TypedConstant,
    ) {
        visit_typed_clause_guard_module_select(
            self,
            location,
            field_start,
            definition_location,
            type_,
            label,
            module_name,
            module_alias,
            literal,
        );
    }

    fn visit_typed_expr_bit_array_segment(&mut self, segment: &'ast TypedExprBitArraySegment) {
        visit_typed_expr_bit_array_segment(self, segment);
    }

    fn visit_typed_bit_array_option(&mut self, option: &'ast BitArrayOption<TypedExpr>) {
        visit_typed_bit_array_option(self, option);
    }

    fn visit_typed_pattern(&mut self, pattern: &'ast TypedPattern) {
        visit_typed_pattern(self, pattern);
    }

    fn visit_typed_pattern_int(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) {
        visit_typed_pattern_int(self, location, value);
    }

    fn visit_typed_pattern_float(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) {
        visit_typed_pattern_float(self, location, value);
    }

    fn visit_typed_pattern_string(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) {
        visit_typed_pattern_string(self, location, value);
    }

    fn visit_typed_pattern_variable(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast EcoString,
        type_: &'ast Arc<Type>,
        origin: &'ast VariableOrigin,
    ) {
        visit_typed_pattern_variable(self, location, name, type_, origin);
    }

    fn visit_typed_pattern_bit_array_size(&mut self, size: &'ast TypedBitArraySize) {
        visit_typed_pattern_bit_array_size(self, size);
    }

    fn visit_typed_bit_array_size_int(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) {
        visit_typed_bit_array_size_int(self, location, value);
    }

    fn visit_typed_bit_array_size_variable(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast EcoString,
        constructor: &'ast Option<Box<ValueConstructor>>,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_bit_array_size_variable(self, location, name, constructor, type_);
    }

    fn visit_typed_pattern_assign(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast EcoString,
        pattern: &'ast TypedPattern,
    ) {
        visit_typed_pattern_assign(self, location, name, pattern);
    }

    fn visit_typed_pattern_discard(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast EcoString,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_pattern_discard(self, location, name, type_);
    }

    fn visit_typed_pattern_list(
        &mut self,
        location: &'ast SrcSpan,
        elements: &'ast Vec<TypedPattern>,
        tail: &'ast Option<Box<TypedTailPattern>>,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_pattern_list(self, location, elements, tail, type_);
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_pattern_constructor(
        &mut self,
        location: &'ast SrcSpan,
        name_location: &'ast SrcSpan,
        name: &'ast EcoString,
        arguments: &'ast Vec<CallArg<TypedPattern>>,
        module: &'ast Option<(EcoString, SrcSpan)>,
        constructor: &'ast Inferred<PatternConstructor>,
        spread: &'ast Option<SrcSpan>,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_pattern_constructor(
            self,
            location,
            name_location,
            name,
            arguments,
            module,
            constructor,
            spread,
            type_,
        );
    }

    fn visit_typed_pattern_call_arg(&mut self, arg: &'ast CallArg<TypedPattern>) {
        visit_typed_pattern_call_arg(self, arg);
    }

    fn visit_typed_pattern_tuple(
        &mut self,
        location: &'ast SrcSpan,
        elements: &'ast Vec<TypedPattern>,
    ) {
        visit_typed_pattern_tuple(self, location, elements);
    }

    fn visit_typed_pattern_bit_array(
        &mut self,
        location: &'ast SrcSpan,
        segments: &'ast [TypedPatternBitArraySegment],
    ) {
        visit_typed_pattern_bit_array(self, location, segments);
    }

    fn visit_typed_pattern_bit_array_option(&mut self, option: &'ast BitArrayOption<TypedPattern>) {
        visit_typed_pattern_bit_array_option(self, option);
    }

    fn visit_typed_pattern_string_prefix(
        &mut self,
        location: &'ast SrcSpan,
        left_location: &'ast SrcSpan,
        left_side_assignment: &'ast Option<(EcoString, SrcSpan)>,
        right_location: &'ast SrcSpan,
        left_side_string: &'ast EcoString,
        right_side_assignment: &'ast AssignName,
    ) {
        visit_typed_pattern_string_prefix(
            self,
            location,
            left_location,
            left_side_assignment,
            right_location,
            left_side_string,
            right_side_assignment,
        );
    }

    fn visit_typed_pattern_invalid(&mut self, location: &'ast SrcSpan, type_: &'ast Arc<Type>) {
        visit_typed_pattern_invalid(self, location, type_);
    }

    fn visit_type_ast(
        &mut self,
        node: &'ast TypeAst,
        // The type inferred for this annotation.
        // It might not always be possible to infer one in presence of type
        // errors, so this is optional!
        inferred_type: Option<Arc<Type>>,
    ) {
        visit_type_ast(self, node, inferred_type);
    }

    fn visit_type_ast_constructor(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast TypeAstConstructorName,
        arguments: &'ast [TypeAst],
        inferred_arguments_types: Option<Vec<Arc<Type>>>,
    ) {
        visit_type_ast_constructor(self, location, name, arguments, inferred_arguments_types);
    }

    fn visit_type_ast_fn(
        &mut self,
        location: &'ast SrcSpan,
        arguments: &'ast [TypeAst],
        arguments_types: Option<Vec<Arc<Type>>>,
        return_: &'ast TypeAst,
        return_type: Option<Arc<Type>>,
    ) {
        visit_type_ast_fn(
            self,
            location,
            arguments,
            arguments_types,
            return_,
            return_type,
        );
    }

    fn visit_type_ast_var(&mut self, location: &'ast SrcSpan, name: &'ast EcoString) {
        visit_type_ast_var(self, location, name);
    }

    fn visit_type_ast_tuple(
        &mut self,
        location: &'ast SrcSpan,
        elements: &'ast [TypeAst],
        elements_types: Option<&Vec<Arc<Type>>>,
    ) {
        visit_type_ast_tuple(self, location, elements, elements_types);
    }

    fn visit_type_ast_hole(
        &mut self,
        location: &'ast SrcSpan,
        name: &'ast EcoString,
        type_: Option<Arc<Type>>,
    ) {
        visit_type_ast_hole(self, location, name, type_);
    }

    fn visit_typed_constant(&mut self, constant: &'ast TypedConstant) {
        visit_typed_constant(self, constant);
    }

    fn visit_typed_constant_todo(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        message: &'ast Option<Box<TypedConstant>>,
    ) {
        visit_typed_constant_todo(self, location, type_, message);
    }

    fn visit_typed_constant_int(
        &mut self,
        location: &'ast SrcSpan,
        value: &'ast EcoString,
        int_value: &'ast BigInt,
    ) {
        visit_typed_constant_int(self, location, value, int_value);
    }

    fn visit_typed_constant_float(
        &mut self,
        location: &'ast SrcSpan,
        value: &'ast EcoString,
        float_value: &'ast LiteralFloatValue,
    ) {
        visit_typed_constant_float(self, location, value, float_value);
    }

    fn visit_typed_constant_string(&mut self, location: &'ast SrcSpan, value: &'ast EcoString) {
        visit_typed_constant_string(self, location, value);
    }

    fn visit_typed_constant_tuple(
        &mut self,
        location: &'ast SrcSpan,
        elements: &'ast Vec<TypedConstant>,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_constant_tuple(self, location, elements, type_);
    }

    fn visit_typed_constant_list(
        &mut self,
        location: &'ast SrcSpan,
        elements: &'ast Vec<TypedConstant>,
        type_: &'ast Arc<Type>,
        tail: &'ast Option<Box<TypedConstant>>,
    ) {
        visit_typed_constant_list(self, location, elements, type_, tail);
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_constant_record(
        &mut self,
        location: &'ast SrcSpan,
        arguments_start_position: &'ast u32,
        module: &'ast Option<(EcoString, SrcSpan)>,
        name: &'ast EcoString,
        arguments: &'ast Option<Vec<CallArg<TypedConstant>>>,
        type_: &'ast Arc<Type>,
        field_map: &'ast Inferred<FieldMap>,
        record_constructor: &'ast Option<Box<ValueConstructor>>,
    ) {
        visit_typed_constant_record(
            self,
            location,
            arguments_start_position,
            module,
            name,
            arguments,
            type_,
            field_map,
            record_constructor,
        );
    }

    #[allow(clippy::too_many_arguments)]
    fn visit_typed_constant_record_update(
        &mut self,
        location: &'ast SrcSpan,
        constructor_location: &'ast SrcSpan,
        module: &'ast Option<(EcoString, SrcSpan)>,
        name: &'ast EcoString,
        record: &'ast RecordBeingUpdated<TypedConstant>,
        arguments: &'ast [RecordUpdateArg<TypedConstant>],
        type_: &'ast Arc<Type>,
        field_map: &'ast Inferred<FieldMap>,
    ) {
        visit_typed_constant_record_update(
            self,
            location,
            constructor_location,
            module,
            name,
            record,
            arguments,
            type_,
            field_map,
        );
    }

    fn visit_typed_constant_bit_array(
        &mut self,
        location: &'ast SrcSpan,
        segments: &'ast [TypedConstantBitArraySegment],
    ) {
        visit_typed_constant_bit_array(self, location, segments);
    }

    fn visit_typed_constant_var(
        &mut self,
        location: &'ast SrcSpan,
        module: &'ast Option<(EcoString, SrcSpan)>,
        name: &'ast EcoString,
        constructor: &'ast Option<Box<ValueConstructor>>,
        type_: &'ast Arc<Type>,
    ) {
        visit_typed_constant_var(self, location, module, name, constructor, type_);
    }

    fn visit_typed_constant_string_concatenation(
        &mut self,
        location: &'ast SrcSpan,
        left: &'ast TypedConstant,
        right: &'ast TypedConstant,
    ) {
        visit_typed_constant_string_concatenation(self, location, left, right);
    }

    fn visit_typed_constant_invalid(
        &mut self,
        location: &'ast SrcSpan,
        type_: &'ast Arc<Type>,
        extra_information: &'ast Option<InvalidExpression>,
    ) {
        visit_typed_constant_invalid(self, location, type_, extra_information);
    }
}

fn visit_typed_constant_invalid<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Type,
    _extra_information: &'a Option<InvalidExpression>,
) {
    // No further traversal needed for constant invalid expressions
}

fn visit_typed_constant_string_concatenation<'a, V: Visit<'a> + ?Sized>(
    v: &mut V,
    _location: &'a SrcSpan,
    left: &'a TypedConstant,
    right: &'a TypedConstant,
) {
    v.visit_typed_constant(left);
    v.visit_typed_constant(right);
}

pub fn visit_typed_constant_var<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _module: &'a Option<(EcoString, SrcSpan)>,
    _name: &'a EcoString,
    _constructor: &'a Option<Box<ValueConstructor>>,
    _type_: &'a Arc<Type>,
) {
    // No further traversal needed for constant vars
}

fn visit_typed_constant_bit_array<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _segments: &'a [TypedConstantBitArraySegment],
) {
    // TODO
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_constant_record<'a, V: Visit<'a> + ?Sized>(
    v: &mut V,
    _location: &'a SrcSpan,
    _arguments_start_position: &'a u32,
    _module: &'a Option<(EcoString, SrcSpan)>,
    _name: &'a EcoString,
    arguments: &'a Option<Vec<CallArg<TypedConstant>>>,
    _type_: &'a Arc<Type>,
    _field_map: &'a Inferred<FieldMap>,
    _record_constructor: &'a Option<Box<ValueConstructor>>,
) {
    for argument in arguments.iter().flatten() {
        v.visit_typed_constant(&argument.value);
    }
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_constant_record_update<'a, V: Visit<'a> + ?Sized>(
    v: &mut V,
    _location: &'a SrcSpan,
    _constructor_location: &'a SrcSpan,
    _module: &'a Option<(EcoString, SrcSpan)>,
    _name: &'a EcoString,
    record: &'a RecordBeingUpdated<TypedConstant>,
    arguments: &'a [RecordUpdateArg<TypedConstant>],
    _type_: &'a Arc<Type>,
    _field_map: &'a Inferred<FieldMap>,
) {
    v.visit_typed_constant(&record.base);
    for argument in arguments {
        v.visit_typed_constant(&argument.value);
    }
}

fn visit_typed_constant_list<'a, V: Visit<'a> + ?Sized>(
    v: &mut V,
    _location: &'a SrcSpan,
    elements: &'a Vec<TypedConstant>,
    _type_: &'a Arc<Type>,
    tail: &'a Option<Box<TypedConstant>>,
) {
    for element in elements {
        v.visit_typed_constant(element);
    }
    if let Some(tail) = tail {
        v.visit_typed_constant(tail);
    }
}

fn visit_typed_constant_tuple<'a, V: Visit<'a> + ?Sized>(
    v: &mut V,
    _location: &'a SrcSpan,
    elements: &'a Vec<TypedConstant>,
    _type_: &'a Arc<Type>,
) {
    for element in elements {
        v.visit_typed_constant(element);
    }
}

fn visit_typed_constant_string<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _value: &'a EcoString,
) {
    // No further traversal needed for constant strings
}

fn visit_typed_constant_float<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _value: &'a EcoString,
    _float_value: &'a LiteralFloatValue,
) {
    // No further traversal needed for constant floats
}

fn visit_typed_constant_int<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _value: &'a EcoString,
    _int_value: &'a BigInt,
) {
    // No further traversal needed for constant ints
}

pub fn visit_typed_constant_todo<'a, V: Visit<'a> + ?Sized>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _message: &'a Option<Box<TypedConstant>>,
) {
    // No further traversal needed for constant todos
}

pub fn visit_typed_module<'a, V>(v: &mut V, module: &'a TypedModule)
where
    V: Visit<'a> + ?Sized,
{
    let TypedDefinitions {
        imports,
        constants,
        custom_types,
        type_aliases,
        functions,
    } = &module.definitions;

    for import in imports {
        v.visit_typed_import(import);
    }

    for constant in constants {
        v.visit_typed_module_constant(constant);
    }

    for custom_type in custom_types {
        v.visit_typed_custom_type(custom_type);
    }

    for type_alias in type_aliases {
        v.visit_typed_type_alias(type_alias);
    }

    for function in functions {
        v.visit_typed_function(function);
    }
}

pub fn visit_typed_function<'a, V>(v: &mut V, fun: &'a TypedFunction)
where
    V: Visit<'a> + ?Sized,
{
    for argument in fun.arguments.iter() {
        if let Some(annotation) = &argument.annotation {
            v.visit_type_ast(annotation, Some(argument.type_.clone()));
        }
    }
    if let Some(annotation) = &fun.return_annotation {
        v.visit_type_ast(annotation, Some(fun.return_type.clone()));
    }

    for statement in &fun.body {
        v.visit_typed_statement(statement);
    }
}

pub fn visit_type_ast<'a, V>(v: &mut V, node: &'a TypeAst, type_: Option<Arc<Type>>)
where
    V: Visit<'a> + ?Sized,
{
    match node {
        TypeAst::Constructor(super::TypeAstConstructor {
            location,
            arguments,
            name,
            start_parentheses: _,
        }) => {
            v.visit_type_ast_constructor(
                location,
                name,
                arguments,
                type_.and_then(|type_| type_.constructor_types()),
            );
        }
        TypeAst::Fn(super::TypeAstFn {
            location,
            arguments,
            return_,
        }) => {
            let (arguments_types, return_type) = match type_.and_then(|type_| type_.fn_types()) {
                Some((arguments_types, return_type)) => (Some(arguments_types), Some(return_type)),
                None => (None, None),
            };

            v.visit_type_ast_fn(location, arguments, arguments_types, return_, return_type);
        }
        TypeAst::Var(super::TypeAstVar { location, name }) => {
            v.visit_type_ast_var(location, name);
        }
        TypeAst::Tuple(super::TypeAstTuple { location, elements }) => {
            let elements_types = if let Some(Type::Tuple { elements }) = type_.as_deref() {
                Some(elements)
            } else {
                None
            };
            v.visit_type_ast_tuple(location, elements, elements_types);
        }
        TypeAst::Hole(super::TypeAstHole { location, name }) => {
            v.visit_type_ast_hole(location, name, type_);
        }
    }
}

pub fn visit_type_ast_constructor<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a TypeAstConstructorName,
    arguments: &'a [TypeAst],
    inferred_arguments_types: Option<Vec<Arc<Type>>>,
) where
    V: Visit<'a> + ?Sized,
{
    for (i, argument) in arguments.iter().enumerate() {
        let argument_type = inferred_arguments_types
            .as_ref()
            .and_then(|types| types.get(i));
        v.visit_type_ast(argument, argument_type.cloned());
    }
}

pub fn visit_type_ast_fn<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    arguments: &'a [TypeAst],
    arguments_types: Option<Vec<Arc<Type>>>,
    return_: &'a TypeAst,
    return_type: Option<Arc<Type>>,
) where
    V: Visit<'a> + ?Sized,
{
    for (i, argument) in arguments.iter().enumerate() {
        v.visit_type_ast(
            argument,
            arguments_types
                .as_ref()
                .and_then(|types| types.get(i))
                .cloned(),
        );
    }
    v.visit_type_ast(return_, return_type);
}

pub fn visit_type_ast_var<'a, V>(_v: &mut V, _location: &'a SrcSpan, _name: &'a EcoString)
where
    V: Visit<'a> + ?Sized,
{
    // No further traversal needed for variables
}

pub fn visit_type_ast_tuple<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    elements: &'a [TypeAst],
    elements_types: Option<&Vec<Arc<Type>>>,
) where
    V: Visit<'a> + ?Sized,
{
    for (i, element) in elements.iter().enumerate() {
        let element_type = elements_types
            .as_ref()
            .and_then(|types| types.get(i))
            .cloned();
        v.visit_type_ast(element, element_type);
    }
}

pub fn visit_type_ast_hole<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a EcoString,
    _type_: Option<Arc<Type>>,
) where
    V: Visit<'a> + ?Sized,
{
    // No further traversal needed for holes
}

pub fn visit_typed_module_constant<'a, V>(v: &mut V, constant: &'a TypedModuleConstant)
where
    V: Visit<'a> + ?Sized,
{
    if let Some(annotation) = &constant.annotation {
        v.visit_type_ast(annotation, Some(constant.type_.clone()));
    }
    v.visit_typed_constant(&constant.value);
}

pub fn visit_typed_constant<'a, V: Visit<'a> + ?Sized>(v: &mut V, constant: &'a TypedConstant) {
    match constant {
        super::Constant::Todo {
            location,
            type_,
            message,
        } => v.visit_typed_constant_todo(location, type_, message),
        super::Constant::Int {
            location,
            value,
            int_value,
        } => v.visit_typed_constant_int(location, value, int_value),
        super::Constant::Float {
            location,
            value,
            float_value,
        } => v.visit_typed_constant_float(location, value, float_value),
        super::Constant::String { location, value } => {
            v.visit_typed_constant_string(location, value);
        }
        super::Constant::Tuple {
            location,
            elements,
            type_,
        } => v.visit_typed_constant_tuple(location, elements, type_),
        super::Constant::List {
            location,
            elements,
            type_,
            tail,
        } => v.visit_typed_constant_list(location, elements, type_, tail),
        super::Constant::Record {
            location,
            arguments_start_position,
            module,
            name,
            arguments,
            type_,
            field_map,
            record_constructor,
        } => v.visit_typed_constant_record(
            location,
            arguments_start_position,
            module,
            name,
            arguments,
            type_,
            field_map,
            record_constructor,
        ),
        super::Constant::RecordUpdate {
            location,
            constructor_location,
            module,
            name,
            record,
            arguments,
            type_,
            field_map,
        } => v.visit_typed_constant_record_update(
            location,
            constructor_location,
            module,
            name,
            record,
            arguments,
            type_,
            field_map,
        ),
        super::Constant::BitArray { location, segments } => {
            v.visit_typed_constant_bit_array(location, segments);
        }
        super::Constant::Var {
            location,
            module,
            name,
            constructor,
            type_,
        } => v.visit_typed_constant_var(location, module, name, constructor, type_),
        super::Constant::StringConcatenation {
            location,
            left,
            right,
        } => v.visit_typed_constant_string_concatenation(location, left, right),
        super::Constant::Invalid {
            location,
            type_,
            extra_information,
        } => v.visit_typed_constant_invalid(location, type_, extra_information),
    }
}

pub fn visit_typed_custom_type<'a, V>(v: &mut V, custom_type: &'a TypedCustomType)
where
    V: Visit<'a> + ?Sized,
{
    for record in &custom_type.constructors {
        for argument in &record.arguments {
            v.visit_type_ast(&argument.ast, Some(argument.type_.clone()));
        }
    }
}

pub fn visit_typed_type_alias<'a, V>(v: &mut V, type_alias: &'a TypedTypeAlias)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_type_ast(&type_alias.type_ast, Some(type_alias.type_.clone()));
}

pub fn visit_typed_import<'a, V>(_v: &mut V, _import: &'a TypedImport)
where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_expr<'a, V>(v: &mut V, node: &'a TypedExpr)
where
    V: Visit<'a> + ?Sized,
{
    match node {
        TypedExpr::Int {
            location,
            type_,
            value,
            int_value,
        } => v.visit_typed_expr_int(location, type_, value, int_value),
        TypedExpr::Float {
            location,
            type_,
            value,
            float_value: _,
        } => v.visit_typed_expr_float(location, type_, value),
        TypedExpr::String {
            location,
            type_,
            value,
        } => v.visit_typed_expr_string(location, type_, value),
        TypedExpr::Block {
            location,
            statements,
        } => v.visit_typed_expr_block(location, statements),
        TypedExpr::Pipeline {
            location,
            first_value,
            assignments,
            finally,
            finally_kind,
        } => v.visit_typed_expr_pipeline(location, first_value, assignments, finally, finally_kind),
        TypedExpr::Var {
            location,
            constructor,
            name,
        } => v.visit_typed_expr_var(location, constructor, name),
        TypedExpr::Fn {
            location,
            type_,
            kind,
            arguments,
            body,
            return_annotation,
            purity: _,
        } => v.visit_typed_expr_fn(location, type_, kind, arguments, body, return_annotation),
        TypedExpr::List {
            location,
            type_,
            elements,
            tail,
        } => v.visit_typed_expr_list(location, type_, elements, tail),
        TypedExpr::Call {
            location,
            type_,
            fun,
            arguments,
            open_parenthesis,
        } => v.visit_typed_expr_call(location, type_, fun, arguments, open_parenthesis),
        TypedExpr::BinOp {
            location,
            type_,
            operator,
            operator_start,
            left,
            right,
        } => v.visit_typed_expr_bin_op(location, type_, operator, operator_start, left, right),
        TypedExpr::Case {
            location,
            type_,
            subjects,
            clauses,
            compiled_case,
        } => v.visit_typed_expr_case(location, type_, subjects, clauses, compiled_case),
        TypedExpr::RecordAccess {
            location,
            field_start,
            type_,
            label,
            index,
            record,
            documentation,
        } => v.visit_typed_expr_record_access(
            location,
            field_start,
            type_,
            label,
            index,
            record,
            documentation,
        ),
        TypedExpr::ModuleSelect {
            location,
            field_start,
            type_,
            label,
            module_name,
            module_alias,
            constructor,
        } => v.visit_typed_expr_module_select(
            location,
            field_start,
            type_,
            label,
            module_name,
            module_alias,
            constructor,
        ),
        TypedExpr::Tuple {
            location,
            type_,
            elements,
        } => v.visit_typed_expr_tuple(location, type_, elements),
        TypedExpr::TupleIndex {
            location,
            type_,
            index,
            tuple,
        } => v.visit_typed_expr_tuple_index(location, type_, index, tuple),
        TypedExpr::Todo {
            location,
            message,
            kind,
            type_,
        } => v.visit_typed_expr_todo(location, message, kind, type_),
        TypedExpr::Panic {
            location,
            message,
            type_,
        } => v.visit_typed_expr_panic(location, message, type_),
        TypedExpr::BitArray {
            location,
            type_,
            segments,
        } => v.visit_typed_expr_bit_array(location, type_, segments),
        TypedExpr::RecordUpdate {
            location,
            spread_start,
            type_,
            updated_record,
            updated_record_assigned_name,
            constructor,
            arguments,
        } => v.visit_typed_expr_record_update(
            location,
            spread_start,
            type_,
            updated_record,
            updated_record_assigned_name,
            constructor,
            arguments,
        ),
        TypedExpr::NegateBool { location, value } => {
            v.visit_typed_expr_negate_bool(location, value);
        }
        TypedExpr::NegateInt { location, value } => v.visit_typed_expr_negate_int(location, value),
        TypedExpr::Invalid {
            location,
            type_,
            extra_information,
        } => v.visit_typed_expr_invalid(location, type_, extra_information),
        TypedExpr::Echo {
            location,
            expression,
            message,
            type_,
        } => v.visit_typed_expr_echo(location, type_, expression, message),
        TypedExpr::PositionalAccess { .. } => {}
    }
}

pub fn visit_typed_expr_int<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _string_value: &'a EcoString,
    _int_value: &'a BigInt,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_expr_float<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _value: &'a EcoString,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_expr_string<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _value: &'a EcoString,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_expr_block<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    statements: &'a [TypedStatement],
) where
    V: Visit<'a> + ?Sized,
{
    for statement in statements {
        v.visit_typed_statement(statement);
    }
}

pub fn visit_typed_expr_pipeline<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    first_value: &'a TypedPipelineAssignment,
    assignments: &'a [(TypedPipelineAssignment, PipelineAssignmentKind)],
    finally: &'a TypedExpr,
    _finally_kind: &'a PipelineAssignmentKind,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_pipeline_assignment(first_value);
    for (assignment, _kind) in assignments {
        v.visit_typed_pipeline_assignment(assignment);
    }

    v.visit_typed_expr(finally);
}

pub fn visit_typed_pipeline_assignment<'a, V>(v: &mut V, assignment: &'a TypedPipelineAssignment)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(&assignment.value);
}

pub fn visit_typed_expr_var<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _constructor: &'a ValueConstructor,
    _name: &'a EcoString,
) where
    V: Visit<'a> + ?Sized,
{
    /* TODO */
}

pub fn visit_typed_expr_fn<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    type_: &'a Arc<Type>,
    _kind: &'a FunctionLiteralKind,
    arguments: &'a [TypedArg],
    body: &'a Vec1<TypedStatement>,
    return_annotation: &'a Option<TypeAst>,
) where
    V: Visit<'a> + ?Sized,
{
    for argument in arguments {
        if let Some(annotation) = &argument.annotation {
            v.visit_type_ast(annotation, Some(argument.type_.clone()));
        }
    }
    if let Some(return_) = return_annotation {
        v.visit_type_ast(return_, type_.fn_types().map(|(_, return_)| return_));
    }

    for statement in body {
        v.visit_typed_statement(statement);
    }
}

pub fn visit_typed_expr_list<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    elements: &'a [TypedExpr],
    tail: &'a Option<Box<TypedExpr>>,
) where
    V: Visit<'a> + ?Sized,
{
    for element in elements {
        v.visit_typed_expr(element);
    }

    if let Some(tail) = tail {
        v.visit_typed_expr(tail);
    }
}

pub fn visit_typed_expr_call<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    fun: &'a TypedExpr,
    arguments: &'a [TypedCallArg],
    _arguments_start: &'a Option<u32>,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(fun);
    for argument in arguments {
        v.visit_typed_call_arg(argument);
    }
}

pub fn visit_typed_expr_bin_op<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _operator: &'a BinOp,
    _operator_start: &'a u32,
    left: &'a TypedExpr,
    right: &'a TypedExpr,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(left);
    v.visit_typed_expr(right);
}

pub fn visit_typed_expr_case<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    subjects: &'a [TypedExpr],
    clauses: &'a [TypedClause],
    _compiled_case: &'a CompiledCase,
) where
    V: Visit<'a> + ?Sized,
{
    for subject in subjects {
        v.visit_typed_expr(subject);
    }

    for clause in clauses {
        v.visit_typed_clause(clause);
    }
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_expr_record_access<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _field_start: &'a u32,
    _type_: &'a Arc<Type>,
    _label: &'a EcoString,
    _index: &'a u64,
    record: &'a TypedExpr,
    _documentation: &'a Option<EcoString>,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(record);
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_expr_module_select<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _field_start: &'a u32,
    _type_: &'a Arc<Type>,
    _label: &'a EcoString,
    _module_name: &'a EcoString,
    _module_alias: &'a EcoString,
    _constructor: &'a ModuleValueConstructor,
) where
    V: Visit<'a> + ?Sized,
{
    /* TODO */
}

pub fn visit_typed_expr_tuple<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    elements: &'a [TypedExpr],
) where
    V: Visit<'a> + ?Sized,
{
    for element in elements {
        v.visit_typed_expr(element);
    }
}

pub fn visit_typed_expr_tuple_index<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _index: &'a u64,
    tuple: &'a TypedExpr,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(tuple);
}

pub fn visit_typed_expr_todo<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    message: &'a Option<Box<TypedExpr>>,
    _kind: &'a TodoKind,
    _type_: &'a Arc<Type>,
) where
    V: Visit<'a> + ?Sized,
{
    if let Some(message) = message {
        v.visit_typed_expr(message);
    }
}

pub fn visit_typed_expr_echo<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    expression: &'a Option<Box<TypedExpr>>,
    message: &'a Option<Box<TypedExpr>>,
) where
    V: Visit<'a> + ?Sized,
{
    if let Some(expression) = expression {
        v.visit_typed_expr(expression);
    }
    if let Some(message) = message {
        v.visit_typed_expr(message);
    }
}

pub fn visit_typed_expr_panic<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    message: &'a Option<Box<TypedExpr>>,
    _type_: &'a Arc<Type>,
) where
    V: Visit<'a> + ?Sized,
{
    if let Some(message) = message {
        v.visit_typed_expr(message);
    }
}

pub fn visit_typed_expr_bit_array<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    segments: &'a [TypedExprBitArraySegment],
) where
    V: Visit<'a> + ?Sized,
{
    for segment in segments {
        v.visit_typed_expr_bit_array_segment(segment);
    }
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_expr_record_update<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _spread_start: &'a u32,
    _type_: &'a Arc<Type>,
    updated_record: &'a TypedExpr,
    _updated_record_assigned_name: &'a Option<EcoString>,
    constructor: &'a TypedExpr,
    arguments: &'a [TypedCallArg],
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(constructor);
    v.visit_typed_expr(updated_record);
    for argument in arguments {
        v.visit_typed_call_arg(argument);
    }
}

pub fn visit_typed_expr_negate_bool<'a, V>(v: &mut V, _location: &'a SrcSpan, value: &'a TypedExpr)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(value);
}

pub fn visit_typed_expr_negate_int<'a, V>(v: &mut V, _location: &'a SrcSpan, value: &'a TypedExpr)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(value);
}

pub fn visit_typed_statement<'a, V>(v: &mut V, statement: &'a TypedStatement)
where
    V: Visit<'a> + ?Sized,
{
    match statement {
        Statement::Expression(expression) => v.visit_typed_expr(expression),
        Statement::Assignment(assignment) => v.visit_typed_assignment(assignment),
        Statement::Use(use_) => v.visit_typed_use(use_),
        Statement::Assert(assert) => v.visit_typed_assert(assert),
    }
}

pub fn visit_typed_assignment<'a, V>(v: &mut V, assignment: &'a TypedAssignment)
where
    V: Visit<'a> + ?Sized,
{
    if let Some(annotation) = &assignment.annotation {
        v.visit_type_ast(annotation, Some(assignment.type_()));
    }
    v.visit_typed_expr(&assignment.value);
    v.visit_typed_pattern(&assignment.pattern);
}

pub fn visit_typed_use<'a, V>(v: &mut V, use_: &'a TypedUse)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(&use_.call);
    // TODO: We should also visit the typed patterns!!
}

pub fn visit_typed_assert<'a, V>(v: &mut V, assert: &'a TypedAssert)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(&assert.value);
    if let Some(message) = &assert.message {
        v.visit_typed_expr(message);
    }
}

pub fn visit_typed_call_arg<'a, V>(v: &mut V, arg: &'a TypedCallArg)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(&arg.value);
}

pub fn visit_typed_clause<'a, V>(v: &mut V, clause: &'a TypedClause)
where
    V: Visit<'a> + ?Sized,
{
    for pattern in clause.pattern.iter() {
        v.visit_typed_pattern(pattern);
    }
    for patterns in clause.alternative_patterns.iter() {
        for pattern in patterns {
            v.visit_typed_pattern(pattern);
        }
    }
    if let Some(guard) = &clause.guard {
        v.visit_typed_clause_guard(guard);
    }
    v.visit_typed_expr(&clause.then);
}

pub fn visit_typed_clause_guard<'a, V>(v: &mut V, guard: &'a TypedClauseGuard)
where
    V: Visit<'a> + ?Sized,
{
    match guard {
        super::ClauseGuard::BinaryOperator {
            left,
            right,
            location,
            operator,
            operator_start,
        } => v.visit_typed_clause_guard_bin_op(left, right, operator, operator_start, location),
        super::ClauseGuard::Block { location: _, value } => v.visit_typed_clause_guard(value),
        super::ClauseGuard::Not {
            location: _,
            expression,
        } => v.visit_typed_clause_guard(expression),
        super::ClauseGuard::Var {
            location,
            type_,
            name,
            definition_location,
            origin,
        } => v.visit_typed_clause_guard_var(location, name, type_, definition_location, origin),
        super::ClauseGuard::TupleIndex {
            location,
            index,
            type_,
            tuple,
        } => v.visit_typed_clause_guard_tuple_index(location, index, type_, tuple),
        super::ClauseGuard::FieldAccess {
            label_location,
            index,
            label,
            type_,
            container,
        } => {
            v.visit_typed_clause_guard_field_access(label_location, index, label, type_, container);
        }
        super::ClauseGuard::ModuleSelect {
            location,
            field_start,
            definition_location,
            type_,
            label,
            module_name,
            module_alias,
            literal,
        } => v.visit_typed_clause_guard_module_select(
            location,
            field_start,
            definition_location,
            type_,
            label,
            module_name,
            module_alias,
            literal,
        ),
        super::ClauseGuard::Constant(constant) => v.visit_typed_constant(constant),
        super::ClauseGuard::Invalid { .. } => (),
    }
}

pub fn visit_typed_clause_guard_bin_op<'a, V>(
    v: &mut V,
    left: &'a TypedClauseGuard,
    right: &'a TypedClauseGuard,
    _operator: &'a BinOp,
    _operator_start: &'a u32,
    _location: &'a SrcSpan,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_clause_guard(left);
    v.visit_typed_clause_guard(right);
}

pub fn visit_typed_clause_guard_var<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a EcoString,
    _type_: &'a Arc<Type>,
    _definition_location: &'a SrcSpan,
    _origin: &'a VariableOrigin,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_clause_guard_tuple_index<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _index: &'a u64,
    _type_: &'a Arc<Type>,
    tuple: &'a TypedClauseGuard,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_clause_guard(tuple);
}

pub fn visit_typed_clause_guard_field_access<'a, V>(
    v: &mut V,
    _label_location: &'a SrcSpan,
    _index: &'a Option<u64>,
    _label: &'a EcoString,
    _type_: &'a Arc<Type>,
    container: &'a TypedClauseGuard,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_clause_guard(container);
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_clause_guard_module_select<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _field_start: &'a u32,
    _definition_location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _label: &'a EcoString,
    _module_name: &'a EcoString,
    _module_alias: &'a EcoString,
    _literal: &'a TypedConstant,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_expr_bit_array_segment<'a, V>(v: &mut V, segment: &'a TypedExprBitArraySegment)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_expr(&segment.value);
    for option in &segment.options {
        v.visit_typed_bit_array_option(option);
    }
}

pub fn visit_typed_bit_array_option<'a, V>(v: &mut V, option: &'a BitArrayOption<TypedExpr>)
where
    V: Visit<'a> + ?Sized,
{
    match option {
        BitArrayOption::Bytes { location: _ } => { /* TODO */ }
        BitArrayOption::Int { location: _ } => { /* TODO */ }
        BitArrayOption::Float { location: _ } => { /* TODO */ }
        BitArrayOption::Bits { location: _ } => { /* TODO */ }
        BitArrayOption::Utf8 { location: _ } => { /* TODO */ }
        BitArrayOption::Utf16 { location: _ } => { /* TODO */ }
        BitArrayOption::Utf32 { location: _ } => { /* TODO */ }
        BitArrayOption::Utf8Codepoint { location: _ } => { /* TODO */ }
        BitArrayOption::Utf16Codepoint { location: _ } => { /* TODO */ }
        BitArrayOption::Utf32Codepoint { location: _ } => { /* TODO */ }
        BitArrayOption::Signed { location: _ } => { /* TODO */ }
        BitArrayOption::Unsigned { location: _ } => { /* TODO */ }
        BitArrayOption::Big { location: _ } => { /* TODO */ }
        BitArrayOption::Little { location: _ } => { /* TODO */ }
        BitArrayOption::Native { location: _ } => { /* TODO */ }
        BitArrayOption::Size {
            location: _,
            value,
            short_form: _,
        } => {
            v.visit_typed_expr(value);
        }
        BitArrayOption::Unit {
            location: _,
            value: _,
        } => { /* TODO */ }
    }
}

pub fn visit_typed_pattern<'a, V>(v: &mut V, pattern: &'a TypedPattern)
where
    V: Visit<'a> + ?Sized,
{
    match pattern {
        Pattern::Int {
            location,
            value,
            int_value: _,
        } => v.visit_typed_pattern_int(location, value),
        Pattern::Float {
            location,
            value,
            float_value: _,
        } => v.visit_typed_pattern_float(location, value),
        Pattern::String { location, value } => v.visit_typed_pattern_string(location, value),
        Pattern::Variable {
            location,
            name,
            type_,
            origin,
        } => v.visit_typed_pattern_variable(location, name, type_, origin),
        Pattern::BitArraySize(size) => v.visit_typed_pattern_bit_array_size(size),
        Pattern::Assign {
            location,
            name,
            pattern,
        } => v.visit_typed_pattern_assign(location, name, pattern),
        Pattern::Discard {
            location,
            name,
            type_,
        } => v.visit_typed_pattern_discard(location, name, type_),
        Pattern::List {
            location,
            elements,
            tail,
            type_,
        } => v.visit_typed_pattern_list(location, elements, tail, type_),
        Pattern::Constructor {
            location,
            name_location,
            name,
            arguments,
            module,
            constructor,
            spread,
            type_,
        } => v.visit_typed_pattern_constructor(
            location,
            name_location,
            name,
            arguments,
            module,
            constructor,
            spread,
            type_,
        ),
        Pattern::Tuple { location, elements } => v.visit_typed_pattern_tuple(location, elements),
        Pattern::BitArray { location, segments } => {
            v.visit_typed_pattern_bit_array(location, segments);
        }
        Pattern::StringPrefix {
            location,
            left_location,
            left_side_assignment,
            right_location,
            left_side_string,
            right_side_assignment,
        } => v.visit_typed_pattern_string_prefix(
            location,
            left_location,
            left_side_assignment,
            right_location,
            left_side_string,
            right_side_assignment,
        ),
        Pattern::Invalid { location, type_ } => v.visit_typed_pattern_invalid(location, type_),
    }
}

fn visit_typed_pattern_int<'a, V>(_v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString)
where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_float<'a, V>(_v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString)
where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_string<'a, V>(_v: &mut V, _location: &'a SrcSpan, _value: &'a EcoString)
where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_variable<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a EcoString,
    _type_: &'a Arc<Type>,
    _origin: &'a VariableOrigin,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_bit_array_size<'a, V>(v: &mut V, size: &'a TypedBitArraySize)
where
    V: Visit<'a> + ?Sized,
{
    match size {
        BitArraySize::Int {
            location,
            value,
            int_value: _,
        } => v.visit_typed_bit_array_size_int(location, value),
        BitArraySize::Variable {
            location,
            name,
            constructor,
            type_,
        } => v.visit_typed_bit_array_size_variable(location, name, constructor, type_),
        BitArraySize::BinaryOperator { left, right, .. } => {
            v.visit_typed_pattern_bit_array_size(left);
            v.visit_typed_pattern_bit_array_size(right);
        }
        BitArraySize::Block { inner, .. } => v.visit_typed_pattern_bit_array_size(inner),
    }
}

pub fn visit_typed_bit_array_size_int<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _value: &'a EcoString,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_bit_array_size_variable<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a EcoString,
    _constructor: &'a Option<Box<ValueConstructor>>,
    _type_: &'a Arc<Type>,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_assign<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a EcoString,
    pattern: &'a TypedPattern,
) where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_pattern(pattern);
}

pub fn visit_typed_pattern_discard<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _name: &'a EcoString,
    _type_: &'a Arc<Type>,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_list<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    elements: &'a Vec<TypedPattern>,
    tail: &'a Option<Box<TypedTailPattern>>,
    _type_: &'a Arc<Type>,
) where
    V: Visit<'a> + ?Sized,
{
    for element in elements {
        v.visit_typed_pattern(element);
    }
    if let Some(tail) = tail {
        v.visit_typed_pattern(&tail.pattern);
    }
}

#[allow(clippy::too_many_arguments)]
pub fn visit_typed_pattern_constructor<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    _name_location: &'a SrcSpan,
    _name: &'a EcoString,
    arguments: &'a Vec<CallArg<TypedPattern>>,
    _module: &'a Option<(EcoString, SrcSpan)>,
    _constructor: &'a Inferred<PatternConstructor>,
    _spread: &'a Option<SrcSpan>,
    _type_: &'a Arc<Type>,
) where
    V: Visit<'a> + ?Sized,
{
    for argument in arguments {
        v.visit_typed_pattern_call_arg(argument);
    }
}

pub fn visit_typed_pattern_call_arg<'a, V>(v: &mut V, argument: &'a CallArg<TypedPattern>)
where
    V: Visit<'a> + ?Sized,
{
    v.visit_typed_pattern(&argument.value);
}

pub fn visit_typed_pattern_tuple<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    elements: &'a Vec<TypedPattern>,
) where
    V: Visit<'a> + ?Sized,
{
    for element in elements {
        v.visit_typed_pattern(element);
    }
}

pub fn visit_typed_pattern_bit_array<'a, V>(
    v: &mut V,
    _location: &'a SrcSpan,
    segments: &'a [TypedPatternBitArraySegment],
) where
    V: Visit<'a> + ?Sized,
{
    for segment in segments {
        v.visit_typed_pattern(&segment.value);
        for option in segment.options.iter() {
            v.visit_typed_pattern_bit_array_option(option);
        }
    }
}

pub fn visit_typed_pattern_bit_array_option<'a, V>(
    v: &mut V,
    option: &'a BitArrayOption<TypedPattern>,
) where
    V: Visit<'a> + ?Sized,
{
    match option {
        BitArrayOption::Bytes { location: _ } => { /* TODO */ }
        BitArrayOption::Int { location: _ } => { /* TODO */ }
        BitArrayOption::Float { location: _ } => { /* TODO */ }
        BitArrayOption::Bits { location: _ } => { /* TODO */ }
        BitArrayOption::Utf8 { location: _ } => { /* TODO */ }
        BitArrayOption::Utf16 { location: _ } => { /* TODO */ }
        BitArrayOption::Utf32 { location: _ } => { /* TODO */ }
        BitArrayOption::Utf8Codepoint { location: _ } => { /* TODO */ }
        BitArrayOption::Utf16Codepoint { location: _ } => { /* TODO */ }
        BitArrayOption::Utf32Codepoint { location: _ } => { /* TODO */ }
        BitArrayOption::Signed { location: _ } => { /* TODO */ }
        BitArrayOption::Unsigned { location: _ } => { /* TODO */ }
        BitArrayOption::Big { location: _ } => { /* TODO */ }
        BitArrayOption::Little { location: _ } => { /* TODO */ }
        BitArrayOption::Native { location: _ } => { /* TODO */ }
        BitArrayOption::Size {
            location: _,
            value,
            short_form: _,
        } => {
            v.visit_typed_pattern(value);
        }
        BitArrayOption::Unit {
            location: _,
            value: _,
        } => { /* TODO */ }
    }
}

pub fn visit_typed_pattern_string_prefix<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _left_location: &'a SrcSpan,
    _left_side_assignment: &'a Option<(EcoString, SrcSpan)>,
    _right_location: &'a SrcSpan,
    _left_side_string: &'a EcoString,
    _right_side_assignment: &'a AssignName,
) where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_pattern_invalid<'a, V>(_v: &mut V, _location: &'a SrcSpan, _type_: &'a Arc<Type>)
where
    V: Visit<'a> + ?Sized,
{
}

pub fn visit_typed_expr_invalid<'a, V>(
    _v: &mut V,
    _location: &'a SrcSpan,
    _type_: &'a Arc<Type>,
    _extra_information: &'a Option<InvalidExpression>,
) where
    V: Visit<'a> + ?Sized,
{
}
