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

use hexpm::version::Version;
use rand::Rng;
use type_::{AccessorsMap, FieldMap, RecordAccessor};

use super::*;
use crate::{
    analyse::Inferred,
    ast::{
        BitArrayOption, BitArraySegment, CallArg, Constant, Publicity, SrcSpan, TypedConstant,
        TypedConstantBitArraySegmentOption,
    },
    build::Origin,
    line_numbers::LineNumbers,
    parse::LiteralFloatValue,
    reference::{ModuleNameReference, Reference, ReferenceKind},
    type_::{
        self, Deprecation, ModuleInterface, Opaque, References, Type, TypeAliasConstructor,
        TypeConstructor, TypeValueConstructor, TypeValueConstructorField, TypeVariantConstructors,
        ValueConstructor, ValueConstructorVariant,
        expression::{Implementations, Purity},
        prelude,
    },
    uid::UniqueIdGenerator,
};
use std::{collections::HashMap, sync::Arc};

use pretty_assertions::assert_eq;

fn roundtrip(input: &ModuleInterface) -> ModuleInterface {
    let buffer = encode(input).unwrap();
    let ids = UniqueIdGenerator::new();
    decode(buffer.as_slice(), ids).unwrap()
}

fn constant_module(constant: TypedConstant) -> ModuleInterface {
    ModuleInterface {
        warnings: vec![],
        is_internal: true,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleConstant {
                    documentation: Some("Some documentation".into()),
                    literal: constant,
                    location: SrcSpan::default(),
                    module: "one/two".into(),
                    implementations: Implementations {
                        gleam: true,
                        uses_erlang_externals: false,
                        uses_javascript_externals: false,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: true,
                    },
                    name: "one".into(),
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    }
}

fn bit_array_segment_option_module(option: TypedConstantBitArraySegmentOption) -> ModuleInterface {
    constant_module(Constant::BitArray {
        location: SrcSpan::default(),
        segments: vec![BitArraySegment {
            location: SrcSpan::default(),
            value: Box::new(Constant::Int {
                location: SrcSpan::default(),
                value: "1".into(),
                int_value: 1.into(),
            }),
            options: vec![option],
            type_: type_::int(),
        }],
    })
}

#[test]
fn empty_module() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: true,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "one/two".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn with_line_numbers() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "one/two".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(
            "const a = 1
        const b = 2
        const c = 3",
        ),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_private_type() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b".into(),
        types: [(
            "ListIntType".into(),
            TypeConstructor {
                type_: type_::list(type_::int()),
                publicity: Publicity::Private,
                origin: SrcSpan::default(),
                module: "the/module".into(),
                parameters: vec![],
                deprecation: Deprecation::NotDeprecated,
                documentation: None,
            },
        )]
        .into(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_app_type() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b".into(),
        types: [(
            "ListIntType".into(),
            TypeConstructor {
                type_: type_::list(type_::int()),
                publicity: Publicity::Public,
                origin: SrcSpan::default(),
                module: "the/module".into(),
                parameters: vec![],
                deprecation: Deprecation::NotDeprecated,
                documentation: None,
            },
        )]
        .into(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_fn_type() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b".into(),
        types: [(
            "FnType".into(),
            TypeConstructor {
                type_: type_::fn_(vec![type_::nil(), type_::float()], type_::int()),
                publicity: Publicity::Public,
                origin: SrcSpan::default(),
                module: "the/module".into(),
                parameters: vec![],
                deprecation: Deprecation::NotDeprecated,
                documentation: None,
            },
        )]
        .into(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_tuple_type() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b".into(),
        types: [(
            "TupleType".into(),
            TypeConstructor {
                type_: type_::tuple(vec![type_::nil(), type_::float(), type_::int()]),
                publicity: Publicity::Public,
                origin: SrcSpan::default(),
                module: "the/module".into(),
                parameters: vec![],
                deprecation: Deprecation::NotDeprecated,
                documentation: None,
            },
        )]
        .into(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_generic_type() {
    let t0 = type_::generic_var(0);
    let t1 = type_::generic_var(1);
    let t7 = type_::generic_var(7);
    let t8 = type_::generic_var(8);

    fn make(t1: Arc<Type>, t2: Arc<Type>) -> ModuleInterface {
        ModuleInterface {
            warnings: vec![],
            is_internal: false,
            package: "some_package".into(),
            origin: Origin::Src,
            name: "a/b".into(),
            types: [(
                "TupleType".into(),
                TypeConstructor {
                    type_: type_::tuple(vec![t1.clone(), t1.clone(), t2.clone()]),
                    publicity: Publicity::Public,
                    origin: SrcSpan::default(),
                    module: "the/module".into(),
                    parameters: vec![t1, t2],
                    deprecation: Deprecation::NotDeprecated,
                    documentation: None,
                },
            )]
            .into(),
            types_value_constructors: HashMap::new(),
            values: HashMap::new(),
            accessors: HashMap::new(),
            line_numbers: LineNumbers::new(""),
            src_path: "some_path".into(),
            minimum_required_version: Version::new(0, 1, 0),
            type_aliases: HashMap::new(),
            documentation: Vec::new(),
            contains_echo: false,
            references: References::default(),
            inline_functions: HashMap::new(),
        }
    }

    assert_eq!(roundtrip(&make(t7, t8)), make(t0, t1));
}

#[test]
fn module_with_type_links() {
    let linked_type = type_::link(type_::int());
    let type_ = type_::int();

    fn make(type_: Arc<Type>) -> ModuleInterface {
        ModuleInterface {
            warnings: vec![],
            is_internal: false,
            package: "some_package".into(),
            origin: Origin::Src,
            name: "a".into(),
            types: [(
                "SomeType".into(),
                TypeConstructor {
                    type_,
                    publicity: Publicity::Public,
                    origin: SrcSpan::default(),
                    module: "a".into(),
                    parameters: vec![],
                    deprecation: Deprecation::NotDeprecated,
                    documentation: None,
                },
            )]
            .into(),
            types_value_constructors: HashMap::new(),
            values: HashMap::new(),
            accessors: HashMap::new(),
            line_numbers: LineNumbers::new(""),
            src_path: "some_path".into(),
            minimum_required_version: Version::new(0, 1, 0),
            type_aliases: HashMap::new(),
            documentation: Vec::new(),
            contains_echo: false,
            references: References::default(),
            inline_functions: HashMap::new(),
        }
    }

    assert_eq!(roundtrip(&make(linked_type)), make(type_));
}

#[test]
fn module_with_type_constructor_documentation() {
    let linked_type = type_::link(type_::int());
    let type_ = type_::int();

    fn make(type_: Arc<Type>) -> ModuleInterface {
        ModuleInterface {
            warnings: vec![],
            is_internal: false,
            package: "some_package".into(),
            origin: Origin::Src,
            name: "a".into(),
            types: [(
                "SomeType".into(),
                TypeConstructor {
                    type_,
                    publicity: Publicity::Public,
                    origin: SrcSpan::default(),
                    module: "a".into(),
                    parameters: vec![],
                    deprecation: Deprecation::NotDeprecated,
                    documentation: Some("type documentation".into()),
                },
            )]
            .into(),
            types_value_constructors: HashMap::new(),
            values: HashMap::new(),
            accessors: HashMap::new(),
            line_numbers: LineNumbers::new(""),
            src_path: "some_path".into(),
            minimum_required_version: Version::new(0, 1, 0),
            type_aliases: HashMap::new(),
            documentation: Vec::new(),
            contains_echo: false,
            references: References::default(),
            inline_functions: HashMap::new(),
        }
    }

    assert_eq!(roundtrip(&make(linked_type)), make(type_));
}

#[test]
fn module_with_type_constructor_origin() {
    let linked_type = type_::link(type_::int());
    let type_ = type_::int();

    fn make(type_: Arc<Type>) -> ModuleInterface {
        ModuleInterface {
            warnings: vec![],
            is_internal: false,
            package: "some_package".into(),
            origin: Origin::Src,
            name: "a".into(),
            types: [(
                "SomeType".into(),
                TypeConstructor {
                    type_,
                    publicity: Publicity::Public,
                    origin: SrcSpan {
                        start: 535,
                        end: 543,
                    },
                    module: "a".into(),
                    parameters: vec![],
                    deprecation: Deprecation::NotDeprecated,
                    documentation: None,
                },
            )]
            .into(),
            types_value_constructors: HashMap::new(),
            values: HashMap::new(),
            accessors: HashMap::new(),
            line_numbers: LineNumbers::new(""),
            src_path: "some_path".into(),
            minimum_required_version: Version::new(0, 1, 0),
            type_aliases: HashMap::new(),
            documentation: Vec::new(),
            contains_echo: false,
            references: References::default(),
            inline_functions: HashMap::new(),
        }
    }

    assert_eq!(roundtrip(&make(linked_type)), make(type_));
}

#[test]
fn module_type_to_constructors_mapping() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: [(
            "SomeType".into(),
            TypeVariantConstructors {
                type_parameters_ids: vec![0, 1, 2],
                variants: vec![TypeValueConstructor {
                    name: "One".into(),
                    parameters: vec![],
                    documentation: Some("Some documentation".into()),
                }],
                opaque: Opaque::NotOpaque,
            },
        )]
        .into(),
        accessors: HashMap::new(),
        values: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_fn_value() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wobble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 535,
                        end: 1100,
                    },
                    external_erlang: None,
                    external_javascript: None,
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: true,
                        uses_erlang_externals: false,
                        uses_javascript_externals: false,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: true,
                    },
                    purity: Purity::Pure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn deprecated_module_fn_value() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::Deprecated {
                    message: "wibble wobble".into(),
                },
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wobble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 535,
                        end: 1100,
                    },
                    external_erlang: None,
                    external_javascript: None,
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: true,
                        uses_erlang_externals: false,
                        uses_javascript_externals: false,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: true,
                    },
                    purity: Purity::Pure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn private_module_fn_value() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Private,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wobble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 535,
                        end: 1100,
                    },
                    external_erlang: None,
                    external_javascript: None,
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: true,
                        uses_erlang_externals: false,
                        uses_javascript_externals: false,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: true,
                    },
                    purity: Purity::Pure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

// https://github.com/gleam-lang/gleam/commit/c8f3bd0ddbf61c27ea35f37297058ecca7515f6c
#[test]
fn module_fn_value_regression() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b/c".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wabble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 52,
                        end: 1100,
                    },
                    external_erlang: None,
                    external_javascript: None,
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: true,
                        uses_erlang_externals: false,
                        uses_javascript_externals: false,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: true,
                    },
                    purity: Purity::TrustedPure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_fn_value_with_field_map() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wubble!".into()),
                    name: "one".into(),
                    field_map: Some(FieldMap {
                        arity: 20,
                        fields: [("ok".into(), 5), ("ko".into(), 7)].into(),
                    }),
                    external_erlang: None,
                    external_javascript: None,
                    external_cranelift: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan { start: 2, end: 11 },
                    implementations: Implementations {
                        gleam: true,
                        uses_erlang_externals: false,
                        uses_javascript_externals: false,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: true,
                    },
                    purity: Purity::Pure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn record_value() {
    let mut random = rand::rng();

    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::Record {
                    documentation: Some("webble!".into()),
                    name: "one".into(),
                    module: "themodule".into(),
                    field_map: None,
                    arity: random.random(),
                    variants_count: random.random(),
                    location: SrcSpan {
                        start: random.random(),
                        end: random.random(),
                    },
                    variant_index: random.random(),
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn record_value_with_field_map() {
    let mut random = rand::rng();

    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::Record {
                    documentation: Some("wybble!".into()),
                    module: "themodule".into(),
                    name: "one".into(),
                    field_map: Some(FieldMap {
                        arity: random.random(),
                        fields: [
                            ("ok".into(), random.random()),
                            ("ko".into(), random.random()),
                        ]
                        .into(),
                    }),
                    arity: random.random(),
                    variants_count: random.random(),
                    variant_index: random.random(),
                    location: SrcSpan {
                        start: random.random(),
                        end: random.random(),
                    },
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn accessors() {
    let accessors1 = [
        (
            "a".into(),
            RecordAccessor {
                index: 6,
                label: "siiixxx".into(),
                type_: type_::nil(),
                documentation: Some("Here is some documentation".into()),
            },
        ),
        (
            "a".into(),
            RecordAccessor {
                index: 5,
                label: "fiveee".into(),
                type_: type_::float(),
                documentation: None,
            },
        ),
    ];

    let accessors2 = [(
        "a".into(),
        RecordAccessor {
            index: 1,
            label: "ok".into(),
            type_: type_::float(),
            documentation: Some("Documentation for the ok field".into()),
        },
    )];

    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: [
            (
                "one".into(),
                AccessorsMap {
                    publicity: Publicity::Public,
                    type_: type_::int(),
                    shared_accessors: accessors1.clone().into(),
                    variant_specific_accessors: vec![accessors1.into()],
                    variant_positional_accessors: vec![vec![type_::int(), type_::float()]],
                },
            ),
            (
                "two".into(),
                AccessorsMap {
                    publicity: Publicity::Public,
                    type_: type_::int(),
                    shared_accessors: accessors2.clone().into(),
                    variant_specific_accessors: vec![accessors2.into()],
                    variant_positional_accessors: vec![vec![]],
                },
            ),
        ]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn private_accessors() {
    let accessors1 = [
        (
            "a".into(),
            RecordAccessor {
                index: 6,
                label: "siiixxx".into(),
                type_: type_::nil(),
                documentation: None,
            },
        ),
        (
            "a".into(),
            RecordAccessor {
                index: 5,
                label: "fiveee".into(),
                type_: type_::float(),
                documentation: None,
            },
        ),
    ];

    let accessors2 = [(
        "a".into(),
        RecordAccessor {
            index: 1,
            label: "ok".into(),
            type_: type_::float(),
            documentation: None,
        },
    )];

    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        contains_echo: false,
        values: HashMap::new(),
        accessors: [
            (
                "one".into(),
                AccessorsMap {
                    publicity: Publicity::Private,
                    type_: type_::int(),
                    shared_accessors: accessors1.clone().into(),
                    variant_specific_accessors: vec![accessors1.into()],
                    variant_positional_accessors: vec![vec![type_::int(), type_::float()]],
                },
            ),
            (
                "two".into(),
                AccessorsMap {
                    publicity: Publicity::Public,
                    type_: type_::int(),
                    shared_accessors: accessors2.clone().into(),
                    variant_specific_accessors: vec![accessors2.into()],
                    variant_positional_accessors: vec![vec![]],
                },
            ),
        ]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_int() {
    let module = constant_module(Constant::Int {
        location: SrcSpan::default(),
        value: "100".into(),
        int_value: 100.into(),
    });

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_float() {
    let module = constant_module(Constant::Float {
        location: SrcSpan::default(),
        value: "1.0".into(),
        float_value: LiteralFloatValue::ONE,
    });

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_string() {
    let module = constant_module(Constant::String {
        location: SrcSpan::default(),
        value: "hello".into(),
    });

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_tuple() {
    let int_float_tuple_type = type_::tuple(vec![type_::int(), type_::float()]);
    let module = constant_module(Constant::Tuple {
        location: SrcSpan::default(),
        type_: type_::tuple(vec![
            type_::int(),
            type_::float(),
            int_float_tuple_type.clone(),
        ]),
        elements: vec![
            Constant::Int {
                location: SrcSpan::default(),
                value: "1".into(),
                int_value: 1.into(),
            },
            Constant::Float {
                location: SrcSpan::default(),
                value: "1.0".into(),
                float_value: LiteralFloatValue::ONE,
            },
            Constant::Tuple {
                location: SrcSpan::default(),
                type_: int_float_tuple_type,
                elements: vec![
                    Constant::Int {
                        location: SrcSpan::default(),
                        value: "1".into(),
                        int_value: 1.into(),
                    },
                    Constant::Float {
                        location: SrcSpan::default(),
                        value: "1.0".into(),
                        float_value: LiteralFloatValue::ONE,
                    },
                ],
            },
        ],
    });

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_list() {
    let module = constant_module(Constant::List {
        location: SrcSpan::default(),
        type_: type_::int(),
        elements: vec![
            Constant::Int {
                location: SrcSpan::default(),
                value: "1".into(),
                int_value: 1.into(),
            },
            Constant::Int {
                location: SrcSpan::default(),
                value: "2".into(),
                int_value: 2.into(),
            },
            Constant::Int {
                location: SrcSpan::default(),
                value: "3".into(),
                int_value: 3.into(),
            },
        ],
        tail: Some(Box::new(Constant::List {
            location: SrcSpan::default(),
            type_: type_::list(type_::int()),
            elements: vec![
                Constant::Int {
                    location: SrcSpan::default(),
                    value: "4".into(),
                    int_value: 4.into(),
                },
                Constant::Int {
                    location: SrcSpan::default(),
                    value: "5".into(),
                    int_value: 5.into(),
                },
            ],
            tail: None,
        })),
    });

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_record() {
    let module = constant_module(Constant::Record {
        location: SrcSpan::default(),
        module: None,
        name: "".into(),
        arguments: Some(vec![
            CallArg {
                implicit: None,
                label: None,
                location: SrcSpan::default(),
                value: Constant::Float {
                    location: SrcSpan::default(),
                    value: "0.0".into(),
                    float_value: LiteralFloatValue::ZERO,
                },
            },
            CallArg {
                implicit: None,
                label: None,
                location: SrcSpan::default(),
                value: Constant::Int {
                    location: SrcSpan::default(),
                    value: "1".into(),
                    int_value: 1.into(),
                },
            },
        ]),
        type_: type_::int(),
        field_map: Inferred::Unknown,
        record_constructor: None,
    });

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_var() {
    let one_original = Constant::Int {
        location: SrcSpan::default(),
        value: "1".into(),
        int_value: 1.into(),
    };

    let one = Constant::Var {
        location: SrcSpan::default(),
        module: None,
        name: "one_original".into(),
        type_: type_::int(),
        constructor: Some(Box::from(ValueConstructor {
            publicity: Publicity::Public,
            deprecation: Deprecation::NotDeprecated,
            type_: type_::int(),
            variant: ValueConstructorVariant::ModuleConstant {
                documentation: Some("some doc".into()),
                literal: one_original.clone(),
                location: SrcSpan::default(),
                module: "one/two".into(),
                implementations: Implementations {
                    gleam: true,
                    uses_erlang_externals: false,
                    uses_javascript_externals: false,
                    uses_cranelift_externals: false,
                    can_run_on_erlang: true,
                    can_run_on_javascript: true,
                    can_run_on_cranelift: true,
                },
                name: "one_original".into(),
            },
        })),
    };

    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [
            (
                "one".into(),
                ValueConstructor {
                    publicity: Publicity::Public,
                    deprecation: Deprecation::NotDeprecated,
                    type_: type_::int(),
                    variant: ValueConstructorVariant::ModuleConstant {
                        documentation: Some("some doc!!!!!!!!!".into()),
                        literal: one,
                        location: SrcSpan::default(),
                        module: "one/two".into(),
                        implementations: Implementations {
                            gleam: true,
                            uses_erlang_externals: false,
                            uses_javascript_externals: false,
                            uses_cranelift_externals: false,
                            can_run_on_erlang: true,
                            can_run_on_javascript: true,
                            can_run_on_cranelift: true,
                        },
                        name: "one".into(),
                    },
                },
            ),
            (
                "one_original".into(),
                ValueConstructor {
                    publicity: Publicity::Public,
                    deprecation: Deprecation::NotDeprecated,
                    type_: type_::int(),
                    variant: ValueConstructorVariant::ModuleConstant {
                        documentation: Some("some doc yeah".into()),
                        literal: one_original,
                        location: SrcSpan::default(),
                        module: "one/two".into(),
                        implementations: Implementations {
                            gleam: true,
                            uses_erlang_externals: false,
                            uses_javascript_externals: false,
                            uses_cranelift_externals: false,
                            can_run_on_erlang: true,
                            can_run_on_javascript: true,
                            can_run_on_cranelift: true,
                        },
                        name: "one_original".into(),
                    },
                },
            ),
        ]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array() {
    let module = constant_module(Constant::BitArray {
        location: SrcSpan::default(),
        segments: vec![],
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_unit() {
    let module = bit_array_segment_option_module(BitArrayOption::Unit {
        location: SrcSpan::default(),
        value: 234,
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_float() {
    let module = bit_array_segment_option_module(BitArrayOption::Float {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_int() {
    let module = bit_array_segment_option_module(BitArrayOption::Int {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_size() {
    let module = bit_array_segment_option_module(BitArrayOption::Size {
        location: SrcSpan::default(),
        value: Box::new(Constant::Int {
            location: SrcSpan::default(),
            value: "1".into(),
            int_value: 1.into(),
        }),
        short_form: false,
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_size_short_form() {
    let module = bit_array_segment_option_module(BitArrayOption::Size {
        location: SrcSpan::default(),
        value: Box::new(Constant::Int {
            location: SrcSpan::default(),
            value: "1".into(),
            int_value: 1.into(),
        }),
        short_form: true,
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_bit_arry() {
    let module = bit_array_segment_option_module(BitArrayOption::Bits {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_utf8() {
    let module = bit_array_segment_option_module(BitArrayOption::Utf8 {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_utf16() {
    let module = bit_array_segment_option_module(BitArrayOption::Utf16 {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_utf32() {
    let module = bit_array_segment_option_module(BitArrayOption::Utf32 {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_utf8codepoint() {
    let module = bit_array_segment_option_module(BitArrayOption::Utf8Codepoint {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_utf16codepoint() {
    let module = bit_array_segment_option_module(BitArrayOption::Utf16Codepoint {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_utf32codepoint() {
    let module = bit_array_segment_option_module(BitArrayOption::Utf32Codepoint {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_signed() {
    let module = bit_array_segment_option_module(BitArrayOption::Signed {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_unsigned() {
    let module = bit_array_segment_option_module(BitArrayOption::Unsigned {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_big() {
    let module = bit_array_segment_option_module(BitArrayOption::Big {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_little() {
    let module = bit_array_segment_option_module(BitArrayOption::Little {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn constant_bit_array_native() {
    let module = bit_array_segment_option_module(BitArrayOption::Native {
        location: SrcSpan::default(),
    });
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn deprecated_type() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b".into(),
        types: [(
            "ListIntType".into(),
            TypeConstructor {
                type_: type_::list(type_::int()),
                publicity: Publicity::Public,
                origin: SrcSpan::default(),
                module: "the/module".into(),
                parameters: vec![],
                deprecation: Deprecation::Deprecated {
                    message: "oh no".into(),
                },
                documentation: None,
            },
        )]
        .into(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_fn_value_with_external_implementations() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b/c".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Public,
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wabble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 52,
                        end: 1100,
                    },
                    external_erlang: Some(("wibble".into(), "wobble".into())),
                    external_javascript: Some(("wobble".into(), "wibble".into())),
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: false,
                        uses_erlang_externals: true,
                        uses_javascript_externals: true,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: false,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: false,
                    },
                    purity: Purity::Impure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,
        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_containing_echo() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b/c".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [].into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: true,
        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn internal_module_fn() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b/c".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Internal {
                    attribute_location: None,
                },
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wabble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 52,
                        end: 1100,
                    },
                    external_erlang: Some(("wibble".into(), "wobble".into())),
                    external_javascript: Some(("wobble".into(), "wibble".into())),
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: false,
                        uses_erlang_externals: true,
                        uses_javascript_externals: true,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: false,
                    },
                    purity: Purity::Unknown,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn internal_annotated_module_fn() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b/c".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: [(
            "one".into(),
            ValueConstructor {
                publicity: Publicity::Internal {
                    attribute_location: Some(SrcSpan {
                        start: 11,
                        end: 111,
                    }),
                },
                deprecation: Deprecation::NotDeprecated,
                type_: type_::int(),
                variant: ValueConstructorVariant::ModuleFn {
                    documentation: Some("wabble!".into()),
                    name: "one".into(),
                    field_map: None,
                    module: "a".into(),
                    arity: 5,
                    location: SrcSpan {
                        start: 52,
                        end: 1100,
                    },
                    external_erlang: Some(("wibble".into(), "wobble".into())),
                    external_javascript: Some(("wobble".into(), "wibble".into())),
                    external_cranelift: None,
                    implementations: Implementations {
                        gleam: false,
                        uses_erlang_externals: true,
                        uses_javascript_externals: true,
                        uses_cranelift_externals: false,
                        can_run_on_erlang: true,
                        can_run_on_javascript: true,
                        can_run_on_cranelift: false,
                    },
                    purity: Purity::Impure,
                },
            },
        )]
        .into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

// https://github.com/gleam-lang/gleam/issues/2599
#[test]
fn type_variable_ids_in_constructors_are_shared() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a/b/c".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::from([(
            "SomeType".into(),
            TypeVariantConstructors {
                type_parameters_ids: vec![4, 5, 6],
                variants: vec![TypeValueConstructor {
                    name: "One".into(),
                    parameters: vec![
                        TypeValueConstructorField {
                            type_: type_::generic_var(6),
                            label: None,
                            documentation: Some("Here's some documentation".into()),
                        },
                        TypeValueConstructorField {
                            type_: type_::int(),
                            label: None,
                            documentation: None,
                        },
                        TypeValueConstructorField {
                            type_: type_::tuple(vec![type_::generic_var(4), type_::generic_var(5)]),
                            label: None,
                            documentation: None,
                        },
                    ],
                    documentation: None,
                }],
                opaque: Opaque::NotOpaque,
            },
        )]),
        accessors: HashMap::new(),
        values: [].into(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };

    let expected = HashMap::from([(
        "SomeType".into(),
        TypeVariantConstructors {
            type_parameters_ids: vec![0, 1, 2],
            variants: vec![TypeValueConstructor {
                name: "One".into(),
                parameters: vec![
                    TypeValueConstructorField {
                        type_: type_::generic_var(2),
                        label: None,
                        documentation: Some("Here's some documentation".into()),
                    },
                    TypeValueConstructorField {
                        type_: type_::int(),
                        label: None,
                        documentation: None,
                    },
                    TypeValueConstructorField {
                        type_: type_::tuple(vec![type_::generic_var(0), type_::generic_var(1)]),
                        label: None,
                        documentation: None,
                    },
                ],
                documentation: None,
            }],
            opaque: Opaque::NotOpaque,
        },
    )]);

    assert_eq!(roundtrip(&module).types_value_constructors, expected);
}

#[test]
fn type_with_inferred_variant() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        contains_echo: false,
        origin: Origin::Src,
        name: "a/b".into(),
        types: [(
            "Wibble".into(),
            TypeConstructor {
                type_: Arc::new(Type::Named {
                    publicity: Publicity::Internal {
                        attribute_location: None,
                    },
                    package: "some_package".into(),
                    module: "the/module".into(),
                    name: "Wibble".into(),
                    arguments: Vec::new(),
                    inferred_variant: Some(1),
                }),
                publicity: Publicity::Internal {
                    attribute_location: Some(SrcSpan::new(0, 10)),
                },
                origin: SrcSpan::default(),
                module: "the/module".into(),
                parameters: vec![],
                deprecation: Deprecation::NotDeprecated,
                documentation: None,
            },
        )]
        .into(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_type_aliases() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "some_module".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: [(
            "MyAlias".into(),
            TypeAliasConstructor {
                publicity: Publicity::Public,
                module: "some_module".into(),
                type_: prelude::int(),
                arity: 1,
                deprecation: Deprecation::NotDeprecated,
                documentation: Some("Some documentation".into()),
                origin: SrcSpan::default(),
                parameters: vec![type_::generic_var(0)],
            },
        )]
        .into(),
        documentation: Vec::new(),
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_documentation() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "some_module".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        values: HashMap::new(),
        accessors: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: vec![
            "This is a line of documentation".into(),
            "And here is another".into(),
            "And finally, a third".into(),
        ],
        contains_echo: false,

        references: References::default(),
        inline_functions: HashMap::new(),
    };
    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_opaque_type() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: [(
            "SomeType".into(),
            TypeVariantConstructors {
                type_parameters_ids: vec![0, 1, 2],
                variants: vec![TypeValueConstructor {
                    name: "One".into(),
                    parameters: vec![],
                    documentation: Some("Some documentation".into()),
                }],
                opaque: Opaque::Opaque,
            },
        )]
        .into(),
        accessors: HashMap::new(),
        values: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,
        references: References::default(),
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}

#[test]
fn module_with_references() {
    let module = ModuleInterface {
        warnings: vec![],
        is_internal: false,
        package: "some_package".into(),
        origin: Origin::Src,
        name: "a".into(),
        types: HashMap::new(),
        types_value_constructors: HashMap::new(),
        accessors: HashMap::new(),
        values: HashMap::new(),
        line_numbers: LineNumbers::new(""),
        src_path: "some_path".into(),
        minimum_required_version: Version::new(0, 1, 0),
        type_aliases: HashMap::new(),
        documentation: Vec::new(),
        contains_echo: false,
        references: References {
            imported_modules: ["some_module".into(), "some_other_module".into()].into(),
            value_references: [
                (
                    ("some_module".into(), "some_function".into()),
                    vec![
                        Reference {
                            location: SrcSpan::new(1, 6),
                            kind: ReferenceKind::Definition,
                        },
                        Reference {
                            location: SrcSpan::new(7, 11),
                            kind: ReferenceKind::Unqualified,
                        },
                    ],
                ),
                (
                    ("some_other_module".into(), "some_constant".into()),
                    vec![
                        Reference {
                            location: SrcSpan::new(6, 9),
                            kind: ReferenceKind::Import(SrcSpan::new(9, 9)),
                        },
                        Reference {
                            location: SrcSpan::new(90, 108),
                            kind: ReferenceKind::Unqualified,
                        },
                    ],
                ),
                (
                    ("some_other_module".into(), "SomeTypeVariant".into()),
                    vec![
                        Reference {
                            location: SrcSpan::new(26, 35),
                            kind: ReferenceKind::Qualified {
                                module_alias: "some_other_module".into(),
                                module_location: SrcSpan::new(26, 29),
                            },
                        },
                        Reference {
                            location: SrcSpan::new(152, 204),
                            kind: ReferenceKind::Qualified {
                                module_alias: "some_other_module".into(),
                                module_location: SrcSpan::new(26, 29),
                            },
                        },
                        Reference {
                            location: SrcSpan::new(0, 8),
                            kind: ReferenceKind::Qualified {
                                module_alias: "some_alias".into(),
                                module_location: SrcSpan::new(26, 29),
                            },
                        },
                    ],
                ),
            ]
            .into(),
            type_references: [(
                ("some_other_module".into(), "TypeVariant".into()),
                vec![
                    Reference {
                        location: SrcSpan::new(26, 35),
                        kind: ReferenceKind::Qualified {
                            module_alias: "some_alias".into(),
                            module_location: SrcSpan::new(26, 29),
                        },
                    },
                    Reference {
                        location: SrcSpan::new(152, 204),
                        kind: ReferenceKind::Unqualified,
                    },
                    Reference {
                        location: SrcSpan::new(0, 8),
                        kind: ReferenceKind::Definition,
                    },
                ],
            )]
            .into(),
            module_references: [(
                "some_module".into(),
                vec![
                    ModuleNameReference::Import {
                        module_location: SrcSpan::new(5, 20),
                        import_end: 31,
                    },
                    ModuleNameReference::AliasedImport {
                        module_location: SrcSpan::new(5, 20),
                        alias_location: SrcSpan::new(26, 32),
                        alias: "some_alias".into(),
                    },
                    ModuleNameReference::ModuleSelect(SrcSpan::new(92, 100)),
                    ModuleNameReference::AliasedModuleSelect(SrcSpan::new(152, 160)),
                ],
            )]
            .into(),
            label_references: HashMap::new(),
            label_definitions: HashMap::new(),
        },
        inline_functions: HashMap::new(),
    };

    assert_eq!(roundtrip(&module), module);
}
