mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #3748
3748: Implement Chalk's debug methods using TLS r=matklad a=flodiebold
Chalk now panics if we don't implement these methods and run with CHALK_DEBUG, so I thought I'd try to implement them 'properly'. Sadly, it seems impossible to do without transmuting lifetimes somewhere. The problem is that we need a `&dyn HirDatabase` to get names etc., which we can't just put into TLS. I thought I could just use `scoped-tls`, but that doesn't support references to unsized types. So I put the `&dyn` into another struct and put the reference to *that* into the TLS, but I have to transmute the lifetime to 'static for that to work. I think this is sound, but I still don't really want to do it this way...
Having names in the Chalk debug output is very nice, but maybe IDs will have to suffice 😞
Co-authored-by: Florian Diebold <florian.diebold@freiheit.com>
This commit is contained in:
commit
0a891d19ae
6 changed files with 304 additions and 56 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -995,6 +995,7 @@ dependencies = [
|
|||
"ra_prof",
|
||||
"ra_syntax",
|
||||
"rustc-hash",
|
||||
"scoped-tls",
|
||||
"smallvec",
|
||||
"stdx",
|
||||
"test_utils",
|
||||
|
@ -1390,6 +1391,12 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scoped-tls"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
|
|
@ -24,6 +24,8 @@ ra_prof = { path = "../ra_prof" }
|
|||
ra_syntax = { path = "../ra_syntax" }
|
||||
test_utils = { path = "../test_utils" }
|
||||
|
||||
scoped-tls = "1"
|
||||
|
||||
chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "039fc904a05f8cb3d0c682c9a57a63dda7a35356" }
|
||||
chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "039fc904a05f8cb3d0c682c9a57a63dda7a35356" }
|
||||
chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "039fc904a05f8cb3d0c682c9a57a63dda7a35356" }
|
||||
|
|
|
@ -247,19 +247,21 @@ impl HirDisplay for ApplicationTy {
|
|||
}
|
||||
}
|
||||
TypeCtor::Closure { .. } => {
|
||||
let sig = self.parameters[0]
|
||||
.callable_sig(f.db)
|
||||
.expect("first closure parameter should contain signature");
|
||||
if sig.params().is_empty() {
|
||||
write!(f, "||")?;
|
||||
} else if f.omit_verbose_types() {
|
||||
write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
|
||||
let sig = self.parameters[0].callable_sig(f.db);
|
||||
if let Some(sig) = sig {
|
||||
if sig.params().is_empty() {
|
||||
write!(f, "||")?;
|
||||
} else if f.omit_verbose_types() {
|
||||
write!(f, "|{}|", TYPE_HINT_TRUNCATION)?;
|
||||
} else {
|
||||
write!(f, "|")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, "|")?;
|
||||
};
|
||||
write!(f, " -> {}", sig.ret().display(f.db))?;
|
||||
} else {
|
||||
write!(f, "|")?;
|
||||
f.write_joined(sig.params(), ", ")?;
|
||||
write!(f, "|")?;
|
||||
};
|
||||
write!(f, " -> {}", sig.ret().display(f.db))?;
|
||||
write!(f, "{{closure}}")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
|
@ -177,7 +177,7 @@ fn solve(
|
|||
|
||||
let fuel = std::cell::Cell::new(CHALK_SOLVER_FUEL);
|
||||
|
||||
let solution = solver.solve_limited(&context, goal, || {
|
||||
let should_continue = || {
|
||||
context.db.check_canceled();
|
||||
let remaining = fuel.get();
|
||||
fuel.set(remaining - 1);
|
||||
|
@ -185,12 +185,21 @@ fn solve(
|
|||
log::debug!("fuel exhausted");
|
||||
}
|
||||
remaining > 0
|
||||
});
|
||||
};
|
||||
let mut solve = || solver.solve_limited(&context, goal, should_continue);
|
||||
// don't set the TLS for Chalk unless Chalk debugging is active, to make
|
||||
// extra sure we only use it for debugging
|
||||
let solution =
|
||||
if is_chalk_debug() { chalk::tls::set_current_program(db, solve) } else { solve() };
|
||||
|
||||
log::debug!("solve({:?}) => {:?}", goal, solution);
|
||||
solution
|
||||
}
|
||||
|
||||
fn is_chalk_debug() -> bool {
|
||||
std::env::var("CHALK_DEBUG").is_ok()
|
||||
}
|
||||
|
||||
fn solution_from_chalk(
|
||||
db: &dyn HirDatabase,
|
||||
solution: chalk_solve::Solution<Interner>,
|
||||
|
|
|
@ -20,6 +20,8 @@ use crate::{
|
|||
ProjectionTy, Substs, TraitRef, Ty, TypeCtor,
|
||||
};
|
||||
|
||||
pub(super) mod tls;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub struct Interner;
|
||||
|
||||
|
@ -33,90 +35,85 @@ impl chalk_ir::interner::Interner for Interner {
|
|||
type Identifier = TypeAliasId;
|
||||
type DefId = InternId;
|
||||
|
||||
// FIXME: implement these
|
||||
fn debug_struct_id(
|
||||
_type_kind_id: chalk_ir::StructId<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
type_kind_id: StructId,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_struct_id(type_kind_id, fmt)))
|
||||
}
|
||||
|
||||
fn debug_trait_id(
|
||||
_type_kind_id: chalk_ir::TraitId<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
fn debug_trait_id(type_kind_id: TraitId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
|
||||
tls::with_current_program(|prog| Some(prog?.debug_trait_id(type_kind_id, fmt)))
|
||||
}
|
||||
|
||||
fn debug_assoc_type_id(
|
||||
_id: chalk_ir::AssocTypeId<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
fn debug_assoc_type_id(id: AssocTypeId, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
|
||||
tls::with_current_program(|prog| Some(prog?.debug_assoc_type_id(id, fmt)))
|
||||
}
|
||||
|
||||
fn debug_alias(
|
||||
_projection: &chalk_ir::AliasTy<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
alias: &chalk_ir::AliasTy<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_alias(alias, fmt)))
|
||||
}
|
||||
|
||||
fn debug_ty(_ty: &chalk_ir::Ty<Self>, _fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
|
||||
None
|
||||
fn debug_ty(ty: &chalk_ir::Ty<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
|
||||
tls::with_current_program(|prog| Some(prog?.debug_ty(ty, fmt)))
|
||||
}
|
||||
|
||||
fn debug_lifetime(
|
||||
_lifetime: &chalk_ir::Lifetime<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
lifetime: &chalk_ir::Lifetime<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_lifetime(lifetime, fmt)))
|
||||
}
|
||||
|
||||
fn debug_parameter(
|
||||
_parameter: &Parameter<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
parameter: &Parameter<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_parameter(parameter, fmt)))
|
||||
}
|
||||
|
||||
fn debug_goal(_goal: &Goal<Self>, _fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
|
||||
None
|
||||
fn debug_goal(goal: &Goal<Interner>, fmt: &mut fmt::Formatter<'_>) -> Option<fmt::Result> {
|
||||
tls::with_current_program(|prog| Some(prog?.debug_goal(goal, fmt)))
|
||||
}
|
||||
|
||||
fn debug_goals(
|
||||
_goals: &chalk_ir::Goals<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
goals: &chalk_ir::Goals<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_goals(goals, fmt)))
|
||||
}
|
||||
|
||||
fn debug_program_clause_implication(
|
||||
_pci: &chalk_ir::ProgramClauseImplication<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
pci: &chalk_ir::ProgramClauseImplication<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_program_clause_implication(pci, fmt)))
|
||||
}
|
||||
|
||||
fn debug_application_ty(
|
||||
_application_ty: &chalk_ir::ApplicationTy<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
application_ty: &chalk_ir::ApplicationTy<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_application_ty(application_ty, fmt)))
|
||||
}
|
||||
|
||||
fn debug_substitution(
|
||||
_substitution: &chalk_ir::Substitution<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
substitution: &chalk_ir::Substitution<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| Some(prog?.debug_substitution(substitution, fmt)))
|
||||
}
|
||||
|
||||
fn debug_separator_trait_ref(
|
||||
_separator_trait_ref: &chalk_ir::SeparatorTraitRef<Self>,
|
||||
_fmt: &mut fmt::Formatter<'_>,
|
||||
separator_trait_ref: &chalk_ir::SeparatorTraitRef<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Option<fmt::Result> {
|
||||
None
|
||||
tls::with_current_program(|prog| {
|
||||
Some(prog?.debug_separator_trait_ref(separator_trait_ref, fmt))
|
||||
})
|
||||
}
|
||||
|
||||
fn intern_ty(&self, ty: chalk_ir::TyData<Self>) -> Box<chalk_ir::TyData<Self>> {
|
||||
|
|
231
crates/ra_hir_ty/src/traits/chalk/tls.rs
Normal file
231
crates/ra_hir_ty/src/traits/chalk/tls.rs
Normal file
|
@ -0,0 +1,231 @@
|
|||
//! Implementation of Chalk debug helper functions using TLS.
|
||||
use std::fmt;
|
||||
|
||||
use chalk_ir::{AliasTy, Goal, Goals, Lifetime, Parameter, ProgramClauseImplication, TypeName};
|
||||
|
||||
use super::{from_chalk, Interner};
|
||||
use crate::{db::HirDatabase, CallableDef, TypeCtor};
|
||||
use hir_def::{AdtId, AssocContainerId, Lookup, TypeAliasId};
|
||||
|
||||
pub use unsafe_tls::{set_current_program, with_current_program};
|
||||
|
||||
pub struct DebugContext<'a>(&'a (dyn HirDatabase + 'a));
|
||||
|
||||
impl DebugContext<'_> {
|
||||
pub fn debug_struct_id(
|
||||
&self,
|
||||
id: super::StructId,
|
||||
f: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let type_ctor: TypeCtor = from_chalk(self.0, TypeName::Struct(id));
|
||||
match type_ctor {
|
||||
TypeCtor::Bool => write!(f, "bool")?,
|
||||
TypeCtor::Char => write!(f, "char")?,
|
||||
TypeCtor::Int(t) => write!(f, "{}", t)?,
|
||||
TypeCtor::Float(t) => write!(f, "{}", t)?,
|
||||
TypeCtor::Str => write!(f, "str")?,
|
||||
TypeCtor::Slice => write!(f, "slice")?,
|
||||
TypeCtor::Array => write!(f, "array")?,
|
||||
TypeCtor::RawPtr(m) => write!(f, "*{}", m.as_keyword_for_ptr())?,
|
||||
TypeCtor::Ref(m) => write!(f, "&{}", m.as_keyword_for_ref())?,
|
||||
TypeCtor::Never => write!(f, "!")?,
|
||||
TypeCtor::Tuple { .. } => {
|
||||
write!(f, "()")?;
|
||||
}
|
||||
TypeCtor::FnPtr { .. } => {
|
||||
write!(f, "fn")?;
|
||||
}
|
||||
TypeCtor::FnDef(def) => {
|
||||
let name = match def {
|
||||
CallableDef::FunctionId(ff) => self.0.function_data(ff).name.clone(),
|
||||
CallableDef::StructId(s) => self.0.struct_data(s).name.clone(),
|
||||
CallableDef::EnumVariantId(e) => {
|
||||
let enum_data = self.0.enum_data(e.parent);
|
||||
enum_data.variants[e.local_id].name.clone()
|
||||
}
|
||||
};
|
||||
match def {
|
||||
CallableDef::FunctionId(_) => write!(f, "{{fn {}}}", name)?,
|
||||
CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {
|
||||
write!(f, "{{ctor {}}}", name)?
|
||||
}
|
||||
}
|
||||
}
|
||||
TypeCtor::Adt(def_id) => {
|
||||
let name = match def_id {
|
||||
AdtId::StructId(it) => self.0.struct_data(it).name.clone(),
|
||||
AdtId::UnionId(it) => self.0.union_data(it).name.clone(),
|
||||
AdtId::EnumId(it) => self.0.enum_data(it).name.clone(),
|
||||
};
|
||||
write!(f, "{}", name)?;
|
||||
}
|
||||
TypeCtor::AssociatedType(type_alias) => {
|
||||
let trait_ = match type_alias.lookup(self.0.upcast()).container {
|
||||
AssocContainerId::TraitId(it) => it,
|
||||
_ => panic!("not an associated type"),
|
||||
};
|
||||
let trait_name = self.0.trait_data(trait_).name.clone();
|
||||
let name = self.0.type_alias_data(type_alias).name.clone();
|
||||
write!(f, "{}::{}", trait_name, name)?;
|
||||
}
|
||||
TypeCtor::Closure { def, expr } => {
|
||||
write!(f, "{{closure {:?} in {:?}}}", expr.into_raw(), def)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn debug_trait_id(
|
||||
&self,
|
||||
id: super::TraitId,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let trait_: hir_def::TraitId = from_chalk(self.0, id);
|
||||
let trait_data = self.0.trait_data(trait_);
|
||||
write!(fmt, "{}", trait_data.name)
|
||||
}
|
||||
|
||||
pub fn debug_assoc_type_id(
|
||||
&self,
|
||||
id: super::AssocTypeId,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let type_alias: TypeAliasId = from_chalk(self.0, id);
|
||||
let type_alias_data = self.0.type_alias_data(type_alias);
|
||||
let trait_ = match type_alias.lookup(self.0.upcast()).container {
|
||||
AssocContainerId::TraitId(t) => t,
|
||||
_ => panic!("associated type not in trait"),
|
||||
};
|
||||
let trait_data = self.0.trait_data(trait_);
|
||||
write!(fmt, "{}::{}", trait_data.name, type_alias_data.name)
|
||||
}
|
||||
|
||||
pub fn debug_alias(
|
||||
&self,
|
||||
alias: &AliasTy<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let type_alias: TypeAliasId = from_chalk(self.0, alias.associated_ty_id);
|
||||
let type_alias_data = self.0.type_alias_data(type_alias);
|
||||
let trait_ = match type_alias.lookup(self.0.upcast()).container {
|
||||
AssocContainerId::TraitId(t) => t,
|
||||
_ => panic!("associated type not in trait"),
|
||||
};
|
||||
let trait_data = self.0.trait_data(trait_);
|
||||
let params = alias.substitution.parameters(&Interner);
|
||||
write!(
|
||||
fmt,
|
||||
"<{:?} as {}<{:?}>>::{}",
|
||||
¶ms[0],
|
||||
trait_data.name,
|
||||
¶ms[1..],
|
||||
type_alias_data.name
|
||||
)
|
||||
}
|
||||
|
||||
pub fn debug_ty(
|
||||
&self,
|
||||
ty: &chalk_ir::Ty<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", ty.data(&Interner))
|
||||
}
|
||||
|
||||
pub fn debug_lifetime(
|
||||
&self,
|
||||
lifetime: &Lifetime<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", lifetime.data(&Interner))
|
||||
}
|
||||
|
||||
pub fn debug_parameter(
|
||||
&self,
|
||||
parameter: &Parameter<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", parameter.data(&Interner).inner_debug())
|
||||
}
|
||||
|
||||
pub fn debug_goal(
|
||||
&self,
|
||||
goal: &Goal<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
let goal_data = goal.data(&Interner);
|
||||
write!(fmt, "{:?}", goal_data)
|
||||
}
|
||||
|
||||
pub fn debug_goals(
|
||||
&self,
|
||||
goals: &Goals<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", goals.debug(&Interner))
|
||||
}
|
||||
|
||||
pub fn debug_program_clause_implication(
|
||||
&self,
|
||||
pci: &ProgramClauseImplication<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", pci.debug(&Interner))
|
||||
}
|
||||
|
||||
pub fn debug_application_ty(
|
||||
&self,
|
||||
application_ty: &chalk_ir::ApplicationTy<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", application_ty.debug(&Interner))
|
||||
}
|
||||
|
||||
pub fn debug_substitution(
|
||||
&self,
|
||||
substitution: &chalk_ir::Substitution<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", substitution.debug(&Interner))
|
||||
}
|
||||
|
||||
pub fn debug_separator_trait_ref(
|
||||
&self,
|
||||
separator_trait_ref: &chalk_ir::SeparatorTraitRef<Interner>,
|
||||
fmt: &mut fmt::Formatter<'_>,
|
||||
) -> Result<(), fmt::Error> {
|
||||
write!(fmt, "{:?}", separator_trait_ref.debug(&Interner))
|
||||
}
|
||||
}
|
||||
|
||||
mod unsafe_tls {
|
||||
use super::DebugContext;
|
||||
use crate::db::HirDatabase;
|
||||
use scoped_tls::scoped_thread_local;
|
||||
|
||||
scoped_thread_local!(static PROGRAM: DebugContext);
|
||||
|
||||
pub fn with_current_program<R>(
|
||||
op: impl for<'a> FnOnce(Option<&'a DebugContext<'a>>) -> R,
|
||||
) -> R {
|
||||
if PROGRAM.is_set() {
|
||||
PROGRAM.with(|prog| op(Some(prog)))
|
||||
} else {
|
||||
op(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_current_program<OP, R>(p: &dyn HirDatabase, op: OP) -> R
|
||||
where
|
||||
OP: FnOnce() -> R,
|
||||
{
|
||||
let ctx = DebugContext(p);
|
||||
// we're transmuting the lifetime in the DebugContext to static. This is
|
||||
// fine because we only keep the reference for the lifetime of this
|
||||
// function, *and* the only way to access the context is through
|
||||
// `with_current_program`, which hides the lifetime through the `for`
|
||||
// type.
|
||||
let static_p: &DebugContext<'static> =
|
||||
unsafe { std::mem::transmute::<&DebugContext, &DebugContext<'static>>(&ctx) };
|
||||
PROGRAM.set(static_p, || op())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue