argus-lib 0.1.12

Trait debugger analysis for IDE interactions.
use argus_ext::{
  infer::InferCtxtExt as ArgusInferCtxtExt,
  ty::{PredicateExt, PredicateObligationExt},
};
use argus_ser as ser;
use rustc_hir::BodyId;
use rustc_infer::{infer::InferCtxt, traits::PredicateObligation};
use rustc_middle::ty::{self, Predicate};
use serde::Serialize;

use crate::{
  analysis::{EvaluationResult, FulfillmentData},
  types::{Obligation, ObligationNecessity},
};

pub trait InferCtxtExt<'tcx> {
  fn bless_fulfilled<'a>(
    &self,
    obligation: &'a PredicateObligation<'tcx>,
    result: EvaluationResult,
  ) -> FulfillmentData<'a, 'tcx>;

  fn erase_non_local_data(
    &self,
    body_id: BodyId,
    fdata: FulfillmentData<'_, 'tcx>,
  ) -> Obligation;

  fn guess_predicate_necessity(
    &self,
    p: &Predicate<'tcx>,
  ) -> ObligationNecessity;

  fn obligation_necessity(
    &self,
    obligation: &PredicateObligation<'tcx>,
  ) -> ObligationNecessity;
}

impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
  fn guess_predicate_necessity(
    &self,
    p: &Predicate<'tcx>,
  ) -> ObligationNecessity {
    use ObligationNecessity as ON;

    // HACK REMOVE: for the user study we want to reduce
    // noise that otherwise shouldn't be there. Iterator / IntoIterator
    // bounds are common when the solver is scrambling for diagnostic
    // notes and we don't have any problems that "need" them.
    let is_user_study_hack = || {
      use rustc_hir::lang_items::LangItem as LI;
      let items = vec![LI::Iterator, LI::Sized, LI::Deref];
      let lis = self.tcx.lang_items();
      for i in items {
        if let Some(def_id) = lis.get(i) && p.is_trait_pred_rhs(def_id) {
          return true;
        }
      }

      false
    };

    let is_rhs_lang_item = || {
      self
        .tcx
        .lang_items()
        .iter()
        .any(|(_lang_item, def_id)| p.is_trait_pred_rhs(def_id))
    };

    let is_writeable = || {
      use ty::ClauseKind as CK;
      matches!(
        p.kind().skip_binder(),
        ty::PredicateKind::Clause(
          CK::Trait(..)
            | CK::RegionOutlives(..)
            | CK::TypeOutlives(..)
            | CK::Projection(..)
        )
      )
    };

    if !is_writeable() || p.is_lhs_unit() {
      ON::No
    } else if is_user_study_hack() {
      ON::No
    } else if (p.is_trait_predicate() && is_rhs_lang_item())
      || !p.is_trait_predicate()
    {
      ON::OnError
    } else {
      ON::Yes
    }
  }

  /// Determine what level of "necessity" an obligation has.
  ///
  /// For example, obligations with a cause `SizedReturnType`,
  /// with a `self_ty` `()` (unit), is *unnecessary*. Obligations whose
  /// kind is not a Trait Clause, are generally deemed `ForProfessionals`
  /// (that is, you can get them when interested), and others are shown
  /// `OnError`. Necessary obligations are trait predicates where the
  /// type and trait are not `LangItems`.
  fn obligation_necessity(
    &self,
    obligation: &PredicateObligation<'tcx>,
  ) -> ObligationNecessity {
    use rustc_infer::traits::ObligationCauseCode;
    use ObligationNecessity as ON;

    let p = &obligation.predicate;
    let code = obligation.cause.code();

    if matches!(code, ObligationCauseCode::SizedReturnType) && p.is_lhs_unit()
      || matches!(p.as_trait_predicate(), Some(p) if p.self_ty().skip_binder().is_ty_var())
    {
      ON::No
    } else {
      self.guess_predicate_necessity(p)
    }
  }

  fn bless_fulfilled<'a>(
    &self,
    obligation: &'a PredicateObligation<'tcx>,
    result: EvaluationResult,
  ) -> FulfillmentData<'a, 'tcx> {
    FulfillmentData {
      hash: self.predicate_hash(&obligation.predicate).into(),
      obligation,
      result,
    }
  }

  fn erase_non_local_data(
    &self,
    body_id: BodyId,
    fdata: FulfillmentData<'_, 'tcx>,
  ) -> Obligation {
    #[derive(Serialize)]
    #[serde(rename_all = "camelCase")]
    struct Wrapper<'a, 'tcx: 'a>(
      #[serde(with = "ser::ty::PredicateObligationDef")]
      &'a PredicateObligation<'tcx>,
    );

    let obl = &fdata.obligation;
    let range = obl.range(&self.tcx, body_id);
    let necessity = self.obligation_necessity(obl);
    let obligation = crate::tls::unsafe_access_interner(|ty_intern| {
      ser::to_value_expect(self, ty_intern, &Wrapper(obl))
    });

    Obligation {
      obligation,
      hash: fdata.hash,
      range,
      kind: fdata.kind(),
      necessity,
      result: fdata.result,
    }
  }
}