]> git.lizzy.rs Git - rust.git/commitdiff
Implement Chalk's debug methods using TLS
authorFlorian Diebold <florian.diebold@freiheit.com>
Fri, 27 Mar 2020 17:56:18 +0000 (18:56 +0100)
committerFlorian Diebold <flodiebold@gmail.com>
Fri, 10 Apr 2020 13:04:06 +0000 (15:04 +0200)
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
crates/ra_hir_ty/Cargo.toml
crates/ra_hir_ty/src/display.rs
crates/ra_hir_ty/src/traits.rs
crates/ra_hir_ty/src/traits/chalk.rs
crates/ra_hir_ty/src/traits/chalk/tls.rs [new file with mode: 0644]

index 34f05e83a3329d0a4eb25f11b89cd236f15d938d..20e7e745be7a7d4c96d8ec2aebe9fcbf0a076f9e 100644 (file)
@@ -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"
index 9a4a7aa6f7e60f3d8ec806526f89b2fb6a133717..59efc1c31d8c33c10de7a6536d435a3de8d16536 100644 (file)
@@ -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" }
index 0e9313aa12174b57d76802877729bc700e413f9b..d03bbd5a7b5c72e8277d8b946a9e5cc422c0ea9d 100644 (file)
@@ -247,19 +247,21 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
                 }
             }
             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(())
index 5a1e12ce923278bb7b57667a8e2fedd4656be636..21e2333799d2ab84ae574c885e61436b796021e2 100644 (file)
@@ -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>,
index 1bc0f07135812312062e788aca119ea848b8039c..c5f1b52324240d2c184b78096933adf1e86f59d7 100644 (file)
@@ -20,6 +20,8 @@
     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>> {
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 (file)
index 0000000..d9bbb54
--- /dev/null
@@ -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 {}<{:?}>>::{}",
+            &params[0],
+            trait_data.name,
+            &params[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())
+    }
+}