// Copyright (C) 2019-2026 Provable 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 <https://www.gnu.org/licenses/>.

use std::fmt::{Debug, Display};

// TODO: Consolidate errors.

create_messages!(
    TypeCheckerError,
    code_mask: 2000i32,
    code_prefix: "TYC",

    /// For when the type checker encountered an invalid assignment target.
    @formatted
    invalid_assignment_target {
        args: (target: impl Display),
        msg: format!("Invalid assignment target: {target}."),
        help: Some("Valid assignment targets are identifiers, tuple accesses, array accesses, and struct accesses.".to_string()),
    }

    /// For when the user tries to assign to a const input.
    @formatted
    cannot_assign_to_const_input {
        args: (input: impl Display),
        msg: format!(
            "Cannot assign to const input `{input}`",
        ),
        help: None,
    }

    /// For when the user tries to assign to a const input.
    @formatted
    cannot_assign_to_const_var {
        args: (var: impl Display),
        msg: format!(
            "Cannot assign to const variable `{var}`",
        ),
        help: None,
    }

    /// For when the user tries to assign to a const input.
    @formatted
    type_should_be {
        args: (type_: impl Display, expected: impl Display),
        msg: format!(
            "Expected type `{expected}` but type `{type_}` was found",
        ),
        help: None,
    }

    /// For when the type checker cannot determine the type of an expression.
    @formatted
    could_not_determine_type {
        args: (expr: impl Display),
        msg: format!(
            "Could not determine the type of `{expr}`",
        ),
        help: Some("Consider using explicit type annotations.".into()),
    }

    /// For when the user tries to return a unknown variable.
    @formatted
    unknown_sym {
        args: (kind: impl Display, sym: impl Display),
        msg: format!(
            "Unknown {kind} `{sym}`",
        ),
        help: None,
    }

    /// For when the user tries calls a function with the incorrect number of args.
    @formatted
    incorrect_num_args_to_call {
        args: (expected: impl Display, received: impl Display),
        msg: format!(
            "Call expected `{expected}` args, but got `{received}`",
        ),
        help: None,
    }

    /// For when one of the following types was expected.
    @formatted
    expected_one_type_of {
        args: (expected: impl Display, received: impl Display),
        msg: format!(
            "Expected one type from `{expected}`, but got `{received}`",
        ),
        help: None,
    }

    /// For when an integer is not in a valid range.
    /// Also repurposing for when a group value is not valid.
    @formatted
    invalid_int_value {
        args: (value: impl Display, type_: impl Display),
        msg: format!(
            "The value {value} is not a valid `{type_}`",
        ),
        help: None,
    }

    /// For when an invalid core function is used.
    @formatted
    invalid_core_function {
        args: (struct_: impl Display, function: impl Display),
        msg: format!(
            "{struct_}::{function} is not a valid core function.",
        ),
        help: None,
    }

    /// For when a struct is created with the same name as a core type.
    @formatted
    core_type_name_conflict {
        args: (type_: impl Display),
        msg: format!(
            "The type {type_} is a reserved core type name.",
        ),
        help: None,
    }

    /// For when a function doesn't have a return statement.
    @formatted
    function_has_no_return {
        args: (func: impl Display),
        msg: format!(
            "The function {func} has no return statement.",
        ),
        help: None,
    }

    /// For when the user tries initialize a composite with the incorrect number of args.
    @formatted
    incorrect_num_composite_members {
        args: (expected: impl Display, received: impl Display),
        msg: format!(
            "Composite expected `{expected}` members, but got `{received}`",
        ),
        help: None,
    }

    /// For when the user is missing a composite member during initialization.
    @formatted
    missing_composite_member {
        args: (composite: impl Display, member: impl Display),
        msg: format!(
            "Composite initialization expression for `{composite}` is missing member `{member}`.",
        ),
        help: None,
    }

    /// An invalid access call is made e.g., `SHA256::hash()
    @formatted
    invalid_core_function_call {
        args: (expr: impl Display),
        msg: format!(
            "{expr} is not a valid core function call."
        ),
        help: None,
    }

    /// Attempted to define more that one struct member with the same name.
    @formatted
    duplicate_struct_member {
        args: (member_name: impl Display),
        msg: format!(
            "Struct field `{member_name}` is already declared."
        ),
        help: None,
    }

    /// Attempted to define more that one record variable with the same name.
    @formatted
    duplicate_record_variable {
        args: (variable_name: impl Display),
        msg: format!(
            "Record variable `{variable_name}` is already declared."
        ),
        help: None,
    }

    /// Attempted to access an invalid struct.
    @formatted
    undefined_type {
        args: (type_: impl Display),
        msg: format!(
            "The type `{type_}` is not found in the current scope."
        ),
        help: Some("If you are using an external type, make sure to preface with the program name. Ex: `credits.aleo::credits` instead of `credits`".to_string()),
    }

    /// Attempted to access an invalid composite variable.
    @formatted
    invalid_composite_variable {
        args: (variable: impl Display, composite: impl Display),
        msg: format!(
            "Variable {variable} is not a member of {composite}."
        ),
        help: None,
    }

    @formatted
    required_record_variable {
        args: (name: impl Display, type_: impl Display),
        msg: format!("The `record` type requires the variable `{name}: {type_}`."),
        help: None,
    }

    @formatted
    record_var_wrong_type {
        args: (name: impl Display, type_: impl Display),
        msg: format!("The field `{name}` in a `record` must have type `{type_}`."),
        help: None,
    }

    @formatted
    compare_address {
        args: (operator: impl Display),
        msg: format!("Comparison `{operator}` is not supported for the address type."),
        help: None,
    }

    @formatted
    incorrect_tuple_length {
        args: (expected: impl Display, actual: impl Display),
        msg: format!("Expected a tuple of length `{expected}` found length `{actual}`"),
        help: None,
    }

    @formatted
    invalid_tuple {
        args: (),
        msg: "Tuples must be explicitly typed in Leo".to_string(),
        help: Some("The function definition must match the function return statement".to_string()),
    }

    @formatted
    tuple_out_of_range {
        args: (index: impl Display, length: impl Display),
        msg: format!("Tuple index `{index}` out of range for a tuple with length `{length}`"),
        help: None,
    }

    @formatted
    unreachable_code_after_return {
        args: (),
        msg: format!("Cannot reach the following statement."),
        help: Some("Remove the unreachable code.".to_string()),
    }

    @formatted
    loop_body_contains_return {
        args: (),
        msg: format!("Loop body contains a return statement or always returns."),
        help: Some("Remove the code in the loop body that always returns.".to_string()),
    }

    // TODO: Consider emitting a warning instead of an error.
    @formatted
    unknown_annotation {
        args: (annotation: impl Display),
        msg: format!("Unknown annotation: `{annotation}`."),
        help: None,
    }

    @formatted
    regular_function_inputs_cannot_have_modes {
        args: (),
        msg: format!("Regular fns cannot have modes associated with their inputs."),
        help: Some("Consider removing the mode or moving this function inside the `program` block to make it an entry point fn.".to_string()),
    }

    // Not currently used
    @formatted
    final_function_input_cannot_be_private {
        args: (),
        msg: format!("Final fns cannot have private inputs."),
        help: Some("Use a `public` modifier to the input variable declaration or remove the visibility modifier entirely.".to_string()),
    }

    @formatted
    struct_or_record_cannot_contain_record {
        args: (parent: impl Display, child: impl Display),
        msg: format!("A struct or record cannot contain another record."),
        help: Some(format!("Remove the record `{child}` from `{parent}`.")),
    }

    @formatted
    invalid_mapping_type {
        args: (component: impl Display, type_: impl Display),
        msg: format!("A mapping's {component} cannot be a {type_}"),
        help: None,
    }

    @formatted
    final_fn_input_must_be_public {
        args: (),
        msg: format!("An input to an final fn must be public."),
        help: Some("Use a `public` modifier to the input variable declaration or remove the visibility modifier entirely.".to_string()),
    }

    // Not currently used
    @formatted
    finalize_output_mode_must_be_public {
        args: (),
        msg: format!("An output from a final block must be public."),
        help: Some("Use a `public` modifier to the output type declaration or remove the visibility modifier entirely.".to_string()),
    }

    @formatted
    invalid_operation_outside_finalize {
        args: (operation: impl Display),
        msg: format!("`{operation}` must be inside a `final fn` or a `final` block."),
        help: None,
    }

    @formatted
    loop_body_contains_final {
        args: (),
        msg: format!("Loop body contains a final context."),
        help: Some("Remove the final context.".into()),
    }

    @formatted
    missing_return {
        args: (),
        msg: format!("Function must return a value."),
        help: None,
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    finalize_block_must_not_be_empty {
        args: (),
        msg: format!("A final block cannot be empty."),
        help: None,
    }

    @formatted
    cannot_have_constant_output_mode {
        args: (),
        msg: format!("A returned value cannot be a constant."),
        help: None,
    }

    @formatted
    entry_point_fn_inputs_cannot_be_const {
        args: (),
        msg: format!("Entry point functions cannot have constant inputs."),
        help: None,
    }

    @formatted
    invalid_self_access {
        args: (),
        msg: format!("The allowed accesses to `self` are `self.{{caller, checksum, edition, program_owner, signer}}`."),
        help: None,
    }

    @formatted
    invalid_type {
        args: (type_: impl Display),
        msg: format!("Invalid type `{type_}`"),
        help: None,
    }

    @formatted
    can_only_call_inline_function {
        args: (kind: impl Display),
        msg: format!("Only regular fns can be called from {kind}."),
        help: None,
    }

    @formatted
    cannot_invoke_call_to_local_entry_point_fn {
        args: (),
        msg: format!("Cannot call a local entry point fn from an entry point fn."),
        help: None,
    }

    @formatted
    loop_bound_must_be_a_literal {
        args: (),
        msg: format!("Loop bound must be a literal."),
        help: None,
    }

    @formatted
    strings_are_not_supported {
        args: (),
        msg: format!("Strings are not yet supported."),
        help: None,
    }

    @formatted
    imported_program_cannot_import_program {
        args: (),
        msg: format!("An imported program cannot import another program."),
        help: None,
    }

    @formatted
    too_many_entry_points {
        args: (max: impl Display),
        msg: format!("The number of entry point fns exceeds the maximum. snarkVM allows up to {max} entry point fns within a single program."),
        help: None,
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    assign_unit_expression_to_variable {
        args: (),
        msg: format!("Cannot assign a unit expression to a variable."),
        help: None,
    }

    @formatted
    nested_tuple_type {
        args: (),
        msg: format!("A tuple type cannot contain a tuple."),
        help: None,
    }

    @formatted
    composite_data_type_cannot_contain_tuple {
        args: (data_type: impl Display),
        msg: format!("A {data_type} cannot contain a tuple."),
        help: None,
    }

    @formatted
    function_cannot_take_tuple_as_input {
        args: (),
        msg: format!("A function cannot take in a tuple as input."),
        help: None,
    }

    @formatted
    nested_tuple_expression {
        args: (),
        msg: format!("A tuple expression cannot contain another tuple expression."),
        help: None,
    }

    @formatted
    expression_statement_must_be_function_call {
        args: (),
        msg: format!("An expression statement must be a function call."),
        help: None,
    }

    @formatted
    lhs_tuple_element_must_be_an_identifier {
        args: (),
        msg: format!("Tuples on the left-hand side of a `DefinitionStatement` can only contain identifiers."),
        help: None,
    }

    @formatted
    lhs_must_be_identifier_or_tuple {
        args: (),
        msg: format!("The left-hand side of a `DefinitionStatement` can only be an identifier or tuple. Note that a tuple must contain at least two elements."),
        help: None,
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    unit_expression_only_in_return_statements {
        args: (),
        msg: format!("Unit expressions can only be used in return statements."),
        help: None,
    }

    @formatted
    function_cannot_input_or_output_a_record {
        args: (),
        msg: format!("Only entry point fns can have a record as input or output."),
        help: None,
    }

    @backtraced
    cyclic_composite_dependency {
        args: (path: Vec<impl Display>),
        msg: {
            let path_string = path.into_iter().map(|name| format!("`{name}`")).collect::<Vec<String>>().join(" --> ");
            format!("Cyclic dependency between composites: {path_string}")
        },
        help: None,
    }

    @backtraced
    cyclic_function_dependency {
        args: (path: Vec<impl Display>),
        msg: {
            let path_string = path.into_iter().map(|name| format!("`{name}`")).collect::<Vec<String>>().join(" --> ");
            format!("Cyclic dependency between functions: {path_string}")
        },
        help: None,
    }

    @formatted
    struct_cannot_have_member_mode {
        args: (),
        msg: format!("A struct cannot have a member with mode `constant`, `private`, or `public`."),
        help: None,
    }

    // Unused
    @formatted
    cannot_call_external_inline_function {
        args: (),
        msg: format!("Cannot call an external inline function."),
        help: None,
    }

    @formatted
    too_many_mappings {
        args: (max: impl Display),
        msg: format!("The number of mappings exceeds the maximum. snarkVM allows up to {max} mappings within a single program."),
        help: None,
    }

    /// A call to an invalid associated constant is made e.g., `bool::MAX`
    @formatted
    invalid_associated_constant {
        args: (expr: impl Display),
        msg: format!(
            "{expr} is not a valid associated constant."
        ),
        help: None,
    }

    /// For when an invalid core constant is called.
    @formatted
    invalid_core_constant {
        args: (type_: impl Display, constant: impl Display),
        msg: format! (
            "{type_}::{constant} is not a valid core constant.",
        ),
        help: None,
    }

    /// For when an invalid field of block is called.
    @formatted
    invalid_block_access {
        args: (),
        msg: format!("The allowed accesses to `block` are `block.height` and `block.timestamp`."),
        help: None,
    }

    @formatted
    invalid_operation_inside_finalize {
        args: (operation: impl Display),
        msg: format!("`{operation}` is not a valid operand in a finalization context."),
        help: None,
    }

    @formatted
    operation_must_be_in_final_block_or_function {
        args: (),
        msg: "This operation can only be used in a final fn, a final block, or script.".to_string(),
        help: None,
    }

    @formatted
    loop_range_decreasing {
        args: (),
        msg: format!("The loop range must be increasing."),
        help: None,
    }

    @formatted
    loop_bound_type_mismatch {
        args: (),
        msg: format!("The loop bounds must be same type"),
        help: None,
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    const_declaration_must_be_literal_or_tuple_of_literals {
        args: (),
        msg: format!("The value of a const declaration must be a literal"),
        help: None,
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    loop_bound_must_be_literal_or_const {
        args: (),
        msg: format!("The loop bound must be a literal or a const"),
        help: None,
    }

    @formatted
    incorrect_num_tuple_elements {
        args: (identifiers: impl Display, types: impl Display),
        msg: format!("Expected a tuple with {types} elements, found one with {identifiers} elements"),
        help: None,
    }

    @formatted
    const_declaration_can_only_have_one_binding {
        args: (),
        msg: format!("A constant declaration statement can only bind a single value"),
        help: None,
    }

    @formatted
    stub_functions_must_not_be_inlines {
        args: (),
        msg: format!("Function stubs must be entry point fns or regular fns"),
        help: None,
    }

    @formatted
    stub_functions_must_be_empty {
        args: (),
        msg: format!("Functions stubs must be empty"),
        help: None,
    }

    @formatted
    array_empty {
        args: (),
        msg: format!("An array cannot be empty"),
        help: None,
    }

    @formatted
    array_too_large {
        args: (size: impl Display, max: impl Display),
        msg: format!("An array cannot have more than {max} elements, found one with {size} elements"),
        help: None,
    }

    @formatted
    array_element_cannot_be_tuple {
        args: (),
        msg: format!("An array cannot have a tuple as an element type"),
        help: None,
    }

    @formatted
    array_element_cannot_be_record {
        args: (),
        msg: format!("An array cannot have a record as an element type"),
        help: None,
    }

    @formatted
    stubs_cannot_have_non_record_structs {
        args: (),
        msg: format!("Stubs can only have records, entry point fns, regular fns, mappings and imports -- found non-record struct"),
        help: None,
    }

    @formatted
    stubs_cannot_have_const_declarations {
        args: (),
        msg: format!("Stubs can only have records, entry point fns, regular fns, mappings and imports -- found const declaration"),
        help: None,
    }

    @formatted
    stub_name_mismatch {
        args: (stub_name: impl Display, program_name: impl Display),
        msg: format!("`stub` name `{stub_name}` does not match program name `{program_name}`"),
        help: Some("Check that the name you used as a dependency in program.json matches the name you used to import the program in the main leo file.".to_string()),
    }

    @formatted
    no_entry_points {
        args: (),
        msg: "A program must have at least one entry point fn.".to_string(),
        help: None,
    }

    @formatted
    cannot_define_external_struct {
        args: (struct_: impl Display),
        msg: format!("Cannot define external struct `{struct_}`"),
        help: Some("Copy the external definition of the struct into the current program, and then define without the `.aleo` extension.".to_string()),
    }

    @formatted
    struct_definitions_dont_match {
        args: (struct_: impl Display, program_1: impl Display, program_2: impl Display),
        msg: format!("The definition for `{struct_}` in program `{program_1}.aleo` does not match the definition in program `{program_2}.aleo`"),
        help: Some("Check that the struct definition in the current program matches the definition in the imported program.".to_string()),
    }

    @formatted
    entry_point_fn_final_invalid_output {
        args: (),
        msg: "An entry point fn returning Final must return a Final as the final output, and in no other position return a Final.".to_string(),
        help: Some("Example: `fn foo() -> (u8, bool, Final) {...}`".to_string()),
    }

    @formatted
    must_propagate_all_finals {
        args: (never_propagated: impl Display),
        msg: format!("All Finals generated from external calls must be inserted into a final block in the order they were called. The following were never were: {never_propagated}"),
        help: Some("Example: `fn foo() -> Final { let a: Final = b.aleo::bar(); return final {{ a.run(); }}; }`".to_string()),
    }

    // deprecated
    @formatted
    missing_final_operation_in_entry_point_with_final {
        args: (),
        msg: "An entry point fn returning `Final` must contain at least one finalization operation — either a call to a `final fn` or a `final` block.".to_string(),
        help: Some("Example: `fn foo() -> Final { return final {{ /* on-chain code */ }}; }`".to_string()),
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    final_function_input_length_mismatch {
        args: (expected: impl Display, received: impl Display),
        msg: format!("Expected `{expected}` inputs, but got `{received}`"),
        help: Some("Check that the number of arguments passed in are the same as the number in the function signature. Ex: `final fn foo(a: u8, b: u8)` has two input arguments.".to_string()),
    }

    @formatted
    invalid_final_access {
        args: (num: impl Display, len: impl Display),
        msg: format!(
            "Cannot access argument `{num}` from Final. The Final only has `{len}` arguments."
        ),
        help: None,
    }

    @formatted
    final_access_must_be_number {
        args: (name: impl Display),
        msg: format!("Final access must be a number not `{name}`."),
        help: Some(" Final arguments must be addressed by their index. Ex: `f.1.3`.".to_string()),
    }

    // TODO: This error is deprecated. Remove.
    @formatted
    no_path_awaits_all_finals_exactly_once {
        args: (num_total_paths: impl Display),
        msg: format!("Finals must be run exactly once. Out of `{num_total_paths}`, there does not exist a single path in which all Finals are run exactly once."),
        help: Some("Ex: for `f: Final` call `f.run()` to run a Final. Remove duplicate Final run redundancies, and add Final runs for un-run Finals.".to_string()),
    }

    // TODO: This error is deprecated. Remove.
    @formatted
    final_runs_missing {
        args: (unawaited: impl Display),
        msg: format!("The following Finals were never run: {unawaited}"),
        help: Some("Ex: for `f: Final` call `f.run()` to run a Final.".to_string()),
    }

    @formatted
    cannot_reassign_final_variable {
        args: (var: impl Display),
        msg: format!("Cannot reassign variable `{var}` since it has type Final."),
        help: Some("Finals can only be defined as the result of final fn calls or final blocks.".to_string()),
    }

    // TODO: This error is deprecated. Remove.
    @formatted
    invalid_await_call {
        args: (),
        msg: "Not a valid run call.".to_string(),
        help: Some("Ex: for `f: Final` call `f.run()` to run a Final.".to_string()),
    }

    @formatted
    can_only_run_one_final_at_a_time {
        args: (),
        msg: "Must run exactly one Final at a time".to_string(),
        help: Some("Ex: for `f: Final` call `f.run()` to run a Final.".to_string()),
    }

    // TODO: This error is deprecated. Remove.
    @formatted
    expected_final {
        args: (type_: impl Display),
        msg: format!("Expected a Final, but found `{type_}`"),
        help: Some("Only Finals can be run.".to_string()),
    }

    @formatted
    invalid_method_call {
        args: (),
        msg: "Not a valid method call.".to_string(),
        help: Some("For a `f: Final`, call the associated method `f.run()`.".to_string()),
    }

    // deprecated
    @formatted
    final_call_in_conditional {
        args: (),
        msg: "Cannot call a final fn in a conditional block.".to_string(),
        help: Some("Move the final fn call outside of the conditional block.".to_string()),
    }

    // deprecated
    @formatted
    must_call_final_fn_once {
        args: (),
        msg: "Must call exactly one local final fn per entry point fn.".to_string(),
        help: Some("Move the final fn call outside of the entry point fn.".to_string()),
    }

    // deprecated
    @formatted
    final_call_can_only_be_done_from_final_entry_point {
        args: (),
        msg: "Can only call a final fn from an entry point fn returning Final.".to_string(),
        help: Some("Move the final fn call inside an entry point fn that returns Final.".to_string()),
    }

    @formatted
    external_call_after_final {
        args: (kind: impl Display),
        msg: format!("External calls must appear before the local final {kind}."),
        help: Some(format!("Reorder your code so the external entry point fn call happens before the local final {kind}.")),
    }

    @formatted
    unknown_final_consumed {
        args: (fin: impl Display),
        msg: format!("Unknown Final consumed: `{fin}`"),
        help: Some("Make sure the Final is defined and consumed exactly once.".to_string()),
    }

    @formatted
    not_all_finals_consumed {
        args: (unconsumed: impl Display),
        msg: format!("Not all Finals were consumed: {unconsumed}"),
        help: Some("Make sure all Finals are consumed exactly once. Consume by passing to a final fn call or final block.".to_string()),
    }

    @formatted
    entry_point_missing_final_to_return {
        args: (),
        msg: "An entry point fn returning Final must return a Final.".to_string(),
        help: Some("Instantiate a final block inside the entry point fn body so that there is a Final to return.".to_string()),
    }

    @formatted
    final_fn_cannot_return_value {
        args: (),
        msg: "A final fn is not allowed to return a value.".to_string(),
        help: Some("Remove an output type in the function signature, and remove the return statement from the function. Note that the Final returned by final fn is automatically inferred, and must not be explicitly written.".to_string()),
    }

    // deprecated
    @formatted
    return_type_of_finalize_function_is_final {
        args: (),
        msg: "The output of a final fn must be assigned to a `Final` type.".to_string(),
        help: None,
    }

    @formatted
    cannot_modify_external_container {
        args: (operation: impl Display, kind: impl Display),
        msg: format!("Cannot use operation `{operation}` on external {kind}s."),
        help: Some(format!("The only valid operations on external {kind}s are {}.",
            if kind.to_string() == "vector" {
                "`get` and `len`"
            } else if kind.to_string() == "mapping" {
                "`contains`, `get`, and `get_or_use`"
            } else {
                panic!("no other kinds expected here")
            }
        )),
    }

    @formatted
    final_cannot_assign_outside_conditional {
        args: (variable: impl Display, kind: impl Display),
        msg: format!("Cannot re-assign to `{variable}` from a conditional scope to an outer scope in a final {kind}."),
        help: Some("This is a fundamental restriction that can often be avoided by using a ternary operator `?` or re-declaring the variable in the current scope. In the future, ARC XXXX (https://github.com/ProvableHQ/ARCs) will support more complex assignments in final fns.".to_string()),
    }

    @formatted
    only_entry_point_can_return_final {
        args: (),
        msg: "Only entry point fns can return a Final.".to_string(),
        help: Some("Move this function inside the `program` block to make it an entry point fn.".to_string()),
    }

    // deprecated
    @formatted
    final_fn_not_found {
        args: (name: impl Display),
        msg: format!("The final fn `{name}` does not exist."),
        help: Some(format!("Ensure that `{name}` is defined as a `final fn` in the current program.")),
    }

    @formatted
    empty_struct {
        args: (),
        msg: "A struct must have at least one member.".to_string(),
        help: None,
    }

    @formatted
    empty_function_arglist {
        args: (),
        msg: format!("Cannot define a function with no parameters."),
        help: None,
    }

    @formatted
    composite_data_type_cannot_contain_final {
        args: (data_type: impl Display),
        msg: format!("A {data_type} cannot contain a Final."),
        help: None,
    }

    @formatted
    array_element_cannot_be_final {
        args: (),
        msg: format!("An array cannot have a Final as an element type."),
        help: None,
    }

    @formatted
    no_final_parameters {
        args: (),
        msg: format!("Finals may only appear as parameters to final fn."),
        help: None,
    }

    /// For when the user tries to assign to a const input.
    ///
    /// This is a replacement for `type_should_be` with a slightly better message.
    @formatted
    type_should_be2 {
        args: (type_: impl Display, expected: impl Display),
        msg: format!(
            "Expected {expected} but type `{type_}` was found.",
        ),
        help: None,
    }

    @formatted
    ternary_branch_mismatch {
        args: (type1: impl Display, type2: impl Display),
        msg: format!(
            "Received different types `{type1}` and `{type2}` for the arms of a ternary conditional."
        ),
        help: Some("Make both branches the same type.".into()),
    }

    @formatted
    operation_types_mismatch {
        args: (operation: impl Display, type1: impl Display, type2: impl Display),
        msg: format!(
            "Received different types `{type1}` and `{type2}` for the operation `{operation}`."
        ),
        help: Some("Make both operands the same type.".into()),
    }

    @formatted
    mul_types_mismatch {
        args: (type1: impl Display, type2: impl Display),
        msg: format!(
            "Received types `{type1}` and `{type2}` for the operation `*`."
        ),
        help: Some("Valid operands are two integers of the same type, two fields, or a scalar and a group.".into()),
    }

    @formatted
    pow_types_mismatch {
        args: (type1: impl Display, type2: impl Display),
        msg: format!(
            "Received types `{type1}` and `{type2}` for the operation `pow`."
        ),
        help: Some("Valid operands are two fields, or an integer base and a `u8`, `u16`, or `u32` exponent.".into()),
    }

    @formatted
    shift_type_magnitude {
        args: (operation: impl Display, rhs_type: impl Display),
        msg: format!(
            "Received type `{rhs_type}` for the second operand of the operation `{operation}`."
        ),
        help: Some("Valid second operands are `u8`, `u16`, or `u32`".into()),
    }

    @formatted
    unit_type_only_return {
        args: (),
        msg: "The unit type () may appear only as the return type of a function.".to_string(),
        help: None,
    }

    @formatted
    final_error_member {
        args: (num: impl Display),
        msg: format!("Cannot access argument `{num}` from Final."),
        help: Some(
            "Ensure that the final fn is not called multiple times with incompatible types.".to_string()
        ),
    }

    @formatted
    cannot_reassign_mapping {
        args: (var: impl Display),
        msg: format!("Cannot assign to the mapping `{var}`."),
        help: None,
    }

    @formatted
    records_not_allowed_inside_final {
        args: (),
        msg: format!("records cannot be instantiated in a final context."),
        help: None,
    }

    @formatted
    script_in_non_test {
        args: (func: impl Display),
        msg: format!("`script` {func} appears in a non-test program."),
        help: Some("Move this to a test program, or replace it with a function.".to_string()),
    }

    @formatted
    non_script_calls_script {
        args: (call: impl Display),
        msg: format!("`script` {call} is called by a non-`script`."),
        help: None,
    }

    @formatted
    annotation_error {
        args: (message: impl Display),
        msg: format!("Invalid annotation: {message}."),
        help: None,
    }

    @formatted
    ternary_over_external_records {
        args: (ty: impl Display),
        msg: format!("Cannot apply ternary conditional to type `{ty}`."),
        help: Some("Ternary conditionals may not contain an external record type.".to_string()),
    }

    // TODO: unused.
    @formatted
    assignment_to_external_record {
        args: (ty: impl Display),
        msg: format!("Cannot assign to type `{ty}` or a member thereof."),
        help: Some("External record types and tuples containing them may not be assigned to.".to_string()),
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    illegal_name {
        args: (item_name: impl Display, item_type: impl Display, keyword: impl Display),
        msg: format!("`{item_name}` is an invalid {item_type} name. A {item_type} cannot have \"{keyword}\" in its name."),
        help: None,
    }

    @formatted
    record_prefixed_by_other_record {
        args: (r1: impl Display, r2: impl Display),
        msg: format!("Record name `{r1}` is prefixed by the record name `{r2}`. Record names must not be prefixes of other record names."),
        help: None,
    }

    @formatted
    range_bounds_type_mismatch {
        args: (),
        msg: format!("mismatched types in loop iterator range bounds"),
        help: None,
    }

    @formatted
    assignment_to_external_record_member {
        args: (ty: impl Display),
        msg: format!("Cannot assign to a member of the external record `{ty}`."),
        help: None,
    }

    @formatted
    assignment_to_external_record_cond {
        args: (ty: impl Display),
        msg: format!("Cannot assign to the external record type `{ty}` in this location."),
        help: Some("External record variables may not be assigned to in narrower conditional scopes than they were defined.".into()),
    }

    @formatted
    assignment_to_external_record_tuple_cond {
        args: (ty: impl Display),
        msg: format!("Cannot assign to the tuple type `{ty}` containing an external record in this location."),
        help: Some("Tuples containing external records may not be assigned to in narrower conditional scopes than they were defined.".into()),
    }

    @formatted
    hexbin_literal_nonintegers {
        args: (),
        msg: format!("Hex, octal, and binary literals may only be used for integer types."),
        help: None,
    }

    @formatted
    unexpected_unsuffixed_numeral {
        args: (expected: impl Display),
        msg: format!(
            "Expected {expected} but an unsuffixed numeral was found.",
        ),
        help: None,
    }

    @formatted
    incorrect_num_const_args {
        args: (kind: impl Display, expected: impl Display, received: impl Display),
        msg: format!(
            "{kind} expected `{expected}` const args, but got `{received}`",
        ),
        help: None,
    }

    @formatted
    bad_const_generic_type {
        args: (found: impl Display),
        msg: format!("A generic const parameter must be a `bool`, an integer, a `scalar`, a `group`, a `field`, or an `address`, but {found} was found"),
        help: None,
    }

    /// For when the user tries to assign to a generic const function parameter.
    @formatted
    cannot_assign_to_generic_const_function_parameter {
        args: (param: impl Display),
        msg: format!(
            "Cannot assign to const parameter `{param}`",
        ),
        help: None,
    }

    @formatted
    only_inline_can_have_const_generics {
        args: (),
        msg: format!("Only inlined functions can have generic const parameters."),
        help: None,
    }

    @formatted
    array_too_large_for_u32 {
        args: (),
        msg: format!("An array length must be small enough to fit in a `u32`"),
        help: None,
    }

    @formatted
    unexpected_record_const_parameters {
        args: (),
        msg: format!("Records cannot be declared with generic const parameters."),
        help: None,
    }

    @formatted
    unexpected_const_args {
        args: (item: impl Display),
        msg: format!("unexpected generic const argment for {item}."),
        help: Some("If this is an external struct, consider using a resolved non-generic version of it instead. External structs can't be instantiated with const arguments".to_string()),
    }

    @formatted
    invalid_operation_inside_final_block {
        args: (operation: impl Display),
        msg: format!("Invalid expression in a final block. `{operation}` cannot be used directly here."),
        help: Some(format!("Bind `{operation}` to a variable before the `final` block: `let val = {operation};`")),
    }

    @formatted
    illegal_final_block_location {
        args: (),
        msg: "`final` blocks are only allowed inside an entry point fn returning `Final` or a script function.".to_string(),
        help: Some("Try moving this `final` block into an entry point fn or a script function.".to_string()),
    }

    // deprecated
    @formatted
    conflicting_final_call_and_block {
        args: (),
        msg: "An entry point fn cannot contain both a `final fn` call and a `final` block at the same time.".to_string(),
        help: Some("Refactor the entry point fn to use either a `final fn` call or a `final` block, but not both.".to_string()),
    }

    @formatted
    multiple_final_blocks_not_allowed {
        args: (),
        msg: "An entry point fn cannot contain more than one `final` block.".to_string(),
        help: Some("Combine the logic into a single `final` block, or restructure your code to avoid multiple final blocks within the same entry point fn.".to_string()),
    }

    @formatted
    final_block_in_conditional {
        args: (),
        msg: "`final` blocks are not allowed inside conditional blocks.".to_string(),
        help: Some("Refactor your code to move the `final` block outside of the conditional block.".to_string()),
    }

    @formatted
    cannot_use_private_inpt_in_final_block {
        args: (),
        msg: format!("`private` inputs cannot be used inside final blocks."),
        help: None,
    }

    @formatted
    final_block_cannot_return {
        args: (),
        msg: "A `final` block cannot contain a `return` statement.".to_string(),
        help: None,
    }

    // deprecated
    @formatted
    invalid_final_block_final_access {
        args: (),
        msg: format!(
            "Cannot access argument from Final produced by a `final` block."
        ),
        help: None,
    }

    @formatted
    cannot_assign_to_vars_outside_final_block {
        args: (input: impl Display),
        msg: format!(
            "Cannot assign to `{input}` inside a `final` block because it was declared outside the block."
        ),
        help: None,
    }

    @formatted
    custom {
        args: (msg: impl Display),
        msg: msg.to_string(),
        help: None,
    }

    @formatted
    constructor_can_only_return_unit {
        args: (expression: impl Display),
        msg: format!("Constructors can only return unit, but found `{expression}`."),
        help: None,
    }

    @formatted
    none_found_non_optional {
        args: (expected: impl Display),
        msg: format!(
            "Found `none`, but the expected type `{expected}` is not an optional type.",
        ),
        help: None,
    }

    // TODO This error is unused. Remove it in a future version.
    @formatted
    optional_wrapping_of_records_unsupported {
        args: (ty: impl Display),
        msg: format!(
            "The type `{ty}` cannot be wrapped in an optional because it is a record.",
        ),
        help: None,
    }

    @formatted
    optional_wrapping_unsupported {
        args: (ty: impl Display),
        msg: format!(
            "The type `{ty}` cannot be wrapped in an optional.",
        ),
        help: Some("Optionals cannot wrap signatures, finals, mappings, tuples, vectors, records, arrays whose \
                    elements are optional-unsafe, or structures containing any such types.".to_string()),
    }

    @formatted
    optional_type_not_allowed_in_mapping {
        args: (ty: impl Display, kind: impl Display),
        msg: format!(
            "The type `{ty}` is or contains an optional type which cannot be used as the {kind} in a mapping",
        ),
        help: None,
    }

    @formatted
    record_field_cannot_be_optional {
        args: (name: impl Display, ty: impl Display),
        msg: format!(
            "The field `{name}` in this record has type `{ty}`, which is or contains an optional type.",
        ),
        help: Some(
            "Records cannot have fields that are optional or contain optionals. Consider moving the optionality outside the record.".to_string()
        ),
    }

    @formatted
    const_cannot_be_optional {
        args: (),
        msg: format!(
            "Constants cannot have an optional type or a type that contains an optional",
        ),
        help: None,
    }

    @formatted
    function_cannot_take_option_as_input {
        args: (name: impl Display, ty: impl Display),
        msg: format!(
            "The input `{name}` has type `{ty}`, which is or contains an optional type and is not allowed as an input to an entry point fn.",
        ),
        help: Some(
            "Inputs to entry point fn definitions cannot be optional or contain optionals. Consider moving the optionality outside the call site.".to_string()
        ),
    }

    @formatted
    function_cannot_return_option_as_output {
        args: (ty: impl Display),
        msg: format!(
            "This function has an output of type `{ty}`, which is or contains an optional type and is not allowed as an output of an entry point fn.",
        ),
        help: Some(
            "Outputs of entry point fns cannot be optional or contain optionals. Consider moving the optionality outside the function call.".to_string()
        ),
    }

    @formatted
    invalid_storage_type {
        args: (type_: impl Display),
        msg: format!("{type_} is an invalid storage type"),
        help: None,
    }

    @formatted
    storage_vectors_cannot_be_moved_or_assigned {
        args: (),
        msg: format!(
            "Storage vectors cannot be moved or assigned. You can only access or modify them using methods like `get`, `push`, or `pop`."
        ),
        help: None,
    }

    @formatted
    function_has_too_many_inputs {
        args: (variant: impl Display, name: impl Display, limit: usize, actual: usize),
        msg: format!(
            "The {variant} `{name}` has {actual} input parameters, which exceeds the allowed limit of {limit}.",
        ),
        help: Some(
            "Consider reducing the number of input parameters. You might combine some parameters into a struct or refactor the {variant} to simplify its signature.".to_string()
        ),
    }

    @formatted
    function_has_too_many_outputs {
        args: (variant: impl Display, name: impl Display, limit: usize, actual: usize),
        msg: format!(
            "The {variant} `{name}` has {actual} output parameters, which exceeds the allowed limit of {limit}.",
        ),
        help: Some(
            "Consider reducing the number of output parameters. You might combine some parameters into a struct or refactor the {variant} to simplify its signature.".to_string()
        ),
    }

    @formatted
    empty_function_args {
        args: (),
        msg: format!("Cannot define a function with only empty parameters."),
        help: None,
    }

    @formatted
    zero_size_struct {
        args: (),
        msg: "A struct must have at least one member of non-zero size.".to_string(),
        help: None,
    }

    /// For when an invalid intrinsic is used.
    @formatted
    invalid_intrinsic {
        args: (intr: impl Display),
        msg: format!(
            "{intr} is not a valid intrinsic.",
        ),
        help: None,
    }

    @formatted
    cannot_instantiate_external_record  {
        args: (loc: impl Display),
        msg: format!(
            "Cannot create external record `{loc}`. Records can only be created in the program that they are defined in",
        ),
        help: None,
    }

    @formatted
    cannot_modify_external_storage_variable {
        args: (),
        msg: format!("Assignment to storage variables of another program is not allowed. You can only modify storage declared in the current program."),
        help: None,
    }

    @formatted
    no_inline_not_allowed_on_final_fn {
        args: (),
        msg: format!("`@no_inline` is not allowed on `final fn` functions because they must always be inlined."),
        help: None,
    }

    @formatted
    record_captured_by_final_block {
        args: (var_name: impl Display),
        msg: format!("A `final` block cannot capture the record variable `{var_name}`. Records cannot be used in on-chain code."),
        help: Some(format!("Extract the needed fields before the `final` block. For example: `let val = {var_name}.field_name;`")),
    }

    @formatted
    dynamic_call_not_allowed_here {
        args: (context: impl Display),
        msg: format!("Dynamic calls can only be made from an entry point, but found one in {context}."),
        help: None,
    }

    @formatted
    dyn_record_field_requires_type {
        args: (field: impl Display),
        msg: format!("Accessing field `{field}` on a `dyn record` requires a type annotation."),
        help: Some(format!("Use `let x: <type> = r.{field};` or `r.{field} as <type>`.")),
    }

    @formatted
    cannot_cast_to_dyn_record {
        args: (type_: impl Display),
        msg: format!("Cannot cast `{type_}` to `dyn record`: only concrete record types can be cast to `dyn record`."),
        help: None,
    }

    @formatted
    dynamic_call_min_args {
        args: (found: impl Display),
        msg: format!("`_dynamic_call` requires at least 3 arguments (program, network, function), but found {found}."),
        help: None,
    }

    @formatted
    dynamic_intrinsic_wrong_arg_count {
        args: (name: impl Display, expected: impl Display, found: impl Display),
        msg: format!("`{name}` requires {expected} arguments, but found {found}."),
        help: None,
    }

    @formatted
    dynamic_intrinsic_missing_type_param {
        args: (name: impl Display),
        msg: format!("`{name}` requires exactly one type parameter, e.g. `{name}::[u64](...)`"),
        help: None,
    }

    @formatted
    dynamic_call_input_type_count_mismatch {
        args: (annotated: impl Display, actual: impl Display),
        msg: format!("`_dynamic_call` has {annotated} input type annotation(s) but {actual} call argument(s) were provided (excluding the 3 target arguments)."),
        help: Some("The number of type annotations before the return type must match the number of call arguments after program/network/function.".to_string()),
    }

    @formatted
    dynamic_call_in_conditional {
        args: (),
        msg: "Dynamic calls cannot be used inside a conditional branch.".to_string(),
        help: Some("Move the dynamic call outside the `if`/`else` block.".to_string()),
    }

    @formatted
    dynamic_call_constant_not_allowed {
        args: (),
        msg: "`constant` visibility is not allowed in `_dynamic_call` type annotations.".to_string(),
        help: Some("Use `public` or `private` instead.".to_string()),
    }

    /// For when an interface definition appears inside a library module.
    @formatted
    interfaces_not_allowed_in_library_modules {
        args: (),
        msg: "Interface definitions are not allowed inside library modules.".to_string(),
        help: None,
    }

    @formatted
    dynamic_call_record_arg_requires_dyn_record {
        args: (record_type: impl Display),
        msg: format!("Dynamic call argument has record type `{record_type}`, but dynamic calls require `dyn record`."),
        help: Some("Cast your record value with `my_arg as dyn record`.".to_string()),
    }

    @formatted
    dynamic_call_record_return_is_dyn_record {
        args: (record_type: impl Display),
        msg: format!("Dynamic call returns record type `{record_type}`, but dynamic calls return `dyn record`."),
        help: Some("Dynamic calls cannot return concrete record types. Access fields on the returned `dyn record` with type annotations: `let x: u64 = result.field;`".to_string()),
    }
);
