use rustc_ast::{LitFloatType, LitIntType, LitKind};
use rustc_hir;
use rustc_macros::StableHash;

use crate::ty::{self, Ty, TyCtxt};

/// Input argument for `tcx.lit_to_const`.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, StableHash)]
pub struct LitToConstInput<'tcx> {
    /// The absolute value of the resultant constant.
    pub lit: LitKind,
    /// The type of the constant.
    ///
    /// `None` is used by const generics when the type of the constant is unknown, e.g.
    /// if there are inference variables
    pub ty: Option<Ty<'tcx>>,
    /// If the constant is negative.
    pub neg: bool,
}

/// Checks whether a literal can be interpreted as a const of the given type.
pub fn const_lit_matches_ty<'tcx>(
    tcx: TyCtxt<'tcx>,
    kind: &LitKind,
    ty: Ty<'tcx>,
    neg: bool,
) -> bool {
    match (*kind, ty.kind()) {
        (LitKind::Str(..), ty::Ref(_, inner_ty, _)) if inner_ty.is_str() => true,
        (LitKind::Str(..), ty::Str) if tcx.features().deref_patterns() => true,
        (LitKind::ByteStr(..), ty::Ref(_, inner_ty, _))
            if let ty::Slice(ty) | ty::Array(ty, _) = inner_ty.kind()
                && matches!(ty.kind(), ty::Uint(ty::UintTy::U8)) =>
        {
            true
        }
        (LitKind::ByteStr(..), ty::Slice(inner_ty) | ty::Array(inner_ty, _))
            if tcx.features().deref_patterns()
                && matches!(inner_ty.kind(), ty::Uint(ty::UintTy::U8)) =>
        {
            true
        }
        (LitKind::Byte(..), ty::Uint(ty::UintTy::U8)) => true,
        (LitKind::CStr(..), ty::Ref(_, inner_ty, _))
            if matches!(inner_ty.kind(), ty::Adt(def, _)
                if tcx.is_lang_item(def.did(), rustc_hir::LangItem::CStr)) =>
        {
            true
        }
        (LitKind::Int(_, LitIntType::Unsigned(lit_ty)), ty::Uint(expect_ty)) if !neg => {
            lit_ty == *expect_ty
        }
        (LitKind::Int(_, LitIntType::Signed(lit_ty)), ty::Int(expect_ty)) => lit_ty == *expect_ty,
        (LitKind::Int(_, LitIntType::Unsuffixed), ty::Uint(_)) if !neg => true,
        (LitKind::Int(_, LitIntType::Unsuffixed), ty::Int(_)) => true,
        (LitKind::Bool(..), ty::Bool) => true,
        (LitKind::Float(_, LitFloatType::Suffixed(lit_ty)), ty::Float(expect_ty)) => {
            lit_ty == *expect_ty
        }
        (LitKind::Float(_, LitFloatType::Unsuffixed), ty::Float(_)) => true,
        (LitKind::Char(..), ty::Char) => true,
        (LitKind::Err(..), _) => true,
        _ => false,
    }
}
