//! Item signature IR definitions

use std::cell::LazyCell;

use base_db::SourceDatabase;
use bitflags::bitflags;
use cfg::{CfgExpr, CfgOptions};
use hir_expand::{
    InFile, Intern, Lookup,
    name::{AsName, Name},
};
use intern::sym;
use la_arena::{Arena, Idx};
use rustc_abi::{ExternAbi, IntegerType, ReprOptions};
use syntax::{
    AstNode, NodeOrToken, SyntaxNodePtr, T,
    ast::{self, HasGenericParams, HasName, HasVisibility, IsString},
};
use thin_vec::ThinVec;
use triomphe::Arc;

use crate::{
    ConstId, EnumId, EnumVariantId, EnumVariantLoc, ExternBlockId, FunctionId, FxIndexMap,
    HasModule, ImplId, ItemContainerId, LoweringMode, ModuleId, StaticId, StructId, TraitId,
    TypeAliasId, UnionId, VariantId,
    attrs::AttrFlags,
    expr_store::{
        Body, ExpressionStore, ExpressionStoreBuilder, ExpressionStoreSourceMap,
        lower::{
            ExprCollector, lower_function, lower_generic_params, lower_trait, lower_type_alias,
        },
    },
    hir::{ExprId, PatId, generics::GenericParams},
    item_tree::{FieldsShape, RawVisibility, visibility_from_ast},
    src::HasSource,
    type_ref::{ConstRef, TraitRef, TypeBound, TypeRefId},
};

#[inline]
fn as_name_opt(name: Option<ast::Name>) -> Name {
    name.map_or_else(Name::missing, |it| it.as_name())
}

#[derive(Debug, PartialEq, Eq)]
pub struct StructSignature {
    pub name: Name,
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub flags: StructFlags,
    pub shape: FieldsShape,
}

bitflags! {
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub struct StructFlags: u16 {
        /// Indicates whether this struct has `#[repr]`.
        const HAS_REPR = 1 << 0;
        /// Indicates whether the struct has a `#[rustc_has_incoherent_inherent_impls]` attribute.
        const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 1;
        /// Indicates whether the struct has a `#[fundamental]` attribute.
        const FUNDAMENTAL      = 1 << 2;
        /// Indicates whether the struct is `PhantomData`.
        const IS_PHANTOM_DATA  = 1 << 3;
        /// Indicates whether this struct is `Box`.
        const IS_BOX           = 1 << 4;
        /// Indicates whether this struct is `ManuallyDrop`.
        const IS_MANUALLY_DROP = 1 << 5;
        /// Indicates whether this struct is `UnsafeCell`.
        const IS_UNSAFE_CELL   = 1 << 6;
        /// Indicates whether this struct is `UnsafePinned`.
        const IS_UNSAFE_PINNED = 1 << 7;
        /// Indicates whether this struct is `CovariantUnsafeCell`.
        const IS_COVARIANT_UNSAFE_CELL = 1 << 8;
    }
}

#[salsa::tracked]
impl StructSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: StructId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: StructId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);
        let InFile { file_id, value: source } = loc.source(db);
        let attrs = AttrFlags::query(db, id.into());

        let mut flags = StructFlags::empty();
        if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) {
            flags |= StructFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS;
        }
        if attrs.contains(AttrFlags::FUNDAMENTAL) {
            flags |= StructFlags::FUNDAMENTAL;
        }
        if attrs.contains(AttrFlags::HAS_REPR) {
            flags |= StructFlags::HAS_REPR;
        }
        if let Some(lang) = attrs.lang_item_with_attrs(db, id.into()) {
            match lang {
                _ if lang == sym::phantom_data => flags |= StructFlags::IS_PHANTOM_DATA,
                _ if lang == sym::owned_box => flags |= StructFlags::IS_BOX,
                _ if lang == sym::manually_drop => flags |= StructFlags::IS_MANUALLY_DROP,
                _ if lang == sym::unsafe_cell => flags |= StructFlags::IS_UNSAFE_CELL,
                _ if lang == sym::covariant_unsafe_cell => {
                    flags |= StructFlags::IS_COVARIANT_UNSAFE_CELL
                }
                _ if lang == sym::unsafe_pinned => flags |= StructFlags::IS_UNSAFE_PINNED,
                _ => (),
            }
        }
        let shape = adt_shape(source.kind());

        let (store, generic_params, source_map) = lower_generic_params(
            db,
            loc.container,
            id.into(),
            file_id,
            source.generic_param_list(),
            source.where_clause(),
            LoweringMode::Analysis,
        );
        (
            Arc::new(StructSignature {
                generic_params,
                store,
                flags,
                shape,
                name: as_name_opt(source.name()),
            }),
            source_map,
        )
    }

    #[inline]
    pub fn repr(&self, db: &dyn SourceDatabase, id: StructId) -> Option<ReprOptions> {
        if self.flags.contains(StructFlags::HAS_REPR) {
            AttrFlags::repr_assume_has(db, id.into())
        } else {
            None
        }
    }
}

#[inline]
fn adt_shape(adt_kind: ast::StructKind) -> FieldsShape {
    match adt_kind {
        ast::StructKind::Record(_) => FieldsShape::Record,
        ast::StructKind::Tuple(_) => FieldsShape::Tuple,
        ast::StructKind::Unit => FieldsShape::Unit,
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct UnionSignature {
    pub name: Name,
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub flags: StructFlags,
}

#[salsa::tracked]
impl UnionSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: UnionId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: UnionId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);
        let attrs = AttrFlags::query(db, id.into());
        let mut flags = StructFlags::empty();
        if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) {
            flags |= StructFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS;
        }
        if attrs.contains(AttrFlags::FUNDAMENTAL) {
            flags |= StructFlags::FUNDAMENTAL;
        }
        if attrs.contains(AttrFlags::HAS_REPR) {
            flags |= StructFlags::HAS_REPR;
        }

        let InFile { file_id, value: source } = loc.source(db);
        let (store, generic_params, source_map) = lower_generic_params(
            db,
            loc.container,
            id.into(),
            file_id,
            source.generic_param_list(),
            source.where_clause(),
            LoweringMode::Analysis,
        );
        (
            Arc::new(UnionSignature {
                generic_params,
                store,
                flags,
                name: as_name_opt(source.name()),
            }),
            source_map,
        )
    }

    #[inline]
    pub fn repr(&self, db: &dyn SourceDatabase, id: UnionId) -> Option<ReprOptions> {
        if self.flags.contains(StructFlags::HAS_REPR) {
            AttrFlags::repr_assume_has(db, id.into())
        } else {
            None
        }
    }
}

bitflags! {
    #[derive(Debug, Copy, Clone, PartialEq, Eq)]
    pub struct EnumFlags: u8 {
        /// Indicates whether this enum has `#[repr]`.
        const HAS_REPR = 1 << 0;
        /// Indicates whether the enum has a `#[rustc_has_incoherent_inherent_impls]` attribute.
        const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS  = 1 << 1;
        /// Whether this enum has `#[fundamental]`.
        const FUNDAMENTAL = 1 << 2;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct EnumSignature {
    pub name: Name,
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub flags: EnumFlags,
}

#[salsa::tracked]
impl EnumSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: EnumId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: EnumId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);
        let attrs = AttrFlags::query(db, id.into());
        let mut flags = EnumFlags::empty();
        if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) {
            flags |= EnumFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS;
        }
        if attrs.contains(AttrFlags::HAS_REPR) {
            flags |= EnumFlags::HAS_REPR;
        }
        if attrs.contains(AttrFlags::FUNDAMENTAL) {
            flags |= EnumFlags::FUNDAMENTAL;
        }

        let InFile { file_id, value: source } = loc.source(db);
        let (store, generic_params, source_map) = lower_generic_params(
            db,
            loc.container,
            id.into(),
            file_id,
            source.generic_param_list(),
            source.where_clause(),
            LoweringMode::Analysis,
        );

        (
            Arc::new(EnumSignature {
                generic_params,
                store,
                flags,
                name: as_name_opt(source.name()),
            }),
            source_map,
        )
    }
}

impl EnumSignature {
    pub fn variant_body_type(db: &dyn SourceDatabase, id: EnumId) -> IntegerType {
        match AttrFlags::repr(db, id.into()) {
            Some(ReprOptions { int: Some(builtin), .. }) => builtin,
            _ => IntegerType::Pointer(true),
        }
    }

    #[inline]
    pub fn repr(&self, db: &dyn SourceDatabase, id: EnumId) -> Option<ReprOptions> {
        if self.flags.contains(EnumFlags::HAS_REPR) {
            AttrFlags::repr_assume_has(db, id.into())
        } else {
            None
        }
    }
}
bitflags::bitflags! {
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
    pub struct ConstFlags: u8 {
        const HAS_BODY = 1 << 1;
        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 7;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct ConstSignature {
    pub name: Option<Name>,
    // generic_params: GenericParams,
    pub store: ExpressionStore,
    pub type_ref: TypeRefId,
    pub flags: ConstFlags,
}

#[salsa::tracked]
impl ConstSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: ConstId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: ConstId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);

        let module = loc.container.module(db);
        let attrs = AttrFlags::query(db, id.into());
        let mut flags = ConstFlags::empty();
        if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) {
            flags |= ConstFlags::RUSTC_ALLOW_INCOHERENT_IMPL;
        }
        let source = loc.source(db);
        if source.value.body().is_some() {
            flags.insert(ConstFlags::HAS_BODY);
        }

        let (store, source_map, type_ref) =
            crate::expr_store::lower::lower_type_ref(db, module, source.as_ref().map(|it| it.ty()));

        (
            Arc::new(ConstSignature {
                store,
                type_ref,
                flags,
                name: source.value.name().map(|it| it.as_name()),
            }),
            source_map,
        )
    }
}

impl ConstSignature {
    pub fn has_body(&self) -> bool {
        self.flags.contains(ConstFlags::HAS_BODY)
    }
}

bitflags::bitflags! {
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
    pub struct StaticFlags: u8 {
        const HAS_BODY = 1 << 1;
        const MUTABLE    = 1 << 3;
        const UNSAFE     = 1 << 4;
        const EXPLICIT_SAFE = 1 << 5;
        const EXTERN     = 1 << 6;
        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 7;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct StaticSignature {
    pub name: Name,

    // generic_params: GenericParams,
    pub store: ExpressionStore,
    pub type_ref: TypeRefId,
    pub flags: StaticFlags,
}

#[salsa::tracked]
impl StaticSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: StaticId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: StaticId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);

        let module = loc.container.module(db);
        let attrs = AttrFlags::query(db, id.into());
        let mut flags = StaticFlags::empty();
        if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) {
            flags |= StaticFlags::RUSTC_ALLOW_INCOHERENT_IMPL;
        }

        if matches!(loc.container, ItemContainerId::ExternBlockId(_)) {
            flags.insert(StaticFlags::EXTERN);
        }

        let source = loc.source(db);
        if source.value.body().is_some() {
            flags.insert(StaticFlags::HAS_BODY);
        }
        if source.value.mut_token().is_some() {
            flags.insert(StaticFlags::MUTABLE);
        }
        if source.value.unsafe_token().is_some() {
            flags.insert(StaticFlags::UNSAFE);
        }
        if source.value.safe_token().is_some() {
            flags.insert(StaticFlags::EXPLICIT_SAFE);
        }

        let (store, source_map, type_ref) =
            crate::expr_store::lower::lower_type_ref(db, module, source.as_ref().map(|it| it.ty()));

        (
            Arc::new(StaticSignature {
                store,
                type_ref,
                flags,
                name: as_name_opt(source.value.name()),
            }),
            source_map,
        )
    }
}

bitflags::bitflags! {
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
    pub struct ImplFlags: u8 {
        const NEGATIVE = 1 << 1;
        const DEFAULT = 1 << 2;
        const UNSAFE = 1 << 3;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct ImplSignature {
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub self_ty: TypeRefId,
    pub target_trait: Option<TraitRef>,
    pub flags: ImplFlags,
}

#[salsa::tracked]
impl ImplSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: ImplId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: ImplId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);

        let mut flags = ImplFlags::empty();
        let src = loc.source(db);
        if src.value.unsafe_token().is_some() {
            flags.insert(ImplFlags::UNSAFE);
        }
        if src.value.excl_token().is_some() {
            flags.insert(ImplFlags::NEGATIVE);
        }
        if src.value.default_token().is_some() {
            flags.insert(ImplFlags::DEFAULT);
        }

        let (store, source_map, self_ty, target_trait, generic_params) =
            crate::expr_store::lower::lower_impl(db, loc.container, src, id);

        (
            Arc::new(ImplSignature { store, generic_params, self_ty, target_trait, flags }),
            source_map,
        )
    }
}

impl ImplSignature {
    #[inline]
    pub fn is_negative(&self) -> bool {
        self.flags.contains(ImplFlags::NEGATIVE)
    }

    #[inline]
    pub fn is_default(&self) -> bool {
        self.flags.contains(ImplFlags::DEFAULT)
    }
}

bitflags::bitflags! {
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
    pub struct TraitFlags: u16 {
        const RUSTC_HAS_INCOHERENT_INHERENT_IMPLS = 1 << 1;
        const FUNDAMENTAL = 1 << 2;
        const UNSAFE = 1 << 3;
        const AUTO = 1 << 4;
        const SKIP_ARRAY_DURING_METHOD_DISPATCH = 1 << 5;
        const SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH = 1 << 6;
        const RUSTC_PAREN_SUGAR = 1 << 7;
        const COINDUCTIVE = 1 << 8;
        const ALIAS = 1 << 9;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct TraitSignature {
    pub name: Name,
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub flags: TraitFlags,
}

#[salsa::tracked]
impl TraitSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: TraitId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: TraitId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);

        let mut flags = TraitFlags::empty();
        let attrs = AttrFlags::query(db, id.into());
        let source = loc.source(db);
        if source.value.auto_token().is_some() {
            flags.insert(TraitFlags::AUTO | TraitFlags::COINDUCTIVE);
        }
        if source.value.unsafe_token().is_some() {
            flags.insert(TraitFlags::UNSAFE);
        }
        if source.value.eq_token().is_some() {
            flags.insert(TraitFlags::ALIAS);
        }
        if attrs.contains(AttrFlags::FUNDAMENTAL) {
            flags |= TraitFlags::FUNDAMENTAL;
        }
        if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) {
            flags |= TraitFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS;
        }
        if attrs.contains(AttrFlags::RUSTC_PAREN_SUGAR) {
            flags |= TraitFlags::RUSTC_PAREN_SUGAR;
        }
        if attrs.contains(AttrFlags::RUSTC_COINDUCTIVE) {
            flags |= TraitFlags::COINDUCTIVE;
        }

        if attrs.contains(AttrFlags::RUSTC_SKIP_ARRAY_DURING_METHOD_DISPATCH) {
            flags |= TraitFlags::SKIP_ARRAY_DURING_METHOD_DISPATCH;
        }
        if attrs.contains(AttrFlags::RUSTC_SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH) {
            flags |= TraitFlags::SKIP_BOXED_SLICE_DURING_METHOD_DISPATCH;
        }

        let name = as_name_opt(source.value.name());
        let (store, source_map, generic_params) = lower_trait(db, loc.container, source, id);

        (Arc::new(TraitSignature { store, generic_params, flags, name }), source_map)
    }
}

bitflags! {
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
    pub struct FnFlags: u16 {
        const HAS_BODY = 1 << 1;
        const DEFAULT = 1 << 2;
        const CONST = 1 << 3;
        const ASYNC = 1 << 4;
        const GEN = 1 << 5;
        const UNSAFE = 1 << 6;
        const HAS_VARARGS = 1 << 7;
        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 8;
        const HAS_SELF_PARAM = 1 << 9;
        /// The `#[target_feature]` attribute is necessary to check safety (with RFC 2396),
        /// but keeping it for all functions will consume a lot of memory when there are
        /// only very few functions with it. So we only encode its existence here, and lookup
        /// it if needed.
        const HAS_TARGET_FEATURE = 1 << 10;
        const DEPRECATED_SAFE_2024 = 1 << 11;
        const EXPLICIT_SAFE = 1 << 12;
        const HAS_LEGACY_CONST_GENERICS = 1 << 13;
        const RUSTC_INTRINSIC = 1 << 14;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct FunctionSignature {
    pub name: Name,
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub params: Box<[TypeRefId]>,
    pub ret_type: Option<TypeRefId>,
    pub abi: ExternAbi,
    pub flags: FnFlags,
}

#[salsa::tracked]
impl FunctionSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: FunctionId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: FunctionId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);
        let module = loc.container.module(db);

        let mut flags = FnFlags::empty();
        let attrs = AttrFlags::query(db, id.into());
        if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) {
            flags.insert(FnFlags::RUSTC_ALLOW_INCOHERENT_IMPL);
        }

        if attrs.contains(AttrFlags::HAS_TARGET_FEATURE) {
            flags.insert(FnFlags::HAS_TARGET_FEATURE);
        }

        if attrs.contains(AttrFlags::RUSTC_INTRINSIC) {
            flags.insert(FnFlags::RUSTC_INTRINSIC);
        }
        if attrs.contains(AttrFlags::HAS_LEGACY_CONST_GENERICS) {
            flags.insert(FnFlags::HAS_LEGACY_CONST_GENERICS);
        }

        let source = loc.source(db);

        if source.value.unsafe_token().is_some() {
            if attrs.contains(AttrFlags::RUSTC_DEPRECATED_SAFE_2024) {
                flags.insert(FnFlags::DEPRECATED_SAFE_2024);
            } else {
                flags.insert(FnFlags::UNSAFE);
            }
        }
        if source.value.async_token().is_some() {
            flags.insert(FnFlags::ASYNC);
        }
        if source.value.gen_token().is_some() {
            flags.insert(FnFlags::GEN);
        }
        if source.value.const_token().is_some() {
            flags.insert(FnFlags::CONST);
        }
        if source.value.default_token().is_some() {
            flags.insert(FnFlags::DEFAULT);
        }
        if source.value.safe_token().is_some() {
            flags.insert(FnFlags::EXPLICIT_SAFE);
        }
        if source.value.body().is_some() {
            flags.insert(FnFlags::HAS_BODY);
        }

        let name = as_name_opt(source.value.name());
        let abi = source
            .value
            .abi()
            .map(|abi| {
                abi.abi_string()
                    .and_then(|abi| abi.text_without_quotes().parse().ok())
                    .unwrap_or(ExternAbi::FALLBACK)
            })
            .or_else(|| match loc.container {
                ItemContainerId::ExternBlockId(extern_block) => {
                    Some(extern_block_abi(db, extern_block))
                }
                ItemContainerId::ModuleId(_)
                | ItemContainerId::ImplId(_)
                | ItemContainerId::TraitId(_) => None,
            })
            .unwrap_or(ExternAbi::Rust);
        let (store, source_map, generic_params, params, ret_type, self_param, variadic) =
            lower_function(db, module, source, id);
        if self_param {
            flags.insert(FnFlags::HAS_SELF_PARAM);
        }
        if variadic {
            flags.insert(FnFlags::HAS_VARARGS);
        }
        (
            Arc::new(FunctionSignature {
                generic_params,
                store,
                params,
                ret_type,
                abi,
                flags,
                name,
            }),
            source_map,
        )
    }
}

impl FunctionSignature {
    pub fn has_body(&self) -> bool {
        self.flags.contains(FnFlags::HAS_BODY)
    }

    /// True if the first param is `self`. This is relevant to decide whether this
    /// can be called as a method.
    pub fn has_self_param(&self) -> bool {
        self.flags.contains(FnFlags::HAS_SELF_PARAM)
    }

    pub fn is_default(&self) -> bool {
        self.flags.contains(FnFlags::DEFAULT)
    }

    pub fn is_const(&self) -> bool {
        self.flags.contains(FnFlags::CONST)
    }

    pub fn is_async(&self) -> bool {
        self.flags.contains(FnFlags::ASYNC)
    }

    pub fn is_gen(&self) -> bool {
        self.flags.contains(FnFlags::GEN)
    }

    pub fn is_unsafe(&self) -> bool {
        self.flags.contains(FnFlags::UNSAFE)
    }

    pub fn is_deprecated_safe_2024(&self) -> bool {
        self.flags.contains(FnFlags::DEPRECATED_SAFE_2024)
    }

    pub fn is_safe(&self) -> bool {
        self.flags.contains(FnFlags::EXPLICIT_SAFE)
    }

    pub fn is_varargs(&self) -> bool {
        self.flags.contains(FnFlags::HAS_VARARGS)
    }

    pub fn has_target_feature(&self) -> bool {
        self.flags.contains(FnFlags::HAS_TARGET_FEATURE)
    }

    #[inline]
    pub fn legacy_const_generics_indices<'db>(
        &self,
        db: &'db dyn SourceDatabase,
        id: FunctionId,
    ) -> Option<&'db [u32]> {
        if !self.flags.contains(FnFlags::HAS_LEGACY_CONST_GENERICS) {
            return None;
        }

        AttrFlags::legacy_const_generic_indices(db, id).as_deref()
    }

    pub fn is_intrinsic(db: &dyn SourceDatabase, id: FunctionId) -> bool {
        let data = FunctionSignature::of(db, id);
        data.flags.contains(FnFlags::RUSTC_INTRINSIC)
    }
}

bitflags! {
    #[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
    pub struct TypeAliasFlags: u8 {
        const RUSTC_HAS_INCOHERENT_INHERENT_IMPL = 1 << 1;
        const IS_EXTERN = 1 << 6;
        const RUSTC_ALLOW_INCOHERENT_IMPL = 1 << 7;
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct TypeAliasSignature {
    pub name: Name,
    pub generic_params: GenericParams,
    pub store: ExpressionStore,
    pub bounds: Box<[TypeBound]>,
    pub ty: Option<TypeRefId>,
    pub flags: TypeAliasFlags,
}

#[salsa::tracked]
impl TypeAliasSignature {
    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: TypeAliasId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }

    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: TypeAliasId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let loc = id.lookup(db);

        let mut flags = TypeAliasFlags::empty();
        let attrs = AttrFlags::query(db, id.into());
        if attrs.contains(AttrFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPLS) {
            flags.insert(TypeAliasFlags::RUSTC_HAS_INCOHERENT_INHERENT_IMPL);
        }
        if attrs.contains(AttrFlags::RUSTC_ALLOW_INCOHERENT_IMPL) {
            flags.insert(TypeAliasFlags::RUSTC_ALLOW_INCOHERENT_IMPL);
        }
        if matches!(loc.container, ItemContainerId::ExternBlockId(_)) {
            flags.insert(TypeAliasFlags::IS_EXTERN);
        }
        let source = loc.source(db);
        let name = as_name_opt(source.value.name());
        let (store, source_map, generic_params, bounds, ty) =
            lower_type_alias(db, loc.container.module(db), source, id);

        (
            Arc::new(TypeAliasSignature { store, generic_params, flags, bounds, name, ty }),
            source_map,
        )
    }
}

#[derive(Debug, PartialEq, Eq)]
pub struct FunctionBody {
    pub store: ExpressionStore,
    pub parameters: Box<[PatId]>,
}

#[derive(Debug, PartialEq, Eq)]
pub struct SimpleBody {
    pub store: ExpressionStore,
}
pub type StaticBody = SimpleBody;
pub type ConstBody = SimpleBody;
pub type EnumVariantBody = SimpleBody;

#[derive(Debug, PartialEq, Eq)]
pub struct VariantFieldsBody {
    pub store: ExpressionStore,
    pub fields: Box<[Option<ExprId>]>,
}

/// A single field of an enum variant or struct
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FieldData {
    pub name: Name,
    pub type_ref: TypeRefId,
    pub visibility: RawVisibility,
    pub is_unsafe: bool,
    pub default_value: Option<ConstRef>,
}

pub type LocalFieldId = Idx<FieldData>;

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VariantFields {
    fields: Arena<FieldData>,
    pub store: ExpressionStore,
    pub shape: FieldsShape,
}

#[salsa::tracked]
impl VariantFields {
    #[salsa::tracked(returns(ref))]
    pub fn with_source_map(
        db: &dyn SourceDatabase,
        id: VariantId,
    ) -> (Arc<Self>, ExpressionStoreSourceMap) {
        let (shape, result) = match id {
            VariantId::EnumVariantId(id) => {
                let loc = id.lookup(db);
                let parent = loc.parent.lookup(db);
                let source = loc.source(db);
                let shape = adt_shape(source.value.kind());
                let enum_vis = Some(source.value.parent_enum().visibility());
                let fields = lower_field_list(
                    db,
                    parent.container,
                    source.map(|src| src.field_list()),
                    enum_vis,
                );
                (shape, fields)
            }
            VariantId::StructId(id) => {
                let loc = id.lookup(db);
                let source = loc.source(db);
                let shape = adt_shape(source.value.kind());
                let fields =
                    lower_field_list(db, loc.container, source.map(|src| src.field_list()), None);
                (shape, fields)
            }
            VariantId::UnionId(id) => {
                let loc = id.lookup(db);
                let source = loc.source(db);
                let fields = lower_field_list(
                    db,
                    loc.container,
                    source.map(|src| src.record_field_list().map(ast::FieldList::RecordFieldList)),
                    None,
                );
                (FieldsShape::Record, fields)
            }
        };
        match result {
            Some((fields, store, source_map)) => {
                (Arc::new(VariantFields { fields, store, shape }), source_map)
            }
            None => {
                let source_map = ExpressionStoreSourceMap::default();
                (
                    Arc::new(VariantFields {
                        fields: Arena::default(),
                        store: ExpressionStoreBuilder::default().finish().0,
                        shape,
                    }),
                    source_map,
                )
            }
        }
    }

    #[salsa::tracked(returns(deref))]
    pub fn of(db: &dyn SourceDatabase, id: VariantId) -> Arc<Self> {
        Self::with_source_map(db, id).0.clone()
    }
}

impl VariantFields {
    pub fn len(&self) -> usize {
        self.fields.len()
    }

    pub fn fields(&self) -> &Arena<FieldData> {
        &self.fields
    }

    pub fn field(&self, name: &Name) -> Option<LocalFieldId> {
        self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None })
    }
}

fn lower_field_list(
    db: &dyn SourceDatabase,
    module: ModuleId,
    fields: InFile<Option<ast::FieldList>>,
    override_visibility: Option<Option<ast::Visibility>>,
) -> Option<(Arena<FieldData>, ExpressionStore, ExpressionStoreSourceMap)> {
    let file_id = fields.file_id;
    match fields.value? {
        ast::FieldList::RecordFieldList(fields) => lower_fields(
            db,
            module,
            InFile::new(file_id, fields.fields().map(|field| (field.ty(), field))),
            |_, field| as_name_opt(field.name()),
            override_visibility,
        ),
        ast::FieldList::TupleFieldList(fields) => lower_fields(
            db,
            module,
            InFile::new(file_id, fields.fields().map(|field| (field.ty(), field))),
            |idx, _| Name::new_tuple_field(idx),
            override_visibility,
        ),
    }
}

fn lower_fields<Field: ast::HasAttrs + ast::HasVisibility>(
    db: &dyn SourceDatabase,
    module: ModuleId,
    fields: InFile<impl Iterator<Item = (Option<ast::Type>, Field)>>,
    mut field_name: impl FnMut(usize, &Field) -> Name,
    override_visibility: Option<Option<ast::Visibility>>,
) -> Option<(Arena<FieldData>, ExpressionStore, ExpressionStoreSourceMap)> {
    let cfg_options = module.krate(db).cfg_options(db);
    let mut col = ExprCollector::new(db, module, fields.file_id, crate::LoweringMode::Analysis);
    let override_visibility = override_visibility.map(|vis| {
        LazyCell::new(|| {
            let span_map = fields.file_id.span_map(db);
            visibility_from_ast(db, vis, &mut |range| span_map.span_for_range(range).ctx)
        })
    });

    let mut arena = Arena::new();
    let mut idx = 0;
    let mut has_fields = false;
    for (ty, field) in fields.value {
        has_fields = true;
        match AttrFlags::is_cfg_enabled_for(&field, cfg_options) {
            Ok(()) => {
                let type_ref =
                    col.lower_type_ref_opt(ty, &mut ExprCollector::impl_trait_error_allocator);
                let visibility = override_visibility.as_ref().map_or_else(
                    || {
                        visibility_from_ast(db, field.visibility(), &mut |range| {
                            col.span_map().span_for_range(range).ctx
                        })
                    },
                    |it| RawVisibility::clone(it),
                );
                let is_unsafe = field
                    .syntax()
                    .children_with_tokens()
                    .filter_map(NodeOrToken::into_token)
                    .any(|token| token.kind() == T![unsafe]);
                let name = field_name(idx, &field);

                // Check if field has default value (only for record fields)
                let default_value = ast::RecordField::cast(field.syntax().clone())
                    .and_then(|rf| rf.eq_token().is_some().then_some(rf.default_val()))
                    .flatten()
                    .map(|expr| col.lower_const_arg(expr));

                arena.alloc(FieldData { name, type_ref, visibility, is_unsafe, default_value });
                idx += 1;
            }
            Err(cfg) => {
                col.store.diagnostics.push(
                    crate::expr_store::ExpressionStoreDiagnostics::InactiveCode {
                        node: InFile::new(fields.file_id, SyntaxNodePtr::new(field.syntax())),
                        cfg,
                        opts: cfg_options.clone(),
                    },
                );
            }
        }
    }
    if !has_fields {
        return None;
    }
    let (store, source_map) = col.store.finish();
    arena.shrink_to_fit();
    Some((arena, store, source_map))
}

#[derive(Debug, PartialEq, Eq)]
pub struct InactiveEnumVariantCode {
    pub cfg: CfgExpr,
    pub opts: CfgOptions,
    pub ast_id: span::FileAstId<ast::Variant>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EnumVariants {
    pub variants: FxIndexMap<Name, (EnumVariantId, FieldsShape)>,
}

#[salsa::tracked]
impl EnumVariants {
    #[salsa::tracked(returns(ref))]
    pub(crate) fn of(
        db: &dyn SourceDatabase,
        e: EnumId,
    ) -> (EnumVariants, ThinVec<InactiveEnumVariantCode>) {
        let loc = e.lookup(db);
        let source = loc.source(db);
        let ast_id_map = source.file_id.ast_id_map(db);

        let mut diagnostics = ThinVec::new();
        let cfg_options = loc.container.krate(db).cfg_options(db);
        let Some(variants) = source.value.variant_list() else {
            return (EnumVariants { variants: FxIndexMap::default() }, ThinVec::new());
        };
        let mut variants = variants
            .variants()
            .filter_map(|variant| {
                let ast_id = ast_id_map.ast_id(&variant);
                match AttrFlags::is_cfg_enabled_for(&variant, cfg_options) {
                    Ok(()) => {
                        let name = as_name_opt(variant.name());
                        let enum_variant = EnumVariantLoc {
                            id: source.with_value(ast_id),
                            parent: e,
                            name: name.clone(),
                        }
                        .intern(db);
                        let shape = adt_shape(variant.kind());
                        Some((name, (enum_variant, shape)))
                    }
                    Err(cfg) => {
                        diagnostics.push(InactiveEnumVariantCode {
                            ast_id,
                            cfg,
                            opts: cfg_options.clone(),
                        });
                        None
                    }
                }
            })
            .collect::<FxIndexMap<_, _>>();
        variants.shrink_to_fit();
        diagnostics.shrink_to_fit();

        (EnumVariants { variants }, diagnostics)
    }
}

impl EnumVariants {
    pub fn variant(&self, name: &Name) -> Option<EnumVariantId> {
        self.variants.get(name).map(|&(id, _)| id)
    }

    pub fn variant_name_by_id(&self, variant_id: EnumVariantId) -> Option<Name> {
        self.variants
            .iter()
            .find_map(|(name, (id, _))| if *id == variant_id { Some(name.clone()) } else { None })
    }

    // [Adopted from rustc](https://github.com/rust-lang/rust/blob/bd53aa3bf7a24a70d763182303bd75e5fc51a9af/compiler/rustc_middle/src/ty/adt.rs#L446-L448)
    pub fn is_payload_free(&self, db: &dyn SourceDatabase) -> bool {
        self.variants.values().all(|&(v, shape)| {
            // The condition check order is slightly modified from rustc
            // to improve performance by early returning with relatively fast checks

            // The outer if condition is whether this variant has const ctor or not
            if !matches!(shape, FieldsShape::Unit) {
                let variant = v.fields(db);
                if !variant.fields().is_empty() {
                    return false;
                }

                let body = Body::of(db, v.into());
                // A variant with explicit discriminant
                if !matches!(body[body.root_expr()], crate::hir::Expr::Missing) {
                    return false;
                }
            }
            true
        })
    }
}

#[salsa::tracked(returns(copy))]
pub(crate) fn extern_block_abi(db: &dyn SourceDatabase, extern_block: ExternBlockId) -> ExternAbi {
    let source = extern_block.lookup(db).source(db);
    source
        .value
        .abi()
        .and_then(|abi| abi.abi_string()?.text_without_quotes().parse().ok())
        .unwrap_or(ExternAbi::FALLBACK)
}
