From a0a80a41034b1240dc3e8fd794ae1d4f77714d99 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Fri, 27 Mar 2020 18:56:18 +0100 Subject: [PATCH] Implement Chalk's debug methods using TLS 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. --- Cargo.lock | 7 + crates/ra_hir_ty/Cargo.toml | 2 + crates/ra_hir_ty/src/display.rs | 26 +-- crates/ra_hir_ty/src/traits.rs | 13 +- crates/ra_hir_ty/src/traits/chalk.rs | 81 ++++---- crates/ra_hir_ty/src/traits/chalk/tls.rs | 231 +++++++++++++++++++++++ 6 files changed, 304 insertions(+), 56 deletions(-) create mode 100644 crates/ra_hir_ty/src/traits/chalk/tls.rs diff --git a/Cargo.lock b/Cargo.lock index 34f05e83a3..20e7e745be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 9a4a7aa6f7..59efc1c31d 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -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" } diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs index 0e9313aa12..d03bbd5a7b 100644 --- a/crates/ra_hir_ty/src/display.rs +++ b/crates/ra_hir_ty/src/display.rs @@ -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(()) diff --git a/crates/ra_hir_ty/src/traits.rs b/crates/ra_hir_ty/src/traits.rs index 5a1e12ce92..21e2333799 100644 --- a/crates/ra_hir_ty/src/traits.rs +++ b/crates/ra_hir_ty/src/traits.rs @@ -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, diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index 1bc0f07135..c5f1b52324 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -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, - _fmt: &mut fmt::Formatter<'_>, + type_kind_id: StructId, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - 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, - _fmt: &mut fmt::Formatter<'_>, - ) -> Option { - None + fn debug_trait_id(type_kind_id: TraitId, fmt: &mut fmt::Formatter<'_>) -> Option { + tls::with_current_program(|prog| Some(prog?.debug_trait_id(type_kind_id, fmt))) } - fn debug_assoc_type_id( - _id: chalk_ir::AssocTypeId, - _fmt: &mut fmt::Formatter<'_>, - ) -> Option { - None + fn debug_assoc_type_id(id: AssocTypeId, fmt: &mut fmt::Formatter<'_>) -> Option { + tls::with_current_program(|prog| Some(prog?.debug_assoc_type_id(id, fmt))) } fn debug_alias( - _projection: &chalk_ir::AliasTy, - _fmt: &mut fmt::Formatter<'_>, + alias: &chalk_ir::AliasTy, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_alias(alias, fmt))) } - fn debug_ty(_ty: &chalk_ir::Ty, _fmt: &mut fmt::Formatter<'_>) -> Option { - None + fn debug_ty(ty: &chalk_ir::Ty, fmt: &mut fmt::Formatter<'_>) -> Option { + tls::with_current_program(|prog| Some(prog?.debug_ty(ty, fmt))) } fn debug_lifetime( - _lifetime: &chalk_ir::Lifetime, - _fmt: &mut fmt::Formatter<'_>, + lifetime: &chalk_ir::Lifetime, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_lifetime(lifetime, fmt))) } fn debug_parameter( - _parameter: &Parameter, - _fmt: &mut fmt::Formatter<'_>, + parameter: &Parameter, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_parameter(parameter, fmt))) } - fn debug_goal(_goal: &Goal, _fmt: &mut fmt::Formatter<'_>) -> Option { - None + fn debug_goal(goal: &Goal, fmt: &mut fmt::Formatter<'_>) -> Option { + tls::with_current_program(|prog| Some(prog?.debug_goal(goal, fmt))) } fn debug_goals( - _goals: &chalk_ir::Goals, - _fmt: &mut fmt::Formatter<'_>, + goals: &chalk_ir::Goals, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_goals(goals, fmt))) } fn debug_program_clause_implication( - _pci: &chalk_ir::ProgramClauseImplication, - _fmt: &mut fmt::Formatter<'_>, + pci: &chalk_ir::ProgramClauseImplication, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_program_clause_implication(pci, fmt))) } fn debug_application_ty( - _application_ty: &chalk_ir::ApplicationTy, - _fmt: &mut fmt::Formatter<'_>, + application_ty: &chalk_ir::ApplicationTy, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_application_ty(application_ty, fmt))) } fn debug_substitution( - _substitution: &chalk_ir::Substitution, - _fmt: &mut fmt::Formatter<'_>, + substitution: &chalk_ir::Substitution, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| Some(prog?.debug_substitution(substitution, fmt))) } fn debug_separator_trait_ref( - _separator_trait_ref: &chalk_ir::SeparatorTraitRef, - _fmt: &mut fmt::Formatter<'_>, + separator_trait_ref: &chalk_ir::SeparatorTraitRef, + fmt: &mut fmt::Formatter<'_>, ) -> Option { - None + tls::with_current_program(|prog| { + Some(prog?.debug_separator_trait_ref(separator_trait_ref, fmt)) + }) } fn intern_ty(&self, ty: chalk_ir::TyData) -> Box> { diff --git a/crates/ra_hir_ty/src/traits/chalk/tls.rs b/crates/ra_hir_ty/src/traits/chalk/tls.rs new file mode 100644 index 0000000000..d9bbb54a5b --- /dev/null +++ b/crates/ra_hir_ty/src/traits/chalk/tls.rs @@ -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, + 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, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + write!(fmt, "{:?}", ty.data(&Interner)) + } + + pub fn debug_lifetime( + &self, + lifetime: &Lifetime, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + write!(fmt, "{:?}", lifetime.data(&Interner)) + } + + pub fn debug_parameter( + &self, + parameter: &Parameter, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + write!(fmt, "{:?}", parameter.data(&Interner).inner_debug()) + } + + pub fn debug_goal( + &self, + goal: &Goal, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + let goal_data = goal.data(&Interner); + write!(fmt, "{:?}", goal_data) + } + + pub fn debug_goals( + &self, + goals: &Goals, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + write!(fmt, "{:?}", goals.debug(&Interner)) + } + + pub fn debug_program_clause_implication( + &self, + pci: &ProgramClauseImplication, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + write!(fmt, "{:?}", pci.debug(&Interner)) + } + + pub fn debug_application_ty( + &self, + application_ty: &chalk_ir::ApplicationTy, + fmt: &mut fmt::Formatter<'_>, + ) -> Result<(), fmt::Error> { + write!(fmt, "{:?}", application_ty.debug(&Interner)) + } + + pub fn debug_substitution( + &self, + substitution: &chalk_ir::Substitution, + 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, + 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( + 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(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()) + } +}