From 66f04e6be54c47104877bff777b7042960d04393 Mon Sep 17 00:00:00 2001 From: Frizi Date: Sun, 24 Nov 2019 17:36:30 +0100 Subject: Show missing struct fields in the error message --- crates/ra_hir/src/diagnostics.rs | 7 ++++++- crates/ra_hir/src/ty/tests.rs | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs index dafacba70..6db499e06 100644 --- a/crates/ra_hir/src/diagnostics.rs +++ b/crates/ra_hir/src/diagnostics.rs @@ -39,7 +39,12 @@ pub struct MissingFields { impl Diagnostic for MissingFields { fn message(&self) -> String { - "fill structure fields".to_string() + use std::fmt::Write; + let mut message = String::from("Missing structure fields:\n"); + for field in &self.missed_fields { + write!(message, "- {}\n", field).unwrap(); + } + message } fn source(&self) -> Source { Source { file_id: self.file, value: self.field_list.into() } diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs index 3209c66bd..98eb863cb 100644 --- a/crates/ra_hir/src/ty/tests.rs +++ b/crates/ra_hir/src/ty/tests.rs @@ -4832,7 +4832,8 @@ fn no_such_field_diagnostics() { assert_snapshot!(diagnostics, @r###" "baz: 62": no such field - "{\n foo: 92,\n baz: 62,\n }": fill structure fields + "{\n foo: 92,\n baz: 62,\n }": Missing structure fields: + - bar "### ); } -- cgit v1.2.3 From bd53bd80bff9a1f320615a975235399b1fa4792e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 12:45:45 +0300 Subject: Push resolver up --- crates/ra_hir/src/ty/autoderef.rs | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs index 41c99d227..86b08ae6f 100644 --- a/crates/ra_hir/src/ty/autoderef.rs +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -8,10 +8,14 @@ use std::iter::successors; use hir_def::{lang_item::LangItemTarget, resolver::Resolver}; use hir_expand::name; use log::{info, warn}; +use ra_db::CrateId; use crate::{db::HirDatabase, Trait}; -use super::{traits::Solution, Canonical, Substs, Ty, TypeWalk}; +use super::{ + traits::{InEnvironment, Solution}, + Canonical, Substs, Ty, TypeWalk, +}; const AUTODEREF_RECURSION_LIMIT: usize = 10; @@ -31,16 +35,17 @@ pub(crate) fn deref( if let Some(derefed) = ty.value.builtin_deref() { Some(Canonical { value: derefed, num_vars: ty.num_vars }) } else { - deref_by_trait(db, resolver, ty) + let krate = resolver.krate()?; + let environment = super::lower::trait_env(db, resolver); + deref_by_trait(db, krate, InEnvironment { value: ty, environment }) } } fn deref_by_trait( db: &impl HirDatabase, - resolver: &Resolver, - ty: &Canonical, + krate: CrateId, + ty: InEnvironment<&Canonical>, ) -> Option> { - let krate = resolver.krate()?; let deref_trait = match db.lang_item(krate.into(), "deref".into())? { LangItemTarget::TraitId(t) => Trait::from(t), _ => return None, @@ -56,10 +61,8 @@ fn deref_by_trait( // FIXME make the Canonical handling nicer - let env = super::lower::trait_env(db, resolver); - let parameters = Substs::build_for_generics(&generic_params) - .push(ty.value.clone().shift_bound_vars(1)) + .push(ty.value.value.clone().shift_bound_vars(1)) .build(); let projection = super::traits::ProjectionPredicate { @@ -69,9 +72,9 @@ fn deref_by_trait( let obligation = super::Obligation::Projection(projection); - let in_env = super::traits::InEnvironment { value: obligation, environment: env }; + let in_env = InEnvironment { value: obligation, environment: ty.environment }; - let canonical = super::Canonical { num_vars: 1 + ty.num_vars, value: in_env }; + let canonical = super::Canonical { num_vars: 1 + ty.value.num_vars, value: in_env }; let solution = db.trait_solve(krate.into(), canonical)?; @@ -89,14 +92,14 @@ fn deref_by_trait( // the case. for i in 1..vars.0.num_vars { if vars.0.value[i] != Ty::Bound((i - 1) as u32) { - warn!("complex solution for derefing {:?}: {:?}, ignoring", ty, solution); + warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.value, solution); return None; } } Some(Canonical { value: vars.0.value[0].clone(), num_vars: vars.0.num_vars }) } Solution::Ambig(_) => { - info!("Ambiguous solution for derefing {:?}: {:?}", ty, solution); + info!("Ambiguous solution for derefing {:?}: {:?}", ty.value, solution); None } } -- cgit v1.2.3 From 8c3e372835243c922b0eff7ca23f79f227991e88 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 13:10:26 +0300 Subject: Remove Resolver from autoderef Resolver holds onto too much context, including local scopes. Let's try to pass in only what is necessary -- the trait environment. --- crates/ra_hir/src/source_binder.rs | 10 ++++++-- crates/ra_hir/src/ty/autoderef.rs | 24 ++++++++++--------- crates/ra_hir/src/ty/infer.rs | 3 +-- crates/ra_hir/src/ty/infer/coerce.rs | 13 ++++++---- crates/ra_hir/src/ty/infer/expr.rs | 40 ++++++++++++++++++++----------- crates/ra_hir/src/ty/lower.rs | 21 ++++++++-------- crates/ra_hir/src/ty/method_resolution.rs | 11 +++++---- 7 files changed, 73 insertions(+), 49 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 31390bb7f..b4f0e81d3 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -26,7 +26,10 @@ use ra_syntax::{ use crate::{ db::HirDatabase, expr::{BodySourceMap, ExprScopes, ScopeId}, - ty::method_resolution::{self, implements_trait}, + ty::{ + method_resolution::{self, implements_trait}, + TraitEnvironment, + }, Adt, AssocItem, Const, DefWithBody, Either, Enum, EnumVariant, FromSource, Function, GenericParam, Local, MacroDef, Name, Path, ScopeDef, Static, Struct, Trait, Ty, TypeAlias, }; @@ -408,7 +411,10 @@ impl SourceAnalyzer { // There should be no inference vars in types passed here // FIXME check that? let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; - crate::ty::autoderef(db, &self.resolver, canonical).map(|canonical| canonical.value) + let krate = self.resolver.krate(); + let environment = TraitEnvironment::lower(db, &self.resolver); + let ty = crate::ty::InEnvironment { value: canonical, environment }; + crate::ty::autoderef(db, krate, ty).map(|canonical| canonical.value) } /// Checks that particular type `ty` implements `std::future::Future`. diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs index 86b08ae6f..44547197c 100644 --- a/crates/ra_hir/src/ty/autoderef.rs +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -5,7 +5,7 @@ use std::iter::successors; -use hir_def::{lang_item::LangItemTarget, resolver::Resolver}; +use hir_def::lang_item::LangItemTarget; use hir_expand::name; use log::{info, warn}; use ra_db::CrateId; @@ -21,23 +21,25 @@ const AUTODEREF_RECURSION_LIMIT: usize = 10; pub(crate) fn autoderef<'a>( db: &'a impl HirDatabase, - resolver: &'a Resolver, - ty: Canonical, + krate: Option, + ty: InEnvironment>, ) -> impl Iterator> + 'a { - successors(Some(ty), move |ty| deref(db, resolver, ty)).take(AUTODEREF_RECURSION_LIMIT) + let InEnvironment { value: ty, environment } = ty; + successors(Some(ty), move |ty| { + deref(db, krate?, InEnvironment { value: ty, environment: environment.clone() }) + }) + .take(AUTODEREF_RECURSION_LIMIT) } pub(crate) fn deref( db: &impl HirDatabase, - resolver: &Resolver, - ty: &Canonical, + krate: CrateId, + ty: InEnvironment<&Canonical>, ) -> Option> { - if let Some(derefed) = ty.value.builtin_deref() { - Some(Canonical { value: derefed, num_vars: ty.num_vars }) + if let Some(derefed) = ty.value.value.builtin_deref() { + Some(Canonical { value: derefed, num_vars: ty.value.num_vars }) } else { - let krate = resolver.krate()?; - let environment = super::lower::trait_env(db, resolver); - deref_by_trait(db, krate, InEnvironment { value: ty, environment }) + deref_by_trait(db, krate, ty) } } diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index ddc7d262a..6fd00d457 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -34,7 +34,6 @@ use ra_prof::profile; use test_utils::tested_by; use super::{ - lower, traits::{Guidance, Obligation, ProjectionPredicate, Solution}, ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypableDef, TypeCtor, TypeWalk, Uncertain, @@ -216,7 +215,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { var_unification_table: InPlaceUnificationTable::new(), obligations: Vec::default(), return_ty: Ty::Unknown, // set in collect_fn_signature - trait_env: lower::trait_env(db, &resolver), + trait_env: TraitEnvironment::lower(db, &resolver), coerce_unsized_map: Self::init_coerce_unsized_map(db, &resolver), db, owner, diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs index 54765da35..4b53bba73 100644 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ b/crates/ra_hir/src/ty/infer/coerce.rs @@ -14,7 +14,7 @@ use crate::{ Adt, Mutability, }; -use super::{InferTy, InferenceContext, TypeVarValue}; +use super::{InEnvironment, InferTy, InferenceContext, TypeVarValue}; impl<'a, D: HirDatabase> InferenceContext<'a, D> { /// Unify two types, but may coerce the first one to the second one @@ -320,9 +320,14 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let canonicalized = self.canonicalizer().canonicalize_ty(from_ty.clone()); let to_ty = self.resolve_ty_shallow(&to_ty); // FIXME: Auto DerefMut - for derefed_ty in - autoderef::autoderef(self.db, &self.resolver.clone(), canonicalized.value.clone()) - { + for derefed_ty in autoderef::autoderef( + self.db, + self.resolver.krate(), + InEnvironment { + value: canonicalized.value.clone(), + environment: self.trait_env.clone(), + }, + ) { let derefed_ty = canonicalized.decanonicalize_ty(derefed_ty.value); match (&*self.resolve_ty_shallow(&derefed_ty), &*to_ty) { // Stop when constructor matches. diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 663ff9435..194e55819 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -15,9 +15,9 @@ use crate::{ db::HirDatabase, expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, ty::{ - autoderef, method_resolution, op, CallableDef, InferTy, IntTy, Mutability, Namespace, - Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk, - Uncertain, + autoderef, method_resolution, op, traits::InEnvironment, CallableDef, InferTy, IntTy, + Mutability, Namespace, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, + TypeCtor, TypeWalk, Uncertain, }, Adt, Name, }; @@ -245,8 +245,11 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let canonicalized = self.canonicalizer().canonicalize_ty(receiver_ty); let ty = autoderef::autoderef( self.db, - &self.resolver.clone(), - canonicalized.value.clone(), + self.resolver.krate(), + InEnvironment { + value: canonicalized.value.clone(), + environment: self.trait_env.clone(), + }, ) .find_map(|derefed_ty| match canonicalized.decanonicalize_ty(derefed_ty.value) { Ty::Apply(a_ty) => match a_ty.ctor { @@ -337,16 +340,25 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Expr::UnaryOp { expr, op } => { let inner_ty = self.infer_expr(*expr, &Expectation::none()); match op { - UnaryOp::Deref => { - let canonicalized = self.canonicalizer().canonicalize_ty(inner_ty); - if let Some(derefed_ty) = - autoderef::deref(self.db, &self.resolver, &canonicalized.value) - { - canonicalized.decanonicalize_ty(derefed_ty.value) - } else { - Ty::Unknown + UnaryOp::Deref => match self.resolver.krate() { + Some(krate) => { + let canonicalized = self.canonicalizer().canonicalize_ty(inner_ty); + match autoderef::deref( + self.db, + krate, + InEnvironment { + value: &canonicalized.value, + environment: self.trait_env.clone(), + }, + ) { + Some(derefed_ty) => { + canonicalized.decanonicalize_ty(derefed_ty.value) + } + None => Ty::Unknown, + } } - } + None => Ty::Unknown, + }, UnaryOp::Neg => { match &inner_ty { Ty::Apply(a_ty) => match a_ty.ctor { diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index a39beb2a0..b76929501 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -19,8 +19,8 @@ use hir_def::{ use ra_arena::map::ArenaMap; use super::{ - FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, - TypeWalk, + FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, + Ty, TypeCtor, TypeWalk, }; use crate::{ db::HirDatabase, @@ -591,16 +591,15 @@ pub(crate) fn generic_predicates_for_param_query( .collect() } -pub(crate) fn trait_env( - db: &impl HirDatabase, - resolver: &Resolver, -) -> Arc { - let predicates = resolver - .where_predicates_in_scope() - .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) - .collect::>(); +impl TraitEnvironment { + pub(crate) fn lower(db: &impl HirDatabase, resolver: &Resolver) -> Arc { + let predicates = resolver + .where_predicates_in_scope() + .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) + .collect::>(); - Arc::new(super::TraitEnvironment { predicates }) + Arc::new(TraitEnvironment { predicates }) + } } /// Resolve the where clause(s) of an item with generics. diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index caa5f5f74..f7905b5ff 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -15,7 +15,7 @@ use crate::{ AssocItem, Crate, Function, ImplBlock, Module, Mutability, Name, Trait, }; -use super::{autoderef, lower, Canonical, InEnvironment, TraitEnvironment, TraitRef}; +use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; /// This is used as a key for indexing impls. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -179,8 +179,9 @@ pub(crate) fn iterate_method_candidates( // Also note that when we've got a receiver like &S, even if the method we // find in the end takes &self, we still do the autoderef step (just as // rustc does an autoderef and then autoref again). - - for derefed_ty in autoderef::autoderef(db, resolver, ty.clone()) { + let environment = TraitEnvironment::lower(db, resolver); + let ty = InEnvironment { value: ty.clone(), environment }; + for derefed_ty in autoderef::autoderef(db, resolver.krate(), ty) { if let Some(result) = iterate_inherent_methods( &derefed_ty, db, @@ -230,7 +231,7 @@ fn iterate_trait_method_candidates( ) -> Option { let krate = resolver.krate()?; // FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that) - let env = lower::trait_env(db, resolver); + let env = TraitEnvironment::lower(db, resolver); // if ty is `impl Trait` or `dyn Trait`, the trait doesn't need to be in scope let inherent_trait = ty.value.inherent_trait().into_iter(); // if we have `T: Trait` in the param env, the trait doesn't need to be in scope @@ -324,7 +325,7 @@ pub(crate) fn implements_trait( // anyway, but currently Chalk doesn't implement `dyn/impl Trait` yet return true; } - let env = lower::trait_env(db, resolver); + let env = TraitEnvironment::lower(db, resolver); let goal = generic_implements_goal(db, env, trait_, ty.clone()); let solution = db.trait_solve(krate, goal); -- cgit v1.2.3 From c2a16632d0773dec707acb215297ef55b5c880fe Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 15:39:12 +0300 Subject: Use GenericDefId more --- crates/ra_hir/src/db.rs | 4 ++-- crates/ra_hir/src/from_id.rs | 17 ++--------------- crates/ra_hir/src/ty.rs | 6 +++--- crates/ra_hir/src/ty/lower.rs | 16 ++++++++-------- crates/ra_hir/src/ty/traits/chalk.rs | 10 +++++----- 5 files changed, 20 insertions(+), 33 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 5084bbacf..7ec04ad73 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -24,7 +24,7 @@ pub use hir_def::{ RawItemsWithSourceMapQuery, StaticDataQuery, StructDataQuery, TraitDataQuery, TypeAliasDataQuery, }, - LocalStructFieldId, VariantId, + GenericDefId, LocalStructFieldId, VariantId, }; pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, @@ -54,7 +54,7 @@ pub trait HirDatabase: DefDatabase { ) -> Arc<[GenericPredicate]>; #[salsa::invoke(crate::ty::generic_predicates_query)] - fn generic_predicates(&self, def: GenericDef) -> Arc<[GenericPredicate]>; + fn generic_predicates(&self, def: GenericDefId) -> Arc<[GenericPredicate]>; #[salsa::invoke(crate::ty::generic_defaults_query)] fn generic_defaults(&self, def: GenericDef) -> Substs; diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs index 529ac8251..3a27d6f0c 100644 --- a/crates/ra_hir/src/from_id.rs +++ b/crates/ra_hir/src/from_id.rs @@ -9,9 +9,8 @@ use hir_def::{ }; use crate::{ - ty::{CallableDef, TypableDef}, - Adt, AssocItem, AttrDef, Const, Crate, DefWithBody, EnumVariant, Function, GenericDef, - ModuleDef, Static, StructField, TypeAlias, VariantDef, + ty::TypableDef, Adt, AssocItem, AttrDef, Const, Crate, DefWithBody, EnumVariant, Function, + GenericDef, ModuleDef, Static, StructField, TypeAlias, VariantDef, }; impl From for Crate { @@ -214,18 +213,6 @@ impl From for GenericDefId { } } -impl From for GenericDefId { - fn from(def: CallableDef) -> Self { - match def { - CallableDef::Function(it) => it.id.into(), - CallableDef::Struct(it) => it.id.into(), - CallableDef::EnumVariant(it) => { - EnumVariantId { parent: it.parent.id, local_id: it.id }.into() - } - } - } -} - impl From for VariantId { fn from(def: VariantDef) -> Self { match def { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index f62316c1f..2473ac574 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -17,7 +17,7 @@ use std::ops::Deref; use std::sync::Arc; use std::{fmt, iter, mem}; -use hir_def::{generics::GenericParams, AdtId}; +use hir_def::{generics::GenericParams, AdtId, GenericDefId}; use ra_db::{impl_intern_key, salsa}; use crate::{ @@ -176,7 +176,7 @@ impl TypeCtor { } } - pub fn as_generic_def(self) -> Option { + pub fn as_generic_def(self) -> Option { match self { TypeCtor::Bool | TypeCtor::Char @@ -193,7 +193,7 @@ impl TypeCtor { | TypeCtor::Closure { .. } => None, TypeCtor::Adt(adt) => Some(adt.into()), TypeCtor::FnDef(callable) => Some(callable.into()), - TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()), + TypeCtor::AssociatedType(type_alias) => Some(type_alias.id.into()), } } } diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index b76929501..da3c8e94a 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -14,7 +14,7 @@ use hir_def::{ path::{GenericArg, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, - AdtId, GenericDefId, LocalStructFieldId, VariantId, + AdtId, EnumVariantId, GenericDefId, LocalStructFieldId, VariantId, }; use ra_arena::map::ArenaMap; @@ -605,9 +605,9 @@ impl TraitEnvironment { /// Resolve the where clause(s) of an item with generics. pub(crate) fn generic_predicates_query( db: &impl HirDatabase, - def: GenericDef, + def: GenericDefId, ) -> Arc<[GenericPredicate]> { - let resolver = GenericDefId::from(def).resolver(db); + let resolver = def.resolver(db); resolver .where_predicates_in_scope() .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) @@ -819,12 +819,12 @@ impl CallableDef { } } -impl From for GenericDef { - fn from(def: CallableDef) -> GenericDef { +impl From for GenericDefId { + fn from(def: CallableDef) -> GenericDefId { match def { - CallableDef::Function(f) => f.into(), - CallableDef::Struct(s) => s.into(), - CallableDef::EnumVariant(e) => e.into(), + CallableDef::Function(f) => f.id.into(), + CallableDef::Struct(s) => s.id.into(), + CallableDef::EnumVariant(e) => EnumVariantId::from(e).into(), } } } diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 1a93e5e50..a0dbf6305 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -9,7 +9,7 @@ use chalk_ir::{ }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; -use hir_def::lang_item::LangItemTarget; +use hir_def::{lang_item::LangItemTarget, GenericDefId}; use hir_expand::name; use ra_db::salsa::{InternId, InternKey}; @@ -19,7 +19,7 @@ use crate::{ db::HirDatabase, ty::display::HirDisplay, ty::{ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, - Crate, GenericDef, ImplBlock, Trait, TypeAlias, + Crate, ImplBlock, Trait, TypeAlias, }; /// This represents a trait whose name we could not resolve. @@ -402,7 +402,7 @@ fn make_binders(value: T, num_vars: usize) -> chalk_ir::Binders { fn convert_where_clauses( db: &impl HirDatabase, - def: GenericDef, + def: GenericDefId, substs: &Substs, ) -> Vec> { let generic_predicates = db.generic_predicates(def); @@ -561,7 +561,7 @@ pub(crate) fn trait_datum_query( marker: false, fundamental: false, }; - let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); + let where_clauses = convert_where_clauses(db, trait_.id.into(), &bound_vars); let associated_ty_ids = trait_ .items(db) .into_iter() @@ -643,7 +643,7 @@ fn impl_block_datum( } else { chalk_rust_ir::ImplType::External }; - let where_clauses = convert_where_clauses(db, impl_block.into(), &bound_vars); + let where_clauses = convert_where_clauses(db, impl_block.id.into(), &bound_vars); let negative = impl_block.is_negative(db); debug!( "impl {:?}: {}{} where {:?}", -- cgit v1.2.3 From 5f39c5794ec2521e9bb4c108771a1644269859ab Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 15:41:53 +0300 Subject: Use GenericDefIdMore --- crates/ra_hir/src/code_model.rs | 18 +++---------- crates/ra_hir/src/db.rs | 6 ++--- crates/ra_hir/src/from_id.rs | 24 ++++++++---------- crates/ra_hir/src/source_binder.rs | 4 +-- crates/ra_hir/src/ty.rs | 8 +++--- crates/ra_hir/src/ty/infer/path.rs | 2 +- crates/ra_hir/src/ty/lower.rs | 32 +++++++++++++----------- crates/ra_hir/src/ty/method_resolution.rs | 2 +- crates/ra_hir/src/ty/traits/chalk.rs | 2 +- crates/ra_ide_api/src/completion/presentation.rs | 2 +- 10 files changed, 44 insertions(+), 56 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 3f44a50c4..534f1f8e9 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -11,9 +11,9 @@ use hir_def::{ per_ns::PerNs, resolver::{HasResolver, TypeNs}, type_ref::TypeRef, - AstItemDef, ConstId, ContainerId, EnumId, FunctionId, HasModule, ImplId, LocalEnumVariantId, - LocalImportId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, StaticId, StructId, - TraitId, TypeAliasId, UnionId, + AstItemDef, ConstId, ContainerId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, + LocalEnumVariantId, LocalImportId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, + StaticId, StructId, TraitId, TypeAliasId, UnionId, }; use hir_expand::{ diagnostics::DiagnosticSink, @@ -897,16 +897,6 @@ impl_froms!( Const ); -impl From for GenericDef { - fn from(item: AssocItem) -> Self { - match item { - AssocItem::Function(f) => f.into(), - AssocItem::Const(c) => c.into(), - AssocItem::TypeAlias(t) => t.into(), - } - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct Local { pub(crate) parent: DefWithBody, @@ -960,7 +950,7 @@ impl Local { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct GenericParam { - pub(crate) parent: GenericDef, + pub(crate) parent: GenericDefId, pub(crate) idx: u32, } diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 7ec04ad73..b034d4e44 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -12,7 +12,7 @@ use crate::{ CallableDef, FnSig, GenericPredicate, InferenceResult, Namespace, Substs, Ty, TypableDef, TypeCtor, }, - Crate, DefWithBody, GenericDef, ImplBlock, Trait, + Crate, DefWithBody, ImplBlock, Trait, }; pub use hir_def::{ @@ -49,7 +49,7 @@ pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::ty::generic_predicates_for_param_query)] fn generic_predicates_for_param( &self, - def: GenericDef, + def: GenericDefId, param_idx: u32, ) -> Arc<[GenericPredicate]>; @@ -57,7 +57,7 @@ pub trait HirDatabase: DefDatabase { fn generic_predicates(&self, def: GenericDefId) -> Arc<[GenericPredicate]>; #[salsa::invoke(crate::ty::generic_defaults_query)] - fn generic_defaults(&self, def: GenericDef) -> Substs; + fn generic_defaults(&self, def: GenericDefId) -> Substs; #[salsa::invoke(crate::ty::method_resolution::CrateImplBlocks::impls_in_crate_query)] fn impls_in_crate(&self, krate: Crate) -> Arc; diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs index 3a27d6f0c..619f6055e 100644 --- a/crates/ra_hir/src/from_id.rs +++ b/crates/ra_hir/src/from_id.rs @@ -137,20 +137,6 @@ impl From for GenericDefId { } } -impl From for GenericDef { - fn from(def: GenericDefId) -> Self { - match def { - GenericDefId::FunctionId(it) => GenericDef::Function(it.into()), - GenericDefId::AdtId(it) => GenericDef::Adt(it.into()), - GenericDefId::TraitId(it) => GenericDef::Trait(it.into()), - GenericDefId::TypeAliasId(it) => GenericDef::TypeAlias(it.into()), - GenericDefId::ImplId(it) => GenericDef::ImplBlock(it.into()), - GenericDefId::EnumVariantId(it) => GenericDef::EnumVariant(it.into()), - GenericDefId::ConstId(it) => GenericDef::Const(it.into()), - } - } -} - impl From for TypableDef { fn from(id: AdtId) -> Self { Adt::from(id).into() @@ -244,3 +230,13 @@ impl From for AttrDefId { } } } + +impl From for GenericDefId { + fn from(item: AssocItem) -> Self { + match item { + AssocItem::Function(f) => f.id.into(), + AssocItem::Const(c) => c.id.into(), + AssocItem::TypeAlias(t) => t.id.into(), + } + } +} diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index b4f0e81d3..cbfeca3ab 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -251,7 +251,7 @@ impl SourceAnalyzer { let types = self.resolver.resolve_path_in_type_ns_fully(db, &path).map(|ty| match ty { TypeNs::SelfType(it) => PathResolution::SelfType(it.into()), TypeNs::GenericParam(idx) => PathResolution::GenericParam(GenericParam { - parent: self.resolver.generic_def().unwrap().into(), + parent: self.resolver.generic_def().unwrap(), idx, }), TypeNs::AdtSelfType(it) | TypeNs::AdtId(it) => { @@ -326,7 +326,7 @@ impl SourceAnalyzer { resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()), resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()), resolver::ScopeDef::GenericParam(idx) => { - let parent = self.resolver.generic_def().unwrap().into(); + let parent = self.resolver.generic_def().unwrap(); ScopeDef::GenericParam(GenericParam { parent, idx }) } resolver::ScopeDef::Local(pat_id) => { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 2473ac574..1e05ac3f2 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -21,8 +21,8 @@ use hir_def::{generics::GenericParams, AdtId, GenericDefId}; use ra_db::{impl_intern_key, salsa}; use crate::{ - db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, DefWithBody, FloatTy, - GenericDef, IntTy, Mutability, Name, Trait, TypeAlias, Uncertain, + db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, DefWithBody, FloatTy, IntTy, + Mutability, Name, Trait, TypeAlias, Uncertain, }; use display::{HirDisplay, HirFormatter}; @@ -356,9 +356,9 @@ impl Substs { ) } - pub fn build_for_def(db: &impl HirDatabase, def: impl Into) -> SubstsBuilder { + pub fn build_for_def(db: &impl HirDatabase, def: impl Into) -> SubstsBuilder { let def = def.into(); - let params = db.generic_params(def.into()); + let params = db.generic_params(def); let param_count = params.count_params_including_parent(); Substs::builder(param_count) } diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs index ee54d8217..6165eba4f 100644 --- a/crates/ra_hir/src/ty/infer/path.rs +++ b/crates/ra_hir/src/ty/infer/path.rs @@ -203,7 +203,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Container::ImplBlock(_) => self.find_self_types(&def, ty.clone()), Container::Trait(t) => { // we're picking this method - let trait_substs = Substs::build_for_def(self.db, t) + let trait_substs = Substs::build_for_def(self.db, t.id) .push(ty.clone()) .fill(std::iter::repeat_with(|| self.new_type_var())) .build(); diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index da3c8e94a..1ceafd9b1 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -29,8 +29,8 @@ use crate::{ Adt, }, util::make_mut_slice, - Const, Enum, EnumVariant, Function, GenericDef, ImplBlock, ModuleDef, Path, Static, Struct, - Trait, TypeAlias, Union, + Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Path, Static, Struct, Trait, + TypeAlias, Union, }; // FIXME: this is only really used in `type_for_def`, which contains a bunch of @@ -261,8 +261,10 @@ impl Ty { let traits = traits_from_env.flat_map(|t| t.all_super_traits(db)); for t in traits { if let Some(associated_ty) = t.associated_type_by_name(db, &segment.name) { - let substs = - Substs::build_for_def(db, t).push(self_ty.clone()).fill_with_unknown().build(); + let substs = Substs::build_for_def(db, t.id) + .push(self_ty.clone()) + .fill_with_unknown() + .build(); // FIXME handle type parameters on the segment return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); } @@ -287,11 +289,11 @@ impl Ty { segment: &PathSegment, resolved: TypableDef, ) -> Substs { - let def_generic: Option = match resolved { - TypableDef::Function(func) => Some(func.into()), + let def_generic: Option = match resolved { + TypableDef::Function(func) => Some(func.id.into()), TypableDef::Adt(adt) => Some(adt.into()), - TypableDef::EnumVariant(var) => Some(var.parent_enum(db).into()), - TypableDef::TypeAlias(t) => Some(t.into()), + TypableDef::EnumVariant(var) => Some(var.parent_enum(db).id.into()), + TypableDef::TypeAlias(t) => Some(t.id.into()), TypableDef::Const(_) | TypableDef::Static(_) | TypableDef::BuiltinType(_) => None, }; substs_from_path_segment(db, resolver, segment, def_generic, false) @@ -338,7 +340,7 @@ pub(super) fn substs_from_path_segment( db: &impl HirDatabase, resolver: &Resolver, segment: &PathSegment, - def_generic: Option, + def_generic: Option, add_self_param: bool, ) -> Substs { let mut substs = Vec::new(); @@ -376,7 +378,7 @@ pub(super) fn substs_from_path_segment( // handle defaults if let Some(def_generic) = def_generic { - let default_substs = db.generic_defaults(def_generic); + let default_substs = db.generic_defaults(def_generic.into()); assert_eq!(substs.len(), default_substs.len()); for (i, default_ty) in default_substs.iter().enumerate() { @@ -439,7 +441,7 @@ impl TraitRef { ) -> Substs { let has_self_param = segment.args_and_bindings.as_ref().map(|a| a.has_self_type).unwrap_or(false); - substs_from_path_segment(db, resolver, segment, Some(resolved.into()), !has_self_param) + substs_from_path_segment(db, resolver, segment, Some(resolved.id.into()), !has_self_param) } pub(crate) fn for_trait(db: &impl HirDatabase, trait_: Trait) -> TraitRef { @@ -579,10 +581,10 @@ pub(crate) fn field_types_query( /// these are fine: `T: Foo, U: Foo<()>`. pub(crate) fn generic_predicates_for_param_query( db: &impl HirDatabase, - def: GenericDef, + def: GenericDefId, param_idx: u32, ) -> Arc<[GenericPredicate]> { - let resolver = GenericDefId::from(def).resolver(db); + let resolver = def.resolver(db); resolver .where_predicates_in_scope() // we have to filter out all other predicates *first*, before attempting to lower them @@ -615,8 +617,8 @@ pub(crate) fn generic_predicates_query( } /// Resolve the default type params from generics -pub(crate) fn generic_defaults_query(db: &impl HirDatabase, def: GenericDef) -> Substs { - let resolver = GenericDefId::from(def).resolver(db); +pub(crate) fn generic_defaults_query(db: &impl HirDatabase, def: GenericDefId) -> Substs { + let resolver = def.resolver(db); let generic_params = db.generic_params(def.into()); let defaults = generic_params diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index f7905b5ff..c5ab690eb 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -365,7 +365,7 @@ fn generic_implements_goal( self_ty: Canonical, ) -> Canonical> { let num_vars = self_ty.num_vars; - let substs = super::Substs::build_for_def(db, trait_) + let substs = super::Substs::build_for_def(db, trait_.id) .push(self_ty.value) .fill_with_bound_vars(num_vars as u32) .build(); diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index a0dbf6305..0272dd9ae 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -736,7 +736,7 @@ fn closure_fn_trait_impl_datum( let trait_ref = TraitRef { trait_, - substs: Substs::build_for_def(db, trait_).push(self_ty).push(arg_ty).build(), + substs: Substs::build_for_def(db, trait_.id).push(self_ty).push(arg_ty).build(), }; let output_ty_id = AssocTyValue::ClosureFnTraitImplOutput(data.clone()).to_chalk(db); diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs index bac3f7582..85b053a6e 100644 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ b/crates/ra_ide_api/src/completion/presentation.rs @@ -292,7 +292,7 @@ fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { } fn has_non_default_type_params(def: hir::GenericDef, db: &db::RootDatabase) -> bool { - let subst = db.generic_defaults(def); + let subst = db.generic_defaults(def.into()); subst.iter().any(|ty| ty == &Ty::Unknown) } -- cgit v1.2.3 From 9047a4ad4620d3b37d17a11e6dee0ea4ffbd7af1 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 16:16:41 +0300 Subject: Use more IDs --- crates/ra_hir/src/ty/lower.rs | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 1ceafd9b1..348c5f67d 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -14,7 +14,7 @@ use hir_def::{ path::{GenericArg, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, - AdtId, EnumVariantId, GenericDefId, LocalStructFieldId, VariantId, + AdtId, EnumVariantId, FunctionId, GenericDefId, LocalStructFieldId, StructId, VariantId, }; use ra_arena::map::ArenaMap; @@ -546,9 +546,9 @@ pub(crate) fn type_for_def(db: &impl HirDatabase, def: TypableDef, ns: Namespace /// Build the signature of a callable item (function, struct or enum variant). pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSig { match def { - CallableDef::Function(f) => fn_sig_for_fn(db, f), - CallableDef::Struct(s) => fn_sig_for_struct_constructor(db, s), - CallableDef::EnumVariant(e) => fn_sig_for_enum_variant_constructor(db, e), + CallableDef::Function(f) => fn_sig_for_fn(db, f.id), + CallableDef::Struct(s) => fn_sig_for_struct_constructor(db, s.id), + CallableDef::EnumVariant(e) => fn_sig_for_enum_variant_constructor(db, e.into()), } } @@ -630,9 +630,9 @@ pub(crate) fn generic_defaults_query(db: &impl HirDatabase, def: GenericDefId) - Substs(defaults) } -fn fn_sig_for_fn(db: &impl HirDatabase, def: Function) -> FnSig { - let data = db.function_data(def.id); - let resolver = def.id.resolver(db); +fn fn_sig_for_fn(db: &impl HirDatabase, def: FunctionId) -> FnSig { + let data = db.function_data(def); + let resolver = def.resolver(db); let params = data.params.iter().map(|tr| Ty::from_hir(db, &resolver, tr)).collect::>(); let ret = Ty::from_hir(db, &resolver, &data.ret_type); FnSig::from_params_and_return(params, ret) @@ -703,15 +703,15 @@ impl From> for Uncertain { } } -fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> FnSig { - let struct_data = db.struct_data(def.id.into()); +fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> FnSig { + let struct_data = db.struct_data(def.into()); let fields = struct_data.variant_data.fields(); - let resolver = def.id.resolver(db); + let resolver = def.resolver(db); let params = fields .iter() .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) .collect::>(); - let ret = type_for_adt(db, def); + let ret = type_for_adt(db, Struct::from(def)); FnSig::from_params_and_return(params, ret) } @@ -726,17 +726,18 @@ fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { Ty::apply(TypeCtor::FnDef(def.into()), substs) } -fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariant) -> FnSig { - let var_data = def.variant_data(db); - let fields = var_data.fields(); - let resolver = def.parent.id.resolver(db); +fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> FnSig { + let enum_data = db.enum_data(def.parent); + let var_data = &enum_data.variants[def.local_id]; + let fields = var_data.variant_data.fields(); + let resolver = def.parent.resolver(db); let params = fields .iter() .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) .collect::>(); - let generics = db.generic_params(def.parent_enum(db).id.into()); + let generics = db.generic_params(def.parent.into()); let substs = Substs::identity(&generics); - let ret = type_for_adt(db, def.parent_enum(db)).subst(&substs); + let ret = type_for_adt(db, Enum::from(def.parent)).subst(&substs); FnSig::from_params_and_return(params, ret) } -- cgit v1.2.3 From 78791d6fac1ff426264e636545a07664d83d2039 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 16:26:52 +0300 Subject: Use ids for Callable --- crates/ra_hir/src/ty.rs | 19 +++++++++++++------ crates/ra_hir/src/ty/infer/expr.rs | 14 +++++++++----- crates/ra_hir/src/ty/lower.rs | 38 ++++++++++++++++++++------------------ crates/ra_ide_api/src/call_info.rs | 13 ++++++++----- 4 files changed, 50 insertions(+), 34 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 1e05ac3f2..8c045aaef 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -171,7 +171,7 @@ impl TypeCtor { | TypeCtor::Tuple { .. } => None, TypeCtor::Closure { def, .. } => def.krate(db), TypeCtor::Adt(adt) => adt.krate(db), - TypeCtor::FnDef(callable) => callable.krate(db), + TypeCtor::FnDef(callable) => Some(callable.krate(db).into()), TypeCtor::AssociatedType(type_alias) => type_alias.krate(db), } } @@ -856,13 +856,20 @@ impl HirDisplay for ApplicationTy { TypeCtor::FnDef(def) => { let sig = f.db.callable_item_signature(def); let name = match def { - CallableDef::Function(ff) => ff.name(f.db), - CallableDef::Struct(s) => s.name(f.db).unwrap_or_else(Name::missing), - CallableDef::EnumVariant(e) => e.name(f.db).unwrap_or_else(Name::missing), + CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), + CallableDef::StructId(s) => { + f.db.struct_data(s.0).name.clone().unwrap_or_else(Name::missing) + } + CallableDef::EnumVariantId(e) => { + let enum_data = f.db.enum_data(e.parent); + enum_data.variants[e.local_id].name.clone().unwrap_or_else(Name::missing) + } }; match def { - CallableDef::Function(_) => write!(f, "fn {}", name)?, - CallableDef::Struct(_) | CallableDef::EnumVariant(_) => write!(f, "{}", name)?, + CallableDef::FunctionId(_) => write!(f, "fn {}", name)?, + CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => { + write!(f, "{}", name)? + } } if self.parameters.len() > 0 { write!(f, "<")?; diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 194e55819..1d6df2b7a 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -8,6 +8,7 @@ use hir_def::{ generics::GenericParams, path::{GenericArg, GenericArgs}, resolver::resolver_for_expr, + ContainerId, Lookup, }; use hir_expand::name; @@ -660,18 +661,21 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } // add obligation for trait implementation, if this is a trait method match def { - CallableDef::Function(f) => { - if let Some(trait_) = f.parent_trait(self.db) { + CallableDef::FunctionId(f) => { + if let ContainerId::TraitId(trait_) = f.lookup(self.db).container { // construct a TraitDef let substs = a_ty.parameters.prefix( self.db - .generic_params(trait_.id.into()) + .generic_params(trait_.into()) .count_params_including_parent(), ); - self.obligations.push(Obligation::Trait(TraitRef { trait_, substs })); + self.obligations.push(Obligation::Trait(TraitRef { + trait_: trait_.into(), + substs, + })); } } - CallableDef::Struct(_) | CallableDef::EnumVariant(_) => {} + CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {} } } } diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 348c5f67d..27cfe00c1 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -14,9 +14,11 @@ use hir_def::{ path::{GenericArg, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, - AdtId, EnumVariantId, FunctionId, GenericDefId, LocalStructFieldId, StructId, VariantId, + AdtId, AstItemDef, EnumVariantId, FunctionId, GenericDefId, HasModule, LocalStructFieldId, + Lookup, StructId, VariantId, }; use ra_arena::map::ArenaMap; +use ra_db::CrateId; use super::{ FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, @@ -546,9 +548,9 @@ pub(crate) fn type_for_def(db: &impl HirDatabase, def: TypableDef, ns: Namespace /// Build the signature of a callable item (function, struct or enum variant). pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSig { match def { - CallableDef::Function(f) => fn_sig_for_fn(db, f.id), - CallableDef::Struct(s) => fn_sig_for_struct_constructor(db, s.id), - CallableDef::EnumVariant(e) => fn_sig_for_enum_variant_constructor(db, e.into()), + CallableDef::FunctionId(f) => fn_sig_for_fn(db, f), + CallableDef::StructId(s) => fn_sig_for_struct_constructor(db, s), + CallableDef::EnumVariantId(e) => fn_sig_for_enum_variant_constructor(db, e), } } @@ -643,7 +645,7 @@ fn fn_sig_for_fn(db: &impl HirDatabase, def: FunctionId) -> FnSig { fn type_for_fn(db: &impl HirDatabase, def: Function) -> Ty { let generics = db.generic_params(def.id.into()); let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.into()), substs) + Ty::apply(TypeCtor::FnDef(def.id.into()), substs) } /// Build the declared type of a const. @@ -723,7 +725,7 @@ fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { } let generics = db.generic_params(def.id.into()); let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.into()), substs) + Ty::apply(TypeCtor::FnDef(def.id.into()), substs) } fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> FnSig { @@ -749,7 +751,7 @@ fn type_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariant) -> } let generics = db.generic_params(def.parent_enum(db).id.into()); let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.into()), substs) + Ty::apply(TypeCtor::FnDef(EnumVariantId::from(def).into()), substs) } fn type_for_adt(db: &impl HirDatabase, adt: impl Into) -> Ty { @@ -806,18 +808,18 @@ impl From for Option { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CallableDef { - Function(Function), - Struct(Struct), - EnumVariant(EnumVariant), + FunctionId(FunctionId), + StructId(StructId), + EnumVariantId(EnumVariantId), } -impl_froms!(CallableDef: Function, Struct, EnumVariant); +impl_froms!(CallableDef: FunctionId, StructId, EnumVariantId); impl CallableDef { - pub fn krate(self, db: &impl HirDatabase) -> Option { + pub fn krate(self, db: &impl HirDatabase) -> CrateId { match self { - CallableDef::Function(f) => f.krate(db), - CallableDef::Struct(s) => s.krate(db), - CallableDef::EnumVariant(e) => e.parent_enum(db).krate(db), + CallableDef::FunctionId(f) => f.lookup(db).module(db).krate, + CallableDef::StructId(s) => s.0.module(db).krate, + CallableDef::EnumVariantId(e) => e.parent.module(db).krate, } } } @@ -825,9 +827,9 @@ impl CallableDef { impl From for GenericDefId { fn from(def: CallableDef) -> GenericDefId { match def { - CallableDef::Function(f) => f.id.into(), - CallableDef::Struct(s) => s.id.into(), - CallableDef::EnumVariant(e) => EnumVariantId::from(e).into(), + CallableDef::FunctionId(f) => f.into(), + CallableDef::StructId(s) => s.into(), + CallableDef::EnumVariantId(e) => e.into(), } } } diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 7c367230e..9beceb29c 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -26,14 +26,17 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { - //FIXME: apply subst + //FIXME: don't poke into Ty let (callable_def, _subst) = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; match callable_def { - hir::CallableDef::Function(it) => { - (CallInfo::with_fn(db, it), it.has_self_param(db)) + hir::CallableDef::FunctionId(it) => { + let fn_def = it.into(); + (CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db)) + } + hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false), + hir::CallableDef::EnumVariantId(it) => { + (CallInfo::with_enum_variant(db, it.into())?, false) } - hir::CallableDef::Struct(it) => (CallInfo::with_struct(db, it)?, false), - hir::CallableDef::EnumVariant(it) => (CallInfo::with_enum_variant(db, it)?, false), } } FnCallNode::MethodCallExpr(expr) => { -- cgit v1.2.3 From e1c0bdaf75f8d88a5c28b3e44def17d91d4f46b3 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 16:55:09 +0300 Subject: Introduce dedicated AST node for union Although structs and unions have the same syntax and differ only in the keyword, re-using the single syntax node for both of them leads to confusion in practice, and propagates further down the hir in an upleasent way. Moreover, static and consts also share syntax, but we use different nodes for them. --- crates/ra_parser/src/grammar/items.rs | 10 +- crates/ra_parser/src/grammar/items/adt.rs | 176 +++++++++++++++++++++ crates/ra_parser/src/grammar/items/nominal.rs | 167 ------------------- crates/ra_parser/src/syntax_kind/generated.rs | 1 + crates/ra_syntax/src/ast/generated.rs | 28 ++++ crates/ra_syntax/src/grammar.ron | 10 ++ .../parser/inline/ok/0068_union_items.txt | 4 +- .../test_data/parser/ok/0035_weird_exprs.txt | 2 +- 8 files changed, 223 insertions(+), 175 deletions(-) create mode 100644 crates/ra_parser/src/grammar/items/adt.rs delete mode 100644 crates/ra_parser/src/grammar/items/nominal.rs (limited to 'crates') diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs index 85f7eeb00..630e6ce64 100644 --- a/crates/ra_parser/src/grammar/items.rs +++ b/crates/ra_parser/src/grammar/items.rs @@ -1,13 +1,13 @@ //! FIXME: write short doc here mod consts; -mod nominal; +mod adt; mod traits; mod use_item; pub(crate) use self::{ expressions::{match_arm_list, record_field_list}, - nominal::{enum_variant_list, record_field_def_list}, + adt::{enum_variant_list, record_field_def_list}, traits::{impl_item_list, trait_item_list}, use_item::use_tree_list, }; @@ -247,7 +247,7 @@ fn items_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> { // a: i32, // b: f32, // } - nominal::struct_def(p, m, T![struct]); + adt::struct_def(p, m); } IDENT if p.at_contextual_kw("union") && p.nth(1) == IDENT => { // test union_items @@ -256,9 +256,9 @@ fn items_without_modifiers(p: &mut Parser, m: Marker) -> Result<(), Marker> { // a: i32, // b: f32, // } - nominal::struct_def(p, m, T![union]); + adt::union_def(p, m); } - T![enum] => nominal::enum_def(p, m), + T![enum] => adt::enum_def(p, m), T![use] => use_item::use_item(p, m), T![const] if (la == IDENT || la == T![_] || la == T![mut]) => consts::const_def(p, m), T![static] => consts::static_def(p, m), diff --git a/crates/ra_parser/src/grammar/items/adt.rs b/crates/ra_parser/src/grammar/items/adt.rs new file mode 100644 index 000000000..c777bc9d0 --- /dev/null +++ b/crates/ra_parser/src/grammar/items/adt.rs @@ -0,0 +1,176 @@ +//! FIXME: write short doc here + +use super::*; + +pub(super) fn struct_def(p: &mut Parser, m: Marker) { + assert!(p.at(T![struct])); + p.bump(T![struct]); + struct_or_union(p, m, T![struct], STRUCT_DEF); +} + +pub(super) fn union_def(p: &mut Parser, m: Marker) { + assert!(p.at_contextual_kw("union")); + p.bump_remap(T![union]); + struct_or_union(p, m, T![union], UNION_DEF); +} + +fn struct_or_union(p: &mut Parser, m: Marker, kw: SyntaxKind, def: SyntaxKind) { + name_r(p, ITEM_RECOVERY_SET); + type_params::opt_type_param_list(p); + match p.current() { + T![where] => { + type_params::opt_where_clause(p); + match p.current() { + T![;] => { + p.bump(T![;]); + } + T!['{'] => record_field_def_list(p), + _ => { + //FIXME: special case `(` error message + p.error("expected `;` or `{`"); + } + } + } + T![;] if kw == T![struct] => { + p.bump(T![;]); + } + T!['{'] => record_field_def_list(p), + T!['('] if kw == T![struct] => { + tuple_field_def_list(p); + // test tuple_struct_where + // struct Test(T) where T: Clone; + // struct Test(T); + type_params::opt_where_clause(p); + p.expect(T![;]); + } + _ if kw == T![struct] => { + p.error("expected `;`, `{`, or `(`"); + } + _ => { + p.error("expected `{`"); + } + } + m.complete(p, def); +} + +pub(super) fn enum_def(p: &mut Parser, m: Marker) { + assert!(p.at(T![enum])); + p.bump(T![enum]); + name_r(p, ITEM_RECOVERY_SET); + type_params::opt_type_param_list(p); + type_params::opt_where_clause(p); + if p.at(T!['{']) { + enum_variant_list(p); + } else { + p.error("expected `{`") + } + m.complete(p, ENUM_DEF); +} + +pub(crate) fn enum_variant_list(p: &mut Parser) { + assert!(p.at(T!['{'])); + let m = p.start(); + p.bump(T!['{']); + while !p.at(EOF) && !p.at(T!['}']) { + if p.at(T!['{']) { + error_block(p, "expected enum variant"); + continue; + } + let var = p.start(); + attributes::outer_attributes(p); + if p.at(IDENT) { + name(p); + match p.current() { + T!['{'] => record_field_def_list(p), + T!['('] => tuple_field_def_list(p), + T![=] => { + p.bump(T![=]); + expressions::expr(p); + } + _ => (), + } + var.complete(p, ENUM_VARIANT); + } else { + var.abandon(p); + p.err_and_bump("expected enum variant"); + } + if !p.at(T!['}']) { + p.expect(T![,]); + } + } + p.expect(T!['}']); + m.complete(p, ENUM_VARIANT_LIST); +} + +pub(crate) fn record_field_def_list(p: &mut Parser) { + assert!(p.at(T!['{'])); + let m = p.start(); + p.bump(T!['{']); + while !p.at(T!['}']) && !p.at(EOF) { + if p.at(T!['{']) { + error_block(p, "expected field"); + continue; + } + record_field_def(p); + if !p.at(T!['}']) { + p.expect(T![,]); + } + } + p.expect(T!['}']); + m.complete(p, RECORD_FIELD_DEF_LIST); + + fn record_field_def(p: &mut Parser) { + let m = p.start(); + // test record_field_attrs + // struct S { + // #[serde(with = "url_serde")] + // pub uri: Uri, + // } + attributes::outer_attributes(p); + opt_visibility(p); + if p.at(IDENT) { + name(p); + p.expect(T![:]); + types::type_(p); + m.complete(p, RECORD_FIELD_DEF); + } else { + m.abandon(p); + p.err_and_bump("expected field declaration"); + } + } +} + +fn tuple_field_def_list(p: &mut Parser) { + assert!(p.at(T!['('])); + let m = p.start(); + if !p.expect(T!['(']) { + return; + } + while !p.at(T![')']) && !p.at(EOF) { + let m = p.start(); + // test tuple_field_attrs + // struct S ( + // #[serde(with = "url_serde")] + // pub Uri, + // ); + // + // enum S { + // Uri(#[serde(with = "url_serde")] Uri), + // } + attributes::outer_attributes(p); + opt_visibility(p); + if !p.at_ts(types::TYPE_FIRST) { + p.error("expected a type"); + m.complete(p, ERROR); + break; + } + types::type_(p); + m.complete(p, TUPLE_FIELD_DEF); + + if !p.at(T![')']) { + p.expect(T![,]); + } + } + p.expect(T![')']); + m.complete(p, TUPLE_FIELD_DEF_LIST); +} diff --git a/crates/ra_parser/src/grammar/items/nominal.rs b/crates/ra_parser/src/grammar/items/nominal.rs deleted file mode 100644 index 9d8fb8486..000000000 --- a/crates/ra_parser/src/grammar/items/nominal.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! FIXME: write short doc here - -use super::*; - -pub(super) fn struct_def(p: &mut Parser, m: Marker, kind: SyntaxKind) { - assert!(p.at(T![struct]) || p.at_contextual_kw("union")); - p.bump_remap(kind); - - name_r(p, ITEM_RECOVERY_SET); - type_params::opt_type_param_list(p); - match p.current() { - T![where] => { - type_params::opt_where_clause(p); - match p.current() { - T![;] => { - p.bump(T![;]); - } - T!['{'] => record_field_def_list(p), - _ => { - //FIXME: special case `(` error message - p.error("expected `;` or `{`"); - } - } - } - T![;] if kind == T![struct] => { - p.bump(T![;]); - } - T!['{'] => record_field_def_list(p), - T!['('] if kind == T![struct] => { - tuple_field_def_list(p); - // test tuple_struct_where - // struct Test(T) where T: Clone; - // struct Test(T); - type_params::opt_where_clause(p); - p.expect(T![;]); - } - _ if kind == T![struct] => { - p.error("expected `;`, `{`, or `(`"); - } - _ => { - p.error("expected `{`"); - } - } - m.complete(p, STRUCT_DEF); -} - -pub(super) fn enum_def(p: &mut Parser, m: Marker) { - assert!(p.at(T![enum])); - p.bump(T![enum]); - name_r(p, ITEM_RECOVERY_SET); - type_params::opt_type_param_list(p); - type_params::opt_where_clause(p); - if p.at(T!['{']) { - enum_variant_list(p); - } else { - p.error("expected `{`") - } - m.complete(p, ENUM_DEF); -} - -pub(crate) fn enum_variant_list(p: &mut Parser) { - assert!(p.at(T!['{'])); - let m = p.start(); - p.bump(T!['{']); - while !p.at(EOF) && !p.at(T!['}']) { - if p.at(T!['{']) { - error_block(p, "expected enum variant"); - continue; - } - let var = p.start(); - attributes::outer_attributes(p); - if p.at(IDENT) { - name(p); - match p.current() { - T!['{'] => record_field_def_list(p), - T!['('] => tuple_field_def_list(p), - T![=] => { - p.bump(T![=]); - expressions::expr(p); - } - _ => (), - } - var.complete(p, ENUM_VARIANT); - } else { - var.abandon(p); - p.err_and_bump("expected enum variant"); - } - if !p.at(T!['}']) { - p.expect(T![,]); - } - } - p.expect(T!['}']); - m.complete(p, ENUM_VARIANT_LIST); -} - -pub(crate) fn record_field_def_list(p: &mut Parser) { - assert!(p.at(T!['{'])); - let m = p.start(); - p.bump(T!['{']); - while !p.at(T!['}']) && !p.at(EOF) { - if p.at(T!['{']) { - error_block(p, "expected field"); - continue; - } - record_field_def(p); - if !p.at(T!['}']) { - p.expect(T![,]); - } - } - p.expect(T!['}']); - m.complete(p, RECORD_FIELD_DEF_LIST); - - fn record_field_def(p: &mut Parser) { - let m = p.start(); - // test record_field_attrs - // struct S { - // #[serde(with = "url_serde")] - // pub uri: Uri, - // } - attributes::outer_attributes(p); - opt_visibility(p); - if p.at(IDENT) { - name(p); - p.expect(T![:]); - types::type_(p); - m.complete(p, RECORD_FIELD_DEF); - } else { - m.abandon(p); - p.err_and_bump("expected field declaration"); - } - } -} - -fn tuple_field_def_list(p: &mut Parser) { - assert!(p.at(T!['('])); - let m = p.start(); - if !p.expect(T!['(']) { - return; - } - while !p.at(T![')']) && !p.at(EOF) { - let m = p.start(); - // test tuple_field_attrs - // struct S ( - // #[serde(with = "url_serde")] - // pub Uri, - // ); - // - // enum S { - // Uri(#[serde(with = "url_serde")] Uri), - // } - attributes::outer_attributes(p); - opt_visibility(p); - if !p.at_ts(types::TYPE_FIRST) { - p.error("expected a type"); - m.complete(p, ERROR); - break; - } - types::type_(p); - m.complete(p, TUPLE_FIELD_DEF); - - if !p.at(T![')']) { - p.expect(T![,]); - } - } - p.expect(T![')']); - m.complete(p, TUPLE_FIELD_DEF_LIST); -} diff --git a/crates/ra_parser/src/syntax_kind/generated.rs b/crates/ra_parser/src/syntax_kind/generated.rs index 96b5bce88..fe0fcdb33 100644 --- a/crates/ra_parser/src/syntax_kind/generated.rs +++ b/crates/ra_parser/src/syntax_kind/generated.rs @@ -122,6 +122,7 @@ pub enum SyntaxKind { R_DOLLAR, SOURCE_FILE, STRUCT_DEF, + UNION_DEF, ENUM_DEF, FN_DEF, RET_TYPE, diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index de506d7cd..1a03ae56c 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -3789,6 +3789,34 @@ impl AstNode for TypeRef { } impl TypeRef {} #[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct UnionDef { + pub(crate) syntax: SyntaxNode, +} +impl AstNode for UnionDef { + fn can_cast(kind: SyntaxKind) -> bool { + match kind { + UNION_DEF => true, + _ => false, + } + } + fn cast(syntax: SyntaxNode) -> Option { + if Self::can_cast(syntax.kind()) { + Some(Self { syntax }) + } else { + None + } + } + fn syntax(&self) -> &SyntaxNode { + &self.syntax + } +} +impl ast::VisibilityOwner for UnionDef {} +impl ast::NameOwner for UnionDef {} +impl ast::TypeParamsOwner for UnionDef {} +impl ast::AttrsOwner for UnionDef {} +impl ast::DocCommentsOwner for UnionDef {} +impl UnionDef {} +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UseItem { pub(crate) syntax: SyntaxNode, } diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index 88d1dc109..c16bed891 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -126,6 +126,7 @@ Grammar( "SOURCE_FILE", "STRUCT_DEF", + "UNION_DEF", "ENUM_DEF", "FN_DEF", "RET_TYPE", @@ -285,6 +286,15 @@ Grammar( "DocCommentsOwner" ] ), + "UnionDef": ( + traits: [ + "VisibilityOwner", + "NameOwner", + "TypeParamsOwner", + "AttrsOwner", + "DocCommentsOwner" + ] + ), "RecordFieldDefList": (collections: [("fields", "RecordFieldDef")]), "RecordFieldDef": ( traits: [ diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0068_union_items.txt b/crates/ra_syntax/test_data/parser/inline/ok/0068_union_items.txt index f9ace02ee..9d7982684 100644 --- a/crates/ra_syntax/test_data/parser/inline/ok/0068_union_items.txt +++ b/crates/ra_syntax/test_data/parser/inline/ok/0068_union_items.txt @@ -1,5 +1,5 @@ SOURCE_FILE@[0; 51) - STRUCT_DEF@[0; 12) + UNION_DEF@[0; 12) UNION_KW@[0; 5) "union" WHITESPACE@[5; 6) " " NAME@[6; 9) @@ -9,7 +9,7 @@ SOURCE_FILE@[0; 51) L_CURLY@[10; 11) "{" R_CURLY@[11; 12) "}" WHITESPACE@[12; 13) "\n" - STRUCT_DEF@[13; 50) + UNION_DEF@[13; 50) UNION_KW@[13; 18) "union" WHITESPACE@[18; 19) " " NAME@[19; 22) diff --git a/crates/ra_syntax/test_data/parser/ok/0035_weird_exprs.txt b/crates/ra_syntax/test_data/parser/ok/0035_weird_exprs.txt index 3260cc589..90538b90d 100644 --- a/crates/ra_syntax/test_data/parser/ok/0035_weird_exprs.txt +++ b/crates/ra_syntax/test_data/parser/ok/0035_weird_exprs.txt @@ -1592,7 +1592,7 @@ SOURCE_FILE@[0; 3813) BLOCK@[2845; 2906) L_CURLY@[2845; 2846) "{" WHITESPACE@[2846; 2851) "\n " - STRUCT_DEF@[2851; 2904) + UNION_DEF@[2851; 2904) UNION_KW@[2851; 2856) "union" WHITESPACE@[2856; 2857) " " NAME@[2857; 2862) -- cgit v1.2.3 From 5fd68b592938b6a4c074146c1b22ea0f6908fe26 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 17:30:50 +0300 Subject: Fix hir for ast::UnionDef --- crates/ra_assists/src/assists/add_new.rs | 4 ++-- crates/ra_hir/src/code_model.rs | 6 +++--- crates/ra_hir/src/code_model/src.rs | 8 +++---- crates/ra_hir/src/from_source.rs | 13 ++++++----- crates/ra_hir/src/ty.rs | 2 +- crates/ra_hir/src/ty/infer/coerce.rs | 2 +- crates/ra_hir/src/ty/lower.rs | 4 ++-- crates/ra_hir_def/src/adt.rs | 18 +++++++++++++--- crates/ra_hir_def/src/attr.rs | 4 ++-- crates/ra_hir_def/src/db.rs | 12 +++++++---- crates/ra_hir_def/src/docs.rs | 4 ++-- crates/ra_hir_def/src/generics.rs | 6 ++---- crates/ra_hir_def/src/lib.rs | 32 ++++++++++++---------------- crates/ra_hir_def/src/nameres/collector.rs | 12 +++++------ crates/ra_hir_def/src/nameres/raw.rs | 13 +++++------ crates/ra_hir_def/src/nameres/tests.rs | 7 ++++++ crates/ra_ide_api/src/display/short_label.rs | 6 ++++++ crates/ra_ide_api/src/impls.rs | 4 ++++ crates/ra_parser/src/grammar/items.rs | 2 +- crates/ra_syntax/src/ast/extensions.rs | 11 ---------- crates/ra_syntax/src/ast/generated.rs | 28 ++++++++++++++++++++---- crates/ra_syntax/src/grammar.ron | 9 ++++---- 22 files changed, 121 insertions(+), 86 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/assists/add_new.rs b/crates/ra_assists/src/assists/add_new.rs index ee8bff346..8f68bd5fb 100644 --- a/crates/ra_assists/src/assists/add_new.rs +++ b/crates/ra_assists/src/assists/add_new.rs @@ -35,8 +35,8 @@ pub(crate) fn add_new(ctx: AssistCtx) -> Option { let strukt = ctx.find_node_at_offset::()?; // We want to only apply this to non-union structs with named fields - let field_list = match (strukt.kind(), strukt.is_union()) { - (StructKind::Record(named), false) => named, + let field_list = match strukt.kind() { + StructKind::Record(named) => named, _ => return None, }; diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 534f1f8e9..ae730beb5 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -320,7 +320,7 @@ pub struct Struct { impl Struct { pub fn module(self, db: &impl DefDatabase) -> Module { - Module { id: self.id.0.module(db) } + Module { id: self.id.module(db) } } pub fn krate(self, db: &impl DefDatabase) -> Option { @@ -369,11 +369,11 @@ pub struct Union { impl Union { pub fn name(self, db: &impl DefDatabase) -> Option { - db.struct_data(self.id.into()).name.clone() + db.union_data(self.id).name.clone() } pub fn module(self, db: &impl DefDatabase) -> Module { - Module { id: self.id.0.module(db) } + Module { id: self.id.module(db) } } pub fn ty(self, db: &impl HirDatabase) -> Ty { diff --git a/crates/ra_hir/src/code_model/src.rs b/crates/ra_hir/src/code_model/src.rs index a4e317c20..076d86f2b 100644 --- a/crates/ra_hir/src/code_model/src.rs +++ b/crates/ra_hir/src/code_model/src.rs @@ -51,13 +51,13 @@ impl HasSource for StructField { impl HasSource for Struct { type Ast = ast::StructDef; fn source(self, db: &impl DefDatabase) -> Source { - self.id.0.source(db) + self.id.source(db) } } impl HasSource for Union { - type Ast = ast::StructDef; - fn source(self, db: &impl DefDatabase) -> Source { - self.id.0.source(db) + type Ast = ast::UnionDef; + fn source(self, db: &impl DefDatabase) -> Source { + self.id.source(db) } } impl HasSource for Enum { diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs index 1e7c22774..95db7161b 100644 --- a/crates/ra_hir/src/from_source.rs +++ b/crates/ra_hir/src/from_source.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use hir_def::{AstItemDef, LocationCtx, ModuleId, StructId, StructOrUnionId, UnionId}; +use hir_def::{AstItemDef, LocationCtx, ModuleId}; use hir_expand::{name::AsName, AstId, MacroDefId, MacroDefKind}; use ra_syntax::{ ast::{self, AstNode, NameOwner}, @@ -19,19 +19,18 @@ pub trait FromSource: Sized { fn from_source(db: &(impl DefDatabase + AstDatabase), src: Source) -> Option; } -// FIXIME: these two impls are wrong, `ast::StructDef` might produce either a struct or a union impl FromSource for Struct { type Ast = ast::StructDef; fn from_source(db: &(impl DefDatabase + AstDatabase), src: Source) -> Option { - let id: StructOrUnionId = from_source(db, src)?; - Some(Struct { id: StructId(id) }) + let id = from_source(db, src)?; + Some(Struct { id }) } } impl FromSource for Union { - type Ast = ast::StructDef; + type Ast = ast::UnionDef; fn from_source(db: &(impl DefDatabase + AstDatabase), src: Source) -> Option { - let id: StructOrUnionId = from_source(db, src)?; - Some(Union { id: UnionId(id) }) + let id = from_source(db, src)?; + Some(Union { id }) } } impl FromSource for Enum { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 8c045aaef..3cbcbd1d0 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -858,7 +858,7 @@ impl HirDisplay for ApplicationTy { let name = match def { CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), CallableDef::StructId(s) => { - f.db.struct_data(s.0).name.clone().unwrap_or_else(Name::missing) + f.db.struct_data(s).name.clone().unwrap_or_else(Name::missing) } CallableDef::EnumVariantId(e) => { let enum_data = f.db.enum_data(e.parent); diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs index 4b53bba73..bb9a2e427 100644 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ b/crates/ra_hir/src/ty/infer/coerce.rs @@ -246,7 +246,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { ty_app!(TypeCtor::Adt(Adt::Struct(struct2)), st2), ) if struct1 == struct2 => { let field_tys = self.db.field_types(struct1.id.into()); - let struct_data = self.db.struct_data(struct1.id.0); + let struct_data = self.db.struct_data(struct1.id); let mut fields = struct_data.variant_data.fields().iter(); let (last_field_id, _data) = fields.next_back()?; diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 27cfe00c1..89bc4b9ae 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -560,7 +560,7 @@ pub(crate) fn field_types_query( variant_id: VariantId, ) -> Arc> { let (resolver, var_data) = match variant_id { - VariantId::StructId(it) => (it.resolver(db), db.struct_data(it.0).variant_data.clone()), + VariantId::StructId(it) => (it.resolver(db), db.struct_data(it).variant_data.clone()), VariantId::EnumVariantId(it) => ( it.parent.resolver(db), db.enum_data(it.parent).variants[it.local_id].variant_data.clone(), @@ -818,7 +818,7 @@ impl CallableDef { pub fn krate(self, db: &impl HirDatabase) -> CrateId { match self { CallableDef::FunctionId(f) => f.lookup(db).module(db).krate, - CallableDef::StructId(s) => s.0.module(db).krate, + CallableDef::StructId(s) => s.module(db).krate, CallableDef::EnumVariantId(e) => e.parent.module(db).krate, } } diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index c9f30923e..71014826e 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs @@ -12,7 +12,7 @@ use ra_syntax::ast::{self, NameOwner, TypeAscriptionOwner}; use crate::{ db::DefDatabase, trace::Trace, type_ref::TypeRef, AstItemDef, EnumId, HasChildSource, - LocalEnumVariantId, LocalStructFieldId, StructOrUnionId, VariantId, + LocalEnumVariantId, LocalStructFieldId, StructId, UnionId, VariantId, }; /// Note that we use `StructData` for unions as well! @@ -49,13 +49,25 @@ pub struct StructFieldData { } impl StructData { - pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructOrUnionId) -> Arc { + pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructId) -> Arc { let src = id.source(db); let name = src.value.name().map(|n| n.as_name()); let variant_data = VariantData::new(src.value.kind()); let variant_data = Arc::new(variant_data); Arc::new(StructData { name, variant_data }) } + pub(crate) fn union_data_query(db: &impl DefDatabase, id: UnionId) -> Arc { + let src = id.source(db); + let name = src.value.name().map(|n| n.as_name()); + let variant_data = VariantData::new( + src.value + .record_field_def_list() + .map(ast::StructKind::Record) + .unwrap_or(ast::StructKind::Unit), + ); + let variant_data = Arc::new(variant_data); + Arc::new(StructData { name, variant_data }) + } } impl EnumData { @@ -137,7 +149,7 @@ impl HasChildSource for VariantId { let src = it.parent.child_source(db); src.map(|map| map[it.local_id].kind()) } - VariantId::StructId(it) => it.0.source(db).map(|it| it.kind()), + VariantId::StructId(it) => it.source(db).map(|it| it.kind()), }; let mut trace = Trace::new_for_map(); lower_struct(&mut trace, &src.value); diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 53456fc08..298608e27 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -54,9 +54,9 @@ impl Attrs { Attrs::from_attrs_owner(db, src.map(|it| it as &dyn AttrsOwner)) } AttrDefId::AdtId(it) => match it { - AdtId::StructId(it) => attrs_from_ast(it.0.lookup_intern(db).ast_id, db), + AdtId::StructId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), AdtId::EnumId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), - AdtId::UnionId(it) => attrs_from_ast(it.0.lookup_intern(db).ast_id, db), + AdtId::UnionId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), }, AttrDefId::TraitId(it) => attrs_from_ast(it.lookup_intern(db).ast_id, db), AttrDefId::MacroDefId(it) => attrs_from_ast(it.ast_id, db), diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs index 32adb11bd..ef5611ffc 100644 --- a/crates/ra_hir_def/src/db.rs +++ b/crates/ra_hir_def/src/db.rs @@ -18,8 +18,8 @@ use crate::{ CrateDefMap, }, AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, FunctionId, FunctionLoc, GenericDefId, - ImplId, ItemLoc, ModuleId, StaticId, StaticLoc, StructOrUnionId, TraitId, TypeAliasId, - TypeAliasLoc, + ImplId, ItemLoc, ModuleId, StaticId, StaticLoc, StructId, TraitId, TypeAliasId, TypeAliasLoc, + UnionId, }; #[salsa::query_group(InternDatabaseStorage)] @@ -27,7 +27,9 @@ pub trait InternDatabase: SourceDatabase { #[salsa::interned] fn intern_function(&self, loc: FunctionLoc) -> FunctionId; #[salsa::interned] - fn intern_struct_or_union(&self, loc: ItemLoc) -> StructOrUnionId; + fn intern_struct(&self, loc: ItemLoc) -> StructId; + #[salsa::interned] + fn intern_union(&self, loc: ItemLoc) -> UnionId; #[salsa::interned] fn intern_enum(&self, loc: ItemLoc) -> EnumId; #[salsa::interned] @@ -57,7 +59,9 @@ pub trait DefDatabase: InternDatabase + AstDatabase { fn crate_def_map(&self, krate: CrateId) -> Arc; #[salsa::invoke(StructData::struct_data_query)] - fn struct_data(&self, id: StructOrUnionId) -> Arc; + fn struct_data(&self, id: StructId) -> Arc; + #[salsa::invoke(StructData::union_data_query)] + fn union_data(&self, id: UnionId) -> Arc; #[salsa::invoke(EnumData::enum_data_query)] fn enum_data(&self, e: EnumId) -> Arc; diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index 90a8627bc..4749b642f 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -47,9 +47,9 @@ impl Documentation { } } AttrDefId::AdtId(it) => match it { - AdtId::StructId(it) => docs_from_ast(&it.0.source(db).value), + AdtId::StructId(it) => docs_from_ast(&it.source(db).value), AdtId::EnumId(it) => docs_from_ast(&it.source(db).value), - AdtId::UnionId(it) => docs_from_ast(&it.0.source(db).value), + AdtId::UnionId(it) => docs_from_ast(&it.source(db).value), }, AttrDefId::EnumVariantId(it) => { let src = it.parent.child_source(db); diff --git a/crates/ra_hir_def/src/generics.rs b/crates/ra_hir_def/src/generics.rs index 015fe772e..3f94e40e4 100644 --- a/crates/ra_hir_def/src/generics.rs +++ b/crates/ra_hir_def/src/generics.rs @@ -60,10 +60,8 @@ impl GenericParams { // FIXME: add `: Sized` bound for everything except for `Self` in traits match def { GenericDefId::FunctionId(it) => generics.fill(&it.lookup(db).source(db).value, start), - GenericDefId::AdtId(AdtId::StructId(it)) => { - generics.fill(&it.0.source(db).value, start) - } - GenericDefId::AdtId(AdtId::UnionId(it)) => generics.fill(&it.0.source(db).value, start), + GenericDefId::AdtId(AdtId::StructId(it)) => generics.fill(&it.source(db).value, start), + GenericDefId::AdtId(AdtId::UnionId(it)) => generics.fill(&it.source(db).value, start), GenericDefId::AdtId(AdtId::EnumId(it)) => generics.fill(&it.source(db).value, start), GenericDefId::TraitId(it) => { // traits get the Self type as an implicit first type parameter diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 8e8c2d749..5f11be114 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -141,30 +141,26 @@ impl Lookup for FunctionId { } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct StructOrUnionId(salsa::InternId); -impl_intern_key!(StructOrUnionId); -impl AstItemDef for StructOrUnionId { +pub struct StructId(salsa::InternId); +impl_intern_key!(StructId); +impl AstItemDef for StructId { fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { - db.intern_struct_or_union(loc) + db.intern_struct(loc) } fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { - db.lookup_intern_struct_or_union(self) + db.lookup_intern_struct(self) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct StructId(pub StructOrUnionId); -impl From for StructOrUnionId { - fn from(id: StructId) -> StructOrUnionId { - id.0 +pub struct UnionId(salsa::InternId); +impl_intern_key!(UnionId); +impl AstItemDef for UnionId { + fn intern(db: &impl InternDatabase, loc: ItemLoc) -> Self { + db.intern_union(loc) } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct UnionId(pub StructOrUnionId); -impl From for StructOrUnionId { - fn from(id: UnionId) -> StructOrUnionId { - id.0 + fn lookup_intern(self, db: &impl InternDatabase) -> ItemLoc { + db.lookup_intern_union(self) } } @@ -485,8 +481,8 @@ impl HasModule for ConstLoc { impl HasModule for AdtId { fn module(&self, db: &impl db::DefDatabase) -> ModuleId { match self { - AdtId::StructId(it) => it.0.module(db), - AdtId::UnionId(it) => it.0.module(db), + AdtId::StructId(it) => it.module(db), + AdtId::UnionId(it) => it.module(db), AdtId::EnumId(it) => it.module(db), } } diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 41becf8df..4ff6f72cf 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -25,7 +25,7 @@ use crate::{ per_ns::PerNs, AdtId, AstId, AstItemDef, ConstLoc, ContainerId, EnumId, EnumVariantId, FunctionLoc, ImplId, Intern, LocalImportId, LocalModuleId, LocationCtx, ModuleDefId, ModuleId, StaticLoc, StructId, - StructOrUnionId, TraitId, TypeAliasLoc, UnionId, + TraitId, TypeAliasLoc, UnionId, }; pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> CrateDefMap { @@ -698,14 +698,12 @@ where PerNs::values(def.into()) } raw::DefKind::Struct(ast_id) => { - let id = StructOrUnionId::from_ast_id(ctx, ast_id).into(); - let s = StructId(id).into(); - PerNs::both(s, s) + let id = StructId::from_ast_id(ctx, ast_id).into(); + PerNs::both(id, id) } raw::DefKind::Union(ast_id) => { - let id = StructOrUnionId::from_ast_id(ctx, ast_id).into(); - let u = UnionId(id).into(); - PerNs::both(u, u) + let id = UnionId::from_ast_id(ctx, ast_id).into(); + PerNs::both(id, id) } raw::DefKind::Enum(ast_id) => PerNs::types(EnumId::from_ast_id(ctx, ast_id).into()), raw::DefKind::Const(ast_id) => { diff --git a/crates/ra_hir_def/src/nameres/raw.rs b/crates/ra_hir_def/src/nameres/raw.rs index 401af031c..6eb106094 100644 --- a/crates/ra_hir_def/src/nameres/raw.rs +++ b/crates/ra_hir_def/src/nameres/raw.rs @@ -176,7 +176,7 @@ pub(super) struct DefData { pub(super) enum DefKind { Function(FileAstId), Struct(FileAstId), - Union(FileAstId), + Union(FileAstId), Enum(FileAstId), Const(FileAstId), Static(FileAstId), @@ -246,11 +246,12 @@ impl RawItemsCollector { ast::ModuleItem::StructDef(it) => { let id = self.source_ast_id_map.ast_id(&it); let name = it.name(); - if it.is_union() { - (DefKind::Union(id), name) - } else { - (DefKind::Struct(id), name) - } + (DefKind::Struct(id), name) + } + ast::ModuleItem::UnionDef(it) => { + let id = self.source_ast_id_map.ast_id(&it); + let name = it.name(); + (DefKind::Union(id), name) } ast::ModuleItem::EnumDef(it) => { (DefKind::Enum(self.source_ast_id_map.ast_id(&it)), it.name()) diff --git a/crates/ra_hir_def/src/nameres/tests.rs b/crates/ra_hir_def/src/nameres/tests.rs index f502f1cb3..87fcd617c 100644 --- a/crates/ra_hir_def/src/nameres/tests.rs +++ b/crates/ra_hir_def/src/nameres/tests.rs @@ -82,6 +82,12 @@ fn crate_def_map_smoke_test() { //- /foo/bar.rs pub struct Baz; + + union U { + to_be: bool, + not_to_be: u8, + } + enum E { V } ", ); @@ -99,6 +105,7 @@ fn crate_def_map_smoke_test() { ⋮crate::foo::bar ⋮Baz: t v ⋮E: t + ⋮U: t v "###) } diff --git a/crates/ra_ide_api/src/display/short_label.rs b/crates/ra_ide_api/src/display/short_label.rs index 5d2bce3d2..9ffc9b980 100644 --- a/crates/ra_ide_api/src/display/short_label.rs +++ b/crates/ra_ide_api/src/display/short_label.rs @@ -19,6 +19,12 @@ impl ShortLabel for ast::StructDef { } } +impl ShortLabel for ast::UnionDef { + fn short_label(&self) -> Option { + short_label_from_node(self, "union ") + } +} + impl ShortLabel for ast::EnumDef { fn short_label(&self) -> Option { short_label_from_node(self, "enum ") diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs index 3e3012559..2b3100fc3 100644 --- a/crates/ra_ide_api/src/impls.rs +++ b/crates/ra_ide_api/src/impls.rs @@ -49,6 +49,10 @@ fn impls_for_def( let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; hir::Enum::from_source(db, src)?.ty(db) } + ast::NominalDef::UnionDef(def) => { + let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; + hir::Union::from_source(db, src)?.ty(db) + } }; let krate = module.krate(); diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs index 630e6ce64..370990e21 100644 --- a/crates/ra_parser/src/grammar/items.rs +++ b/crates/ra_parser/src/grammar/items.rs @@ -6,8 +6,8 @@ mod traits; mod use_item; pub(crate) use self::{ - expressions::{match_arm_list, record_field_list}, adt::{enum_variant_list, record_field_def_list}, + expressions::{match_arm_list, record_field_list}, traits::{impl_item_list, trait_item_list}, use_item::use_tree_list, }; diff --git a/crates/ra_syntax/src/ast/extensions.rs b/crates/ra_syntax/src/ast/extensions.rs index 513ed7920..a8f625176 100644 --- a/crates/ra_syntax/src/ast/extensions.rs +++ b/crates/ra_syntax/src/ast/extensions.rs @@ -196,17 +196,6 @@ impl StructKind { } impl ast::StructDef { - pub fn is_union(&self) -> bool { - for child in self.syntax().children_with_tokens() { - match child.kind() { - T![struct] => return false, - T![union] => return true, - _ => (), - } - } - false - } - pub fn kind(&self) -> StructKind { StructKind::from_node(self) } diff --git a/crates/ra_syntax/src/ast/generated.rs b/crates/ra_syntax/src/ast/generated.rs index 1a03ae56c..c06076e3d 100644 --- a/crates/ra_syntax/src/ast/generated.rs +++ b/crates/ra_syntax/src/ast/generated.rs @@ -1856,6 +1856,7 @@ impl Module { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ModuleItem { StructDef(StructDef), + UnionDef(UnionDef), EnumDef(EnumDef), FnDef(FnDef), TraitDef(TraitDef), @@ -1872,6 +1873,11 @@ impl From for ModuleItem { ModuleItem::StructDef(node) } } +impl From for ModuleItem { + fn from(node: UnionDef) -> ModuleItem { + ModuleItem::UnionDef(node) + } +} impl From for ModuleItem { fn from(node: EnumDef) -> ModuleItem { ModuleItem::EnumDef(node) @@ -1925,14 +1931,15 @@ impl From for ModuleItem { impl AstNode for ModuleItem { fn can_cast(kind: SyntaxKind) -> bool { match kind { - STRUCT_DEF | ENUM_DEF | FN_DEF | TRAIT_DEF | TYPE_ALIAS_DEF | IMPL_BLOCK | USE_ITEM - | EXTERN_CRATE_ITEM | CONST_DEF | STATIC_DEF | MODULE => true, + STRUCT_DEF | UNION_DEF | ENUM_DEF | FN_DEF | TRAIT_DEF | TYPE_ALIAS_DEF + | IMPL_BLOCK | USE_ITEM | EXTERN_CRATE_ITEM | CONST_DEF | STATIC_DEF | MODULE => true, _ => false, } } fn cast(syntax: SyntaxNode) -> Option { let res = match syntax.kind() { STRUCT_DEF => ModuleItem::StructDef(StructDef { syntax }), + UNION_DEF => ModuleItem::UnionDef(UnionDef { syntax }), ENUM_DEF => ModuleItem::EnumDef(EnumDef { syntax }), FN_DEF => ModuleItem::FnDef(FnDef { syntax }), TRAIT_DEF => ModuleItem::TraitDef(TraitDef { syntax }), @@ -1950,6 +1957,7 @@ impl AstNode for ModuleItem { fn syntax(&self) -> &SyntaxNode { match self { ModuleItem::StructDef(it) => &it.syntax, + ModuleItem::UnionDef(it) => &it.syntax, ModuleItem::EnumDef(it) => &it.syntax, ModuleItem::FnDef(it) => &it.syntax, ModuleItem::TraitDef(it) => &it.syntax, @@ -2038,6 +2046,7 @@ impl NeverType {} pub enum NominalDef { StructDef(StructDef), EnumDef(EnumDef), + UnionDef(UnionDef), } impl From for NominalDef { fn from(node: StructDef) -> NominalDef { @@ -2049,10 +2058,15 @@ impl From for NominalDef { NominalDef::EnumDef(node) } } +impl From for NominalDef { + fn from(node: UnionDef) -> NominalDef { + NominalDef::UnionDef(node) + } +} impl AstNode for NominalDef { fn can_cast(kind: SyntaxKind) -> bool { match kind { - STRUCT_DEF | ENUM_DEF => true, + STRUCT_DEF | ENUM_DEF | UNION_DEF => true, _ => false, } } @@ -2060,6 +2074,7 @@ impl AstNode for NominalDef { let res = match syntax.kind() { STRUCT_DEF => NominalDef::StructDef(StructDef { syntax }), ENUM_DEF => NominalDef::EnumDef(EnumDef { syntax }), + UNION_DEF => NominalDef::UnionDef(UnionDef { syntax }), _ => return None, }; Some(res) @@ -2068,6 +2083,7 @@ impl AstNode for NominalDef { match self { NominalDef::StructDef(it) => &it.syntax, NominalDef::EnumDef(it) => &it.syntax, + NominalDef::UnionDef(it) => &it.syntax, } } } @@ -3815,7 +3831,11 @@ impl ast::NameOwner for UnionDef {} impl ast::TypeParamsOwner for UnionDef {} impl ast::AttrsOwner for UnionDef {} impl ast::DocCommentsOwner for UnionDef {} -impl UnionDef {} +impl UnionDef { + pub fn record_field_def_list(&self) -> Option { + AstChildren::new(&self.syntax).next() + } +} #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct UseItem { pub(crate) syntax: SyntaxNode, diff --git a/crates/ra_syntax/src/grammar.ron b/crates/ra_syntax/src/grammar.ron index c16bed891..d1be40abe 100644 --- a/crates/ra_syntax/src/grammar.ron +++ b/crates/ra_syntax/src/grammar.ron @@ -293,7 +293,8 @@ Grammar( "TypeParamsOwner", "AttrsOwner", "DocCommentsOwner" - ] + ], + options: ["RecordFieldDefList"], ), "RecordFieldDefList": (collections: [("fields", "RecordFieldDef")]), "RecordFieldDef": ( @@ -398,7 +399,7 @@ Grammar( ]), "NominalDef": ( - enum: ["StructDef", "EnumDef"], + enum: ["StructDef", "EnumDef", "UnionDef"], traits: [ "NameOwner", "TypeParamsOwner", @@ -406,9 +407,9 @@ Grammar( ], ), "ModuleItem": ( - enum: ["StructDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", + enum: ["StructDef", "UnionDef", "EnumDef", "FnDef", "TraitDef", "TypeAliasDef", "ImplBlock", "UseItem", "ExternCrateItem", "ConstDef", "StaticDef", "Module" ], - traits: ["AttrsOwner"] + traits: ["AttrsOwner"], ), "ImplItem": ( enum: ["FnDef", "TypeAliasDef", "ConstDef"], -- cgit v1.2.3 From 1455663ea15ecbfbe87b4b5be6919aa35dd0b260 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 17:34:15 +0300 Subject: Fixme for union fields --- crates/ra_hir/src/ty/infer/expr.rs | 2 ++ crates/ra_hir/src/ty/lower.rs | 1 + crates/ra_hir_def/src/adt.rs | 5 +++++ crates/ra_hir_def/src/lib.rs | 1 + 4 files changed, 9 insertions(+) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 1d6df2b7a..994a6d7e9 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -263,6 +263,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { .clone() .subst(&a_ty.parameters) }), + // FIXME: + TypeCtor::Adt(Adt::Union(_)) => None, _ => None, }, _ => None, diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 89bc4b9ae..eb51d31bd 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -561,6 +561,7 @@ pub(crate) fn field_types_query( ) -> Arc> { let (resolver, var_data) = match variant_id { VariantId::StructId(it) => (it.resolver(db), db.struct_data(it).variant_data.clone()), + VariantId::UnionId(it) => (it.resolver(db), db.union_data(it).variant_data.clone()), VariantId::EnumVariantId(it) => ( it.parent.resolver(db), db.enum_data(it.parent).variants[it.local_id].variant_data.clone(), diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index 71014826e..0091bfbc3 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs @@ -150,6 +150,11 @@ impl HasChildSource for VariantId { src.map(|map| map[it.local_id].kind()) } VariantId::StructId(it) => it.source(db).map(|it| it.kind()), + VariantId::UnionId(it) => it.source(db).map(|it| { + it.record_field_def_list() + .map(ast::StructKind::Record) + .unwrap_or(ast::StructKind::Unit) + }), }; let mut trace = Trace::new_for_map(); lower_struct(&mut trace, &src.value); diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 5f11be114..a88a78b38 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -431,6 +431,7 @@ impl_froms!( pub enum VariantId { EnumVariantId(EnumVariantId), StructId(StructId), + UnionId(UnionId), } impl_froms!(VariantId: EnumVariantId, StructId); -- cgit v1.2.3 From 3e32ac4f866e2b9430dd1e91b2c7fa3824c646a9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 18:31:48 +0300 Subject: More ids in Ty --- crates/ra_hir/src/code_model.rs | 8 -------- crates/ra_hir/src/ty.rs | 11 ++++++----- crates/ra_hir/src/ty/infer/expr.rs | 6 ++++-- crates/ra_hir/src/ty/traits.rs | 5 +++-- 4 files changed, 13 insertions(+), 17 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index ae730beb5..f7fc80d4e 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -538,14 +538,6 @@ pub enum DefWithBody { impl_froms!(DefWithBody: Function, Const, Static); impl DefWithBody { - pub(crate) fn krate(self, db: &impl HirDatabase) -> Option { - match self { - DefWithBody::Const(c) => c.krate(db), - DefWithBody::Function(f) => f.krate(db), - DefWithBody::Static(s) => s.krate(db), - } - } - pub fn module(self, db: &impl HirDatabase) -> Module { match self { DefWithBody::Const(c) => c.module(db), diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 3cbcbd1d0..bac21732e 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -17,12 +17,12 @@ use std::ops::Deref; use std::sync::Arc; use std::{fmt, iter, mem}; -use hir_def::{generics::GenericParams, AdtId, GenericDefId}; +use hir_def::{generics::GenericParams, AdtId, DefWithBodyId, GenericDefId}; use ra_db::{impl_intern_key, salsa}; use crate::{ - db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, DefWithBody, FloatTy, IntTy, - Mutability, Name, Trait, TypeAlias, Uncertain, + db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, FloatTy, IntTy, Mutability, + Name, Trait, TypeAlias, Uncertain, }; use display::{HirDisplay, HirFormatter}; @@ -113,7 +113,7 @@ pub enum TypeCtor { /// /// The closure signature is stored in a `FnPtr` type in the first type /// parameter. - Closure { def: DefWithBody, expr: ExprId }, + Closure { def: DefWithBodyId, expr: ExprId }, } /// This exists just for Chalk, because Chalk just has a single `StructId` where @@ -169,7 +169,8 @@ impl TypeCtor { | TypeCtor::Ref(_) | TypeCtor::FnPtr { .. } | TypeCtor::Tuple { .. } => None, - TypeCtor::Closure { def, .. } => def.krate(db), + // Closure's krate is irrelevant for coherence I would think? + TypeCtor::Closure { .. } => None, TypeCtor::Adt(adt) => adt.krate(db), TypeCtor::FnDef(callable) => Some(callable.krate(db).into()), TypeCtor::AssociatedType(type_alias) => type_alias.krate(db), diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 994a6d7e9..b581d192f 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -137,8 +137,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, Substs(sig_tys.into()), ); - let closure_ty = - Ty::apply_one(TypeCtor::Closure { def: self.owner, expr: tgt_expr }, sig_ty); + let closure_ty = Ty::apply_one( + TypeCtor::Closure { def: self.owner.into(), expr: tgt_expr }, + sig_ty, + ); // Eagerly try to relate the closure type with the expected // type, otherwise we often won't have enough information to diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 268fa09e4..b9a5d651f 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -2,13 +2,14 @@ use std::sync::{Arc, Mutex}; use chalk_ir::{cast::Cast, family::ChalkIr}; +use hir_def::DefWithBodyId; use log::debug; use ra_db::{impl_intern_key, salsa}; use ra_prof::profile; use rustc_hash::FxHashSet; use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; -use crate::{db::HirDatabase, expr::ExprId, Crate, DefWithBody, ImplBlock, Trait, TypeAlias}; +use crate::{db::HirDatabase, expr::ExprId, Crate, ImplBlock, Trait, TypeAlias}; use self::chalk::{from_chalk, ToChalk}; @@ -290,7 +291,7 @@ impl FnTrait { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ClosureFnTraitImplData { - def: DefWithBody, + def: DefWithBodyId, expr: ExprId, fn_trait: FnTrait, } -- cgit v1.2.3 From 6d2ec8765d418b365dfaf472ab9b2b53b8eeafa9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 18:44:36 +0300 Subject: Use TypeAliasId in Ty, pt 1 --- crates/ra_hir/src/ty.rs | 26 ++++++++++++++++---------- crates/ra_hir/src/ty/traits/chalk.rs | 14 +++++++++++++- 2 files changed, 29 insertions(+), 11 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index bac21732e..8a184de71 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -17,7 +17,10 @@ use std::ops::Deref; use std::sync::Arc; use std::{fmt, iter, mem}; -use hir_def::{generics::GenericParams, AdtId, DefWithBodyId, GenericDefId}; +use hir_def::{ + generics::GenericParams, AdtId, ContainerId, DefWithBodyId, GenericDefId, HasModule, Lookup, + TypeAliasId, +}; use ra_db::{impl_intern_key, salsa}; use crate::{ @@ -107,7 +110,7 @@ pub enum TypeCtor { /// when we have tried to normalize a projection like `T::Item` but /// couldn't find a better representation. In that case, we generate /// an **application type** like `(Iterator::Item)`. - AssociatedType(TypeAlias), + AssociatedType(TypeAliasId), /// The type of a specific closure. /// @@ -147,7 +150,7 @@ impl TypeCtor { generic_params.count_params_including_parent() } TypeCtor::AssociatedType(type_alias) => { - let generic_params = db.generic_params(type_alias.id.into()); + let generic_params = db.generic_params(type_alias.into()); generic_params.count_params_including_parent() } TypeCtor::FnPtr { num_args } => num_args as usize + 1, @@ -173,7 +176,9 @@ impl TypeCtor { TypeCtor::Closure { .. } => None, TypeCtor::Adt(adt) => adt.krate(db), TypeCtor::FnDef(callable) => Some(callable.krate(db).into()), - TypeCtor::AssociatedType(type_alias) => type_alias.krate(db), + TypeCtor::AssociatedType(type_alias) => { + Some(type_alias.lookup(db).module(db).krate.into()) + } } } @@ -194,7 +199,7 @@ impl TypeCtor { | TypeCtor::Closure { .. } => None, TypeCtor::Adt(adt) => Some(adt.into()), TypeCtor::FnDef(callable) => Some(callable.into()), - TypeCtor::AssociatedType(type_alias) => Some(type_alias.id.into()), + TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()), } } } @@ -896,11 +901,12 @@ impl HirDisplay for ApplicationTy { } } TypeCtor::AssociatedType(type_alias) => { - let trait_name = type_alias - .parent_trait(f.db) - .and_then(|t| t.name(f.db)) - .unwrap_or_else(Name::missing); - let name = type_alias.name(f.db); + let trait_ = match type_alias.lookup(f.db).container { + ContainerId::TraitId(it) => it, + _ => panic!("not an associated type"), + }; + let trait_name = f.db.trait_data(trait_).name.clone().unwrap_or_else(Name::missing); + let name = f.db.type_alias_data(type_alias).name.clone(); write!(f, "{}::{}", trait_name, name)?; if self.parameters.len() > 0 { write!(f, "<")?; diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 0272dd9ae..fd2f1b174 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -9,7 +9,7 @@ use chalk_ir::{ }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; -use hir_def::{lang_item::LangItemTarget, GenericDefId}; +use hir_def::{lang_item::LangItemTarget, GenericDefId, TypeAliasId}; use hir_expand::name; use ra_db::salsa::{InternId, InternKey}; @@ -215,6 +215,18 @@ impl ToChalk for TypeAlias { } } +impl ToChalk for TypeAliasId { + type Chalk = chalk_ir::TypeId; + + fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TypeId { + chalk_ir::TypeId(id_to_chalk(self)) + } + + fn from_chalk(_db: &impl HirDatabase, type_alias_id: chalk_ir::TypeId) -> TypeAliasId { + id_from_chalk(type_alias_id.0) + } +} + impl ToChalk for AssocTyValue { type Chalk = chalk_rust_ir::AssociatedTyValueId; -- cgit v1.2.3 From 1a0da6d4dad846568042f85ad7225b45b3275e49 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Mon, 25 Nov 2019 18:58:17 +0300 Subject: Use TypeAliasId in Ty, pt 2 --- crates/ra_hir/src/ty.rs | 35 ++++++++++++++++++----------------- crates/ra_hir/src/ty/autoderef.rs | 2 +- crates/ra_hir/src/ty/infer/expr.rs | 6 +++--- crates/ra_hir/src/ty/lower.rs | 9 ++++++--- crates/ra_hir/src/ty/traits/chalk.rs | 34 ++++++++++++---------------------- 5 files changed, 40 insertions(+), 46 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 8a184de71..388530f31 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -19,13 +19,13 @@ use std::{fmt, iter, mem}; use hir_def::{ generics::GenericParams, AdtId, ContainerId, DefWithBodyId, GenericDefId, HasModule, Lookup, - TypeAliasId, + TraitId, TypeAliasId, }; use ra_db::{impl_intern_key, salsa}; use crate::{ db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, FloatTy, IntTy, Mutability, - Name, Trait, TypeAlias, Uncertain, + Name, Trait, Uncertain, }; use display::{HirDisplay, HirFormatter}; @@ -218,18 +218,19 @@ pub struct ApplicationTy { /// trait and all its parameters are fully known. #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct ProjectionTy { - pub associated_ty: TypeAlias, + pub associated_ty: TypeAliasId, pub parameters: Substs, } impl ProjectionTy { pub fn trait_ref(&self, db: &impl HirDatabase) -> TraitRef { - TraitRef { - trait_: self - .associated_ty - .parent_trait(db) - .expect("projection ty without parent trait"), - substs: self.parameters.clone(), + TraitRef { trait_: self.trait_(db).into(), substs: self.parameters.clone() } + } + + fn trait_(&self, db: &impl HirDatabase) -> TraitId { + match self.associated_ty.lookup(db).container { + ContainerId::TraitId(it) => it, + _ => panic!("projection ty without parent trait"), } } } @@ -933,18 +934,15 @@ impl HirDisplay for ProjectionTy { return write!(f, "…"); } - let trait_name = self - .associated_ty - .parent_trait(f.db) - .and_then(|t| t.name(f.db)) - .unwrap_or_else(Name::missing); + let trait_name = + f.db.trait_data(self.trait_(f.db)).name.clone().unwrap_or_else(Name::missing); write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; if self.parameters.len() > 1 { write!(f, "<")?; f.write_joined(&self.parameters[1..], ", ")?; write!(f, ">")?; } - write!(f, ">::{}", self.associated_ty.name(f.db))?; + write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?; Ok(()) } } @@ -1007,7 +1005,10 @@ impl HirDisplay for Ty { write!(f, "<")?; angle_open = true; } - let name = projection_pred.projection_ty.associated_ty.name(f.db); + let name = + f.db.type_alias_data(projection_pred.projection_ty.associated_ty) + .name + .clone(); write!(f, "{} = ", name)?; projection_pred.ty.hir_fmt(f)?; } @@ -1083,7 +1084,7 @@ impl HirDisplay for GenericPredicate { write!( f, ">::{} = {}", - projection_pred.projection_ty.associated_ty.name(f.db), + f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name, projection_pred.ty.display(f.db) )?; } diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs index 44547197c..9e7593b8b 100644 --- a/crates/ra_hir/src/ty/autoderef.rs +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -69,7 +69,7 @@ fn deref_by_trait( let projection = super::traits::ProjectionPredicate { ty: Ty::Bound(0), - projection_ty: super::ProjectionTy { associated_ty: target, parameters }, + projection_ty: super::ProjectionTy { associated_ty: target.id, parameters }, }; let obligation = super::Obligation::Projection(projection); diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index b581d192f..316cdc880 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -101,7 +101,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let projection = ProjectionPredicate { ty: pat_ty.clone(), projection_ty: ProjectionTy { - associated_ty: into_iter_item_alias, + associated_ty: into_iter_item_alias.id, parameters: Substs::single(iterable_ty), }, }; @@ -283,7 +283,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let projection = ProjectionPredicate { ty: ty.clone(), projection_ty: ProjectionTy { - associated_ty: future_future_output_alias, + associated_ty: future_future_output_alias.id, parameters: Substs::single(inner_ty), }, }; @@ -302,7 +302,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let projection = ProjectionPredicate { ty: ty.clone(), projection_ty: ProjectionTy { - associated_ty: ops_try_ok_alias, + associated_ty: ops_try_ok_alias.id, parameters: Substs::single(inner_ty), }, }; diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index eb51d31bd..d7d4bb0d6 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -176,7 +176,7 @@ impl Ty { Some(associated_ty) => { // FIXME handle type parameters on the segment Ty::Projection(ProjectionTy { - associated_ty, + associated_ty: associated_ty.id, parameters: trait_ref.substs, }) } @@ -268,7 +268,10 @@ impl Ty { .fill_with_unknown() .build(); // FIXME handle type parameters on the segment - return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); + return Ty::Projection(ProjectionTy { + associated_ty: associated_ty.id, + parameters: substs, + }); } } Ty::Unknown @@ -508,7 +511,7 @@ fn assoc_type_bindings_from_type_bound<'a>( let associated_ty = match trait_ref.trait_.associated_type_by_name_including_super_traits(db, &name) { None => return GenericPredicate::Error, - Some(t) => t, + Some(t) => t.id, }; let projection_ty = ProjectionTy { associated_ty, parameters: trait_ref.substs.clone() }; diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index fd2f1b174..06388a3ce 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -9,7 +9,7 @@ use chalk_ir::{ }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; -use hir_def::{lang_item::LangItemTarget, GenericDefId, TypeAliasId}; +use hir_def::{lang_item::LangItemTarget, ContainerId, GenericDefId, Lookup, TypeAliasId}; use hir_expand::name; use ra_db::salsa::{InternId, InternKey}; @@ -203,18 +203,6 @@ impl ToChalk for Impl { } } -impl ToChalk for TypeAlias { - type Chalk = chalk_ir::TypeId; - - fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TypeId { - chalk_ir::TypeId(id_to_chalk(self.id)) - } - - fn from_chalk(_db: &impl HirDatabase, type_alias_id: chalk_ir::TypeId) -> TypeAlias { - TypeAlias { id: id_from_chalk(type_alias_id.0) } - } -} - impl ToChalk for TypeAliasId { type Chalk = chalk_ir::TypeId; @@ -516,21 +504,21 @@ pub(crate) fn associated_ty_data_query( id: TypeId, ) -> Arc> { debug!("associated_ty_data {:?}", id); - let type_alias: TypeAlias = from_chalk(db, id); - let trait_ = match type_alias.container(db) { - Some(crate::Container::Trait(t)) => t, + let type_alias: TypeAliasId = from_chalk(db, id); + let trait_ = match type_alias.lookup(db).container { + ContainerId::TraitId(t) => t, _ => panic!("associated type not in trait"), }; - let generic_params = db.generic_params(type_alias.id.into()); + let generic_params = db.generic_params(type_alias.into()); let bound_data = chalk_rust_ir::AssociatedTyDatumBound { // FIXME add bounds and where clauses bounds: vec![], where_clauses: vec![], }; let datum = AssociatedTyDatum { - trait_id: trait_.to_chalk(db), + trait_id: Trait::from(trait_).to_chalk(db), id, - name: lalrpop_intern::intern(&type_alias.name(db).to_string()), + name: lalrpop_intern::intern(&db.type_alias_data(type_alias).name.to_string()), binders: make_binders(bound_data, generic_params.count_params_including_parent()), }; Arc::new(datum) @@ -578,7 +566,7 @@ pub(crate) fn trait_datum_query( .items(db) .into_iter() .filter_map(|trait_item| match trait_item { - crate::AssocItem::TypeAlias(type_alias) => Some(type_alias), + crate::AssocItem::TypeAlias(type_alias) => Some(type_alias.id), _ => None, }) .map(|type_alias| type_alias.to_chalk(db)) @@ -797,7 +785,8 @@ fn type_alias_associated_ty_value( .trait_; let assoc_ty = trait_ .associated_type_by_name(db, &type_alias.name(db)) - .expect("assoc ty value should not exist"); // validated when building the impl data as well + .expect("assoc ty value should not exist") // validated when building the impl data as well + .id; let generic_params = db.generic_params(impl_block.id.into()); let bound_vars = Substs::bound_vars(&generic_params); let ty = db.type_for_def(type_alias.into(), crate::ty::Namespace::Types).subst(&bound_vars); @@ -832,7 +821,8 @@ fn closure_fn_trait_output_assoc_ty_value( let output_ty_id = fn_once_trait .associated_type_by_name(db, &name::OUTPUT_TYPE) - .expect("assoc ty value should not exist"); + .expect("assoc ty value should not exist") + .id; let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: output_ty.to_chalk(db) }; -- cgit v1.2.3 From 245a9b165acb179c40b8c9d4a085e5ccdd4b75d3 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 26 Nov 2019 15:05:53 +0800 Subject: Add hygiene information to SourceAnalyzer --- crates/ra_hir/src/source_binder.rs | 13 ++++++++----- crates/ra_hir_def/src/path.rs | 2 +- crates/ra_ide_api/src/call_info.rs | 9 +++------ crates/ra_ide_api/src/expand_macro.rs | 23 +++++++++++++++++++++++ crates/ra_ide_api/src/references/classify.rs | 2 +- 5 files changed, 36 insertions(+), 13 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index cbfeca3ab..287cea880 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -14,7 +14,8 @@ use hir_def::{ DefWithBodyId, }; use hir_expand::{ - name::AsName, AstId, HirFileId, MacroCallId, MacroCallLoc, MacroFileKind, Source, + hygiene::Hygiene, name::AsName, AstId, HirFileId, MacroCallId, MacroCallLoc, MacroFileKind, + Source, }; use ra_syntax::{ ast::{self, AstNode}, @@ -236,10 +237,10 @@ impl SourceAnalyzer { pub fn resolve_macro_call( &self, db: &impl HirDatabase, - macro_call: &ast::MacroCall, + macro_call: Source<&ast::MacroCall>, ) -> Option { - // This must be a normal source file rather than macro file. - let path = macro_call.path().and_then(Path::from_ast)?; + let hygiene = Hygiene::new(db, macro_call.file_id); + let path = macro_call.value.path().and_then(|ast| Path::from_src(ast, &hygiene))?; self.resolver.resolve_path_as_macro(db, &path).map(|it| it.into()) } @@ -441,12 +442,14 @@ impl SourceAnalyzer { db: &impl HirDatabase, macro_call: Source<&ast::MacroCall>, ) -> Option { - let def = self.resolve_macro_call(db, macro_call.value)?.id; + let def = self.resolve_macro_call(db, macro_call)?.id; let ast_id = AstId::new( macro_call.file_id, db.ast_id_map(macro_call.file_id).ast_id(macro_call.value), ); let macro_call_loc = MacroCallLoc { def, ast_id }; + let kind = to_macro_file_kind(macro_call.value); + dbg!(kind); Some(Expansion { macro_call_id: db.intern_macro(macro_call_loc), macro_file_kind: to_macro_file_kind(macro_call.value), diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs index 0e606fd0e..6810a26db 100644 --- a/crates/ra_hir_def/src/path.rs +++ b/crates/ra_hir_def/src/path.rs @@ -97,7 +97,7 @@ impl Path { /// Converts an `ast::Path` to `Path`. Works with use trees. /// It correctly handles `$crate` based path from macro call. - pub(crate) fn from_src(mut path: ast::Path, hygiene: &Hygiene) -> Option { + pub fn from_src(mut path: ast::Path, hygiene: &Hygiene) -> Option { let mut kind = PathKind::Plain; let mut segments = Vec::new(); loop { diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 9beceb29c..7ebdfc585 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -18,12 +18,9 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { //FIXME: don't poke into Ty @@ -44,7 +41,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { - let macro_def = analyzer.resolve_macro_call(db, &expr)?; + let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?; (CallInfo::with_macro(db, macro_def)?, false) } }; diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs index 0b540b8cd..abc602244 100644 --- a/crates/ra_ide_api/src/expand_macro.rs +++ b/crates/ra_ide_api/src/expand_macro.rs @@ -269,4 +269,27 @@ fn some_thing() -> u32 { assert_eq!(res.name, "foo"); assert_snapshot!(res.expansion, @r###"bar!()"###); } + + #[test] + fn macro_expand_with_dollar_crate() { + let res = check_expand_macro( + r#" + //- /lib.rs + #[macro_export] + macro_rules! bar { + () => {0}; + } + macro_rules! foo { + () => {$crate::bar!()}; + } + + fn main() { + let res = fo<|>o!(); + } + "#, + ); + + assert_eq!(res.name, "foo"); + assert_snapshot!(res.expansion, @r###"0"###); + } } diff --git a/crates/ra_ide_api/src/references/classify.rs b/crates/ra_ide_api/src/references/classify.rs index cab06dea9..227737ad2 100644 --- a/crates/ra_ide_api/src/references/classify.rs +++ b/crates/ra_ide_api/src/references/classify.rs @@ -152,7 +152,7 @@ pub(crate) fn classify_name_ref( if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { tested_by!(goto_definition_works_for_macros); - if let Some(macro_def) = analyzer.resolve_macro_call(db, ¯o_call) { + if let Some(macro_def) = analyzer.resolve_macro_call(db, name_ref.with_value(¯o_call)) { let kind = NameKind::Macro(macro_def); return Some(NameDefinition { kind, container, visibility }); } -- cgit v1.2.3 From 131c2da6bfd8ad6a25a55b4c85081da327ba2acb Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 11:29:20 +0300 Subject: :arrow_up: salsa --- crates/ra_assists/src/test_db.rs | 3 +++ crates/ra_cli/src/analysis_bench.rs | 4 ++-- crates/ra_db/Cargo.toml | 2 +- crates/ra_hir/src/test_db.rs | 4 ++++ crates/ra_hir_def/src/test_db.rs | 4 +++- crates/ra_hir_expand/src/test_db.rs | 4 ++++ crates/ra_ide_api/src/change.rs | 2 +- crates/ra_ide_api/src/db.rs | 3 +++ 8 files changed, 21 insertions(+), 5 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/test_db.rs b/crates/ra_assists/src/test_db.rs index 5f96c974b..523259fd4 100644 --- a/crates/ra_assists/src/test_db.rs +++ b/crates/ra_assists/src/test_db.rs @@ -21,6 +21,9 @@ impl salsa::Database for TestDB { fn salsa_runtime(&self) -> &salsa::Runtime { &self.runtime } + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } } impl std::panic::RefUnwindSafe for TestDB {} diff --git a/crates/ra_cli/src/analysis_bench.rs b/crates/ra_cli/src/analysis_bench.rs index 8bbe5d9e8..34105af57 100644 --- a/crates/ra_cli/src/analysis_bench.rs +++ b/crates/ra_cli/src/analysis_bench.rs @@ -91,7 +91,7 @@ fn do_work T, T>(host: &mut AnalysisHost, file_id: FileId, w { let start = Instant::now(); eprint!("trivial change: "); - host.raw_database().salsa_runtime().synthetic_write(Durability::LOW); + host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::LOW); work(&host.analysis()); eprintln!("{:?}", start.elapsed()); } @@ -111,7 +111,7 @@ fn do_work T, T>(host: &mut AnalysisHost, file_id: FileId, w { let start = Instant::now(); eprint!("const change: "); - host.raw_database().salsa_runtime().synthetic_write(Durability::HIGH); + host.raw_database_mut().salsa_runtime_mut().synthetic_write(Durability::HIGH); let res = work(&host.analysis()); eprintln!("{:?}", start.elapsed()); res diff --git a/crates/ra_db/Cargo.toml b/crates/ra_db/Cargo.toml index 4ec09b6d9..7afa5d8fc 100644 --- a/crates/ra_db/Cargo.toml +++ b/crates/ra_db/Cargo.toml @@ -8,7 +8,7 @@ authors = ["rust-analyzer developers"] doctest = false [dependencies] -salsa = "0.13.0" +salsa = "0.14.1" relative-path = "1.0.0" rustc-hash = "1.0" diff --git a/crates/ra_hir/src/test_db.rs b/crates/ra_hir/src/test_db.rs index efee2f658..a2071f71c 100644 --- a/crates/ra_hir/src/test_db.rs +++ b/crates/ra_hir/src/test_db.rs @@ -28,6 +28,10 @@ impl salsa::Database for TestDB { &self.runtime } + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } + fn salsa_event(&self, event: impl Fn() -> salsa::Event) { let mut events = self.events.lock(); if let Some(events) = &mut *events { diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs index 439e8a412..54e3a84bd 100644 --- a/crates/ra_hir_def/src/test_db.rs +++ b/crates/ra_hir_def/src/test_db.rs @@ -24,7 +24,9 @@ impl salsa::Database for TestDB { fn salsa_runtime(&self) -> &salsa::Runtime { &self.runtime } - + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } fn salsa_event(&self, event: impl Fn() -> salsa::Event) { let mut events = self.events.lock().unwrap(); if let Some(events) = &mut *events { diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs index d23e75d9e..918736e2a 100644 --- a/crates/ra_hir_expand/src/test_db.rs +++ b/crates/ra_hir_expand/src/test_db.rs @@ -23,6 +23,10 @@ impl salsa::Database for TestDB { &self.runtime } + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } + fn salsa_event(&self, event: impl Fn() -> salsa::Event) { let mut events = self.events.lock().unwrap(); if let Some(events) = &mut *events { diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs index 0f692460d..84340aff8 100644 --- a/crates/ra_ide_api/src/change.rs +++ b/crates/ra_ide_api/src/change.rs @@ -171,7 +171,7 @@ impl RootDatabase { log::info!("apply_change {:?}", change); { let _p = profile("RootDatabase::apply_change/cancellation"); - self.salsa_runtime().synthetic_write(Durability::LOW); + self.salsa_runtime_mut().synthetic_write(Durability::LOW); } if !change.new_roots.is_empty() { let mut local_roots = Vec::clone(&self.local_roots()); diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs index c2a9dcdd1..f739ebecd 100644 --- a/crates/ra_ide_api/src/db.rs +++ b/crates/ra_ide_api/src/db.rs @@ -65,6 +65,9 @@ impl salsa::Database for RootDatabase { fn salsa_runtime(&self) -> &salsa::Runtime { &self.runtime } + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } fn on_propagated_panic(&self) -> ! { Canceled::throw() } -- cgit v1.2.3 From 0a7ef0933130b8293b09b57382fd61e3d7c46c69 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 11:45:12 +0300 Subject: Use chalk fork to paper over #2052 --- crates/ra_hir/Cargo.toml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index 42ddfecc9..f72574485 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -26,9 +26,11 @@ hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } test_utils = { path = "../test_utils" } ra_prof = { path = "../ra_prof" } -chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "a88cad7f0a69e05ba8f40b74c58a1c229c1b2478" } -chalk-rust-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "a88cad7f0a69e05ba8f40b74c58a1c229c1b2478" } -chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "a88cad7f0a69e05ba8f40b74c58a1c229c1b2478" } +# https://github.com/rust-lang/chalk/pull/294 +chalk-solve = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } +chalk-rust-ir = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } +chalk-ir = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } + lalrpop-intern = "0.15.1" [dev-dependencies] -- cgit v1.2.3 From e5eadb339039e21718d382c0b3d02a4bf053b3f4 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 14:02:57 +0300 Subject: Introduce hir::Type It should provide a convenient API over more low-level Ty --- crates/ra_assists/src/assists/add_explicit_type.rs | 13 +- crates/ra_assists/src/assists/fill_match_arms.rs | 7 +- crates/ra_hir/src/code_model.rs | 166 ++++++++++++++++++++- crates/ra_hir/src/lib.rs | 2 +- crates/ra_hir/src/source_binder.rs | 46 +++--- crates/ra_hir_def/src/lib.rs | 10 ++ crates/ra_ide_api/src/call_info.rs | 4 +- crates/ra_ide_api/src/completion/complete_dot.rs | 36 ++--- .../ra_ide_api/src/completion/complete_postfix.rs | 16 +- .../src/completion/complete_record_literal.rs | 7 +- .../src/completion/complete_record_pattern.rs | 7 +- crates/ra_ide_api/src/completion/presentation.rs | 39 ++--- crates/ra_ide_api/src/goto_type_definition.rs | 4 +- crates/ra_ide_api/src/inlay_hints.rs | 23 +-- crates/ra_ide_api/src/syntax_highlighting.rs | 9 +- 15 files changed, 253 insertions(+), 136 deletions(-) (limited to 'crates') diff --git a/crates/ra_assists/src/assists/add_explicit_type.rs b/crates/ra_assists/src/assists/add_explicit_type.rs index 562a09685..eeb4ff39f 100644 --- a/crates/ra_assists/src/assists/add_explicit_type.rs +++ b/crates/ra_assists/src/assists/add_explicit_type.rs @@ -1,4 +1,4 @@ -use hir::{db::HirDatabase, HirDisplay, Ty}; +use hir::{db::HirDatabase, HirDisplay}; use ra_syntax::{ ast::{self, AstNode, LetStmt, NameOwner}, T, @@ -43,7 +43,7 @@ pub(crate) fn add_explicit_type(ctx: AssistCtx) -> Option) -> Option bool { - match ty { - Ty::Unknown => true, - Ty::Apply(a_ty) => a_ty.parameters.iter().any(is_unknown), - _ => false, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/ra_assists/src/assists/fill_match_arms.rs b/crates/ra_assists/src/assists/fill_match_arms.rs index 8482897c5..b75bd44eb 100644 --- a/crates/ra_assists/src/assists/fill_match_arms.rs +++ b/crates/ra_assists/src/assists/fill_match_arms.rs @@ -83,10 +83,11 @@ fn resolve_enum_def( ) -> Option { let expr_ty = analyzer.type_of(db, &expr)?; - analyzer.autoderef(db, expr_ty).find_map(|ty| match ty.as_adt() { - Some((Adt::Enum(e), _)) => Some(e.source(db).value), + let res = expr_ty.autoderef(db).find_map(|ty| match ty.as_adt() { + Some(Adt::Enum(e)) => Some(e.source(db).value), _ => None, - }) + }); + res } fn build_pat(var: ast::EnumVariant) -> Option { diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index f7fc80d4e..a7bba85e1 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -10,9 +10,9 @@ use hir_def::{ docs::Documentation, per_ns::PerNs, resolver::{HasResolver, TypeNs}, - type_ref::TypeRef, - AstItemDef, ConstId, ContainerId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, - LocalEnumVariantId, LocalImportId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, + type_ref::{Mutability, TypeRef}, + AstItemDef, ConstId, ContainerId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, + ImplId, LocalEnumVariantId, LocalImportId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId, UnionId, }; use hir_expand::{ @@ -26,8 +26,12 @@ use ra_syntax::{ast, AstNode, SyntaxNode}; use crate::{ db::{DefDatabase, HirDatabase}, expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, - ty::{InferenceResult, Namespace, TraitRef}, - Either, Name, Source, Ty, + ty::display::HirFormatter, + ty::{ + self, InEnvironment, InferenceResult, Namespace, TraitEnvironment, TraitRef, Ty, TypeCtor, + TypeWalk, + }, + CallableDef, Either, HirDisplay, Name, Source, }; /// hir::Crate describes a single crate. It's the main interface with which @@ -469,6 +473,10 @@ pub enum Adt { impl_froms!(Adt: Struct, Union, Enum); impl Adt { + pub fn has_non_default_type_params(self, db: &impl HirDatabase) -> bool { + let subst = db.generic_defaults(self.into()); + subst.iter().any(|ty| ty == &Ty::Unknown) + } pub fn ty(self, db: &impl HirDatabase) -> Ty { match self { Adt::Struct(it) => it.ty(db), @@ -777,6 +785,11 @@ pub struct TypeAlias { } impl TypeAlias { + pub fn has_non_default_type_params(self, db: &impl HirDatabase) -> bool { + let subst = db.generic_defaults(self.id.into()); + subst.iter().any(|ty| ty == &Ty::Unknown) + } + pub fn module(self, db: &impl DefDatabase) -> Module { Module { id: self.id.lookup(db).module(db) } } @@ -927,9 +940,14 @@ impl Local { self.parent.module(db) } - pub fn ty(self, db: &impl HirDatabase) -> Ty { + pub fn ty(self, db: &impl HirDatabase) -> Type { let infer = db.infer(self.parent); - infer[self.pat_id].clone() + let ty = infer[self.pat_id].clone(); + let def = DefWithBodyId::from(self.parent); + let resolver = def.resolver(db); + let krate = def.module(db).krate; + let environment = TraitEnvironment::lower(db, &resolver); + Type { krate, ty: InEnvironment { value: ty, environment } } } pub fn source(self, db: &impl HirDatabase) -> Source> { @@ -986,6 +1004,140 @@ impl ImplBlock { } } +#[derive(Clone, PartialEq, Eq)] +pub struct Type { + pub(crate) krate: CrateId, + pub(crate) ty: InEnvironment, +} + +impl Type { + pub fn is_bool(&self) -> bool { + match &self.ty.value { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Bool => true, + _ => false, + }, + _ => false, + } + } + + pub fn is_mutable_reference(&self) -> bool { + match &self.ty.value { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Ref(Mutability::Mut) => true, + _ => false, + }, + _ => false, + } + } + + pub fn is_unknown(&self) -> bool { + match &self.ty.value { + Ty::Unknown => true, + _ => false, + } + } + + // FIXME: this method is broken, as it doesn't take closures into account. + pub fn as_callable(&self) -> Option { + Some(self.ty.value.as_callable()?.0) + } + + pub fn contains_unknown(&self) -> bool { + return go(&self.ty.value); + + fn go(ty: &Ty) -> bool { + match ty { + Ty::Unknown => true, + Ty::Apply(a_ty) => a_ty.parameters.iter().any(go), + _ => false, + } + } + } + + pub fn fields(&self, db: &impl HirDatabase) -> Vec<(StructField, Type)> { + let mut res = Vec::new(); + if let Ty::Apply(a_ty) = &self.ty.value { + match a_ty.ctor { + ty::TypeCtor::Adt(Adt::Struct(s)) => { + for field in s.fields(db) { + let ty = field.ty(db).subst(&a_ty.parameters); + res.push((field, self.derived(ty))); + } + } + _ => {} + } + }; + res + } + + pub fn tuple_fields(&self, _db: &impl HirDatabase) -> Vec { + let mut res = Vec::new(); + if let Ty::Apply(a_ty) = &self.ty.value { + match a_ty.ctor { + ty::TypeCtor::Tuple { .. } => { + for ty in a_ty.parameters.iter() { + let ty = ty.clone().subst(&a_ty.parameters); + res.push(self.derived(ty)); + } + } + _ => {} + } + }; + res + } + + pub fn variant_fields( + &self, + db: &impl HirDatabase, + def: VariantDef, + ) -> Vec<(StructField, Type)> { + // FIXME: check that ty and def match + match &self.ty.value { + Ty::Apply(a_ty) => def + .fields(db) + .into_iter() + .map(|it| (it, self.derived(it.ty(db).subst(&a_ty.parameters)))) + .collect(), + _ => Vec::new(), + } + } + + pub fn autoderef<'a>(&'a self, db: &'a impl HirDatabase) -> impl Iterator + 'a { + // There should be no inference vars in types passed here + // FIXME check that? + let canonical = crate::ty::Canonical { value: self.ty.value.clone(), num_vars: 0 }; + let environment = self.ty.environment.clone(); + let ty = InEnvironment { value: canonical, environment: environment.clone() }; + ty::autoderef(db, Some(self.krate), ty) + .map(|canonical| canonical.value) + .map(move |ty| self.derived(ty)) + } + + // FIXME: remove + pub fn into_ty(self) -> Ty { + self.ty.value + } + + pub fn as_adt(&self) -> Option { + let (adt, _subst) = self.ty.value.as_adt()?; + Some(adt) + } + + fn derived(&self, ty: Ty) -> Type { + Type { + krate: self.krate, + ty: InEnvironment { value: ty, environment: self.ty.environment.clone() }, + } + } +} + +impl HirDisplay for Type { + fn hir_fmt(&self, f: &mut HirFormatter) -> std::fmt::Result { + self.ty.value.hir_fmt(f) + } +} + /// For IDE only pub enum ScopeDef { ModuleDef(ModuleDef), diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 843ce6a88..b88e4c745 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -51,7 +51,7 @@ pub use crate::{ src::HasSource, Adt, AssocItem, AttrDef, Const, Container, Crate, CrateDependency, DefWithBody, Docs, Enum, EnumVariant, FieldSource, Function, GenericDef, GenericParam, HasAttrs, ImplBlock, Import, Local, MacroDef, Module, ModuleDef, ModuleSource, ScopeDef, - Static, Struct, StructField, Trait, TypeAlias, Union, VariantDef, + Static, Struct, StructField, Trait, Type, TypeAlias, Union, VariantDef, }, expr::ExprScopes, from_source::FromSource, diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index cbfeca3ab..95a94c3f0 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -28,10 +28,10 @@ use crate::{ expr::{BodySourceMap, ExprScopes, ScopeId}, ty::{ method_resolution::{self, implements_trait}, - TraitEnvironment, + InEnvironment, TraitEnvironment, Ty, }, Adt, AssocItem, Const, DefWithBody, Either, Enum, EnumVariant, FromSource, Function, - GenericParam, Local, MacroDef, Name, Path, ScopeDef, Static, Struct, Trait, Ty, TypeAlias, + GenericParam, Local, MacroDef, Name, Path, ScopeDef, Static, Struct, Trait, Type, TypeAlias, }; fn try_get_resolver_for_node(db: &impl HirDatabase, node: Source<&SyntaxNode>) -> Option { @@ -198,14 +198,18 @@ impl SourceAnalyzer { self.body_source_map.as_ref()?.node_pat(src) } - pub fn type_of(&self, _db: &impl HirDatabase, expr: &ast::Expr) -> Option { + pub fn type_of(&self, db: &impl HirDatabase, expr: &ast::Expr) -> Option { let expr_id = self.expr_id(expr)?; - Some(self.infer.as_ref()?[expr_id].clone()) + let ty = self.infer.as_ref()?[expr_id].clone(); + let environment = TraitEnvironment::lower(db, &self.resolver); + Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) } - pub fn type_of_pat(&self, _db: &impl HirDatabase, pat: &ast::Pat) -> Option { + pub fn type_of_pat(&self, db: &impl HirDatabase, pat: &ast::Pat) -> Option { let pat_id = self.pat_id(pat)?; - Some(self.infer.as_ref()?[pat_id].clone()) + let ty = self.infer.as_ref()?[pat_id].clone(); + let environment = TraitEnvironment::lower(db, &self.resolver); + Some(Type { krate: self.resolver.krate()?, ty: InEnvironment { value: ty, environment } }) } pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { @@ -361,14 +365,14 @@ impl SourceAnalyzer { pub fn iterate_method_candidates( &self, db: &impl HirDatabase, - ty: Ty, + ty: &Type, name: Option<&Name>, mut callback: impl FnMut(&Ty, Function) -> Option, ) -> Option { // There should be no inference vars in types passed here // FIXME check that? // FIXME replace Unknown by bound vars here - let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; + let canonical = crate::ty::Canonical { value: ty.ty.value.clone(), num_vars: 0 }; method_resolution::iterate_method_candidates( &canonical, db, @@ -403,19 +407,19 @@ impl SourceAnalyzer { ) } - pub fn autoderef<'a>( - &'a self, - db: &'a impl HirDatabase, - ty: Ty, - ) -> impl Iterator + 'a { - // There should be no inference vars in types passed here - // FIXME check that? - let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; - let krate = self.resolver.krate(); - let environment = TraitEnvironment::lower(db, &self.resolver); - let ty = crate::ty::InEnvironment { value: canonical, environment }; - crate::ty::autoderef(db, krate, ty).map(|canonical| canonical.value) - } + // pub fn autoderef<'a>( + // &'a self, + // db: &'a impl HirDatabase, + // ty: Ty, + // ) -> impl Iterator + 'a { + // // There should be no inference vars in types passed here + // // FIXME check that? + // let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; + // let krate = self.resolver.krate(); + // let environment = TraitEnvironment::lower(db, &self.resolver); + // let ty = crate::ty::InEnvironment { value: canonical, environment }; + // crate::ty::autoderef(db, krate, ty).map(|canonical| canonical.value) + // } /// Checks that particular type `ty` implements `std::future::Future`. /// This function is used in `.await` syntax completion. diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index a88a78b38..274dd1467 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -489,6 +489,16 @@ impl HasModule for AdtId { } } +impl HasModule for DefWithBodyId { + fn module(&self, db: &impl db::DefDatabase) -> ModuleId { + match self { + DefWithBodyId::FunctionId(it) => it.lookup(db).module(db), + DefWithBodyId::StaticId(it) => it.lookup(db).module(db), + DefWithBodyId::ConstId(it) => it.lookup(db).module(db), + } + } +} + impl HasModule for StaticLoc { fn module(&self, _db: &impl db::DefDatabase) -> ModuleId { self.container diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs index 9beceb29c..d0283e410 100644 --- a/crates/ra_ide_api/src/call_info.rs +++ b/crates/ra_ide_api/src/call_info.rs @@ -26,8 +26,8 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { - //FIXME: don't poke into Ty - let (callable_def, _subst) = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; + //FIXME: Type::as_callable is broken + let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; match callable_def { hir::CallableDef::FunctionId(it) => { let fn_def = it.into(); diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs index 5a3f9b5f6..b6fe48627 100644 --- a/crates/ra_ide_api/src/completion/complete_dot.rs +++ b/crates/ra_ide_api/src/completion/complete_dot.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use hir::{Adt, Ty, TypeCtor}; +use hir::Type; use crate::completion::completion_item::CompletionKind; use crate::{ @@ -22,12 +22,12 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { }; if !ctx.is_call { - complete_fields(acc, ctx, receiver_ty.clone()); + complete_fields(acc, ctx, &receiver_ty); } - complete_methods(acc, ctx, receiver_ty.clone()); + complete_methods(acc, ctx, &receiver_ty); // Suggest .await syntax for types that implement Future trait - if ctx.analyzer.impls_future(ctx.db, receiver_ty) { + if ctx.analyzer.impls_future(ctx.db, receiver_ty.into_ty()) { CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") .detail("expr.await") .insert_text("await") @@ -35,28 +35,18 @@ pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { } } -fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) { - for receiver in ctx.analyzer.autoderef(ctx.db, receiver) { - if let Ty::Apply(a_ty) = receiver { - match a_ty.ctor { - TypeCtor::Adt(Adt::Struct(s)) => { - for field in s.fields(ctx.db) { - acc.add_field(ctx, field, &a_ty.parameters); - } - } - // FIXME unions - TypeCtor::Tuple { .. } => { - for (i, ty) in a_ty.parameters.iter().enumerate() { - acc.add_tuple_field(ctx, i, ty); - } - } - _ => {} - } - }; +fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { + for receiver in receiver.autoderef(ctx.db) { + for (field, ty) in receiver.fields(ctx.db) { + acc.add_field(ctx, field, &ty); + } + for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { + acc.add_tuple_field(ctx, i, &ty); + } } } -fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: Ty) { +fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { let mut seen_methods = FxHashSet::default(); ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| { if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) { diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs index 17b75cf7e..646a30c76 100644 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ b/crates/ra_ide_api/src/completion/complete_postfix.rs @@ -1,6 +1,5 @@ //! FIXME: write short doc here -use hir::{Ty, TypeCtor}; use ra_syntax::{ast::AstNode, TextRange, TextUnit}; use ra_text_edit::TextEdit; @@ -30,9 +29,12 @@ pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { dot_receiver.syntax().text().to_string() }; - let receiver_ty = ctx.analyzer.type_of(ctx.db, &dot_receiver); + let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { + Some(it) => it, + None => return, + }; - if is_bool_or_unknown(receiver_ty) { + if receiver_ty.is_bool() || receiver_ty.is_unknown() { postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text)) .add_to(acc); postfix_snippet( @@ -75,14 +77,6 @@ fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: .snippet_edit(edit) } -fn is_bool_or_unknown(ty: Option) -> bool { - match &ty { - Some(Ty::Apply(app)) if app.ctor == TypeCtor::Bool => true, - Some(Ty::Unknown) | None => true, - Some(_) => false, - } -} - #[cfg(test)] mod tests { use insta::assert_debug_snapshot; diff --git a/crates/ra_ide_api/src/completion/complete_record_literal.rs b/crates/ra_ide_api/src/completion/complete_record_literal.rs index 45a4a9738..577c394d2 100644 --- a/crates/ra_ide_api/src/completion/complete_record_literal.rs +++ b/crates/ra_ide_api/src/completion/complete_record_literal.rs @@ -1,7 +1,5 @@ //! FIXME: write short doc here -use hir::Substs; - use crate::completion::{CompletionContext, Completions}; /// Complete fields in fields literals. @@ -15,10 +13,9 @@ pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionCon Some(it) => it, _ => return, }; - let substs = &ty.substs().unwrap_or_else(Substs::empty); - for field in variant.fields(ctx.db) { - acc.add_field(ctx, field, substs); + for (field, field_ty) in ty.variant_fields(ctx.db, variant) { + acc.add_field(ctx, field, &field_ty); } } diff --git a/crates/ra_ide_api/src/completion/complete_record_pattern.rs b/crates/ra_ide_api/src/completion/complete_record_pattern.rs index aa0fd6d24..a56c7e3a1 100644 --- a/crates/ra_ide_api/src/completion/complete_record_pattern.rs +++ b/crates/ra_ide_api/src/completion/complete_record_pattern.rs @@ -1,7 +1,5 @@ //! FIXME: write short doc here -use hir::Substs; - use crate::completion::{CompletionContext, Completions}; pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { @@ -14,10 +12,9 @@ pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionCon Some(it) => it, _ => return, }; - let substs = &ty.substs().unwrap_or_else(Substs::empty); - for field in variant.fields(ctx.db) { - acc.add_field(ctx, field, substs); + for (field, field_ty) in ty.variant_fields(ctx.db, variant) { + acc.add_field(ctx, field, &field_ty); } } diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs index 85b053a6e..5f056730a 100644 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ b/crates/ra_ide_api/src/completion/presentation.rs @@ -1,12 +1,12 @@ //! This modules takes care of rendering various definitions as completion items. -use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, Ty, TypeWalk}; +use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, Type}; use join_to_string::join; use ra_syntax::ast::NameOwner; use test_utils::tested_by; use crate::completion::{ - db, CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, }; use crate::display::{const_label, function_label, macro_label, type_label}; @@ -16,7 +16,7 @@ impl Completions { &mut self, ctx: &CompletionContext, field: hir::StructField, - substs: &hir::Substs, + ty: &Type, ) { let is_deprecated = is_deprecated(field, ctx.db); CompletionItem::new( @@ -25,13 +25,13 @@ impl Completions { field.name(ctx.db).to_string(), ) .kind(CompletionItemKind::Field) - .detail(field.ty(ctx.db).subst(substs).display(ctx.db).to_string()) + .detail(ty.display(ctx.db).to_string()) .set_documentation(field.docs(ctx.db)) .set_deprecated(is_deprecated) .add_to(self); } - pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &hir::Ty) { + pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) .kind(CompletionItemKind::Field) .detail(ty.display(ctx.db).to_string()) @@ -98,7 +98,7 @@ impl Completions { CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); if let ScopeDef::Local(local) = resolution { let ty = local.ty(ctx.db); - if ty != Ty::Unknown { + if !ty.is_unknown() { completion_item = completion_item.detail(ty.display(ctx.db).to_string()); } }; @@ -108,19 +108,17 @@ impl Completions { && !ctx.has_type_args && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") { - let generic_def: Option = match resolution { - ScopeDef::ModuleDef(Adt(it)) => Some((*it).into()), - ScopeDef::ModuleDef(TypeAlias(it)) => Some((*it).into()), - _ => None, + let has_non_default_type_params = match resolution { + ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), + ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), + _ => false, }; - if let Some(def) = generic_def { - if has_non_default_type_params(def, ctx.db) { - tested_by!(inserts_angle_brackets_for_generics); - completion_item = completion_item - .lookup_by(local_name.clone()) - .label(format!("{}<…>", local_name)) - .insert_snippet(format!("{}<$0>", local_name)); - } + if has_non_default_type_params { + tested_by!(inserts_angle_brackets_for_generics); + completion_item = completion_item + .lookup_by(local_name.clone()) + .label(format!("{}<…>", local_name)) + .insert_snippet(format!("{}<$0>", local_name)); } } @@ -291,11 +289,6 @@ fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { node.attrs(db).by_key("deprecated").exists() } -fn has_non_default_type_params(def: hir::GenericDef, db: &db::RootDatabase) -> bool { - let subst = db.generic_defaults(def.into()); - subst.iter().any(|ty| ty == &Ty::Unknown) -} - #[cfg(test)] mod tests { use insta::assert_debug_snapshot; diff --git a/crates/ra_ide_api/src/goto_type_definition.rs b/crates/ra_ide_api/src/goto_type_definition.rs index 28a83a3e2..992a08809 100644 --- a/crates/ra_ide_api/src/goto_type_definition.rs +++ b/crates/ra_ide_api/src/goto_type_definition.rs @@ -24,7 +24,7 @@ pub(crate) fn goto_type_definition( let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None); - let ty: hir::Ty = if let Some(ty) = + let ty: hir::Type = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) { ty @@ -35,7 +35,7 @@ pub(crate) fn goto_type_definition( return None; }; - let adt_def = analyzer.autoderef(db, ty).find_map(|ty| ty.as_adt().map(|adt| adt.0))?; + let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?; let nav = adt_def.to_nav(db); Some(RangeInfo::new(node.text_range(), vec![nav])) diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs index 24a7ca5e7..45149bf0c 100644 --- a/crates/ra_ide_api/src/inlay_hints.rs +++ b/crates/ra_ide_api/src/inlay_hints.rs @@ -1,7 +1,7 @@ //! FIXME: write short doc here use crate::{db::RootDatabase, FileId}; -use hir::{HirDisplay, SourceAnalyzer, Ty}; +use hir::{HirDisplay, SourceAnalyzer}; use ra_syntax::{ ast::{self, AstNode, TypeAscriptionOwner}, match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, @@ -100,8 +100,11 @@ fn get_pat_type_hints( .into_iter() .filter(|pat| !skip_root_pat_hint || pat != original_pat) .filter_map(|pat| { - get_node_displayable_type(db, &analyzer, &pat) - .map(|pat_type| (pat.syntax().text_range(), pat_type)) + let ty = analyzer.type_of_pat(db, &pat)?; + if ty.is_unknown() { + return None; + } + Some((pat.syntax().text_range(), ty)) }) .map(|(range, pat_type)| InlayHint { range, @@ -158,20 +161,6 @@ fn get_leaf_pats(root_pat: ast::Pat) -> Vec { leaf_pats } -fn get_node_displayable_type( - db: &RootDatabase, - analyzer: &SourceAnalyzer, - node_pat: &ast::Pat, -) -> Option { - analyzer.type_of_pat(db, node_pat).and_then(|resolved_type| { - if let Ty::Apply(_) = resolved_type { - Some(resolved_type) - } else { - None - } - }) -} - #[cfg(test)] mod tests { use crate::mock_analysis::single_file; diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs index 2b653fe8f..10165a9bb 100644 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ b/crates/ra_ide_api/src/syntax_highlighting.rs @@ -2,7 +2,7 @@ use rustc_hash::{FxHashMap, FxHashSet}; -use hir::{Mutability, Name, Source}; +use hir::{Name, Source}; use ra_db::SourceDatabase; use ra_prof::profile; use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T}; @@ -230,11 +230,10 @@ fn highlight_name(db: &RootDatabase, name_kind: NameKind) -> &'static str { Local(local) => { if local.is_mut(db) { "variable.mut" + } else if local.ty(db).is_mutable_reference() { + "variable.mut" } else { - match local.ty(db).as_reference() { - Some((_, Mutability::Mut)) => "variable.mut", - _ => "variable", - } + "variable" } } } -- cgit v1.2.3 From a443b5033c2e95ee58bf086f7093ddc610d4f78f Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 14:29:12 +0300 Subject: Id-ify Ty::Adt --- crates/ra_hir/src/code_model.rs | 54 +++++++++++++++++----- crates/ra_hir/src/expr.rs | 4 +- crates/ra_hir/src/from_id.rs | 17 +++++++ crates/ra_hir/src/ty.rs | 12 ++--- crates/ra_hir/src/ty/infer.rs | 4 +- crates/ra_hir/src/ty/infer/coerce.rs | 12 ++--- crates/ra_hir/src/ty/infer/expr.rs | 21 +++++---- crates/ra_hir/src/ty/lower.rs | 2 +- crates/ra_hir/src/ty/method_resolution.rs | 6 ++- crates/ra_hir_def/src/adt.rs | 4 ++ .../ra_ide_api/src/references/name_definition.rs | 1 + 11 files changed, 98 insertions(+), 39 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index a7bba85e1..bb1596bed 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -11,9 +11,9 @@ use hir_def::{ per_ns::PerNs, resolver::{HasResolver, TypeNs}, type_ref::{Mutability, TypeRef}, - AstItemDef, ConstId, ContainerId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, - ImplId, LocalEnumVariantId, LocalImportId, LocalModuleId, LocalStructFieldId, Lookup, ModuleId, - StaticId, StructId, TraitId, TypeAliasId, UnionId, + AdtId, AstItemDef, ConstId, ContainerId, DefWithBodyId, EnumId, FunctionId, GenericDefId, + HasModule, ImplId, LocalEnumVariantId, LocalImportId, LocalModuleId, LocalStructFieldId, + Lookup, ModuleId, StaticId, StructId, TraitId, TypeAliasId, UnionId, }; use hir_expand::{ diagnostics::DiagnosticSink, @@ -383,6 +383,28 @@ impl Union { pub fn ty(self, db: &impl HirDatabase) -> Ty { db.type_for_def(self.into(), Namespace::Types) } + + pub fn fields(self, db: &impl HirDatabase) -> Vec { + db.union_data(self.id) + .variant_data + .fields() + .iter() + .map(|(id, _)| StructField { parent: self.into(), id }) + .collect() + } + + pub fn field(self, db: &impl HirDatabase, name: &Name) -> Option { + db.union_data(self.id) + .variant_data + .fields() + .iter() + .find(|(_id, data)| data.name == *name) + .map(|(id, _)| StructField { parent: self.into(), id }) + } + + fn variant_data(self, db: &impl DefDatabase) -> Arc { + db.union_data(self.id).variant_data.clone() + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] @@ -501,14 +523,16 @@ impl Adt { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum VariantDef { Struct(Struct), + Union(Union), EnumVariant(EnumVariant), } -impl_froms!(VariantDef: Struct, EnumVariant); +impl_froms!(VariantDef: Struct, Union, EnumVariant); impl VariantDef { pub fn fields(self, db: &impl HirDatabase) -> Vec { match self { VariantDef::Struct(it) => it.fields(db), + VariantDef::Union(it) => it.fields(db), VariantDef::EnumVariant(it) => it.fields(db), } } @@ -516,6 +540,7 @@ impl VariantDef { pub(crate) fn field(self, db: &impl HirDatabase, name: &Name) -> Option { match self { VariantDef::Struct(it) => it.field(db, name), + VariantDef::Union(it) => it.field(db, name), VariantDef::EnumVariant(it) => it.field(db, name), } } @@ -523,6 +548,7 @@ impl VariantDef { pub fn module(self, db: &impl HirDatabase) -> Module { match self { VariantDef::Struct(it) => it.module(db), + VariantDef::Union(it) => it.module(db), VariantDef::EnumVariant(it) => it.module(db), } } @@ -530,6 +556,7 @@ impl VariantDef { pub(crate) fn variant_data(self, db: &impl DefDatabase) -> Arc { match self { VariantDef::Struct(it) => it.variant_data(db), + VariantDef::Union(it) => it.variant_data(db), VariantDef::EnumVariant(it) => it.variant_data(db), } } @@ -1056,19 +1083,24 @@ impl Type { } pub fn fields(&self, db: &impl HirDatabase) -> Vec<(StructField, Type)> { - let mut res = Vec::new(); if let Ty::Apply(a_ty) = &self.ty.value { match a_ty.ctor { - ty::TypeCtor::Adt(Adt::Struct(s)) => { - for field in s.fields(db) { - let ty = field.ty(db).subst(&a_ty.parameters); - res.push((field, self.derived(ty))); - } + ty::TypeCtor::Adt(AdtId::StructId(s)) => { + let var_def = s.into(); + return db + .field_types(var_def) + .iter() + .map(|(local_id, ty)| { + let def = StructField { parent: var_def.into(), id: local_id }; + let ty = ty.clone().subst(&a_ty.parameters); + (def, self.derived(ty)) + }) + .collect(); } _ => {} } }; - res + Vec::new() } pub fn tuple_fields(&self, _db: &impl HirDatabase) -> Vec { diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index 43fedde7a..adb9805ab 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use hir_def::{path::known, resolver::HasResolver}; +use hir_def::{path::known, resolver::HasResolver, AdtId}; use hir_expand::diagnostics::DiagnosticSink; use ra_syntax::ast; use ra_syntax::AstPtr; @@ -127,7 +127,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> { _ => return, }; - let std_result_ctor = TypeCtor::Adt(Adt::Enum(std_result_enum.into())); + let std_result_ctor = TypeCtor::Adt(AdtId::EnumId(std_result_enum)); let params = match &mismatch.expected { Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters, _ => return, diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs index 619f6055e..38daa5e59 100644 --- a/crates/ra_hir/src/from_id.rs +++ b/crates/ra_hir/src/from_id.rs @@ -199,11 +199,22 @@ impl From for GenericDefId { } } +impl From for VariantDef { + fn from(def: VariantId) -> Self { + match def { + VariantId::StructId(it) => VariantDef::Struct(it.into()), + VariantId::EnumVariantId(it) => VariantDef::EnumVariant(it.into()), + VariantId::UnionId(it) => VariantDef::Union(it.into()), + } + } +} + impl From for VariantId { fn from(def: VariantDef) -> Self { match def { VariantDef::Struct(it) => VariantId::StructId(it.id), VariantDef::EnumVariant(it) => VariantId::EnumVariantId(it.into()), + VariantDef::Union(it) => VariantId::UnionId(it.id), } } } @@ -214,6 +225,12 @@ impl From for StructFieldId { } } +impl From for StructField { + fn from(def: StructFieldId) -> Self { + StructField { parent: def.parent.into(), id: def.local_id } + } +} + impl From for AttrDefId { fn from(def: AttrDef) -> Self { match def { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 388530f31..bd03055b9 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -58,7 +58,7 @@ pub enum TypeCtor { Float(Uncertain), /// Structures, enumerations and unions. - Adt(Adt), + Adt(AdtId), /// The pointee of a string slice. Written as `str`. Str, @@ -174,7 +174,7 @@ impl TypeCtor { | TypeCtor::Tuple { .. } => None, // Closure's krate is irrelevant for coherence I would think? TypeCtor::Closure { .. } => None, - TypeCtor::Adt(adt) => adt.krate(db), + TypeCtor::Adt(adt) => Some(adt.module(db).krate.into()), TypeCtor::FnDef(callable) => Some(callable.krate(db).into()), TypeCtor::AssociatedType(type_alias) => { Some(type_alias.lookup(db).module(db).krate.into()) @@ -598,7 +598,7 @@ impl Ty { pub fn as_adt(&self) -> Option<(Adt, &Substs)> { match self { Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(adt_def), parameters }) => { - Some((*adt_def, parameters)) + Some(((*adt_def).into(), parameters)) } _ => None, } @@ -889,9 +889,9 @@ impl HirDisplay for ApplicationTy { } TypeCtor::Adt(def_id) => { let name = match def_id { - Adt::Struct(s) => s.name(f.db), - Adt::Union(u) => u.name(f.db), - Adt::Enum(e) => e.name(f.db), + AdtId::StructId(it) => f.db.struct_data(it).name.clone(), + AdtId::UnionId(it) => f.db.union_data(it).name.clone(), + AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), } .unwrap_or_else(Name::missing); write!(f, "{}", name)?; diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index 6fd00d457..fce45321d 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -598,10 +598,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { trait_.associated_type_by_name(self.db, &name::OUTPUT_TYPE) } - fn resolve_boxed_box(&self) -> Option { + fn resolve_boxed_box(&self) -> Option { let path = known::std_boxed_box(); let struct_ = self.resolver.resolve_known_struct(self.db, &path)?; - Some(Adt::Struct(struct_.into())) + Some(struct_.into()) } } diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs index bb9a2e427..5ed4470af 100644 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ b/crates/ra_hir/src/ty/infer/coerce.rs @@ -4,14 +4,14 @@ //! //! See: https://doc.rust-lang.org/nomicon/coercions.html -use hir_def::{lang_item::LangItemTarget, resolver::Resolver}; +use hir_def::{lang_item::LangItemTarget, resolver::Resolver, AdtId}; use rustc_hash::FxHashMap; use test_utils::tested_by; use crate::{ db::HirDatabase, ty::{autoderef, Substs, Ty, TypeCtor, TypeWalk}, - Adt, Mutability, + Mutability, }; use super::{InEnvironment, InferTy, InferenceContext, TypeVarValue}; @@ -242,11 +242,11 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { // - T is not part of the type of any other fields // - Bar: Unsize>, if the last field of Foo has type Bar ( - ty_app!(TypeCtor::Adt(Adt::Struct(struct1)), st1), - ty_app!(TypeCtor::Adt(Adt::Struct(struct2)), st2), + ty_app!(TypeCtor::Adt(AdtId::StructId(struct1)), st1), + ty_app!(TypeCtor::Adt(AdtId::StructId(struct2)), st2), ) if struct1 == struct2 => { - let field_tys = self.db.field_types(struct1.id.into()); - let struct_data = self.db.struct_data(struct1.id); + let field_tys = self.db.field_types((*struct1).into()); + let struct_data = self.db.struct_data(*struct1); let mut fields = struct_data.variant_data.fields().iter(); let (last_field_id, _data) = fields.next_back()?; diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 316cdc880..3d0895dc6 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -8,7 +8,7 @@ use hir_def::{ generics::GenericParams, path::{GenericArg, GenericArgs}, resolver::resolver_for_expr, - ContainerId, Lookup, + AdtId, ContainerId, Lookup, StructFieldId, }; use hir_expand::name; @@ -20,7 +20,7 @@ use crate::{ Mutability, Namespace, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, }, - Adt, Name, + Name, }; use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; @@ -259,14 +259,17 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { TypeCtor::Tuple { .. } => name .as_tuple_index() .and_then(|idx| a_ty.parameters.0.get(idx).cloned()), - TypeCtor::Adt(Adt::Struct(s)) => s.field(self.db, name).map(|field| { - self.write_field_resolution(tgt_expr, field); - self.db.field_types(s.id.into())[field.id] - .clone() - .subst(&a_ty.parameters) - }), + TypeCtor::Adt(AdtId::StructId(s)) => { + self.db.struct_data(s).variant_data.field(name).map(|local_id| { + let field = StructFieldId { parent: s.into(), local_id }.into(); + self.write_field_resolution(tgt_expr, field); + self.db.field_types(s.into())[field.id] + .clone() + .subst(&a_ty.parameters) + }) + } // FIXME: - TypeCtor::Adt(Adt::Union(_)) => None, + TypeCtor::Adt(AdtId::UnionId(_)) => None, _ => None, }, _ => None, diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index d7d4bb0d6..485871e69 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -762,7 +762,7 @@ fn type_for_adt(db: &impl HirDatabase, adt: impl Into) -> Ty { let adt = adt.into(); let adt_id: AdtId = adt.into(); let generics = db.generic_params(adt_id.into()); - Ty::apply(TypeCtor::Adt(adt), Substs::identity(&generics)) + Ty::apply(TypeCtor::Adt(adt_id), Substs::identity(&generics)) } fn type_for_type_alias(db: &impl HirDatabase, t: TypeAlias) -> Ty { diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index c5ab690eb..7f0ff2e8c 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use arrayvec::ArrayVec; -use hir_def::{lang_item::LangItemTarget, resolver::Resolver, AstItemDef}; +use hir_def::{lang_item::LangItemTarget, resolver::Resolver, AstItemDef, HasModule}; use rustc_hash::FxHashMap; use crate::{ @@ -102,7 +102,9 @@ fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option match a_ty.ctor { - TypeCtor::Adt(def_id) => return Some(std::iter::once(def_id.krate(db)?).collect()), + TypeCtor::Adt(def_id) => { + return Some(std::iter::once(def_id.module(db).krate.into()).collect()) + } TypeCtor::Bool => lang_item_crate!("bool"), TypeCtor::Char => lang_item_crate!("char"), TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index 0091bfbc3..0cf418d30 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs @@ -129,6 +129,10 @@ impl VariantData { } } + pub fn field(&self, name: &Name) -> Option { + self.fields().iter().find_map(|(id, data)| if &data.name == name { Some(id) } else { None }) + } + pub fn is_unit(&self) -> bool { match self { VariantData::Unit => true, diff --git a/crates/ra_ide_api/src/references/name_definition.rs b/crates/ra_ide_api/src/references/name_definition.rs index aca23f79e..cf12db066 100644 --- a/crates/ra_ide_api/src/references/name_definition.rs +++ b/crates/ra_ide_api/src/references/name_definition.rs @@ -46,6 +46,7 @@ pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDe let container = parent.module(db); let visibility = match parent { VariantDef::Struct(s) => s.source(db).value.visibility(), + VariantDef::Union(e) => e.source(db).value.visibility(), VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(), }; NameDefinition { kind, container, visibility } -- cgit v1.2.3 From 4c43631829d8bac8b7533c994d8cf1241a95ce70 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 14:35:23 +0300 Subject: Introduce hir_ty --- crates/ra_hir/Cargo.toml | 1 + crates/ra_hir/src/ty/lower.rs | 34 +------ crates/ra_hir/src/ty/primitive.rs | 159 +------------------------------ crates/ra_hir_ty/Cargo.toml | 32 +++++++ crates/ra_hir_ty/src/lib.rs | 3 + crates/ra_hir_ty/src/primitive.rs | 190 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 190 deletions(-) create mode 100644 crates/ra_hir_ty/Cargo.toml create mode 100644 crates/ra_hir_ty/src/lib.rs create mode 100644 crates/ra_hir_ty/src/primitive.rs (limited to 'crates') diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index f72574485..23c056e90 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -23,6 +23,7 @@ mbe = { path = "../ra_mbe", package = "ra_mbe" } tt = { path = "../ra_tt", package = "ra_tt" } hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } +hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } test_utils = { path = "../test_utils" } ra_prof = { path = "../ra_prof" } diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 485871e69..2d23890a5 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -9,7 +9,7 @@ use std::iter; use std::sync::Arc; use hir_def::{ - builtin_type::{BuiltinFloat, BuiltinInt, BuiltinType}, + builtin_type::BuiltinType, generics::WherePredicate, path::{GenericArg, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, @@ -27,7 +27,7 @@ use super::{ use crate::{ db::HirDatabase, ty::{ - primitive::{FloatTy, IntTy, Uncertain}, + primitive::{FloatTy, IntTy}, Adt, }, util::make_mut_slice, @@ -679,36 +679,6 @@ fn type_for_builtin(def: BuiltinType) -> Ty { }) } -impl From for IntTy { - fn from(t: BuiltinInt) -> Self { - IntTy { signedness: t.signedness, bitness: t.bitness } - } -} - -impl From for FloatTy { - fn from(t: BuiltinFloat) -> Self { - FloatTy { bitness: t.bitness } - } -} - -impl From> for Uncertain { - fn from(t: Option) -> Self { - match t { - None => Uncertain::Unknown, - Some(t) => Uncertain::Known(t.into()), - } - } -} - -impl From> for Uncertain { - fn from(t: Option) -> Self { - match t { - None => Uncertain::Unknown, - Some(t) => Uncertain::Known(t.into()), - } - } -} - fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> FnSig { let struct_data = db.struct_data(def.into()); let fields = struct_data.variant_data.fields(); diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs index 47789db87..eb7b5c4ef 100644 --- a/crates/ra_hir/src/ty/primitive.rs +++ b/crates/ra_hir/src/ty/primitive.rs @@ -1,160 +1,3 @@ //! FIXME: write short doc here -use std::fmt; - -pub use hir_def::builtin_type::{FloatBitness, IntBitness, Signedness}; - -#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub enum Uncertain { - Unknown, - Known(T), -} - -impl From for Uncertain { - fn from(ty: IntTy) -> Self { - Uncertain::Known(ty) - } -} - -impl fmt::Display for Uncertain { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Uncertain::Unknown => write!(f, "{{integer}}"), - Uncertain::Known(ty) => write!(f, "{}", ty), - } - } -} - -impl From for Uncertain { - fn from(ty: FloatTy) -> Self { - Uncertain::Known(ty) - } -} - -impl fmt::Display for Uncertain { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Uncertain::Unknown => write!(f, "{{float}}"), - Uncertain::Known(ty) => write!(f, "{}", ty), - } - } -} - -#[derive(Copy, Clone, Eq, PartialEq, Hash)] -pub struct IntTy { - pub signedness: Signedness, - pub bitness: IntBitness, -} - -impl fmt::Debug for IntTy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl fmt::Display for IntTy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.ty_to_string()) - } -} - -impl IntTy { - pub fn isize() -> IntTy { - IntTy { signedness: Signedness::Signed, bitness: IntBitness::Xsize } - } - - pub fn i8() -> IntTy { - IntTy { signedness: Signedness::Signed, bitness: IntBitness::X8 } - } - - pub fn i16() -> IntTy { - IntTy { signedness: Signedness::Signed, bitness: IntBitness::X16 } - } - - pub fn i32() -> IntTy { - IntTy { signedness: Signedness::Signed, bitness: IntBitness::X32 } - } - - pub fn i64() -> IntTy { - IntTy { signedness: Signedness::Signed, bitness: IntBitness::X64 } - } - - pub fn i128() -> IntTy { - IntTy { signedness: Signedness::Signed, bitness: IntBitness::X128 } - } - - pub fn usize() -> IntTy { - IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::Xsize } - } - - pub fn u8() -> IntTy { - IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X8 } - } - - pub fn u16() -> IntTy { - IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X16 } - } - - pub fn u32() -> IntTy { - IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X32 } - } - - pub fn u64() -> IntTy { - IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X64 } - } - - pub fn u128() -> IntTy { - IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X128 } - } - - pub(crate) fn ty_to_string(self) -> &'static str { - match (self.signedness, self.bitness) { - (Signedness::Signed, IntBitness::Xsize) => "isize", - (Signedness::Signed, IntBitness::X8) => "i8", - (Signedness::Signed, IntBitness::X16) => "i16", - (Signedness::Signed, IntBitness::X32) => "i32", - (Signedness::Signed, IntBitness::X64) => "i64", - (Signedness::Signed, IntBitness::X128) => "i128", - (Signedness::Unsigned, IntBitness::Xsize) => "usize", - (Signedness::Unsigned, IntBitness::X8) => "u8", - (Signedness::Unsigned, IntBitness::X16) => "u16", - (Signedness::Unsigned, IntBitness::X32) => "u32", - (Signedness::Unsigned, IntBitness::X64) => "u64", - (Signedness::Unsigned, IntBitness::X128) => "u128", - } - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash)] -pub struct FloatTy { - pub bitness: FloatBitness, -} - -impl fmt::Debug for FloatTy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(self, f) - } -} - -impl fmt::Display for FloatTy { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.ty_to_string()) - } -} - -impl FloatTy { - pub fn f32() -> FloatTy { - FloatTy { bitness: FloatBitness::X32 } - } - - pub fn f64() -> FloatTy { - FloatTy { bitness: FloatBitness::X64 } - } - - pub(crate) fn ty_to_string(self) -> &'static str { - match self.bitness { - FloatBitness::X32 => "f32", - FloatBitness::X64 => "f64", - } - } -} +pub use hir_ty::primitive::{FloatBitness, IntBitness, Signedness, FloatTy, IntTy, Uncertain}; diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml new file mode 100644 index 000000000..70216ab24 --- /dev/null +++ b/crates/ra_hir_ty/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2018" +name = "ra_hir_ty" +version = "0.1.0" +authors = ["rust-analyzer developers"] + +[lib] +doctest = false + +[dependencies] +log = "0.4.5" +rustc-hash = "1.0" +parking_lot = "0.9.0" +ena = "0.13" + +ra_syntax = { path = "../ra_syntax" } +ra_arena = { path = "../ra_arena" } +ra_db = { path = "../ra_db" } +hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } +hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } +test_utils = { path = "../test_utils" } +ra_prof = { path = "../ra_prof" } + +# https://github.com/rust-lang/chalk/pull/294 +chalk-solve = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } +chalk-rust-ir = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } +chalk-ir = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } + +lalrpop-intern = "0.15.1" + +[dev-dependencies] +insta = "0.12.0" diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs new file mode 100644 index 000000000..25bfc1d15 --- /dev/null +++ b/crates/ra_hir_ty/src/lib.rs @@ -0,0 +1,3 @@ +//! FIXME: write short doc here + +pub mod primitive; diff --git a/crates/ra_hir_ty/src/primitive.rs b/crates/ra_hir_ty/src/primitive.rs new file mode 100644 index 000000000..afa22448d --- /dev/null +++ b/crates/ra_hir_ty/src/primitive.rs @@ -0,0 +1,190 @@ +//! FIXME: write short doc here + +use std::fmt; + +pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness}; + +#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] +pub enum Uncertain { + Unknown, + Known(T), +} + +impl From for Uncertain { + fn from(ty: IntTy) -> Self { + Uncertain::Known(ty) + } +} + +impl fmt::Display for Uncertain { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Uncertain::Unknown => write!(f, "{{integer}}"), + Uncertain::Known(ty) => write!(f, "{}", ty), + } + } +} + +impl From for Uncertain { + fn from(ty: FloatTy) -> Self { + Uncertain::Known(ty) + } +} + +impl fmt::Display for Uncertain { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Uncertain::Unknown => write!(f, "{{float}}"), + Uncertain::Known(ty) => write!(f, "{}", ty), + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash)] +pub struct IntTy { + pub signedness: Signedness, + pub bitness: IntBitness, +} + +impl fmt::Debug for IntTy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for IntTy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.ty_to_string()) + } +} + +impl IntTy { + pub fn isize() -> IntTy { + IntTy { signedness: Signedness::Signed, bitness: IntBitness::Xsize } + } + + pub fn i8() -> IntTy { + IntTy { signedness: Signedness::Signed, bitness: IntBitness::X8 } + } + + pub fn i16() -> IntTy { + IntTy { signedness: Signedness::Signed, bitness: IntBitness::X16 } + } + + pub fn i32() -> IntTy { + IntTy { signedness: Signedness::Signed, bitness: IntBitness::X32 } + } + + pub fn i64() -> IntTy { + IntTy { signedness: Signedness::Signed, bitness: IntBitness::X64 } + } + + pub fn i128() -> IntTy { + IntTy { signedness: Signedness::Signed, bitness: IntBitness::X128 } + } + + pub fn usize() -> IntTy { + IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::Xsize } + } + + pub fn u8() -> IntTy { + IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X8 } + } + + pub fn u16() -> IntTy { + IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X16 } + } + + pub fn u32() -> IntTy { + IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X32 } + } + + pub fn u64() -> IntTy { + IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X64 } + } + + pub fn u128() -> IntTy { + IntTy { signedness: Signedness::Unsigned, bitness: IntBitness::X128 } + } + + pub fn ty_to_string(self) -> &'static str { + match (self.signedness, self.bitness) { + (Signedness::Signed, IntBitness::Xsize) => "isize", + (Signedness::Signed, IntBitness::X8) => "i8", + (Signedness::Signed, IntBitness::X16) => "i16", + (Signedness::Signed, IntBitness::X32) => "i32", + (Signedness::Signed, IntBitness::X64) => "i64", + (Signedness::Signed, IntBitness::X128) => "i128", + (Signedness::Unsigned, IntBitness::Xsize) => "usize", + (Signedness::Unsigned, IntBitness::X8) => "u8", + (Signedness::Unsigned, IntBitness::X16) => "u16", + (Signedness::Unsigned, IntBitness::X32) => "u32", + (Signedness::Unsigned, IntBitness::X64) => "u64", + (Signedness::Unsigned, IntBitness::X128) => "u128", + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct FloatTy { + pub bitness: FloatBitness, +} + +impl fmt::Debug for FloatTy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +impl fmt::Display for FloatTy { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.ty_to_string()) + } +} + +impl FloatTy { + pub fn f32() -> FloatTy { + FloatTy { bitness: FloatBitness::X32 } + } + + pub fn f64() -> FloatTy { + FloatTy { bitness: FloatBitness::X64 } + } + + pub fn ty_to_string(self) -> &'static str { + match self.bitness { + FloatBitness::X32 => "f32", + FloatBitness::X64 => "f64", + } + } +} + +impl From for IntTy { + fn from(t: BuiltinInt) -> Self { + IntTy { signedness: t.signedness, bitness: t.bitness } + } +} + +impl From for FloatTy { + fn from(t: BuiltinFloat) -> Self { + FloatTy { bitness: t.bitness } + } +} + +impl From> for Uncertain { + fn from(t: Option) -> Self { + match t { + None => Uncertain::Unknown, + Some(t) => Uncertain::Known(t.into()), + } + } +} + +impl From> for Uncertain { + fn from(t: Option) -> Self { + match t { + None => Uncertain::Unknown, + Some(t) => Uncertain::Known(t.into()), + } + } +} -- cgit v1.2.3 From 8b1f2cd14eded9c2e37f6969a755541f843552b5 Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Tue, 26 Nov 2019 07:29:28 -0500 Subject: Update parking_lot and smallvec to drop some dependencies --- crates/ra_hir/Cargo.toml | 2 +- crates/ra_lsp_server/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index f72574485..16bd4bafc 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -11,7 +11,7 @@ doctest = false arrayvec = "0.5.1" log = "0.4.5" rustc-hash = "1.0" -parking_lot = "0.9.0" +parking_lot = "0.10.0" ena = "0.13" once_cell = "1.0.1" diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 72dbe06dc..58b9cfaa0 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -17,7 +17,7 @@ flexi_logger = "0.14.0" log = "0.4.3" lsp-types = { version = "0.61.0", features = ["proposed"] } rustc-hash = "1.0" -parking_lot = "0.9.0" +parking_lot = "0.10.0" jod-thread = "0.1.0" ra_vfs = "0.5.0" ra_syntax = { path = "../ra_syntax" } -- cgit v1.2.3 From 45d05ed78384b28fbd341068f36912a2e0308292 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 15:27:33 +0300 Subject: id-ify impls_in_crate_query --- crates/ra_hir/src/code_model.rs | 9 +++ crates/ra_hir/src/db.rs | 4 +- crates/ra_hir/src/ty/method_resolution.rs | 101 ++++++++++++++++-------------- crates/ra_hir/src/ty/primitive.rs | 2 +- crates/ra_hir/src/ty/traits.rs | 4 +- crates/ra_ide_api/src/impls.rs | 10 +-- 6 files changed, 74 insertions(+), 56 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index bb1596bed..821f919d4 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -997,6 +997,15 @@ pub struct ImplBlock { } impl ImplBlock { + pub fn all_in_crate(db: &impl HirDatabase, krate: Crate) -> Vec { + let impls = db.impls_in_crate(krate.crate_id); + impls.all_impls().map(Self::from).collect() + } + pub fn for_trait(db: &impl HirDatabase, krate: Crate, trait_: Trait) -> Vec { + let impls = db.impls_in_crate(krate.crate_id); + impls.lookup_impl_blocks_for_trait(trait_).map(Self::from).collect() + } + pub fn target_trait(&self, db: &impl DefDatabase) -> Option { db.impl_data(self.id).target_trait.clone() } diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index b034d4e44..32f05a4d8 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use ra_arena::map::ArenaMap; -use ra_db::salsa; +use ra_db::{salsa, CrateId}; use crate::{ ty::{ @@ -60,7 +60,7 @@ pub trait HirDatabase: DefDatabase { fn generic_defaults(&self, def: GenericDefId) -> Substs; #[salsa::invoke(crate::ty::method_resolution::CrateImplBlocks::impls_in_crate_query)] - fn impls_in_crate(&self, krate: Crate) -> Arc; + fn impls_in_crate(&self, krate: CrateId) -> Arc; #[salsa::invoke(crate::ty::traits::impls_for_trait_query)] fn impls_for_trait(&self, krate: Crate, trait_: Trait) -> Arc<[ImplBlock]>; diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 7f0ff2e8c..489fcd64b 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -5,14 +5,19 @@ use std::sync::Arc; use arrayvec::ArrayVec; -use hir_def::{lang_item::LangItemTarget, resolver::Resolver, AstItemDef, HasModule}; +use hir_def::{ + lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, AssocItemId, AstItemDef, + HasModule, ImplId, TraitId, +}; +use ra_db::CrateId; +use ra_prof::profile; use rustc_hash::FxHashMap; use crate::{ db::HirDatabase, ty::primitive::{FloatBitness, Uncertain}, ty::{Ty, TypeCtor}, - AssocItem, Crate, Function, ImplBlock, Module, Mutability, Name, Trait, + AssocItem, Crate, Function, Mutability, Name, Trait, }; use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; @@ -37,54 +42,58 @@ impl TyFingerprint { #[derive(Debug, PartialEq, Eq)] pub struct CrateImplBlocks { - impls: FxHashMap>, - impls_by_trait: FxHashMap>, + impls: FxHashMap>, + impls_by_trait: FxHashMap>, } impl CrateImplBlocks { pub(crate) fn impls_in_crate_query( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, ) -> Arc { - let mut crate_impl_blocks = + let _p = profile("impls_in_crate_query"); + let mut res = CrateImplBlocks { impls: FxHashMap::default(), impls_by_trait: FxHashMap::default() }; - if let Some(module) = krate.root_module(db) { - crate_impl_blocks.collect_recursive(db, module); + + let crate_def_map = db.crate_def_map(krate); + for (_module_id, module_data) in crate_def_map.modules.iter() { + for &impl_id in module_data.impls.iter() { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + + let target_ty = { Ty::from_hir(db, &resolver, &impl_data.target_type) }; + + match &impl_data.target_trait { + Some(trait_ref) => { + if let Some(tr) = + TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty)) + { + res.impls_by_trait.entry(tr.trait_.id).or_default().push(impl_id); + } + } + None => { + if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { + res.impls.entry(target_ty_fp).or_default().push(impl_id); + } + } + } + } } - Arc::new(crate_impl_blocks) + + Arc::new(res) } - pub fn lookup_impl_blocks(&self, ty: &Ty) -> impl Iterator + '_ { + pub fn lookup_impl_blocks(&self, ty: &Ty) -> impl Iterator + '_ { let fingerprint = TyFingerprint::for_impl(ty); fingerprint.and_then(|f| self.impls.get(&f)).into_iter().flatten().copied() } - pub fn lookup_impl_blocks_for_trait(&self, tr: Trait) -> impl Iterator + '_ { - self.impls_by_trait.get(&tr).into_iter().flatten().copied() + pub fn lookup_impl_blocks_for_trait(&self, tr: Trait) -> impl Iterator + '_ { + self.impls_by_trait.get(&tr.id).into_iter().flatten().copied() } - pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { + pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { self.impls.values().chain(self.impls_by_trait.values()).flatten().copied() } - - fn collect_recursive(&mut self, db: &impl HirDatabase, module: Module) { - for impl_block in module.impl_blocks(db) { - let target_ty = impl_block.target_ty(db); - - if impl_block.target_trait(db).is_some() { - if let Some(tr) = impl_block.target_trait_ref(db) { - self.impls_by_trait.entry(tr.trait_).or_default().push(impl_block); - } - } else { - if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { - self.impls.entry(target_ty_fp).or_default().push(impl_block); - } - } - } - - for child in module.children(db) { - self.collect_recursive(db, child); - } - } } fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option> { @@ -279,14 +288,14 @@ fn iterate_inherent_methods( mut callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { for krate in def_crates(db, krate, &ty.value)? { - let impls = db.impls_in_crate(krate); + let impls = db.impls_in_crate(krate.crate_id); for impl_block in impls.lookup_impl_blocks(&ty.value) { - for item in impl_block.items(db) { + for &item in db.impl_data(impl_block).items.iter() { if !is_valid_candidate(db, name, mode, item) { continue; } - if let Some(result) = callback(&ty.value, item) { + if let Some(result) = callback(&ty.value, item.into()) { return Some(result); } } @@ -299,17 +308,17 @@ fn is_valid_candidate( db: &impl HirDatabase, name: Option<&Name>, mode: LookupMode, - item: AssocItem, + item: AssocItemId, ) -> bool { match item { - AssocItem::Function(m) => { - let data = db.function_data(m.id); - name.map_or(true, |name| data.name == *name) + AssocItemId::FunctionId(m) => { + let data = db.function_data(m); + name.map_or(true, |name| &data.name == name) && (data.has_self_param || mode == LookupMode::Path) } - AssocItem::Const(c) => { - name.map_or(true, |name| Some(name) == c.name(db).as_ref()) - && (mode == LookupMode::Path) + AssocItemId::ConstId(c) => { + let data = db.const_data(c); + name.map_or(true, |name| data.name.as_ref() == Some(name)) && (mode == LookupMode::Path) } _ => false, } @@ -344,11 +353,11 @@ impl Ty { mut callback: impl FnMut(AssocItem) -> Option, ) -> Option { for krate in def_crates(db, krate, &self)? { - let impls = db.impls_in_crate(krate); + let impls = db.impls_in_crate(krate.crate_id); for impl_block in impls.lookup_impl_blocks(&self) { - for item in impl_block.items(db) { - if let Some(result) = callback(item) { + for &item in db.impl_data(impl_block).items.iter() { + if let Some(result) = callback(item.into()) { return Some(result); } } diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs index eb7b5c4ef..12dc96572 100644 --- a/crates/ra_hir/src/ty/primitive.rs +++ b/crates/ra_hir/src/ty/primitive.rs @@ -1,3 +1,3 @@ //! FIXME: write short doc here -pub use hir_ty::primitive::{FloatBitness, IntBitness, Signedness, FloatTy, IntTy, Uncertain}; +pub use hir_ty::primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain}; diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index b9a5d651f..2eeb03099 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -88,8 +88,8 @@ pub(crate) fn impls_for_trait_query( for dep in krate.dependencies(db) { impls.extend(db.impls_for_trait(dep.krate, trait_).iter()); } - let crate_impl_blocks = db.impls_in_crate(krate); - impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_)); + let crate_impl_blocks = db.impls_in_crate(krate.crate_id); + impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_).map(ImplBlock::from)); impls.into_iter().collect() } diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs index 2b3100fc3..b3ebd9145 100644 --- a/crates/ra_ide_api/src/impls.rs +++ b/crates/ra_ide_api/src/impls.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use hir::{db::HirDatabase, ApplicationTy, FromSource, Ty, TypeCtor}; +use hir::{ApplicationTy, FromSource, ImplBlock, Ty, TypeCtor}; use ra_db::SourceDatabase; use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; @@ -56,11 +56,11 @@ fn impls_for_def( }; let krate = module.krate(); - let impls = db.impls_in_crate(krate); + let impls = ImplBlock::all_in_crate(db, krate); Some( impls - .all_impls() + .into_iter() .filter(|impl_block| is_equal_for_find_impls(&ty, &impl_block.target_ty(db))) .map(|imp| imp.to_nav(db)) .collect(), @@ -77,9 +77,9 @@ fn impls_for_trait( let tr = hir::Trait::from_source(db, src)?; let krate = module.krate(); - let impls = db.impls_in_crate(krate); + let impls = ImplBlock::for_trait(db, krate, tr); - Some(impls.lookup_impl_blocks_for_trait(tr).map(|imp| imp.to_nav(db)).collect()) + Some(impls.into_iter().map(|imp| imp.to_nav(db)).collect()) } fn is_equal_for_find_impls(original_ty: &Ty, impl_ty: &Ty) -> bool { -- cgit v1.2.3 From 4e17718a9aaba34533ba6a46d52b4aa959c662c7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 15:40:55 +0300 Subject: Doc primitives --- crates/ra_hir_ty/src/lib.rs | 3 ++- crates/ra_hir_ty/src/primitive.rs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index 25bfc1d15..28859ba63 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -1,3 +1,4 @@ -//! FIXME: write short doc here +//! Work in Progress: everything related to types, type inference and trait +//! solving. pub mod primitive; diff --git a/crates/ra_hir_ty/src/primitive.rs b/crates/ra_hir_ty/src/primitive.rs index afa22448d..02a8179d9 100644 --- a/crates/ra_hir_ty/src/primitive.rs +++ b/crates/ra_hir_ty/src/primitive.rs @@ -1,4 +1,7 @@ -//! FIXME: write short doc here +//! Defines primitive types, which have a couple of peculiarities: +//! +//! * during type inference, they can be uncertain (ie, `let x = 92;`) +//! * they don't belong to any particular crate. use std::fmt; -- cgit v1.2.3 From 0623164c1d1ec461570c7d3d330d7c90fb00cf6e Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Tue, 26 Nov 2019 21:13:36 +0800 Subject: Remove dbg! --- crates/ra_hir/src/source_binder.rs | 2 -- 1 file changed, 2 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 287cea880..144e144bb 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -448,8 +448,6 @@ impl SourceAnalyzer { db.ast_id_map(macro_call.file_id).ast_id(macro_call.value), ); let macro_call_loc = MacroCallLoc { def, ast_id }; - let kind = to_macro_file_kind(macro_call.value); - dbg!(kind); Some(Expansion { macro_call_id: db.intern_macro(macro_call_loc), macro_file_kind: to_macro_file_kind(macro_call.value), -- cgit v1.2.3 From b81548c73a134cd63aefb9ceb20edb399bd3a16c Mon Sep 17 00:00:00 2001 From: Jeremy Kolb Date: Tue, 26 Nov 2019 08:20:40 -0500 Subject: Fix stale crates that snuck in --- crates/ra_hir_ty/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'crates') diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 70216ab24..027b50865 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -10,7 +10,7 @@ doctest = false [dependencies] log = "0.4.5" rustc-hash = "1.0" -parking_lot = "0.9.0" +parking_lot = "0.10.0" ena = "0.13" ra_syntax = { path = "../ra_syntax" } -- cgit v1.2.3 From 4a0792362e2c6cae2809520da454471d5a917384 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 16:59:24 +0300 Subject: Detangle ty from traits a bit --- crates/ra_hir/src/code_model.rs | 53 ++++--------------------------- crates/ra_hir/src/ty.rs | 1 + crates/ra_hir/src/ty/lower.rs | 3 +- crates/ra_hir/src/ty/method_resolution.rs | 5 +-- crates/ra_hir/src/ty/utils.rs | 50 +++++++++++++++++++++++++++++ 5 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 crates/ra_hir/src/ty/utils.rs (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 821f919d4..9930cff66 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -9,7 +9,7 @@ use hir_def::{ builtin_type::BuiltinType, docs::Documentation, per_ns::PerNs, - resolver::{HasResolver, TypeNs}, + resolver::HasResolver, type_ref::{Mutability, TypeRef}, AdtId, AstItemDef, ConstId, ContainerId, DefWithBodyId, EnumId, FunctionId, GenericDefId, HasModule, ImplId, LocalEnumVariantId, LocalImportId, LocalModuleId, LocalStructFieldId, @@ -28,8 +28,8 @@ use crate::{ expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, ty::display::HirFormatter, ty::{ - self, InEnvironment, InferenceResult, Namespace, TraitEnvironment, TraitRef, Ty, TypeCtor, - TypeWalk, + self, utils::all_super_traits, InEnvironment, InferenceResult, Namespace, TraitEnvironment, + TraitRef, Ty, TypeCtor, TypeWalk, }, CallableDef, Either, HirDisplay, Name, Source, }; @@ -740,48 +740,6 @@ impl Trait { db.trait_data(self.id).items.iter().map(|it| (*it).into()).collect() } - fn direct_super_traits(self, db: &impl HirDatabase) -> Vec { - let resolver = self.id.resolver(db); - // returning the iterator directly doesn't easily work because of - // lifetime problems, but since there usually shouldn't be more than a - // few direct traits this should be fine (we could even use some kind of - // SmallVec if performance is a concern) - db.generic_params(self.id.into()) - .where_predicates - .iter() - .filter_map(|pred| match &pred.type_ref { - TypeRef::Path(p) if p.as_ident() == Some(&name::SELF_TYPE) => pred.bound.as_path(), - _ => None, - }) - .filter_map(|path| match resolver.resolve_path_in_type_ns_fully(db, path) { - Some(TypeNs::TraitId(t)) => Some(t), - _ => None, - }) - .map(Trait::from) - .collect() - } - - /// Returns an iterator over the whole super trait hierarchy (including the - /// trait itself). - pub fn all_super_traits(self, db: &impl HirDatabase) -> Vec { - // we need to take care a bit here to avoid infinite loops in case of cycles - // (i.e. if we have `trait A: B; trait B: A;`) - let mut result = vec![self]; - let mut i = 0; - while i < result.len() { - let t = result[i]; - // yeah this is quadratic, but trait hierarchies should be flat - // enough that this doesn't matter - for tt in t.direct_super_traits(db) { - if !result.contains(&tt) { - result.push(tt); - } - } - i += 1; - } - result - } - pub fn associated_type_by_name(self, db: &impl DefDatabase, name: &Name) -> Option { let trait_data = db.trait_data(self.id); let res = @@ -794,7 +752,10 @@ impl Trait { db: &impl HirDatabase, name: &Name, ) -> Option { - self.all_super_traits(db).into_iter().find_map(|t| t.associated_type_by_name(db, name)) + all_super_traits(db, self.id) + .into_iter() + .map(Trait::from) + .find_map(|t| t.associated_type_by_name(db, name)) } pub fn trait_ref(self, db: &impl HirDatabase) -> TraitRef { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index bd03055b9..2a2dc26b4 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -9,6 +9,7 @@ mod op; mod lower; mod infer; pub(crate) mod display; +pub(crate) mod utils; #[cfg(test)] mod tests; diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 2d23890a5..9e3afabe0 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -28,6 +28,7 @@ use crate::{ db::HirDatabase, ty::{ primitive::{FloatTy, IntTy}, + utils::all_super_traits, Adt, }, util::make_mut_slice, @@ -260,7 +261,7 @@ impl Ty { GenericPredicate::Implemented(tr) if tr.self_ty() == &self_ty => Some(tr.trait_), _ => None, }); - let traits = traits_from_env.flat_map(|t| t.all_super_traits(db)); + let traits = traits_from_env.flat_map(|t| all_super_traits(db, t.id)).map(Trait::from); for t in traits { if let Some(associated_ty) = t.associated_type_by_name(db, &segment.name) { let substs = Substs::build_for_def(db, t.id) diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 489fcd64b..0e18684ed 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashMap; use crate::{ db::HirDatabase, ty::primitive::{FloatBitness, Uncertain}, - ty::{Ty, TypeCtor}, + ty::{utils::all_super_traits, Ty, TypeCtor}, AssocItem, Crate, Function, Mutability, Name, Trait, }; @@ -249,7 +249,8 @@ fn iterate_trait_method_candidates( let traits_from_env = env .trait_predicates_for_self_ty(&ty.value) .map(|tr| tr.trait_) - .flat_map(|t| t.all_super_traits(db)); + .flat_map(|t| all_super_traits(db, t.id)) + .map(Trait::from); let traits = inherent_trait .chain(traits_from_env) .chain(resolver.traits_in_scope(db).into_iter().map(Trait::from)); diff --git a/crates/ra_hir/src/ty/utils.rs b/crates/ra_hir/src/ty/utils.rs new file mode 100644 index 000000000..345fa9430 --- /dev/null +++ b/crates/ra_hir/src/ty/utils.rs @@ -0,0 +1,50 @@ +use hir_def::{ + db::DefDatabase, + resolver::{HasResolver, TypeNs}, + type_ref::TypeRef, + TraitId, +}; +use hir_expand::name; + +// FIXME: this is wrong, b/c it can't express `trait T: PartialEq<()>`. +// We should return a `TraitREf` here. +fn direct_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { + let resolver = trait_.resolver(db); + // returning the iterator directly doesn't easily work because of + // lifetime problems, but since there usually shouldn't be more than a + // few direct traits this should be fine (we could even use some kind of + // SmallVec if performance is a concern) + db.generic_params(trait_.into()) + .where_predicates + .iter() + .filter_map(|pred| match &pred.type_ref { + TypeRef::Path(p) if p.as_ident() == Some(&name::SELF_TYPE) => pred.bound.as_path(), + _ => None, + }) + .filter_map(|path| match resolver.resolve_path_in_type_ns_fully(db, path) { + Some(TypeNs::TraitId(t)) => Some(t), + _ => None, + }) + .collect() +} + +/// Returns an iterator over the whole super trait hierarchy (including the +/// trait itself). +pub(crate) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { + // we need to take care a bit here to avoid infinite loops in case of cycles + // (i.e. if we have `trait A: B; trait B: A;`) + let mut result = vec![trait_]; + let mut i = 0; + while i < result.len() { + let t = result[i]; + // yeah this is quadratic, but trait hierarchies should be flat + // enough that this doesn't matter + for tt in direct_super_traits(db, t) { + if !result.contains(&tt) { + result.push(tt); + } + } + i += 1; + } + result +} -- cgit v1.2.3 From 9bc8f1f4f8d7bded19517205f8522a0110204f51 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 17:12:16 +0300 Subject: Store names in TraitData --- crates/ra_hir/src/code_model.rs | 7 ++-- crates/ra_hir/src/ty/method_resolution.rs | 6 ++-- crates/ra_hir_def/src/data.rs | 55 +++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 25 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 9930cff66..9e7a1deec 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -737,14 +737,11 @@ impl Trait { } pub fn items(self, db: &impl DefDatabase) -> Vec { - db.trait_data(self.id).items.iter().map(|it| (*it).into()).collect() + db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect() } pub fn associated_type_by_name(self, db: &impl DefDatabase, name: &Name) -> Option { - let trait_data = db.trait_data(self.id); - let res = - trait_data.associated_types().map(TypeAlias::from).find(|t| &t.name(db) == name)?; - Some(res) + db.trait_data(self.id).associated_type_by_name(name).map(TypeAlias::from) } pub fn associated_type_by_name_including_super_traits( diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 0e18684ed..9988570e8 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -261,8 +261,8 @@ fn iterate_trait_method_candidates( // trait, but if we find out it doesn't, we'll skip the rest of the // iteration let mut known_implemented = false; - for &item in data.items.iter() { - if !is_valid_candidate(db, name, mode, item.into()) { + for (_name, item) in data.items.iter() { + if !is_valid_candidate(db, name, mode, (*item).into()) { continue; } if !known_implemented { @@ -272,7 +272,7 @@ fn iterate_trait_method_candidates( } } known_implemented = true; - if let Some(result) = callback(&ty.value, item.into()) { + if let Some(result) = callback(&ty.value, (*item).into()) { return Some(result); } } diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index 68bea34df..813099a05 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -87,7 +87,7 @@ impl TypeAliasData { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TraitData { pub name: Option, - pub items: Vec, + pub items: Vec<(Name, AssocItemId)>, pub auto: bool, } @@ -97,28 +97,42 @@ impl TraitData { let name = src.value.name().map(|n| n.as_name()); let auto = src.value.is_auto(); let ast_id_map = db.ast_id_map(src.file_id); + + let container = ContainerId::TraitId(tr); let items = if let Some(item_list) = src.value.item_list() { item_list .impl_items() .map(|item_node| match item_node { - ast::ImplItem::FnDef(it) => FunctionLoc { - container: ContainerId::TraitId(tr), - ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), + ast::ImplItem::FnDef(it) => { + let name = it.name().map(|it| it.as_name()).unwrap_or_else(Name::missing); + let def = FunctionLoc { + container, + ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), + } + .intern(db) + .into(); + (name, def) } - .intern(db) - .into(), - ast::ImplItem::ConstDef(it) => ConstLoc { - container: ContainerId::TraitId(tr), - ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), + ast::ImplItem::ConstDef(it) => { + let name = it.name().map(|it| it.as_name()).unwrap_or_else(Name::missing); + let def = ConstLoc { + container, + ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), + } + .intern(db) + .into(); + (name, def) } - .intern(db) - .into(), - ast::ImplItem::TypeAliasDef(it) => TypeAliasLoc { - container: ContainerId::TraitId(tr), - ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), + ast::ImplItem::TypeAliasDef(it) => { + let name = it.name().map(|it| it.as_name()).unwrap_or_else(Name::missing); + let def = TypeAliasLoc { + container, + ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), + } + .intern(db) + .into(); + (name, def) } - .intern(db) - .into(), }) .collect() } else { @@ -128,11 +142,18 @@ impl TraitData { } pub fn associated_types(&self) -> impl Iterator + '_ { - self.items.iter().filter_map(|item| match item { + self.items.iter().filter_map(|(_name, item)| match item { AssocItemId::TypeAliasId(t) => Some(*t), _ => None, }) } + + pub fn associated_type_by_name(&self, name: &Name) -> Option { + self.items.iter().find_map(|(item_name, item)| match item { + AssocItemId::TypeAliasId(t) if item_name == name => Some(*t), + _ => None, + }) + } } #[derive(Debug, Clone, PartialEq, Eq)] -- cgit v1.2.3 From d118997b9318b750676a7fe5b8896219f98d9e6e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 17:21:29 +0300 Subject: Remove assoc_type_by_name helper --- crates/ra_hir/src/code_model.rs | 8 ++------ crates/ra_hir/src/ty/autoderef.rs | 10 +++++----- crates/ra_hir/src/ty/infer.rs | 14 +++++++------- crates/ra_hir/src/ty/lower.rs | 8 +++----- crates/ra_hir/src/ty/traits/chalk.rs | 33 +++++++++++++++++---------------- 5 files changed, 34 insertions(+), 39 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 9e7a1deec..50e6409b1 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -740,10 +740,6 @@ impl Trait { db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect() } - pub fn associated_type_by_name(self, db: &impl DefDatabase, name: &Name) -> Option { - db.trait_data(self.id).associated_type_by_name(name).map(TypeAlias::from) - } - pub fn associated_type_by_name_including_super_traits( self, db: &impl HirDatabase, @@ -751,8 +747,8 @@ impl Trait { ) -> Option { all_super_traits(db, self.id) .into_iter() - .map(Trait::from) - .find_map(|t| t.associated_type_by_name(db, name)) + .find_map(|t| db.trait_data(t).associated_type_by_name(name)) + .map(TypeAlias::from) } pub fn trait_ref(self, db: &impl HirDatabase) -> TraitRef { diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs index 9e7593b8b..ae68234ac 100644 --- a/crates/ra_hir/src/ty/autoderef.rs +++ b/crates/ra_hir/src/ty/autoderef.rs @@ -10,7 +10,7 @@ use hir_expand::name; use log::{info, warn}; use ra_db::CrateId; -use crate::{db::HirDatabase, Trait}; +use crate::db::HirDatabase; use super::{ traits::{InEnvironment, Solution}, @@ -49,12 +49,12 @@ fn deref_by_trait( ty: InEnvironment<&Canonical>, ) -> Option> { let deref_trait = match db.lang_item(krate.into(), "deref".into())? { - LangItemTarget::TraitId(t) => Trait::from(t), + LangItemTarget::TraitId(it) => it, _ => return None, }; - let target = deref_trait.associated_type_by_name(db, &name::TARGET_TYPE)?; + let target = db.trait_data(deref_trait).associated_type_by_name(&name::TARGET_TYPE)?; - let generic_params = db.generic_params(target.id.into()); + let generic_params = db.generic_params(target.into()); if generic_params.count_params_including_parent() != 1 { // the Target type + Deref trait should only have one generic parameter, // namely Deref's Self type @@ -69,7 +69,7 @@ fn deref_by_trait( let projection = super::traits::ProjectionPredicate { ty: Ty::Bound(0), - projection_ty: super::ProjectionTy { associated_ty: target.id, parameters }, + projection_ty: super::ProjectionTy { associated_ty: target, parameters }, }; let obligation = super::Obligation::Projection(projection); diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index fce45321d..b023ae690 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -43,7 +43,7 @@ use crate::{ db::HirDatabase, expr::{BindingAnnotation, Body, ExprId, PatId}, ty::infer::diagnostics::InferenceDiagnostic, - Adt, AssocItem, DefWithBody, FloatTy, Function, IntTy, Path, StructField, Trait, VariantDef, + Adt, AssocItem, DefWithBody, FloatTy, Function, IntTy, Path, StructField, VariantDef, }; macro_rules! ty_app { @@ -582,20 +582,20 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { fn resolve_into_iter_item(&self) -> Option { let path = known::std_iter_into_iterator(); - let trait_: Trait = self.resolver.resolve_known_trait(self.db, &path)?.into(); - trait_.associated_type_by_name(self.db, &name::ITEM_TYPE) + let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; + self.db.trait_data(trait_).associated_type_by_name(&name::ITEM_TYPE).map(TypeAlias::from) } fn resolve_ops_try_ok(&self) -> Option { let path = known::std_ops_try(); - let trait_: Trait = self.resolver.resolve_known_trait(self.db, &path)?.into(); - trait_.associated_type_by_name(self.db, &name::OK_TYPE) + let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; + self.db.trait_data(trait_).associated_type_by_name(&name::OK_TYPE).map(TypeAlias::from) } fn resolve_future_future_output(&self) -> Option { let path = known::std_future_future(); - let trait_: Trait = self.resolver.resolve_known_trait(self.db, &path)?.into(); - trait_.associated_type_by_name(self.db, &name::OUTPUT_TYPE) + let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; + self.db.trait_data(trait_).associated_type_by_name(&name::OUTPUT_TYPE).map(TypeAlias::from) } fn resolve_boxed_box(&self) -> Option { diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 9e3afabe0..0ac7920bb 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -263,16 +263,14 @@ impl Ty { }); let traits = traits_from_env.flat_map(|t| all_super_traits(db, t.id)).map(Trait::from); for t in traits { - if let Some(associated_ty) = t.associated_type_by_name(db, &segment.name) { + if let Some(associated_ty) = db.trait_data(t.id).associated_type_by_name(&segment.name) + { let substs = Substs::build_for_def(db, t.id) .push(self_ty.clone()) .fill_with_unknown() .build(); // FIXME handle type parameters on the segment - return Ty::Projection(ProjectionTy { - associated_ty: associated_ty.id, - parameters: substs, - }); + return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); } } Ty::Unknown diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 06388a3ce..78f4b3e27 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -9,7 +9,7 @@ use chalk_ir::{ }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; -use hir_def::{lang_item::LangItemTarget, ContainerId, GenericDefId, Lookup, TypeAliasId}; +use hir_def::{lang_item::LangItemTarget, ContainerId, GenericDefId, Lookup, TraitId, TypeAliasId}; use hir_expand::name; use ra_db::salsa::{InternId, InternKey}; @@ -459,7 +459,7 @@ where [super::FnTrait::FnOnce, super::FnTrait::FnMut, super::FnTrait::Fn].iter() { if let Some(actual_trait) = get_fn_trait(self.db, self.krate, fn_trait) { - if trait_ == actual_trait { + if trait_.id == actual_trait { let impl_ = super::ClosureFnTraitImplData { def, expr, fn_trait }; result.push(Impl::ClosureFnTraitImpl(impl_).to_chalk(self.db)); } @@ -661,6 +661,7 @@ fn impl_block_datum( }; let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses }; + let trait_data = db.trait_data(trait_.id); let associated_ty_value_ids = impl_block .items(db) .into_iter() @@ -670,7 +671,7 @@ fn impl_block_datum( }) .filter(|type_alias| { // don't include associated types that don't exist in the trait - trait_.associated_type_by_name(db, &type_alias.name(db)).is_some() + trait_data.associated_type_by_name(&type_alias.name(db)).is_some() }) .map(|type_alias| AssocTyValue::TypeAlias(type_alias).to_chalk(db)) .collect(); @@ -713,7 +714,7 @@ fn closure_fn_trait_impl_datum( // and don't want to return a valid value only to find out later that FnOnce // is broken let fn_once_trait = get_fn_trait(db, krate, super::FnTrait::FnOnce)?; - fn_once_trait.associated_type_by_name(db, &name::OUTPUT_TYPE)?; + let _output = db.trait_data(fn_once_trait).associated_type_by_name(&name::OUTPUT_TYPE)?; let num_args: u16 = match &db.body(data.def.into())[data.expr] { crate::expr::Expr::Lambda { args, .. } => args.len() as u16, @@ -735,8 +736,8 @@ fn closure_fn_trait_impl_datum( let self_ty = Ty::apply_one(TypeCtor::Closure { def: data.def, expr: data.expr }, sig_ty); let trait_ref = TraitRef { - trait_, - substs: Substs::build_for_def(db, trait_.id).push(self_ty).push(arg_ty).build(), + trait_: trait_.into(), + substs: Substs::build_for_def(db, trait_).push(self_ty).push(arg_ty).build(), }; let output_ty_id = AssocTyValue::ClosureFnTraitImplOutput(data.clone()).to_chalk(db); @@ -783,10 +784,10 @@ fn type_alias_associated_ty_value( .target_trait_ref(db) .expect("assoc ty value should not exist") // we don't return any assoc ty values if the impl'd trait can't be resolved .trait_; - let assoc_ty = trait_ - .associated_type_by_name(db, &type_alias.name(db)) - .expect("assoc ty value should not exist") // validated when building the impl data as well - .id; + let assoc_ty = db + .trait_data(trait_.id) + .associated_type_by_name(&type_alias.name(db)) + .expect("assoc ty value should not exist"); // validated when building the impl data as well let generic_params = db.generic_params(impl_block.id.into()); let bound_vars = Substs::bound_vars(&generic_params); let ty = db.type_for_def(type_alias.into(), crate::ty::Namespace::Types).subst(&bound_vars); @@ -819,10 +820,10 @@ fn closure_fn_trait_output_assoc_ty_value( let fn_once_trait = get_fn_trait(db, krate, super::FnTrait::FnOnce).expect("assoc ty value should not exist"); - let output_ty_id = fn_once_trait - .associated_type_by_name(db, &name::OUTPUT_TYPE) - .expect("assoc ty value should not exist") - .id; + let output_ty_id = db + .trait_data(fn_once_trait) + .associated_type_by_name(&name::OUTPUT_TYPE) + .expect("assoc ty value should not exist"); let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: output_ty.to_chalk(db) }; @@ -834,10 +835,10 @@ fn closure_fn_trait_output_assoc_ty_value( Arc::new(value) } -fn get_fn_trait(db: &impl HirDatabase, krate: Crate, fn_trait: super::FnTrait) -> Option { +fn get_fn_trait(db: &impl HirDatabase, krate: Crate, fn_trait: super::FnTrait) -> Option { let target = db.lang_item(krate.crate_id, fn_trait.lang_item_name().into())?; match target { - LangItemTarget::TraitId(t) => Some(t.into()), + LangItemTarget::TraitId(t) => Some(t), _ => None, } } -- cgit v1.2.3 From 25b32f9d68e9f01f2b92801f4b35daf2b8b250f7 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 17:26:08 +0300 Subject: Doc --- crates/ra_hir/src/ty/utils.rs | 3 +++ 1 file changed, 3 insertions(+) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/utils.rs b/crates/ra_hir/src/ty/utils.rs index 345fa9430..672804787 100644 --- a/crates/ra_hir/src/ty/utils.rs +++ b/crates/ra_hir/src/ty/utils.rs @@ -1,3 +1,6 @@ +//! Helper functions for working with def, which don't need to be a separate +//! query, but can't be computed directly from `*Data` (ie, which need a `db`). + use hir_def::{ db::DefDatabase, resolver::{HasResolver, TypeNs}, -- cgit v1.2.3 From 24b1e79af51f5af76047a5eee2fe90baf100afca Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 17:42:21 +0300 Subject: Remove another helper --- crates/ra_hir/src/code_model.rs | 15 ++------------- crates/ra_hir/src/ty/lower.rs | 23 +++++++++++++---------- crates/ra_hir/src/ty/utils.rs | 14 ++++++++++++-- 3 files changed, 27 insertions(+), 25 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 50e6409b1..9578c20b0 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -28,8 +28,8 @@ use crate::{ expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, ty::display::HirFormatter, ty::{ - self, utils::all_super_traits, InEnvironment, InferenceResult, Namespace, TraitEnvironment, - TraitRef, Ty, TypeCtor, TypeWalk, + self, InEnvironment, InferenceResult, Namespace, TraitEnvironment, TraitRef, Ty, TypeCtor, + TypeWalk, }, CallableDef, Either, HirDisplay, Name, Source, }; @@ -740,17 +740,6 @@ impl Trait { db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect() } - pub fn associated_type_by_name_including_super_traits( - self, - db: &impl HirDatabase, - name: &Name, - ) -> Option { - all_super_traits(db, self.id) - .into_iter() - .find_map(|t| db.trait_data(t).associated_type_by_name(name)) - .map(TypeAlias::from) - } - pub fn trait_ref(self, db: &impl HirDatabase) -> TraitRef { TraitRef::for_trait(db, self) } diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 0ac7920bb..805a73ff5 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -28,7 +28,7 @@ use crate::{ db::HirDatabase, ty::{ primitive::{FloatTy, IntTy}, - utils::all_super_traits, + utils::{all_super_traits, associated_type_by_name_including_super_traits}, Adt, }, util::make_mut_slice, @@ -170,14 +170,16 @@ impl Ty { ); return if remaining_segments.len() == 1 { let segment = &remaining_segments[0]; - match trait_ref - .trait_ - .associated_type_by_name_including_super_traits(db, &segment.name) - { + let associated_ty = associated_type_by_name_including_super_traits( + db, + trait_ref.trait_.id, + &segment.name, + ); + match associated_ty { Some(associated_ty) => { // FIXME handle type parameters on the segment Ty::Projection(ProjectionTy { - associated_ty: associated_ty.id, + associated_ty, parameters: trait_ref.substs, }) } @@ -508,10 +510,11 @@ fn assoc_type_bindings_from_type_bound<'a>( .flat_map(|args_and_bindings| args_and_bindings.bindings.iter()) .map(move |(name, type_ref)| { let associated_ty = - match trait_ref.trait_.associated_type_by_name_including_super_traits(db, &name) { - None => return GenericPredicate::Error, - Some(t) => t.id, - }; + associated_type_by_name_including_super_traits(db, trait_ref.trait_.id, &name); + let associated_ty = match associated_ty { + None => return GenericPredicate::Error, + Some(t) => t, + }; let projection_ty = ProjectionTy { associated_ty, parameters: trait_ref.substs.clone() }; let ty = Ty::from_hir(db, resolver, type_ref); diff --git a/crates/ra_hir/src/ty/utils.rs b/crates/ra_hir/src/ty/utils.rs index 672804787..52994b9e3 100644 --- a/crates/ra_hir/src/ty/utils.rs +++ b/crates/ra_hir/src/ty/utils.rs @@ -5,9 +5,9 @@ use hir_def::{ db::DefDatabase, resolver::{HasResolver, TypeNs}, type_ref::TypeRef, - TraitId, + TraitId, TypeAliasId, }; -use hir_expand::name; +use hir_expand::name::{self, Name}; // FIXME: this is wrong, b/c it can't express `trait T: PartialEq<()>`. // We should return a `TraitREf` here. @@ -51,3 +51,13 @@ pub(crate) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec Option { + all_super_traits(db, trait_) + .into_iter() + .find_map(|t| db.trait_data(t).associated_type_by_name(name)) +} -- cgit v1.2.3 From 72d8e7e69abca9f27fb3ea386a6879324741e152 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 18:00:36 +0300 Subject: Use TraitId in TraitRef --- crates/ra_hir/src/code_model.rs | 2 +- crates/ra_hir/src/ty.rs | 13 ++++---- crates/ra_hir/src/ty/infer/path.rs | 39 +++++++++++++----------- crates/ra_hir/src/ty/lower.rs | 14 ++++----- crates/ra_hir/src/ty/method_resolution.rs | 18 +++++------ crates/ra_hir/src/ty/traits/chalk.rs | 50 ++++++++++++++----------------- 6 files changed, 68 insertions(+), 68 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 9578c20b0..7b5d78543 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -741,7 +741,7 @@ impl Trait { } pub fn trait_ref(self, db: &impl HirDatabase) -> TraitRef { - TraitRef::for_trait(db, self) + TraitRef::for_trait(db, self.id) } pub fn is_auto(self, db: &impl DefDatabase) -> bool { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 2a2dc26b4..3711068fa 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -26,7 +26,7 @@ use ra_db::{impl_intern_key, salsa}; use crate::{ db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, FloatTy, IntTy, Mutability, - Name, Trait, Uncertain, + Name, Uncertain, }; use display::{HirDisplay, HirFormatter}; @@ -445,7 +445,7 @@ impl Deref for Substs { #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub struct TraitRef { /// FIXME name? - pub trait_: Trait, + pub trait_: TraitId, pub substs: Substs, } @@ -676,7 +676,7 @@ impl Ty { } /// If this is an `impl Trait` or `dyn Trait`, returns that trait. - pub fn inherent_trait(&self) -> Option { + pub fn inherent_trait(&self) -> Option { match self { Ty::Dyn(predicates) | Ty::Opaque(predicates) => { predicates.iter().find_map(|pred| match pred { @@ -988,7 +988,10 @@ impl HirDisplay for Ty { write!( f, "{}", - trait_ref.trait_.name(f.db).unwrap_or_else(Name::missing) + f.db.trait_data(trait_ref.trait_) + .name + .clone() + .unwrap_or_else(Name::missing) )?; if trait_ref.substs.len() > 1 { write!(f, "<")?; @@ -1049,7 +1052,7 @@ impl TraitRef { } else { write!(f, ": ")?; } - write!(f, "{}", self.trait_.name(f.db).unwrap_or_else(Name::missing))?; + write!(f, "{}", f.db.trait_data(self.trait_).name.clone().unwrap_or_else(Name::missing))?; if self.substs.len() > 1 { write!(f, "<")?; f.write_joined(&self.substs[1..], ", ")?; diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs index 6165eba4f..202fff4f3 100644 --- a/crates/ra_hir/src/ty/infer/path.rs +++ b/crates/ra_hir/src/ty/infer/path.rs @@ -143,24 +143,27 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { id: ExprOrPatId, ) -> Option<(ValueNs, Option)> { let trait_ = trait_ref.trait_; - let item = trait_.items(self.db).iter().copied().find_map(|item| match item { - AssocItem::Function(func) => { - if segment.name == func.name(self.db) { - Some(AssocItem::Function(func)) - } else { - None - } - } + let item = + self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id).into()).find_map( + |item| match item { + AssocItem::Function(func) => { + if segment.name == func.name(self.db) { + Some(AssocItem::Function(func)) + } else { + None + } + } - AssocItem::Const(konst) => { - if konst.name(self.db).map_or(false, |n| n == segment.name) { - Some(AssocItem::Const(konst)) - } else { - None - } - } - AssocItem::TypeAlias(_) => None, - })?; + AssocItem::Const(konst) => { + if konst.name(self.db).map_or(false, |n| n == segment.name) { + Some(AssocItem::Const(konst)) + } else { + None + } + } + AssocItem::TypeAlias(_) => None, + }, + )?; let def = match item { AssocItem::Function(f) => ValueNs::FunctionId(f.id), AssocItem::Const(c) => ValueNs::ConstId(c.id), @@ -212,7 +215,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { .fill_with_params() .build(); self.obligations.push(super::Obligation::Trait(TraitRef { - trait_: t, + trait_: t.id, substs: trait_substs, })); Some(substs) diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 805a73ff5..a7149614d 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -15,7 +15,7 @@ use hir_def::{ resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, AdtId, AstItemDef, EnumVariantId, FunctionId, GenericDefId, HasModule, LocalStructFieldId, - Lookup, StructId, VariantId, + Lookup, StructId, TraitId, VariantId, }; use ra_arena::map::ArenaMap; use ra_db::CrateId; @@ -172,7 +172,7 @@ impl Ty { let segment = &remaining_segments[0]; let associated_ty = associated_type_by_name_including_super_traits( db, - trait_ref.trait_.id, + trait_ref.trait_, &segment.name, ); match associated_ty { @@ -263,7 +263,7 @@ impl Ty { GenericPredicate::Implemented(tr) if tr.self_ty() == &self_ty => Some(tr.trait_), _ => None, }); - let traits = traits_from_env.flat_map(|t| all_super_traits(db, t.id)).map(Trait::from); + let traits = traits_from_env.flat_map(|t| all_super_traits(db, t)).map(Trait::from); for t in traits { if let Some(associated_ty) = db.trait_data(t.id).associated_type_by_name(&segment.name) { @@ -423,7 +423,7 @@ impl TraitRef { if let Some(self_ty) = explicit_self_ty { make_mut_slice(&mut substs.0)[0] = self_ty; } - TraitRef { trait_: resolved, substs } + TraitRef { trait_: resolved.id, substs } } pub(crate) fn from_hir( @@ -450,8 +450,8 @@ impl TraitRef { substs_from_path_segment(db, resolver, segment, Some(resolved.id.into()), !has_self_param) } - pub(crate) fn for_trait(db: &impl HirDatabase, trait_: Trait) -> TraitRef { - let substs = Substs::identity(&db.generic_params(trait_.id.into())); + pub(crate) fn for_trait(db: &impl HirDatabase, trait_: TraitId) -> TraitRef { + let substs = Substs::identity(&db.generic_params(trait_.into())); TraitRef { trait_, substs } } @@ -510,7 +510,7 @@ fn assoc_type_bindings_from_type_bound<'a>( .flat_map(|args_and_bindings| args_and_bindings.bindings.iter()) .map(move |(name, type_ref)| { let associated_ty = - associated_type_by_name_including_super_traits(db, trait_ref.trait_.id, &name); + associated_type_by_name_including_super_traits(db, trait_ref.trait_, &name); let associated_ty = match associated_ty { None => return GenericPredicate::Error, Some(t) => t, diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 9988570e8..f1bc638ee 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -68,7 +68,7 @@ impl CrateImplBlocks { if let Some(tr) = TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty)) { - res.impls_by_trait.entry(tr.trait_.id).or_default().push(impl_id); + res.impls_by_trait.entry(tr.trait_).or_default().push(impl_id); } } None => { @@ -249,13 +249,11 @@ fn iterate_trait_method_candidates( let traits_from_env = env .trait_predicates_for_self_ty(&ty.value) .map(|tr| tr.trait_) - .flat_map(|t| all_super_traits(db, t.id)) - .map(Trait::from); - let traits = inherent_trait - .chain(traits_from_env) - .chain(resolver.traits_in_scope(db).into_iter().map(Trait::from)); + .flat_map(|t| all_super_traits(db, t)); + let traits = + inherent_trait.chain(traits_from_env).chain(resolver.traits_in_scope(db).into_iter()); 'traits: for t in traits { - let data = db.trait_data(t.id); + let data = db.trait_data(t); // we'll be lazy about checking whether the type implements the // trait, but if we find out it doesn't, we'll skip the rest of the @@ -330,7 +328,7 @@ pub(crate) fn implements_trait( db: &impl HirDatabase, resolver: &Resolver, krate: Crate, - trait_: Trait, + trait_: TraitId, ) -> bool { if ty.value.inherent_trait() == Some(trait_) { // FIXME this is a bit of a hack, since Chalk should say the same thing @@ -373,11 +371,11 @@ impl Ty { fn generic_implements_goal( db: &impl HirDatabase, env: Arc, - trait_: Trait, + trait_: TraitId, self_ty: Canonical, ) -> Canonical> { let num_vars = self_ty.num_vars; - let substs = super::Substs::build_for_def(db, trait_.id) + let substs = super::Substs::build_for_def(db, trait_) .push(self_ty.value) .fill_with_bound_vars(num_vars as u32) .build(); diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 78f4b3e27..02d37dead 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -9,7 +9,9 @@ use chalk_ir::{ }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; -use hir_def::{lang_item::LangItemTarget, ContainerId, GenericDefId, Lookup, TraitId, TypeAliasId}; +use hir_def::{ + lang_item::LangItemTarget, AstItemDef, ContainerId, GenericDefId, Lookup, TraitId, TypeAliasId, +}; use hir_expand::name; use ra_db::salsa::{InternId, InternKey}; @@ -19,7 +21,7 @@ use crate::{ db::HirDatabase, ty::display::HirDisplay, ty::{ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, - Crate, ImplBlock, Trait, TypeAlias, + Crate, ImplBlock, TypeAlias, }; /// This represents a trait whose name we could not resolve. @@ -167,15 +169,15 @@ impl ToChalk for TraitRef { } } -impl ToChalk for Trait { +impl ToChalk for TraitId { type Chalk = chalk_ir::TraitId; fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TraitId { - chalk_ir::TraitId(id_to_chalk(self.id)) + chalk_ir::TraitId(id_to_chalk(self)) } - fn from_chalk(_db: &impl HirDatabase, trait_id: chalk_ir::TraitId) -> Trait { - Trait { id: id_from_chalk(trait_id.0) } + fn from_chalk(_db: &impl HirDatabase, trait_id: chalk_ir::TraitId) -> TraitId { + id_from_chalk(trait_id.0) } } @@ -443,10 +445,10 @@ where if trait_id == UNKNOWN_TRAIT { return Vec::new(); } - let trait_: Trait = from_chalk(self.db, trait_id); + let trait_: TraitId = from_chalk(self.db, trait_id); let mut result: Vec<_> = self .db - .impls_for_trait(self.krate, trait_) + .impls_for_trait(self.krate, trait_.into()) .iter() .copied() .map(Impl::ImplBlock) @@ -459,7 +461,7 @@ where [super::FnTrait::FnOnce, super::FnTrait::FnMut, super::FnTrait::Fn].iter() { if let Some(actual_trait) = get_fn_trait(self.db, self.krate, fn_trait) { - if trait_.id == actual_trait { + if trait_ == actual_trait { let impl_ = super::ClosureFnTraitImplData { def, expr, fn_trait }; result.push(Impl::ClosureFnTraitImpl(impl_).to_chalk(self.db)); } @@ -516,7 +518,7 @@ pub(crate) fn associated_ty_data_query( where_clauses: vec![], }; let datum = AssociatedTyDatum { - trait_id: Trait::from(trait_).to_chalk(db), + trait_id: trait_.to_chalk(db), id, name: lalrpop_intern::intern(&db.type_alias_data(type_alias).name.to_string()), binders: make_binders(bound_data, generic_params.count_params_including_parent()), @@ -548,29 +550,23 @@ pub(crate) fn trait_datum_query( associated_ty_ids: vec![], }); } - let trait_: Trait = from_chalk(db, trait_id); - debug!("trait {:?} = {:?}", trait_id, trait_.name(db)); - let generic_params = db.generic_params(trait_.id.into()); + let trait_: TraitId = from_chalk(db, trait_id); + let trait_data = db.trait_data(trait_); + debug!("trait {:?} = {:?}", trait_id, trait_data.name); + let generic_params = db.generic_params(trait_.into()); let bound_vars = Substs::bound_vars(&generic_params); let flags = chalk_rust_ir::TraitFlags { - auto: trait_.is_auto(db), - upstream: trait_.module(db).krate() != krate, + auto: trait_data.auto, + upstream: trait_.module(db).krate != krate.crate_id, non_enumerable: true, coinductive: false, // only relevant for Chalk testing // FIXME set these flags correctly marker: false, fundamental: false, }; - let where_clauses = convert_where_clauses(db, trait_.id.into(), &bound_vars); - let associated_ty_ids = trait_ - .items(db) - .into_iter() - .filter_map(|trait_item| match trait_item { - crate::AssocItem::TypeAlias(type_alias) => Some(type_alias.id), - _ => None, - }) - .map(|type_alias| type_alias.to_chalk(db)) - .collect(); + let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); + let associated_ty_ids = + trait_data.associated_types().map(|type_alias| type_alias.to_chalk(db)).collect(); let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses }; let trait_datum = TraitDatum { id: trait_id, @@ -661,7 +657,7 @@ fn impl_block_datum( }; let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses }; - let trait_data = db.trait_data(trait_.id); + let trait_data = db.trait_data(trait_); let associated_ty_value_ids = impl_block .items(db) .into_iter() @@ -785,7 +781,7 @@ fn type_alias_associated_ty_value( .expect("assoc ty value should not exist") // we don't return any assoc ty values if the impl'd trait can't be resolved .trait_; let assoc_ty = db - .trait_data(trait_.id) + .trait_data(trait_) .associated_type_by_name(&type_alias.name(db)) .expect("assoc ty value should not exist"); // validated when building the impl data as well let generic_params = db.generic_params(impl_block.id.into()); -- cgit v1.2.3 From b60b26b8abc9d2bed46278c07415b7b39752040c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 18:02:50 +0300 Subject: Reduce visibility --- crates/ra_hir/src/ty/traits.rs | 5 +++-- crates/ra_hir/src/ty/utils.rs | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 2eeb03099..a91c2476b 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -2,14 +2,15 @@ use std::sync::{Arc, Mutex}; use chalk_ir::{cast::Cast, family::ChalkIr}; -use hir_def::DefWithBodyId; +use hir_def::{expr::ExprId, DefWithBodyId}; use log::debug; use ra_db::{impl_intern_key, salsa}; use ra_prof::profile; use rustc_hash::FxHashSet; +use crate::{db::HirDatabase, Crate, ImplBlock, Trait, TypeAlias}; + use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; -use crate::{db::HirDatabase, expr::ExprId, Crate, ImplBlock, Trait, TypeAlias}; use self::chalk::{from_chalk, ToChalk}; diff --git a/crates/ra_hir/src/ty/utils.rs b/crates/ra_hir/src/ty/utils.rs index 52994b9e3..80ffceb4b 100644 --- a/crates/ra_hir/src/ty/utils.rs +++ b/crates/ra_hir/src/ty/utils.rs @@ -33,7 +33,7 @@ fn direct_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { /// Returns an iterator over the whole super trait hierarchy (including the /// trait itself). -pub(crate) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { +pub(super) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { // we need to take care a bit here to avoid infinite loops in case of cycles // (i.e. if we have `trait A: B; trait B: A;`) let mut result = vec![trait_]; @@ -52,7 +52,7 @@ pub(crate) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec Date: Tue, 26 Nov 2019 18:06:12 +0300 Subject: Cleanup imports --- crates/ra_hir/src/ty.rs | 10 ++++++---- crates/ra_hir/src/ty/lower.rs | 3 +-- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 3711068fa..e420c796f 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -19,14 +19,16 @@ use std::sync::Arc; use std::{fmt, iter, mem}; use hir_def::{ - generics::GenericParams, AdtId, ContainerId, DefWithBodyId, GenericDefId, HasModule, Lookup, - TraitId, TypeAliasId, + expr::ExprId, generics::GenericParams, type_ref::Mutability, AdtId, ContainerId, DefWithBodyId, + GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, }; use ra_db::{impl_intern_key, salsa}; use crate::{ - db::HirDatabase, expr::ExprId, util::make_mut_slice, Adt, Crate, FloatTy, IntTy, Mutability, - Name, Uncertain, + db::HirDatabase, + ty::primitive::{FloatTy, IntTy, Uncertain}, + util::make_mut_slice, + Adt, Crate, Name, }; use display::{HirDisplay, HirFormatter}; diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index a7149614d..c3c47a576 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -29,10 +29,9 @@ use crate::{ ty::{ primitive::{FloatTy, IntTy}, utils::{all_super_traits, associated_type_by_name_including_super_traits}, - Adt, }, util::make_mut_slice, - Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Path, Static, Struct, Trait, + Adt, Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Path, Static, Struct, Trait, TypeAlias, Union, }; -- cgit v1.2.3 From 6fb4871f3149ab32e50052eb6a3bb29f852e6a6b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 19:30:57 +0300 Subject: Add note --- crates/ra_hir_def/src/resolver.rs | 2 ++ 1 file changed, 2 insertions(+) (limited to 'crates') diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs index 95b3c926d..c40f41717 100644 --- a/crates/ra_hir_def/src/resolver.rs +++ b/crates/ra_hir_def/src/resolver.rs @@ -61,6 +61,8 @@ pub enum TypeNs { GenericParam(u32), AdtId(AdtId), AdtSelfType(AdtId), + // Yup, enum variants are added to the types ns, but any usage of variant as + // type is an error. EnumVariantId(EnumVariantId), TypeAliasId(TypeAliasId), BuiltinType(BuiltinType), -- cgit v1.2.3 From 447268ceac497432822e8cf28525a6784f392020 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 27 Nov 2019 01:33:08 +0800 Subject: Hide MacroCallLoc --- crates/ra_hir/src/source_binder.rs | 6 ++---- crates/ra_hir_def/src/body.rs | 5 ++--- crates/ra_hir_def/src/nameres/collector.rs | 7 +++---- crates/ra_hir_expand/src/lib.rs | 14 ++++++++++++-- 4 files changed, 19 insertions(+), 13 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 82cb66583..7c4ebd4b4 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -14,8 +14,7 @@ use hir_def::{ DefWithBodyId, }; use hir_expand::{ - hygiene::Hygiene, name::AsName, AstId, HirFileId, MacroCallId, MacroCallLoc, MacroFileKind, - Source, + hygiene::Hygiene, name::AsName, AstId, HirFileId, MacroCallId, MacroFileKind, Source, }; use ra_syntax::{ ast::{self, AstNode}, @@ -451,9 +450,8 @@ impl SourceAnalyzer { macro_call.file_id, db.ast_id_map(macro_call.file_id).ast_id(macro_call.value), ); - let macro_call_loc = MacroCallLoc { def, ast_id }; Some(Expansion { - macro_call_id: db.intern_macro(macro_call_loc), + macro_call_id: def.as_call_id(db, ast_id), macro_file_kind: to_macro_file_kind(macro_call.value), }) } diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs index d77ccb272..78a532bdd 100644 --- a/crates/ra_hir_def/src/body.rs +++ b/crates/ra_hir_def/src/body.rs @@ -6,8 +6,7 @@ pub mod scope; use std::{ops::Index, sync::Arc}; use hir_expand::{ - either::Either, hygiene::Hygiene, AstId, HirFileId, MacroCallLoc, MacroDefId, MacroFileKind, - Source, + either::Either, hygiene::Hygiene, AstId, HirFileId, MacroDefId, MacroFileKind, Source, }; use ra_arena::{map::ArenaMap, Arena}; use ra_syntax::{ast, AstNode, AstPtr}; @@ -47,7 +46,7 @@ impl Expander { if let Some(path) = macro_call.path().and_then(|path| self.parse_path(path)) { if let Some(def) = self.resolve_path_as_macro(db, &path) { - let call_id = db.intern_macro(MacroCallLoc { def, ast_id }); + let call_id = def.as_call_id(db, ast_id); let file_id = call_id.as_file(MacroFileKind::Expr); if let Some(node) = db.parse_or_expand(file_id) { if let Some(expr) = ast::Expr::cast(node) { diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 4ff6f72cf..ea3abfdae 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -6,7 +6,7 @@ use hir_expand::{ builtin_macro::find_builtin_macro, name::{self, AsName, Name}, - HirFileId, MacroCallId, MacroCallLoc, MacroDefId, MacroDefKind, MacroFileKind, + HirFileId, MacroCallId, MacroDefId, MacroDefKind, MacroFileKind, }; use ra_cfg::CfgOptions; use ra_db::{CrateId, FileId}; @@ -480,7 +480,7 @@ where ); if let Some(def) = resolved_res.resolved_def.take_macros() { - let call_id = self.db.intern_macro(MacroCallLoc { def, ast_id: *ast_id }); + let call_id = def.as_call_id(self.db, *ast_id); resolved.push((*module_id, call_id, def)); res = ReachedFixedPoint::No; return false; @@ -773,8 +773,7 @@ where if let Some(macro_def) = mac.path.as_ident().and_then(|name| { self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) }) { - let macro_call_id = - self.def_collector.db.intern_macro(MacroCallLoc { def: macro_def, ast_id }); + let macro_call_id = macro_def.as_call_id(self.def_collector.db, ast_id); self.def_collector.collect_macro_expansion(self.module_id, macro_call_id, macro_def); return; diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index 4f3ccf1d0..b6a739cda 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs @@ -135,6 +135,16 @@ pub struct MacroDefId { pub kind: MacroDefKind, } +impl MacroDefId { + pub fn as_call_id( + self, + db: &dyn db::AstDatabase, + ast_id: AstId, + ) -> MacroCallId { + db.intern_macro(MacroCallLoc { def: self, ast_id }) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MacroDefKind { Declarative, @@ -143,8 +153,8 @@ pub enum MacroDefKind { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacroCallLoc { - pub def: MacroDefId, - pub ast_id: AstId, + pub(crate) def: MacroDefId, + pub(crate) ast_id: AstId, } impl MacroCallId { -- cgit v1.2.3 From 4e415a269e2d821f31963c05843517c6fde0132d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 21:04:24 +0300 Subject: Remove ns-polymorphic type_for_def --- crates/ra_hir/src/code_model.rs | 15 ++- crates/ra_hir/src/db.rs | 11 +- crates/ra_hir/src/ty.rs | 4 +- crates/ra_hir/src/ty/infer.rs | 53 +++------- crates/ra_hir/src/ty/infer/expr.rs | 10 +- crates/ra_hir/src/ty/infer/path.rs | 7 +- crates/ra_hir/src/ty/lower.rs | 189 +++++++++++++++++------------------ crates/ra_hir/src/ty/traits/chalk.rs | 2 +- crates/ra_ide_api/src/change.rs | 3 +- 9 files changed, 129 insertions(+), 165 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 7b5d78543..c5cf39ee1 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -28,8 +28,7 @@ use crate::{ expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, ty::display::HirFormatter, ty::{ - self, InEnvironment, InferenceResult, Namespace, TraitEnvironment, TraitRef, Ty, TypeCtor, - TypeWalk, + self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, }, CallableDef, Either, HirDisplay, Name, Source, }; @@ -354,11 +353,11 @@ impl Struct { } pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.type_for_def(self.into(), Namespace::Types) + db.ty(self.id.into()) } pub fn constructor_ty(self, db: &impl HirDatabase) -> Ty { - db.type_for_def(self.into(), Namespace::Values) + db.value_ty(self.id.into()) } fn variant_data(self, db: &impl DefDatabase) -> Arc { @@ -381,7 +380,7 @@ impl Union { } pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.type_for_def(self.into(), Namespace::Types) + db.ty(self.id.into()) } pub fn fields(self, db: &impl HirDatabase) -> Vec { @@ -442,7 +441,7 @@ impl Enum { } pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.type_for_def(self.into(), Namespace::Types) + db.ty(self.id.into()) } } @@ -617,7 +616,7 @@ impl Function { } pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.type_for_def(self.into(), Namespace::Values) + db.value_ty(self.id.into()) } pub fn infer(self, db: &impl HirDatabase) -> Arc { @@ -797,7 +796,7 @@ impl TypeAlias { } pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.type_for_def(self.into(), Namespace::Types) + db.ty(self.id.into()) } pub fn name(self, db: &impl DefDatabase) -> Name { diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 32f05a4d8..3b5aa7516 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -9,8 +9,8 @@ use crate::{ ty::{ method_resolution::CrateImplBlocks, traits::{AssocTyValue, Impl}, - CallableDef, FnSig, GenericPredicate, InferenceResult, Namespace, Substs, Ty, TypableDef, - TypeCtor, + CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, + ValueTyDefId, }, Crate, DefWithBody, ImplBlock, Trait, }; @@ -37,8 +37,11 @@ pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::ty::infer_query)] fn infer(&self, def: DefWithBody) -> Arc; - #[salsa::invoke(crate::ty::type_for_def)] - fn type_for_def(&self, def: TypableDef, ns: Namespace) -> Ty; + #[salsa::invoke(crate::ty::ty_query)] + fn ty(&self, def: TyDefId) -> Ty; + + #[salsa::invoke(crate::ty::value_ty_query)] + fn value_ty(&self, def: ValueTyDefId) -> Ty; #[salsa::invoke(crate::ty::field_types_query)] fn field_types(&self, var: VariantId) -> Arc>; diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index e420c796f..680ddc2f9 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -37,8 +37,8 @@ pub(crate) use infer::{infer_query, InferTy, InferenceResult}; pub use lower::CallableDef; pub(crate) use lower::{ callable_item_sig, field_types_query, generic_defaults_query, - generic_predicates_for_param_query, generic_predicates_query, type_for_def, Namespace, - TypableDef, + generic_predicates_for_param_query, generic_predicates_query, ty_query, value_ty_query, + TyDefId, TypableDef, ValueTyDefId, }; pub(crate) use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index b023ae690..beb2efb7a 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -35,15 +35,15 @@ use test_utils::tested_by; use super::{ traits::{Guidance, Obligation, ProjectionPredicate, Solution}, - ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypableDef, - TypeCtor, TypeWalk, Uncertain, + ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, + TypeWalk, Uncertain, }; use crate::{ code_model::TypeAlias, db::HirDatabase, expr::{BindingAnnotation, Body, ExprId, PatId}, ty::infer::diagnostics::InferenceDiagnostic, - Adt, AssocItem, DefWithBody, FloatTy, Function, IntTy, Path, StructField, VariantDef, + AssocItem, DefWithBody, FloatTy, Function, IntTy, Path, StructField, VariantDef, }; macro_rules! ty_app { @@ -520,45 +520,22 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { None => return (Ty::Unknown, None), }; let resolver = &self.resolver; - let def: TypableDef = - // FIXME: this should resolve assoc items as well, see this example: - // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 - match resolver.resolve_path_in_type_ns_fully(self.db, &path) { - Some(TypeNs::AdtId(AdtId::StructId(it))) => it.into(), - Some(TypeNs::AdtId(AdtId::UnionId(it))) => it.into(), - Some(TypeNs::AdtSelfType(adt)) => adt.into(), - Some(TypeNs::EnumVariantId(it)) => it.into(), - Some(TypeNs::TypeAliasId(it)) => it.into(), - - Some(TypeNs::SelfType(_)) | - Some(TypeNs::GenericParam(_)) | - Some(TypeNs::BuiltinType(_)) | - Some(TypeNs::TraitId(_)) | - Some(TypeNs::AdtId(AdtId::EnumId(_))) | - None => { - return (Ty::Unknown, None) - } - }; - // FIXME remove the duplication between here and `Ty::from_path`? - let substs = Ty::substs_from_path(self.db, resolver, path, def); - match def { - TypableDef::Adt(Adt::Struct(s)) => { - let ty = s.ty(self.db); + // FIXME: this should resolve assoc items as well, see this example: + // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 + match resolver.resolve_path_in_type_ns_fully(self.db, &path) { + Some(TypeNs::AdtId(AdtId::StructId(strukt))) => { + let substs = Ty::substs_from_path(self.db, resolver, path, strukt.into()); + let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.apply_substs(substs)); - (ty, Some(s.into())) + (ty, Some(VariantDef::Struct(strukt.into()))) } - TypableDef::EnumVariant(var) => { - let ty = var.parent_enum(self.db).ty(self.db); + Some(TypeNs::EnumVariantId(var)) => { + let substs = Ty::substs_from_path(self.db, resolver, path, var.into()); + let ty = self.db.ty(var.parent.into()); let ty = self.insert_type_vars(ty.apply_substs(substs)); - (ty, Some(var.into())) + (ty, Some(VariantDef::EnumVariant(var.into()))) } - TypableDef::Adt(Adt::Enum(_)) - | TypableDef::Adt(Adt::Union(_)) - | TypableDef::TypeAlias(_) - | TypableDef::Function(_) - | TypableDef::Const(_) - | TypableDef::Static(_) - | TypableDef::BuiltinType(_) => (Ty::Unknown, None), + Some(_) | None => (Ty::Unknown, None), } } diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 3d0895dc6..eb221d6bc 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -17,8 +17,8 @@ use crate::{ expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, ty::{ autoderef, method_resolution, op, traits::InEnvironment, CallableDef, InferTy, IntTy, - Mutability, Namespace, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, - TypeCtor, TypeWalk, Uncertain, + Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, + TypeWalk, Uncertain, }, Name, }; @@ -558,11 +558,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Some((ty, func)) => { let ty = canonicalized_receiver.decanonicalize_ty(ty); self.write_method_resolution(tgt_expr, func); - ( - ty, - self.db.type_for_def(func.into(), Namespace::Values), - Some(self.db.generic_params(func.id.into())), - ) + (ty, self.db.value_ty(func.id.into()), Some(self.db.generic_params(func.id.into()))) } None => (receiver_ty, Ty::Unknown, None), }; diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs index 202fff4f3..be2067dd4 100644 --- a/crates/ra_hir/src/ty/infer/path.rs +++ b/crates/ra_hir/src/ty/infer/path.rs @@ -7,7 +7,7 @@ use hir_def::{ use crate::{ db::HirDatabase, - ty::{method_resolution, Namespace, Substs, Ty, TypableDef, TypeWalk}, + ty::{method_resolution, Substs, Ty, TypeWalk, ValueTyDefId}, AssocItem, Container, Function, Name, Path, }; @@ -56,7 +56,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } }; - let typable: TypableDef = match value { + let typable: ValueTyDefId = match value { ValueNs::LocalBinding(pat) => { let ty = self.result.type_of_pat.get(pat)?.clone(); let ty = self.resolve_ty_as_possible(&mut vec![], ty); @@ -69,11 +69,10 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { ValueNs::EnumVariantId(it) => it.into(), }; - let mut ty = self.db.type_for_def(typable, Namespace::Values); + let mut ty = self.db.value_ty(typable); if let Some(self_subst) = self_subst { ty = ty.subst(&self_subst); } - let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable); let ty = ty.subst(&substs); Some(ty) diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index c3c47a576..709492d21 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -14,8 +14,8 @@ use hir_def::{ path::{GenericArg, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, - AdtId, AstItemDef, EnumVariantId, FunctionId, GenericDefId, HasModule, LocalStructFieldId, - Lookup, StructId, TraitId, VariantId, + AdtId, AstItemDef, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, + LocalStructFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, UnionId, VariantId, }; use ra_arena::map::ArenaMap; use ra_db::CrateId; @@ -35,17 +35,6 @@ use crate::{ TypeAlias, Union, }; -// FIXME: this is only really used in `type_for_def`, which contains a bunch of -// impossible cases. Perhaps we should recombine `TypeableDef` and `Namespace` -// into a `AsTypeDef`, `AsValueDef` enums? -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub enum Namespace { - Types, - Values, - // Note that only type inference uses this enum, and it doesn't care about macros. - // Macro, -} - impl Ty { pub(crate) fn from_hir(db: &impl HirDatabase, resolver: &Resolver, type_ref: &TypeRef) -> Self { match type_ref { @@ -281,27 +270,15 @@ impl Ty { db: &impl HirDatabase, resolver: &Resolver, segment: &PathSegment, - typable: TypableDef, + typable: TyDefId, ) -> Ty { - let ty = db.type_for_def(typable, Namespace::Types); - let substs = Ty::substs_from_path_segment(db, resolver, segment, typable); - ty.subst(&substs) - } - - pub(super) fn substs_from_path_segment( - db: &impl HirDatabase, - resolver: &Resolver, - segment: &PathSegment, - resolved: TypableDef, - ) -> Substs { - let def_generic: Option = match resolved { - TypableDef::Function(func) => Some(func.id.into()), - TypableDef::Adt(adt) => Some(adt.into()), - TypableDef::EnumVariant(var) => Some(var.parent_enum(db).id.into()), - TypableDef::TypeAlias(t) => Some(t.id.into()), - TypableDef::Const(_) | TypableDef::Static(_) | TypableDef::BuiltinType(_) => None, + let generic_def = match typable { + TyDefId::BuiltinType(_) => None, + TyDefId::AdtId(it) => Some(it.into()), + TyDefId::TypeAliasId(it) => Some(it.into()), }; - substs_from_path_segment(db, resolver, segment, def_generic, false) + let substs = substs_from_path_segment(db, resolver, segment, generic_def, false); + db.ty(typable).subst(&substs) } /// Collect generic arguments from a path into a `Substs`. See also @@ -310,17 +287,18 @@ impl Ty { db: &impl HirDatabase, resolver: &Resolver, path: &Path, - resolved: TypableDef, + // Note that we don't call `db.value_type(resolved)` here, + // `ValueTyDefId` is just a convenient way to pass generics and + // special-case enum variants + resolved: ValueTyDefId, ) -> Substs { let last = path.segments.last().expect("path should have at least one segment"); - let segment = match resolved { - TypableDef::Function(_) - | TypableDef::Adt(_) - | TypableDef::Const(_) - | TypableDef::Static(_) - | TypableDef::TypeAlias(_) - | TypableDef::BuiltinType(_) => last, - TypableDef::EnumVariant(_) => { + let (segment, generic_def) = match resolved { + ValueTyDefId::FunctionId(it) => (last, Some(it.into())), + ValueTyDefId::StructId(it) => (last, Some(it.into())), + ValueTyDefId::ConstId(it) => (last, Some(it.into())), + ValueTyDefId::StaticId(_) => (last, None), + ValueTyDefId::EnumVariantId(var) => { // the generic args for an enum variant may be either specified // on the segment referring to the enum, or on the segment // referring to the variant. So `Option::::None` and @@ -334,10 +312,10 @@ impl Ty { // Option::None:: last }; - segment + (segment, Some(var.parent.into())) } }; - Ty::substs_from_path_segment(db, resolver, segment, resolved) + substs_from_path_segment(db, resolver, segment, generic_def, false) } } @@ -522,33 +500,6 @@ fn assoc_type_bindings_from_type_bound<'a>( }) } -/// Build the declared type of an item. This depends on the namespace; e.g. for -/// `struct Foo(usize)`, we have two types: The type of the struct itself, and -/// the constructor function `(usize) -> Foo` which lives in the values -/// namespace. -pub(crate) fn type_for_def(db: &impl HirDatabase, def: TypableDef, ns: Namespace) -> Ty { - match (def, ns) { - (TypableDef::Function(f), Namespace::Values) => type_for_fn(db, f), - (TypableDef::Adt(Adt::Struct(s)), Namespace::Values) => type_for_struct_constructor(db, s), - (TypableDef::Adt(adt), Namespace::Types) => type_for_adt(db, adt), - (TypableDef::EnumVariant(v), Namespace::Values) => type_for_enum_variant_constructor(db, v), - (TypableDef::TypeAlias(t), Namespace::Types) => type_for_type_alias(db, t), - (TypableDef::Const(c), Namespace::Values) => type_for_const(db, c), - (TypableDef::Static(c), Namespace::Values) => type_for_static(db, c), - (TypableDef::BuiltinType(t), Namespace::Types) => type_for_builtin(t), - - // 'error' cases: - (TypableDef::Function(_), Namespace::Types) => Ty::Unknown, - (TypableDef::Adt(Adt::Union(_)), Namespace::Values) => Ty::Unknown, - (TypableDef::Adt(Adt::Enum(_)), Namespace::Values) => Ty::Unknown, - (TypableDef::EnumVariant(_), Namespace::Types) => Ty::Unknown, - (TypableDef::TypeAlias(_), Namespace::Values) => Ty::Unknown, - (TypableDef::Const(_), Namespace::Types) => Ty::Unknown, - (TypableDef::Static(_), Namespace::Types) => Ty::Unknown, - (TypableDef::BuiltinType(_), Namespace::Values) => Ty::Unknown, - } -} - /// Build the signature of a callable item (function, struct or enum variant). pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSig { match def { @@ -647,24 +598,24 @@ fn fn_sig_for_fn(db: &impl HirDatabase, def: FunctionId) -> FnSig { /// Build the declared type of a function. This should not need to look at the /// function body. -fn type_for_fn(db: &impl HirDatabase, def: Function) -> Ty { - let generics = db.generic_params(def.id.into()); +fn type_for_fn(db: &impl HirDatabase, def: FunctionId) -> Ty { + let generics = db.generic_params(def.into()); let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.id.into()), substs) + Ty::apply(TypeCtor::FnDef(def.into()), substs) } /// Build the declared type of a const. -fn type_for_const(db: &impl HirDatabase, def: Const) -> Ty { - let data = db.const_data(def.id); - let resolver = def.id.resolver(db); +fn type_for_const(db: &impl HirDatabase, def: ConstId) -> Ty { + let data = db.const_data(def); + let resolver = def.resolver(db); Ty::from_hir(db, &resolver, &data.type_ref) } /// Build the declared type of a static. -fn type_for_static(db: &impl HirDatabase, def: Static) -> Ty { - let data = db.static_data(def.id); - let resolver = def.id.resolver(db); +fn type_for_static(db: &impl HirDatabase, def: StaticId) -> Ty { + let data = db.static_data(def); + let resolver = def.resolver(db); Ty::from_hir(db, &resolver, &data.type_ref) } @@ -688,19 +639,19 @@ fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> FnSig .iter() .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) .collect::>(); - let ret = type_for_adt(db, Struct::from(def)); + let ret = type_for_adt(db, def.into()); FnSig::from_params_and_return(params, ret) } /// Build the type of a tuple struct constructor. -fn type_for_struct_constructor(db: &impl HirDatabase, def: Struct) -> Ty { - let struct_data = db.struct_data(def.id.into()); +fn type_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> Ty { + let struct_data = db.struct_data(def.into()); if struct_data.variant_data.is_unit() { - return type_for_adt(db, def); // Unit struct + return type_for_adt(db, def.into()); // Unit struct } - let generics = db.generic_params(def.id.into()); + let generics = db.generic_params(def.into()); let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.id.into()), substs) + Ty::apply(TypeCtor::FnDef(def.into()), substs) } fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> FnSig { @@ -714,34 +665,33 @@ fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId .collect::>(); let generics = db.generic_params(def.parent.into()); let substs = Substs::identity(&generics); - let ret = type_for_adt(db, Enum::from(def.parent)).subst(&substs); + let ret = type_for_adt(db, def.parent.into()).subst(&substs); FnSig::from_params_and_return(params, ret) } /// Build the type of a tuple enum variant constructor. -fn type_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariant) -> Ty { - let var_data = def.variant_data(db); +fn type_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> Ty { + let enum_data = db.enum_data(def.parent); + let var_data = &enum_data.variants[def.local_id].variant_data; if var_data.is_unit() { - return type_for_adt(db, def.parent_enum(db)); // Unit variant + return type_for_adt(db, def.parent.into()); // Unit variant } - let generics = db.generic_params(def.parent_enum(db).id.into()); + let generics = db.generic_params(def.parent.into()); let substs = Substs::identity(&generics); Ty::apply(TypeCtor::FnDef(EnumVariantId::from(def).into()), substs) } -fn type_for_adt(db: &impl HirDatabase, adt: impl Into) -> Ty { - let adt = adt.into(); - let adt_id: AdtId = adt.into(); - let generics = db.generic_params(adt_id.into()); - Ty::apply(TypeCtor::Adt(adt_id), Substs::identity(&generics)) +fn type_for_adt(db: &impl HirDatabase, adt: AdtId) -> Ty { + let generics = db.generic_params(adt.into()); + Ty::apply(TypeCtor::Adt(adt), Substs::identity(&generics)) } -fn type_for_type_alias(db: &impl HirDatabase, t: TypeAlias) -> Ty { - let generics = db.generic_params(t.id.into()); - let resolver = t.id.resolver(db); - let type_ref = t.type_ref(db); +fn type_for_type_alias(db: &impl HirDatabase, t: TypeAliasId) -> Ty { + let generics = db.generic_params(t.into()); + let resolver = t.resolver(db); + let type_ref = &db.type_alias_data(t).type_ref; let substs = Substs::identity(&generics); - let inner = Ty::from_hir(db, &resolver, &type_ref.unwrap_or(TypeRef::Error)); + let inner = Ty::from_hir(db, &resolver, type_ref.as_ref().unwrap_or(&TypeRef::Error)); inner.subst(&substs) } @@ -808,3 +758,42 @@ impl From for GenericDefId { } } } + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TyDefId { + BuiltinType(BuiltinType), + AdtId(AdtId), + TypeAliasId(TypeAliasId), +} +impl_froms!(TyDefId: BuiltinType, AdtId(StructId, EnumId, UnionId), TypeAliasId); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ValueTyDefId { + FunctionId(FunctionId), + StructId(StructId), + EnumVariantId(EnumVariantId), + ConstId(ConstId), + StaticId(StaticId), +} +impl_froms!(ValueTyDefId: FunctionId, StructId, EnumVariantId, ConstId, StaticId); + +/// Build the declared type of an item. This depends on the namespace; e.g. for +/// `struct Foo(usize)`, we have two types: The type of the struct itself, and +/// the constructor function `(usize) -> Foo` which lives in the values +/// namespace. +pub(crate) fn ty_query(db: &impl HirDatabase, def: TyDefId) -> Ty { + match def { + TyDefId::BuiltinType(it) => type_for_builtin(it), + TyDefId::AdtId(it) => type_for_adt(db, it), + TyDefId::TypeAliasId(it) => type_for_type_alias(db, it), + } +} +pub(crate) fn value_ty_query(db: &impl HirDatabase, def: ValueTyDefId) -> Ty { + match def { + ValueTyDefId::FunctionId(it) => type_for_fn(db, it), + ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it), + ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it), + ValueTyDefId::ConstId(it) => type_for_const(db, it), + ValueTyDefId::StaticId(it) => type_for_static(db, it), + } +} diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 02d37dead..4b0f4f56c 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -786,7 +786,7 @@ fn type_alias_associated_ty_value( .expect("assoc ty value should not exist"); // validated when building the impl data as well let generic_params = db.generic_params(impl_block.id.into()); let bound_vars = Substs::bound_vars(&generic_params); - let ty = db.type_for_def(type_alias.into(), crate::ty::Namespace::Types).subst(&bound_vars); + let ty = db.ty(type_alias.id.into()).subst(&bound_vars); let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: ty.to_chalk(db) }; let value = chalk_rust_ir::AssociatedTyValue { impl_id, diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs index 84340aff8..4a76d1dd8 100644 --- a/crates/ra_ide_api/src/change.rs +++ b/crates/ra_ide_api/src/change.rs @@ -323,7 +323,8 @@ impl RootDatabase { hir::db::DocumentationQuery hir::db::ExprScopesQuery hir::db::InferQuery - hir::db::TypeForDefQuery + hir::db::TyQuery + hir::db::ValueTyQuery hir::db::FieldTypesQuery hir::db::CallableItemSignatureQuery hir::db::GenericPredicatesQuery -- cgit v1.2.3 From 882fe0a47ee6f60928395326d1f194eec521ce2e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 21:18:26 +0300 Subject: More precise NameKind::Self --- crates/ra_hir/src/code_model.rs | 2 +- crates/ra_ide_api/src/goto_definition.rs | 23 +++--- crates/ra_ide_api/src/hover.rs | 95 ++++++++++------------ crates/ra_ide_api/src/references.rs | 5 +- crates/ra_ide_api/src/references/classify.rs | 3 +- .../ra_ide_api/src/references/name_definition.rs | 6 +- 6 files changed, 63 insertions(+), 71 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index c5cf39ee1..0edcdb177 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -982,7 +982,7 @@ impl ImplBlock { } } -#[derive(Clone, PartialEq, Eq)] +#[derive(Clone, PartialEq, Eq, Debug)] pub struct Type { pub(crate) krate: CrateId, pub(crate) ty: InEnvironment, diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs index b6c72efdf..c10a6c844 100644 --- a/crates/ra_ide_api/src/goto_definition.rs +++ b/crates/ra_ide_api/src/goto_definition.rs @@ -71,10 +71,11 @@ pub(crate) fn reference_definition( Some(nav) => return Exact(nav), None => return Approximate(vec![]), }, - Some(SelfType(ty)) => { - if let Some((adt, _)) = ty.as_adt() { - return Exact(adt.to_nav(db)); - } + Some(SelfType(imp)) => { + // FIXME: ideally, this should point to the type in the impl, and + // not at the whole impl. And goto **type** definition should bring + // us to the actual type + return Exact(imp.to_nav(db)); } Some(Local(local)) => return Exact(local.to_nav(db)), Some(GenericParam(_)) => { @@ -503,7 +504,7 @@ mod tests { } } ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + "impl IMPL_BLOCK FileId(1) [12; 73)", ); check_goto( @@ -516,7 +517,7 @@ mod tests { } } ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + "impl IMPL_BLOCK FileId(1) [12; 73)", ); check_goto( @@ -529,7 +530,7 @@ mod tests { } } ", - "Foo ENUM_DEF FileId(1) [0; 14) [5; 8)", + "impl IMPL_BLOCK FileId(1) [15; 75)", ); check_goto( @@ -541,7 +542,7 @@ mod tests { } } ", - "Foo ENUM_DEF FileId(1) [0; 14) [5; 8)", + "impl IMPL_BLOCK FileId(1) [15; 62)", ); } @@ -560,7 +561,7 @@ mod tests { } } ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + "impl IMPL_BLOCK FileId(1) [49; 115)", ); check_goto( @@ -572,11 +573,11 @@ mod tests { } impl Make for Foo { fn new() -> Self<|> { - Self{} + Self {} } } ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + "impl IMPL_BLOCK FileId(1) [49; 115)", ); } diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs index 9839be985..260a7b869 100644 --- a/crates/ra_ide_api/src/hover.rs +++ b/crates/ra_ide_api/src/hover.rs @@ -133,20 +133,12 @@ fn hover_text_from_name_kind( hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), }, - SelfType(ty) => match ty.as_adt() { - Some((adt_def, _)) => match adt_def { - hir::Adt::Struct(it) => from_def_source(db, it), - hir::Adt::Union(it) => from_def_source(db, it), - hir::Adt::Enum(it) => from_def_source(db, it), - }, - _ => None, - }, Local(_) => { // Hover for these shows type names *no_fallback = true; None } - GenericParam(_) => { + GenericParam(_) | SelfType(_) => { // FIXME: Hover for generic param None } @@ -622,49 +614,52 @@ fn func(foo: i32) { if true { <|>foo; }; } ", ); let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("struct Thing")); - assert_eq!(hover.info.is_exact(), true); - - let (analysis, position) = single_file_with_position( - " - struct Thing { x: u32 } - impl Thing { - fn new() -> Self<|> { - Self { x: 0 } - } - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("struct Thing")); - assert_eq!(hover.info.is_exact(), true); - - let (analysis, position) = single_file_with_position( - " - enum Thing { A } - impl Thing { - pub fn new() -> Self<|> { - Thing::A - } - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); assert_eq!(hover.info.is_exact(), true); - let (analysis, position) = single_file_with_position( - " - enum Thing { A } - impl Thing { - pub fn thing(a: Self<|>) { - } - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); - assert_eq!(hover.info.is_exact(), true); + /* FIXME: revive these tests + let (analysis, position) = single_file_with_position( + " + struct Thing { x: u32 } + impl Thing { + fn new() -> Self<|> { + Self { x: 0 } + } + } + ", + ); + + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); + assert_eq!(hover.info.is_exact(), true); + + let (analysis, position) = single_file_with_position( + " + enum Thing { A } + impl Thing { + pub fn new() -> Self<|> { + Thing::A + } + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); + assert_eq!(hover.info.is_exact(), true); + + let (analysis, position) = single_file_with_position( + " + enum Thing { A } + impl Thing { + pub fn thing(a: Self<|>) { + } + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); + assert_eq!(hover.info.is_exact(), true); + */ } #[test] diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs index cb343e59a..21a1ea69e 100644 --- a/crates/ra_ide_api/src/references.rs +++ b/crates/ra_ide_api/src/references.rs @@ -83,10 +83,7 @@ pub(crate) fn find_all_refs( NameKind::Field(field) => field.to_nav(db), NameKind::AssocItem(assoc) => assoc.to_nav(db), NameKind::Def(def) => NavigationTarget::from_def(db, def)?, - NameKind::SelfType(ref ty) => match ty.as_adt() { - Some((adt, _)) => adt.to_nav(db), - None => return None, - }, + NameKind::SelfType(imp) => imp.to_nav(db), NameKind::Local(local) => local.to_nav(db), NameKind::GenericParam(_) => return None, }; diff --git a/crates/ra_ide_api/src/references/classify.rs b/crates/ra_ide_api/src/references/classify.rs index 227737ad2..5cea805ec 100644 --- a/crates/ra_ide_api/src/references/classify.rs +++ b/crates/ra_ide_api/src/references/classify.rs @@ -178,8 +178,7 @@ pub(crate) fn classify_name_ref( Some(NameDefinition { kind, container, visibility }) } PathResolution::SelfType(impl_block) => { - let ty = impl_block.target_ty(db); - let kind = NameKind::SelfType(ty); + let kind = NameKind::SelfType(impl_block); let container = impl_block.module(db); Some(NameDefinition { kind, container, visibility }) } diff --git a/crates/ra_ide_api/src/references/name_definition.rs b/crates/ra_ide_api/src/references/name_definition.rs index cf12db066..10d3a2364 100644 --- a/crates/ra_ide_api/src/references/name_definition.rs +++ b/crates/ra_ide_api/src/references/name_definition.rs @@ -4,8 +4,8 @@ //! Note that the reference search is possible for not all of the classified items. use hir::{ - Adt, AssocItem, GenericParam, HasSource, Local, MacroDef, Module, ModuleDef, StructField, Ty, - VariantDef, + Adt, AssocItem, GenericParam, HasSource, ImplBlock, Local, MacroDef, Module, ModuleDef, + StructField, VariantDef, }; use ra_syntax::{ast, ast::VisibilityOwner}; @@ -17,7 +17,7 @@ pub enum NameKind { Field(StructField), AssocItem(AssocItem), Def(ModuleDef), - SelfType(Ty), + SelfType(ImplBlock), Local(Local), GenericParam(GenericParam), } -- cgit v1.2.3 From 936c6950e78d073f54c9ba66795f7f6f3abb351b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 21:25:17 +0300 Subject: Remove last traces of adt from Ty --- crates/ra_hir/src/code_model.rs | 2 +- crates/ra_hir/src/expr.rs | 4 ++-- crates/ra_hir/src/ty.rs | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 0edcdb177..a842dfed6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -1104,7 +1104,7 @@ impl Type { pub fn as_adt(&self) -> Option { let (adt, _subst) = self.ty.value.as_adt()?; - Some(adt) + Some(adt.into()) } fn derived(&self, ty: Ty) -> Type { diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs index adb9805ab..5c82c23d6 100644 --- a/crates/ra_hir/src/expr.rs +++ b/crates/ra_hir/src/expr.rs @@ -12,7 +12,7 @@ use crate::{ db::HirDatabase, diagnostics::{MissingFields, MissingOkInTailExpr}, ty::{ApplicationTy, InferenceResult, Ty, TypeCtor}, - Adt, Function, Name, Path, + Function, Name, Path, Struct, }; pub use hir_def::{ @@ -69,7 +69,7 @@ impl<'a, 'b> ExprValidator<'a, 'b> { } let struct_def = match self.infer[id].as_adt() { - Some((Adt::Struct(s), _)) => s, + Some((AdtId::StructId(s), _)) => Struct::from(s), _ => return, }; diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 680ddc2f9..791b6064a 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -22,13 +22,14 @@ use hir_def::{ expr::ExprId, generics::GenericParams, type_ref::Mutability, AdtId, ContainerId, DefWithBodyId, GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, }; +use hir_expand::name::Name; use ra_db::{impl_intern_key, salsa}; use crate::{ db::HirDatabase, ty::primitive::{FloatTy, IntTy, Uncertain}, util::make_mut_slice, - Adt, Crate, Name, + Crate, }; use display::{HirDisplay, HirFormatter}; @@ -598,10 +599,10 @@ impl Ty { } } - pub fn as_adt(&self) -> Option<(Adt, &Substs)> { + pub fn as_adt(&self) -> Option<(AdtId, &Substs)> { match self { Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(adt_def), parameters }) => { - Some(((*adt_def).into(), parameters)) + Some((*adt_def, parameters)) } _ => None, } -- cgit v1.2.3 From cace49e9a79a5fe44cda63964412c5bdce7ee90d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 22:26:47 +0300 Subject: Decouple --- crates/ra_hir/src/code_model.rs | 2 +- crates/ra_hir/src/db.rs | 21 ++++++-------- crates/ra_hir/src/ty/method_resolution.rs | 46 +++++++++++++++---------------- crates/ra_hir/src/ty/traits.rs | 16 +++++------ crates/ra_hir/src/ty/traits/chalk.rs | 2 +- 5 files changed, 42 insertions(+), 45 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index a842dfed6..b4b47057d 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -945,7 +945,7 @@ impl ImplBlock { } pub fn for_trait(db: &impl HirDatabase, krate: Crate, trait_: Trait) -> Vec { let impls = db.impls_in_crate(krate.crate_id); - impls.lookup_impl_blocks_for_trait(trait_).map(Self::from).collect() + impls.lookup_impl_blocks_for_trait(trait_.id).map(Self::from).collect() } pub fn target_trait(&self, db: &impl DefDatabase) -> Option { diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 3b5aa7516..31b21ca84 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -2,6 +2,7 @@ use std::sync::Arc; +use hir_def::{GenericDefId, LocalStructFieldId, TraitId, VariantId}; use ra_arena::map::ArenaMap; use ra_db::{salsa, CrateId}; @@ -12,19 +13,15 @@ use crate::{ CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, ValueTyDefId, }, - Crate, DefWithBody, ImplBlock, Trait, + Crate, DefWithBody, ImplBlock, }; -pub use hir_def::{ - db::{ - BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery, - DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery, - FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternDatabase, - InternDatabaseStorage, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, - RawItemsWithSourceMapQuery, StaticDataQuery, StructDataQuery, TraitDataQuery, - TypeAliasDataQuery, - }, - GenericDefId, LocalStructFieldId, VariantId, +pub use hir_def::db::{ + BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery, + DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery, + FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternDatabase, InternDatabaseStorage, + LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, RawItemsWithSourceMapQuery, + StaticDataQuery, StructDataQuery, TraitDataQuery, TypeAliasDataQuery, }; pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, @@ -66,7 +63,7 @@ pub trait HirDatabase: DefDatabase { fn impls_in_crate(&self, krate: CrateId) -> Arc; #[salsa::invoke(crate::ty::traits::impls_for_trait_query)] - fn impls_for_trait(&self, krate: Crate, trait_: Trait) -> Arc<[ImplBlock]>; + fn impls_for_trait(&self, krate: CrateId, trait_: TraitId) -> Arc<[ImplBlock]>; /// This provides the Chalk trait solver instance. Because Chalk always /// works from a specific crate, this query is keyed on the crate; and diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index f1bc638ee..fdc87a28d 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -6,9 +6,10 @@ use std::sync::Arc; use arrayvec::ArrayVec; use hir_def::{ - lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, AssocItemId, AstItemDef, - HasModule, ImplId, TraitId, + lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, type_ref::Mutability, + AssocItemId, AstItemDef, HasModule, ImplId, TraitId, }; +use hir_expand::name::Name; use ra_db::CrateId; use ra_prof::profile; use rustc_hash::FxHashMap; @@ -17,7 +18,7 @@ use crate::{ db::HirDatabase, ty::primitive::{FloatBitness, Uncertain}, ty::{utils::all_super_traits, Ty, TypeCtor}, - AssocItem, Crate, Function, Mutability, Name, Trait, + AssocItem, Function, }; use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; @@ -87,8 +88,8 @@ impl CrateImplBlocks { fingerprint.and_then(|f| self.impls.get(&f)).into_iter().flatten().copied() } - pub fn lookup_impl_blocks_for_trait(&self, tr: Trait) -> impl Iterator + '_ { - self.impls_by_trait.get(&tr.id).into_iter().flatten().copied() + pub fn lookup_impl_blocks_for_trait(&self, tr: TraitId) -> impl Iterator + '_ { + self.impls_by_trait.get(&tr).into_iter().flatten().copied() } pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { @@ -96,14 +97,18 @@ impl CrateImplBlocks { } } -fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option> { +fn def_crates( + db: &impl HirDatabase, + cur_crate: CrateId, + ty: &Ty, +) -> Option> { // Types like slice can have inherent impls in several crates, (core and alloc). // The corresponding impls are marked with lang items, so we can use them to find the required crates. macro_rules! lang_item_crate { ($($name:expr),+ $(,)?) => {{ let mut v = ArrayVec::<[LangItemTarget; 2]>::new(); $( - v.extend(db.lang_item(cur_crate.crate_id, $name.into())); + v.extend(db.lang_item(cur_crate, $name.into())); )+ v }}; @@ -112,7 +117,7 @@ fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option match a_ty.ctor { TypeCtor::Adt(def_id) => { - return Some(std::iter::once(def_id.module(db).krate.into()).collect()) + return Some(std::iter::once(def_id.module(db).krate).collect()) } TypeCtor::Bool => lang_item_crate!("bool"), TypeCtor::Char => lang_item_crate!("char"), @@ -136,7 +141,7 @@ fn def_crates(db: &impl HirDatabase, cur_crate: Crate, ty: &Ty) -> Option Some(it), _ => None, }) - .map(|it| it.module(db).krate.into()) + .map(|it| it.module(db).krate) .collect(); Some(res) } @@ -193,14 +198,9 @@ pub(crate) fn iterate_method_candidates( let environment = TraitEnvironment::lower(db, resolver); let ty = InEnvironment { value: ty.clone(), environment }; for derefed_ty in autoderef::autoderef(db, resolver.krate(), ty) { - if let Some(result) = iterate_inherent_methods( - &derefed_ty, - db, - name, - mode, - krate.into(), - &mut callback, - ) { + if let Some(result) = + iterate_inherent_methods(&derefed_ty, db, name, mode, krate, &mut callback) + { return Some(result); } if let Some(result) = iterate_trait_method_candidates( @@ -283,11 +283,11 @@ fn iterate_inherent_methods( db: &impl HirDatabase, name: Option<&Name>, mode: LookupMode, - krate: Crate, + krate: CrateId, mut callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { for krate in def_crates(db, krate, &ty.value)? { - let impls = db.impls_in_crate(krate.crate_id); + let impls = db.impls_in_crate(krate); for impl_block in impls.lookup_impl_blocks(&ty.value) { for &item in db.impl_data(impl_block).items.iter() { @@ -327,7 +327,7 @@ pub(crate) fn implements_trait( ty: &Canonical, db: &impl HirDatabase, resolver: &Resolver, - krate: Crate, + krate: CrateId, trait_: TraitId, ) -> bool { if ty.value.inherent_trait() == Some(trait_) { @@ -337,7 +337,7 @@ pub(crate) fn implements_trait( } let env = TraitEnvironment::lower(db, resolver); let goal = generic_implements_goal(db, env, trait_, ty.clone()); - let solution = db.trait_solve(krate, goal); + let solution = db.trait_solve(krate.into(), goal); solution.is_some() } @@ -348,11 +348,11 @@ impl Ty { pub fn iterate_impl_items( self, db: &impl HirDatabase, - krate: Crate, + krate: CrateId, mut callback: impl FnMut(AssocItem) -> Option, ) -> Option { for krate in def_crates(db, krate, &self)? { - let impls = db.impls_in_crate(krate.crate_id); + let impls = db.impls_in_crate(krate); for impl_block in impls.lookup_impl_blocks(&self) { for &item in db.impl_data(impl_block).items.iter() { diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index a91c2476b..637e21e9c 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -2,13 +2,13 @@ use std::sync::{Arc, Mutex}; use chalk_ir::{cast::Cast, family::ChalkIr}; -use hir_def::{expr::ExprId, DefWithBodyId}; +use hir_def::{expr::ExprId, DefWithBodyId, TraitId}; use log::debug; -use ra_db::{impl_intern_key, salsa}; +use ra_db::{impl_intern_key, salsa, CrateId}; use ra_prof::profile; use rustc_hash::FxHashSet; -use crate::{db::HirDatabase, Crate, ImplBlock, Trait, TypeAlias}; +use crate::{db::HirDatabase, Crate, ImplBlock, TypeAlias}; use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; @@ -77,8 +77,8 @@ pub(crate) fn trait_solver_query( /// Collects impls for the given trait in the whole dependency tree of `krate`. pub(crate) fn impls_for_trait_query( db: &impl HirDatabase, - krate: Crate, - trait_: Trait, + krate: CrateId, + trait_: TraitId, ) -> Arc<[ImplBlock]> { let mut impls = FxHashSet::default(); // We call the query recursively here. On the one hand, this means we can @@ -86,10 +86,10 @@ pub(crate) fn impls_for_trait_query( // will only ever get called for a few crates near the root of the tree (the // ones the user is editing), so this may actually be a waste of memory. I'm // doing it like this mainly for simplicity for now. - for dep in krate.dependencies(db) { - impls.extend(db.impls_for_trait(dep.krate, trait_).iter()); + for dep in db.crate_graph().dependencies(krate) { + impls.extend(db.impls_for_trait(dep.crate_id, trait_).iter()); } - let crate_impl_blocks = db.impls_in_crate(krate.crate_id); + let crate_impl_blocks = db.impls_in_crate(krate); impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_).map(ImplBlock::from)); impls.into_iter().collect() } diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 4b0f4f56c..d879382a0 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -448,7 +448,7 @@ where let trait_: TraitId = from_chalk(self.db, trait_id); let mut result: Vec<_> = self .db - .impls_for_trait(self.krate, trait_.into()) + .impls_for_trait(self.krate.crate_id, trait_.into()) .iter() .copied() .map(Impl::ImplBlock) -- cgit v1.2.3 From bed6869865ccfc6e72be26cb2041d83ab5cdbe3c Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Tue, 26 Nov 2019 22:56:07 +0300 Subject: Cleanup --- crates/ra_hir/src/code_model.rs | 49 +++++++++-- crates/ra_hir/src/source_binder.rs | 4 +- crates/ra_hir/src/ty/lower.rs | 2 +- crates/ra_hir/src/ty/method_resolution.rs | 101 +++++++++------------- crates/ra_hir_def/src/resolver.rs | 4 +- crates/ra_ide_api/src/completion/complete_path.rs | 2 +- 6 files changed, 85 insertions(+), 77 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index b4b47057d..54da937ea 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -28,7 +28,8 @@ use crate::{ expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, ty::display::HirFormatter, ty::{ - self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, + self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TyDefId, TypeCtor, + TypeWalk, }, CallableDef, Either, HirDisplay, Name, Source, }; @@ -498,12 +499,9 @@ impl Adt { let subst = db.generic_defaults(self.into()); subst.iter().any(|ty| ty == &Ty::Unknown) } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - match self { - Adt::Struct(it) => it.ty(db), - Adt::Union(it) => it.ty(db), - Adt::Enum(it) => it.ty(db), - } + pub fn ty(self, db: &impl HirDatabase) -> Type { + let id = AdtId::from(self); + Type::from_def(db, id.module(db).krate, id) } pub fn module(self, db: &impl DefDatabase) -> Module { @@ -795,8 +793,8 @@ impl TypeAlias { db.type_alias_data(self.id).type_ref.clone() } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.ty(self.id.into()) + pub fn ty(self, db: &impl HirDatabase) -> Type { + Type::from_def(db, self.id.lookup(db).module(db).krate, self.id) } pub fn name(self, db: &impl DefDatabase) -> Name { @@ -989,6 +987,17 @@ pub struct Type { } impl Type { + fn from_def( + db: &impl HirDatabase, + krate: CrateId, + def: impl HasResolver + Into, + ) -> Type { + let resolver = def.resolver(db); + let environment = TraitEnvironment::lower(db, &resolver); + let ty = db.ty(def.into()); + Type { krate, ty: InEnvironment { value: ty, environment } } + } + pub fn is_bool(&self) -> bool { match &self.ty.value { Ty::Apply(a_ty) => match a_ty.ctor { @@ -1097,6 +1106,28 @@ impl Type { .map(move |ty| self.derived(ty)) } + // This would be nicer if it just returned an iterator, but that runs into + // lifetime problems, because we need to borrow temp `CrateImplBlocks`. + pub fn iterate_impl_items( + self, + db: &impl HirDatabase, + krate: Crate, + mut callback: impl FnMut(AssocItem) -> Option, + ) -> Option { + for krate in self.ty.value.def_crates(db, krate.crate_id)? { + let impls = db.impls_in_crate(krate); + + for impl_block in impls.lookup_impl_blocks(&self.ty.value) { + for &item in db.impl_data(impl_block).items.iter() { + if let Some(result) = callback(item.into()) { + return Some(result); + } + } + } + } + None + } + // FIXME: remove pub fn into_ty(self) -> Ty { self.ty.value diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 7c4ebd4b4..9f3e6c43f 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -389,14 +389,14 @@ impl SourceAnalyzer { pub fn iterate_path_candidates( &self, db: &impl HirDatabase, - ty: Ty, + ty: &Type, name: Option<&Name>, callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { // There should be no inference vars in types passed here // FIXME check that? // FIXME replace Unknown by bound vars here - let canonical = crate::ty::Canonical { value: ty, num_vars: 0 }; + let canonical = crate::ty::Canonical { value: ty.ty.value.clone(), num_vars: 0 }; method_resolution::iterate_method_candidates( &canonical, db, diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 709492d21..1c0f71adc 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -189,7 +189,7 @@ impl Ty { Ty::Param { idx, name } } TypeNs::SelfType(impl_block) => ImplBlock::from(impl_block).target_ty(db), - TypeNs::AdtSelfType(adt) => Adt::from(adt).ty(db), + TypeNs::AdtSelfType(adt) => db.ty(adt.into()), TypeNs::AdtId(it) => Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()), TypeNs::BuiltinType(it) => { diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index fdc87a28d..92645e2a5 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -97,14 +97,15 @@ impl CrateImplBlocks { } } -fn def_crates( - db: &impl HirDatabase, - cur_crate: CrateId, - ty: &Ty, -) -> Option> { - // Types like slice can have inherent impls in several crates, (core and alloc). - // The corresponding impls are marked with lang items, so we can use them to find the required crates. - macro_rules! lang_item_crate { +impl Ty { + pub(crate) fn def_crates( + &self, + db: &impl HirDatabase, + cur_crate: CrateId, + ) -> Option> { + // Types like slice can have inherent impls in several crates, (core and alloc). + // The corresponding impls are marked with lang items, so we can use them to find the required crates. + macro_rules! lang_item_crate { ($($name:expr),+ $(,)?) => {{ let mut v = ArrayVec::<[LangItemTarget; 2]>::new(); $( @@ -114,38 +115,38 @@ fn def_crates( }}; } - let lang_item_targets = match ty { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Adt(def_id) => { - return Some(std::iter::once(def_id.module(db).krate).collect()) - } - TypeCtor::Bool => lang_item_crate!("bool"), - TypeCtor::Char => lang_item_crate!("char"), - TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { - // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) - FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), - FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), + let lang_item_targets = match self { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Adt(def_id) => { + return Some(std::iter::once(def_id.module(db).krate).collect()) + } + TypeCtor::Bool => lang_item_crate!("bool"), + TypeCtor::Char => lang_item_crate!("char"), + TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { + // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) + FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), + FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), + }, + TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), + TypeCtor::Str => lang_item_crate!("str_alloc", "str"), + TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), + TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), + TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"), + _ => return None, }, - TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), - TypeCtor::Str => lang_item_crate!("str_alloc", "str"), - TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), - TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), - TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"), _ => return None, - }, - _ => return None, - }; - let res = lang_item_targets - .into_iter() - .filter_map(|it| match it { - LangItemTarget::ImplBlockId(it) => Some(it), - _ => None, - }) - .map(|it| it.module(db).krate) - .collect(); - Some(res) + }; + let res = lang_item_targets + .into_iter() + .filter_map(|it| match it { + LangItemTarget::ImplBlockId(it) => Some(it), + _ => None, + }) + .map(|it| it.module(db).krate) + .collect(); + Some(res) + } } - /// Look up the method with the given name, returning the actual autoderefed /// receiver type (but without autoref applied yet). pub(crate) fn lookup_method( @@ -286,7 +287,7 @@ fn iterate_inherent_methods( krate: CrateId, mut callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { - for krate in def_crates(db, krate, &ty.value)? { + for krate in ty.value.def_crates(db, krate)? { let impls = db.impls_in_crate(krate); for impl_block in impls.lookup_impl_blocks(&ty.value) { @@ -342,30 +343,6 @@ pub(crate) fn implements_trait( solution.is_some() } -impl Ty { - // This would be nicer if it just returned an iterator, but that runs into - // lifetime problems, because we need to borrow temp `CrateImplBlocks`. - pub fn iterate_impl_items( - self, - db: &impl HirDatabase, - krate: CrateId, - mut callback: impl FnMut(AssocItem) -> Option, - ) -> Option { - for krate in def_crates(db, krate, &self)? { - let impls = db.impls_in_crate(krate); - - for impl_block in impls.lookup_impl_blocks(&self) { - for &item in db.impl_data(impl_block).items.iter() { - if let Some(result) = callback(item.into()) { - return Some(result); - } - } - } - } - None - } -} - /// This creates Substs for a trait with the given Self type and type variables /// for all other parameters, to query Chalk with it. fn generic_implements_goal( diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs index c40f41717..5155365cc 100644 --- a/crates/ra_hir_def/src/resolver.rs +++ b/crates/ra_hir_def/src/resolver.rs @@ -484,7 +484,7 @@ impl Resolver { } } -pub trait HasResolver { +pub trait HasResolver: Copy { /// Builds a resolver for type references inside this def. fn resolver(self, db: &impl DefDatabase) -> Resolver; } @@ -502,7 +502,7 @@ impl HasResolver for TraitId { } } -impl> HasResolver for T { +impl + Copy> HasResolver for T { fn resolver(self, db: &impl DefDatabase) -> Resolver { let def = self.into(); def.module(db) diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs index 63e25e0bf..89e0009a1 100644 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ b/crates/ra_ide_api/src/completion/complete_path.rs @@ -50,7 +50,7 @@ pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), _ => unreachable!(), }; - ctx.analyzer.iterate_path_candidates(ctx.db, ty.clone(), None, |_ty, item| { + ctx.analyzer.iterate_path_candidates(ctx.db, &ty, None, |_ty, item| { match item { hir::AssocItem::Function(func) => { if !func.has_self_param(ctx.db) { -- cgit v1.2.3 From 5b49ad5bd5d78a8425f7162b6387c2cad6e2ef10 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 27 Nov 2019 04:09:30 +0800 Subject: Use a simple progress bar instead of indicatif --- crates/ra_cli/Cargo.toml | 1 - crates/ra_cli/src/analysis_stats.rs | 17 ++--- crates/ra_cli/src/main.rs | 1 + crates/ra_cli/src/progress_bar.rs | 130 ++++++++++++++++++++++++++++++++++++ 4 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 crates/ra_cli/src/progress_bar.rs (limited to 'crates') diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml index c9d3bdb77..3bb475997 100644 --- a/crates/ra_cli/Cargo.toml +++ b/crates/ra_cli/Cargo.toml @@ -8,7 +8,6 @@ publish = false [dependencies] pico-args = "0.3.0" flexi_logger = "0.14.0" -indicatif = "0.13.0" ra_syntax = { path = "../ra_syntax" } ra_ide_api = { path = "../ra_ide_api" } diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs index c4eb28245..8bf3863bb 100644 --- a/crates/ra_cli/src/analysis_stats.rs +++ b/crates/ra_cli/src/analysis_stats.rs @@ -6,7 +6,7 @@ use ra_db::SourceDatabaseExt; use ra_hir::{AssocItem, Crate, HasSource, HirDisplay, ModuleDef, Ty, TypeWalk}; use ra_syntax::AstNode; -use crate::{Result, Verbosity}; +use crate::{progress_bar::ProgressBar, Result, Verbosity}; pub fn run( verbosity: Verbosity, @@ -75,17 +75,14 @@ pub fn run( println!("Item Collection: {:?}, {}", analysis_time.elapsed(), ra_prof::memory_usage()); let inference_time = Instant::now(); - let bar = match verbosity { - Verbosity::Verbose | Verbosity::Normal => indicatif::ProgressBar::with_draw_target( - funcs.len() as u64, - indicatif::ProgressDrawTarget::stderr_nohz(), - ), - Verbosity::Quiet => indicatif::ProgressBar::hidden(), + let mut bar = match verbosity { + Verbosity::Verbose | Verbosity::Normal => ProgressBar::new(funcs.len() as u64), + Verbosity::Quiet => ProgressBar::hidden(), }; - bar.set_style( - indicatif::ProgressStyle::default_bar().template("{wide_bar} {pos}/{len}\n{msg}"), - ); + // bar.set_style( + // indicatif::ProgressStyle::default_bar().template("{wide_bar} {pos}/{len}\n{msg}"), + // ); bar.tick(); let mut num_exprs = 0; let mut num_exprs_unknown = 0; diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index a31fd5d6a..c23d92ec2 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -3,6 +3,7 @@ mod analysis_stats; mod analysis_bench; mod help; +mod progress_bar; use std::{error::Error, fmt::Write, io::Read}; diff --git a/crates/ra_cli/src/progress_bar.rs b/crates/ra_cli/src/progress_bar.rs new file mode 100644 index 000000000..95bfcf4d0 --- /dev/null +++ b/crates/ra_cli/src/progress_bar.rs @@ -0,0 +1,130 @@ +//! A simple progress bar +//! +//! A single thread non-optimized progress bar +use std::io::Write; + +/// A Simple ASCII Progress Bar +pub struct ProgressBar { + curr: f32, + text: String, + anim: usize, + hidden: bool, + + len: u64, + pos: u64, + msg: String, +} + +impl ProgressBar { + const ANIMATION: &'static str = r#"|/-\"#; + const BLOCK_COUNT: usize = 20; + + pub fn new(len: u64) -> ProgressBar { + ProgressBar { + curr: 0.0, + text: String::new(), + anim: 0, + hidden: false, + len, + pos: 0, + msg: String::new(), + } + } + + pub fn hidden() -> ProgressBar { + ProgressBar { + curr: 0.0, + text: String::new(), + anim: 0, + hidden: true, + len: 0, + pos: 0, + msg: String::new(), + } + } + + pub fn set_message(&mut self, msg: &str) { + self.msg = msg.to_string(); + self.tick(); + } + + pub fn println>(&mut self, msg: I) { + self.clear(); + println!("{}", msg.into()); + self.tick(); + } + + pub fn inc(&mut self, delta: u64) { + self.pos += delta; + if self.len == 0 { + self.set_value(0.0) + } else { + self.set_value((self.pos as f32) / (self.len as f32)) + } + self.tick(); + } + + pub fn finish_and_clear(&mut self) { + self.clear(); + } + + pub fn tick(&mut self) { + if self.hidden { + return; + } + + let progress_block: usize = (self.curr * Self::BLOCK_COUNT as f32) as usize; + let percent = (self.curr * 100.0) as u32; + let text = format!( + "[{}{}] {:3>}% {} {}", + "#".repeat(progress_block), + "-".repeat(Self::BLOCK_COUNT - progress_block), + percent, + Self::ANIMATION.chars().nth(self.anim).unwrap(), + self.msg, + ); + + self.anim = (self.anim + 1) % Self::ANIMATION.len(); + self.update_text(&text); + } + + fn update_text(&mut self, text: &str) { + // Get length of common portion + let mut common_prefix_length = 0; + let common_length = usize::min(self.text.len(), text.len()); + + while common_prefix_length < common_length + && text.chars().nth(common_prefix_length).unwrap() + == self.text.chars().nth(common_prefix_length).unwrap() + { + common_prefix_length += 1; + } + + // Backtrack to the first differing character + let mut output = String::new(); + output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); + // Output new suffix + output += &text[common_prefix_length..text.len()]; + + // If the new text is shorter than the old one: delete overlapping characters + if let Some(overlap_count) = self.text.len().checked_sub(text.len()) { + if overlap_count > 0 { + output += &" ".repeat(overlap_count); + output += &"\x08".repeat(overlap_count); + } + } + + let _ = std::io::stdout().write(output.as_bytes()); + let _ = std::io::stdout().flush(); + self.text = text.to_string(); + } + + fn set_value(&mut self, value: f32) { + self.curr = f32::max(0.0, f32::min(1.0, value)); + } + + fn clear(&mut self) { + print!("{}", "\x08".repeat(self.text.len())); + self.text = String::new(); + } +} -- cgit v1.2.3 From 97f6f141ee06ddfb22f8c02223fa71102b670528 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 27 Nov 2019 04:16:03 +0800 Subject: Remove commented code --- crates/ra_cli/src/analysis_stats.rs | 3 --- 1 file changed, 3 deletions(-) (limited to 'crates') diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs index 8bf3863bb..122aa0552 100644 --- a/crates/ra_cli/src/analysis_stats.rs +++ b/crates/ra_cli/src/analysis_stats.rs @@ -80,9 +80,6 @@ pub fn run( Verbosity::Quiet => ProgressBar::hidden(), }; - // bar.set_style( - // indicatif::ProgressStyle::default_bar().template("{wide_bar} {pos}/{len}\n{msg}"), - // ); bar.tick(); let mut num_exprs = 0; let mut num_exprs_unknown = 0; -- cgit v1.2.3 From 27943bead6798cd202d7398f38cecfd3f5840645 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 27 Nov 2019 04:33:40 +0800 Subject: Remove progress bar and add a true counter --- crates/ra_cli/src/analysis_stats.rs | 6 +- crates/ra_cli/src/main.rs | 2 +- crates/ra_cli/src/progress_bar.rs | 130 ----------------------------------- crates/ra_cli/src/progress_report.rs | 113 ++++++++++++++++++++++++++++++ 4 files changed, 117 insertions(+), 134 deletions(-) delete mode 100644 crates/ra_cli/src/progress_bar.rs create mode 100644 crates/ra_cli/src/progress_report.rs (limited to 'crates') diff --git a/crates/ra_cli/src/analysis_stats.rs b/crates/ra_cli/src/analysis_stats.rs index 122aa0552..9b1802a5f 100644 --- a/crates/ra_cli/src/analysis_stats.rs +++ b/crates/ra_cli/src/analysis_stats.rs @@ -6,7 +6,7 @@ use ra_db::SourceDatabaseExt; use ra_hir::{AssocItem, Crate, HasSource, HirDisplay, ModuleDef, Ty, TypeWalk}; use ra_syntax::AstNode; -use crate::{progress_bar::ProgressBar, Result, Verbosity}; +use crate::{progress_report::ProgressReport, Result, Verbosity}; pub fn run( verbosity: Verbosity, @@ -76,8 +76,8 @@ pub fn run( let inference_time = Instant::now(); let mut bar = match verbosity { - Verbosity::Verbose | Verbosity::Normal => ProgressBar::new(funcs.len() as u64), - Verbosity::Quiet => ProgressBar::hidden(), + Verbosity::Verbose | Verbosity::Normal => ProgressReport::new(funcs.len() as u64), + Verbosity::Quiet => ProgressReport::hidden(), }; bar.tick(); diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index c23d92ec2..08f353147 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -3,7 +3,7 @@ mod analysis_stats; mod analysis_bench; mod help; -mod progress_bar; +mod progress_report; use std::{error::Error, fmt::Write, io::Read}; diff --git a/crates/ra_cli/src/progress_bar.rs b/crates/ra_cli/src/progress_bar.rs deleted file mode 100644 index 95bfcf4d0..000000000 --- a/crates/ra_cli/src/progress_bar.rs +++ /dev/null @@ -1,130 +0,0 @@ -//! A simple progress bar -//! -//! A single thread non-optimized progress bar -use std::io::Write; - -/// A Simple ASCII Progress Bar -pub struct ProgressBar { - curr: f32, - text: String, - anim: usize, - hidden: bool, - - len: u64, - pos: u64, - msg: String, -} - -impl ProgressBar { - const ANIMATION: &'static str = r#"|/-\"#; - const BLOCK_COUNT: usize = 20; - - pub fn new(len: u64) -> ProgressBar { - ProgressBar { - curr: 0.0, - text: String::new(), - anim: 0, - hidden: false, - len, - pos: 0, - msg: String::new(), - } - } - - pub fn hidden() -> ProgressBar { - ProgressBar { - curr: 0.0, - text: String::new(), - anim: 0, - hidden: true, - len: 0, - pos: 0, - msg: String::new(), - } - } - - pub fn set_message(&mut self, msg: &str) { - self.msg = msg.to_string(); - self.tick(); - } - - pub fn println>(&mut self, msg: I) { - self.clear(); - println!("{}", msg.into()); - self.tick(); - } - - pub fn inc(&mut self, delta: u64) { - self.pos += delta; - if self.len == 0 { - self.set_value(0.0) - } else { - self.set_value((self.pos as f32) / (self.len as f32)) - } - self.tick(); - } - - pub fn finish_and_clear(&mut self) { - self.clear(); - } - - pub fn tick(&mut self) { - if self.hidden { - return; - } - - let progress_block: usize = (self.curr * Self::BLOCK_COUNT as f32) as usize; - let percent = (self.curr * 100.0) as u32; - let text = format!( - "[{}{}] {:3>}% {} {}", - "#".repeat(progress_block), - "-".repeat(Self::BLOCK_COUNT - progress_block), - percent, - Self::ANIMATION.chars().nth(self.anim).unwrap(), - self.msg, - ); - - self.anim = (self.anim + 1) % Self::ANIMATION.len(); - self.update_text(&text); - } - - fn update_text(&mut self, text: &str) { - // Get length of common portion - let mut common_prefix_length = 0; - let common_length = usize::min(self.text.len(), text.len()); - - while common_prefix_length < common_length - && text.chars().nth(common_prefix_length).unwrap() - == self.text.chars().nth(common_prefix_length).unwrap() - { - common_prefix_length += 1; - } - - // Backtrack to the first differing character - let mut output = String::new(); - output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); - // Output new suffix - output += &text[common_prefix_length..text.len()]; - - // If the new text is shorter than the old one: delete overlapping characters - if let Some(overlap_count) = self.text.len().checked_sub(text.len()) { - if overlap_count > 0 { - output += &" ".repeat(overlap_count); - output += &"\x08".repeat(overlap_count); - } - } - - let _ = std::io::stdout().write(output.as_bytes()); - let _ = std::io::stdout().flush(); - self.text = text.to_string(); - } - - fn set_value(&mut self, value: f32) { - self.curr = f32::max(0.0, f32::min(1.0, value)); - } - - fn clear(&mut self) { - print!("{}", "\x08".repeat(self.text.len())); - self.text = String::new(); - } -} diff --git a/crates/ra_cli/src/progress_report.rs b/crates/ra_cli/src/progress_report.rs new file mode 100644 index 000000000..6ce0d4cab --- /dev/null +++ b/crates/ra_cli/src/progress_report.rs @@ -0,0 +1,113 @@ +//! A simple progress bar +//! +//! A single thread non-optimized progress bar +use std::io::Write; + +/// A Simple ASCII Progress Bar +pub struct ProgressReport { + curr: f32, + text: String, + hidden: bool, + + len: u64, + pos: u64, + msg: String, +} + +impl ProgressReport { + pub fn new(len: u64) -> ProgressReport { + ProgressReport { + curr: 0.0, + text: String::new(), + hidden: false, + len, + pos: 0, + msg: String::new(), + } + } + + pub fn hidden() -> ProgressReport { + ProgressReport { + curr: 0.0, + text: String::new(), + hidden: true, + len: 0, + pos: 0, + msg: String::new(), + } + } + + pub fn set_message(&mut self, msg: &str) { + self.msg = msg.to_string(); + self.tick(); + } + + pub fn println>(&mut self, msg: I) { + self.clear(); + println!("{}", msg.into()); + self.tick(); + } + + pub fn inc(&mut self, delta: u64) { + self.pos += delta; + if self.len == 0 { + self.set_value(0.0) + } else { + self.set_value((self.pos as f32) / (self.len as f32)) + } + self.tick(); + } + + pub fn finish_and_clear(&mut self) { + self.clear(); + } + + pub fn tick(&mut self) { + if self.hidden { + return; + } + let percent = (self.curr * 100.0) as u32; + let text = format!("{}/{} {:3>}% {}", self.pos, self.len, percent, self.msg); + self.update_text(&text); + } + + fn update_text(&mut self, text: &str) { + // Get length of common portion + let mut common_prefix_length = 0; + let common_length = usize::min(self.text.len(), text.len()); + + while common_prefix_length < common_length + && text.chars().nth(common_prefix_length).unwrap() + == self.text.chars().nth(common_prefix_length).unwrap() + { + common_prefix_length += 1; + } + + // Backtrack to the first differing character + let mut output = String::new(); + output += &'\x08'.to_string().repeat(self.text.len() - common_prefix_length); + // Output new suffix + output += &text[common_prefix_length..text.len()]; + + // If the new text is shorter than the old one: delete overlapping characters + if let Some(overlap_count) = self.text.len().checked_sub(text.len()) { + if overlap_count > 0 { + output += &" ".repeat(overlap_count); + output += &"\x08".repeat(overlap_count); + } + } + + let _ = std::io::stdout().write(output.as_bytes()); + let _ = std::io::stdout().flush(); + self.text = text.to_string(); + } + + fn set_value(&mut self, value: f32) { + self.curr = f32::max(0.0, f32::min(1.0, value)); + } + + fn clear(&mut self) { + print!("{}{}", " ".repeat(self.text.len()), "\x08".repeat(self.text.len())); + self.text = String::new(); + } +} -- cgit v1.2.3 From f0aaf3b2964a26a08be13dcf04f31d5bfe46b2f3 Mon Sep 17 00:00:00 2001 From: Edwin Cheng Date: Wed, 27 Nov 2019 05:01:13 +0800 Subject: Fill all last text to space and return the cursor --- crates/ra_cli/src/progress_report.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'crates') diff --git a/crates/ra_cli/src/progress_report.rs b/crates/ra_cli/src/progress_report.rs index 6ce0d4cab..31867a1e9 100644 --- a/crates/ra_cli/src/progress_report.rs +++ b/crates/ra_cli/src/progress_report.rs @@ -107,7 +107,14 @@ impl ProgressReport { } fn clear(&mut self) { - print!("{}{}", " ".repeat(self.text.len()), "\x08".repeat(self.text.len())); + if self.hidden { + return; + } + + // Fill all last text to space and return the cursor + let spaces = " ".repeat(self.text.len()); + let backspaces = "\x08".repeat(self.text.len()); + print!("{}{}{}", backspaces, spaces, backspaces); self.text = String::new(); } } -- cgit v1.2.3 From aa45561183493f274b78ca6be6b841bbc4b29e0d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 09:42:55 +0300 Subject: Crate -> CrateId --- crates/ra_hir/src/db.rs | 14 +++++++------- crates/ra_hir/src/ty.rs | 13 +++++-------- crates/ra_hir/src/ty/traits.rs | 10 +++++----- crates/ra_hir/src/ty/traits/chalk.rs | 35 ++++++++++++++++++++--------------- 4 files changed, 37 insertions(+), 35 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 31b21ca84..17cb63868 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -13,7 +13,7 @@ use crate::{ CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, ValueTyDefId, }, - Crate, DefWithBody, ImplBlock, + DefWithBody, ImplBlock, }; pub use hir_def::db::{ @@ -71,7 +71,7 @@ pub trait HirDatabase: DefDatabase { /// Mutex and the query does an untracked read internally, to make sure the /// cached state is thrown away when input facts change. #[salsa::invoke(crate::ty::traits::trait_solver_query)] - fn trait_solver(&self, krate: Crate) -> crate::ty::traits::TraitSolver; + fn trait_solver(&self, krate: CrateId) -> crate::ty::traits::TraitSolver; // Interned IDs for Chalk integration #[salsa::interned] @@ -93,35 +93,35 @@ pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::ty::traits::chalk::trait_datum_query)] fn trait_datum( &self, - krate: Crate, + krate: CrateId, trait_id: chalk_ir::TraitId, ) -> Arc>; #[salsa::invoke(crate::ty::traits::chalk::struct_datum_query)] fn struct_datum( &self, - krate: Crate, + krate: CrateId, struct_id: chalk_ir::StructId, ) -> Arc>; #[salsa::invoke(crate::ty::traits::chalk::impl_datum_query)] fn impl_datum( &self, - krate: Crate, + krate: CrateId, impl_id: chalk_ir::ImplId, ) -> Arc>; #[salsa::invoke(crate::ty::traits::chalk::associated_ty_value_query)] fn associated_ty_value( &self, - krate: Crate, + krate: CrateId, id: chalk_rust_ir::AssociatedTyValueId, ) -> Arc>; #[salsa::invoke(crate::ty::traits::trait_solve_query)] fn trait_solve( &self, - krate: Crate, + krate: CrateId, goal: crate::ty::Canonical>, ) -> Option; } diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index 791b6064a..a26776b26 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -23,13 +23,12 @@ use hir_def::{ GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, }; use hir_expand::name::Name; -use ra_db::{impl_intern_key, salsa}; +use ra_db::{impl_intern_key, salsa, CrateId}; use crate::{ db::HirDatabase, ty::primitive::{FloatTy, IntTy, Uncertain}, util::make_mut_slice, - Crate, }; use display::{HirDisplay, HirFormatter}; @@ -162,7 +161,7 @@ impl TypeCtor { } } - pub fn krate(self, db: &impl HirDatabase) -> Option { + pub fn krate(self, db: &impl HirDatabase) -> Option { match self { TypeCtor::Bool | TypeCtor::Char @@ -178,11 +177,9 @@ impl TypeCtor { | TypeCtor::Tuple { .. } => None, // Closure's krate is irrelevant for coherence I would think? TypeCtor::Closure { .. } => None, - TypeCtor::Adt(adt) => Some(adt.module(db).krate.into()), - TypeCtor::FnDef(callable) => Some(callable.krate(db).into()), - TypeCtor::AssociatedType(type_alias) => { - Some(type_alias.lookup(db).module(db).krate.into()) - } + TypeCtor::Adt(adt) => Some(adt.module(db).krate), + TypeCtor::FnDef(callable) => Some(callable.krate(db)), + TypeCtor::AssociatedType(type_alias) => Some(type_alias.lookup(db).module(db).krate), } } diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 637e21e9c..39b489a4c 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -8,7 +8,7 @@ use ra_db::{impl_intern_key, salsa, CrateId}; use ra_prof::profile; use rustc_hash::FxHashSet; -use crate::{db::HirDatabase, Crate, ImplBlock, TypeAlias}; +use crate::{db::HirDatabase, ImplBlock, TypeAlias}; use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; @@ -18,7 +18,7 @@ pub(crate) mod chalk; #[derive(Debug, Clone)] pub struct TraitSolver { - krate: Crate, + krate: CrateId, inner: Arc>>, } @@ -60,12 +60,12 @@ const CHALK_SOLVER_MAX_SIZE: usize = 4; #[derive(Debug, Copy, Clone)] struct ChalkContext<'a, DB> { db: &'a DB, - krate: Crate, + krate: CrateId, } pub(crate) fn trait_solver_query( db: &(impl HirDatabase + salsa::Database), - krate: Crate, + krate: CrateId, ) -> TraitSolver { db.salsa_runtime().report_untracked_read(); // krate parameter is just so we cache a unique solver per crate @@ -176,7 +176,7 @@ impl TypeWalk for ProjectionPredicate { /// Solve a trait goal using Chalk. pub(crate) fn trait_solve_query( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, goal: Canonical>, ) -> Option { let _p = profile("trait_solve_query"); diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index d879382a0..49fa95508 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -8,6 +8,7 @@ use chalk_ir::{ TypeKindId, TypeName, UniverseIndex, }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; +use ra_db::CrateId; use hir_def::{ lang_item::LangItemTarget, AstItemDef, ContainerId, GenericDefId, Lookup, TraitId, TypeAliasId, @@ -21,7 +22,7 @@ use crate::{ db::HirDatabase, ty::display::HirDisplay, ty::{ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, - Crate, ImplBlock, TypeAlias, + ImplBlock, TypeAlias, }; /// This represents a trait whose name we could not resolve. @@ -448,7 +449,7 @@ where let trait_: TraitId = from_chalk(self.db, trait_id); let mut result: Vec<_> = self .db - .impls_for_trait(self.krate.crate_id, trait_.into()) + .impls_for_trait(self.krate, trait_.into()) .iter() .copied() .map(Impl::ImplBlock) @@ -487,7 +488,7 @@ where &self, id: chalk_rust_ir::AssociatedTyValueId, ) -> Arc> { - self.db.associated_ty_value(self.krate, id) + self.db.associated_ty_value(self.krate.into(), id) } fn custom_clauses(&self) -> Vec> { vec![] @@ -528,7 +529,7 @@ pub(crate) fn associated_ty_data_query( pub(crate) fn trait_datum_query( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, trait_id: chalk_ir::TraitId, ) -> Arc> { debug!("trait_datum {:?}", trait_id); @@ -557,7 +558,7 @@ pub(crate) fn trait_datum_query( let bound_vars = Substs::bound_vars(&generic_params); let flags = chalk_rust_ir::TraitFlags { auto: trait_data.auto, - upstream: trait_.module(db).krate != krate.crate_id, + upstream: trait_.module(db).krate != krate, non_enumerable: true, coinductive: false, // only relevant for Chalk testing // FIXME set these flags correctly @@ -579,7 +580,7 @@ pub(crate) fn trait_datum_query( pub(crate) fn struct_datum_query( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, struct_id: chalk_ir::StructId, ) -> Arc> { debug!("struct_datum {:?}", struct_id); @@ -611,7 +612,7 @@ pub(crate) fn struct_datum_query( pub(crate) fn impl_datum_query( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, impl_id: ImplId, ) -> Arc> { let _p = ra_prof::profile("impl_datum"); @@ -626,7 +627,7 @@ pub(crate) fn impl_datum_query( fn impl_block_datum( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, impl_id: ImplId, impl_block: ImplBlock, ) -> Option>> { @@ -634,7 +635,7 @@ fn impl_block_datum( let bound_vars = Substs::bound_vars(&generic_params); let trait_ref = impl_block.target_trait_ref(db)?.subst(&bound_vars); let trait_ = trait_ref.trait_; - let impl_type = if impl_block.krate(db) == krate { + let impl_type = if impl_block.krate(db).crate_id == krate { chalk_rust_ir::ImplType::Local } else { chalk_rust_ir::ImplType::External @@ -698,7 +699,7 @@ fn invalid_impl_datum() -> Arc> { fn closure_fn_trait_impl_datum( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, data: super::ClosureFnTraitImplData, ) -> Option>> { // for some closure |X, Y| -> Z: @@ -755,7 +756,7 @@ fn closure_fn_trait_impl_datum( pub(crate) fn associated_ty_value_query( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, id: chalk_rust_ir::AssociatedTyValueId, ) -> Arc> { let data: AssocTyValue = from_chalk(db, id); @@ -771,7 +772,7 @@ pub(crate) fn associated_ty_value_query( fn type_alias_associated_ty_value( db: &impl HirDatabase, - _krate: Crate, + _krate: CrateId, type_alias: TypeAlias, ) -> Arc> { let impl_block = type_alias.impl_block(db).expect("assoc ty value should be in impl"); @@ -798,7 +799,7 @@ fn type_alias_associated_ty_value( fn closure_fn_trait_output_assoc_ty_value( db: &impl HirDatabase, - krate: Crate, + krate: CrateId, data: super::ClosureFnTraitImplData, ) -> Arc> { let impl_id = Impl::ClosureFnTraitImpl(data.clone()).to_chalk(db); @@ -831,8 +832,12 @@ fn closure_fn_trait_output_assoc_ty_value( Arc::new(value) } -fn get_fn_trait(db: &impl HirDatabase, krate: Crate, fn_trait: super::FnTrait) -> Option { - let target = db.lang_item(krate.crate_id, fn_trait.lang_item_name().into())?; +fn get_fn_trait( + db: &impl HirDatabase, + krate: CrateId, + fn_trait: super::FnTrait, +) -> Option { + let target = db.lang_item(krate, fn_trait.lang_item_name().into())?; match target { LangItemTarget::TraitId(t) => Some(t), _ => None, -- cgit v1.2.3 From a306531e6aa7995145dc041166f68ea950aca1a8 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 11:40:10 +0300 Subject: Decouple --- crates/ra_hir/src/ty/method_resolution.rs | 2 +- crates/ra_hir/src/ty/traits.rs | 6 ++--- crates/ra_hir/src/ty/traits/chalk.rs | 40 +++++++++++++++++++------------ 3 files changed, 29 insertions(+), 19 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 92645e2a5..7df2649c9 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -62,7 +62,7 @@ impl CrateImplBlocks { let impl_data = db.impl_data(impl_id); let resolver = impl_id.resolver(db); - let target_ty = { Ty::from_hir(db, &resolver, &impl_data.target_type) }; + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); match &impl_data.target_trait { Some(trait_ref) => { diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 39b489a4c..99afeb35f 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -2,13 +2,13 @@ use std::sync::{Arc, Mutex}; use chalk_ir::{cast::Cast, family::ChalkIr}; -use hir_def::{expr::ExprId, DefWithBodyId, TraitId}; +use hir_def::{expr::ExprId, DefWithBodyId, TraitId, TypeAliasId}; use log::debug; use ra_db::{impl_intern_key, salsa, CrateId}; use ra_prof::profile; use rustc_hash::FxHashSet; -use crate::{db::HirDatabase, ImplBlock, TypeAlias}; +use crate::{db::HirDatabase, ImplBlock}; use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; @@ -317,7 +317,7 @@ impl_intern_key!(GlobalImplId); #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AssocTyValue { /// A normal assoc type value from an impl block. - TypeAlias(TypeAlias), + TypeAlias(TypeAliasId), /// The output type of the Fn trait implementation. ClosureFnTraitImplOutput(ClosureFnTraitImplData), } diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 49fa95508..76ff6f67f 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -11,7 +11,8 @@ use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum use ra_db::CrateId; use hir_def::{ - lang_item::LangItemTarget, AstItemDef, ContainerId, GenericDefId, Lookup, TraitId, TypeAliasId, + lang_item::LangItemTarget, resolver::HasResolver, AstItemDef, ContainerId, GenericDefId, + Lookup, TraitId, TypeAliasId, }; use hir_expand::name; @@ -22,7 +23,7 @@ use crate::{ db::HirDatabase, ty::display::HirDisplay, ty::{ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, - ImplBlock, TypeAlias, + ImplBlock, }; /// This represents a trait whose name we could not resolve. @@ -670,7 +671,7 @@ fn impl_block_datum( // don't include associated types that don't exist in the trait trait_data.associated_type_by_name(&type_alias.name(db)).is_some() }) - .map(|type_alias| AssocTyValue::TypeAlias(type_alias).to_chalk(db)) + .map(|type_alias| AssocTyValue::TypeAlias(type_alias.id).to_chalk(db)) .collect(); debug!("impl_datum: {:?}", impl_datum_bound); let impl_datum = ImplDatum { @@ -773,24 +774,33 @@ pub(crate) fn associated_ty_value_query( fn type_alias_associated_ty_value( db: &impl HirDatabase, _krate: CrateId, - type_alias: TypeAlias, + type_alias: TypeAliasId, ) -> Arc> { - let impl_block = type_alias.impl_block(db).expect("assoc ty value should be in impl"); - let impl_id = Impl::ImplBlock(impl_block).to_chalk(db); - let trait_ = impl_block - .target_trait_ref(db) - .expect("assoc ty value should not exist") // we don't return any assoc ty values if the impl'd trait can't be resolved - .trait_; + let type_alias_data = db.type_alias_data(type_alias); + let impl_id = match type_alias.lookup(db).container { + ContainerId::ImplId(it) => it, + _ => panic!("assoc ty value should be in impl"), + }; + + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + let target_trait = impl_data + .target_trait + .as_ref() + .and_then(|trait_ref| TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty))) + .expect("assoc ty value should not exist"); // we don't return any assoc ty values if the impl'd trait can't be resolved + let assoc_ty = db - .trait_data(trait_) - .associated_type_by_name(&type_alias.name(db)) + .trait_data(target_trait.trait_) + .associated_type_by_name(&type_alias_data.name) .expect("assoc ty value should not exist"); // validated when building the impl data as well - let generic_params = db.generic_params(impl_block.id.into()); + let generic_params = db.generic_params(impl_id.into()); let bound_vars = Substs::bound_vars(&generic_params); - let ty = db.ty(type_alias.id.into()).subst(&bound_vars); + let ty = db.ty(type_alias.into()).subst(&bound_vars); let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: ty.to_chalk(db) }; let value = chalk_rust_ir::AssociatedTyValue { - impl_id, + impl_id: Impl::ImplBlock(impl_id.into()).to_chalk(db), associated_ty_id: assoc_ty.to_chalk(db), value: make_binders(value_bound, bound_vars.len()), }; -- cgit v1.2.3 From 3a0929fca7a52605526c6f89be4e3e86c5d0359d Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 12:02:54 +0300 Subject: Decouple --- crates/ra_hir/src/db.rs | 6 +++--- crates/ra_hir/src/ty/infer/coerce.rs | 21 +++++++++++++++++---- crates/ra_hir/src/ty/traits.rs | 6 +++--- crates/ra_hir/src/ty/traits/chalk.rs | 2 +- 4 files changed, 24 insertions(+), 11 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index 17cb63868..a5bfef91f 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -2,7 +2,7 @@ use std::sync::Arc; -use hir_def::{GenericDefId, LocalStructFieldId, TraitId, VariantId}; +use hir_def::{GenericDefId, ImplId, LocalStructFieldId, TraitId, VariantId}; use ra_arena::map::ArenaMap; use ra_db::{salsa, CrateId}; @@ -13,7 +13,7 @@ use crate::{ CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, ValueTyDefId, }, - DefWithBody, ImplBlock, + DefWithBody, }; pub use hir_def::db::{ @@ -63,7 +63,7 @@ pub trait HirDatabase: DefDatabase { fn impls_in_crate(&self, krate: CrateId) -> Arc; #[salsa::invoke(crate::ty::traits::impls_for_trait_query)] - fn impls_for_trait(&self, krate: CrateId, trait_: TraitId) -> Arc<[ImplBlock]>; + fn impls_for_trait(&self, krate: CrateId, trait_: TraitId) -> Arc<[ImplId]>; /// This provides the Chalk trait solver instance. Because Chalk always /// works from a specific crate, this query is keyed on the crate; and diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs index 5ed4470af..cf45ede7c 100644 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ b/crates/ra_hir/src/ty/infer/coerce.rs @@ -4,13 +4,17 @@ //! //! See: https://doc.rust-lang.org/nomicon/coercions.html -use hir_def::{lang_item::LangItemTarget, resolver::Resolver, AdtId}; +use hir_def::{ + lang_item::LangItemTarget, + resolver::{HasResolver, Resolver}, + AdtId, +}; use rustc_hash::FxHashMap; use test_utils::tested_by; use crate::{ db::HirDatabase, - ty::{autoderef, Substs, Ty, TypeCtor, TypeWalk}, + ty::{autoderef, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, Mutability, }; @@ -57,9 +61,18 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { impls .iter() - .filter_map(|impl_block| { + .filter_map(|&impl_id| { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + // `CoerseUnsized` has one generic parameter for the target type. - let trait_ref = impl_block.target_trait_ref(db)?; + let trait_ref = TraitRef::from_hir( + db, + &resolver, + impl_data.target_trait.as_ref()?, + Some(target_ty), + )?; let cur_from_ty = trait_ref.substs.0.get(0)?; let cur_to_ty = trait_ref.substs.0.get(1)?; diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 99afeb35f..93cb32869 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -2,7 +2,7 @@ use std::sync::{Arc, Mutex}; use chalk_ir::{cast::Cast, family::ChalkIr}; -use hir_def::{expr::ExprId, DefWithBodyId, TraitId, TypeAliasId}; +use hir_def::{expr::ExprId, DefWithBodyId, ImplId, TraitId, TypeAliasId}; use log::debug; use ra_db::{impl_intern_key, salsa, CrateId}; use ra_prof::profile; @@ -79,7 +79,7 @@ pub(crate) fn impls_for_trait_query( db: &impl HirDatabase, krate: CrateId, trait_: TraitId, -) -> Arc<[ImplBlock]> { +) -> Arc<[ImplId]> { let mut impls = FxHashSet::default(); // We call the query recursively here. On the one hand, this means we can // reuse results from queries for different crates; on the other hand, this @@ -90,7 +90,7 @@ pub(crate) fn impls_for_trait_query( impls.extend(db.impls_for_trait(dep.crate_id, trait_).iter()); } let crate_impl_blocks = db.impls_in_crate(krate); - impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_).map(ImplBlock::from)); + impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_)); impls.into_iter().collect() } diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 76ff6f67f..7b2e530a2 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -453,7 +453,7 @@ where .impls_for_trait(self.krate, trait_.into()) .iter() .copied() - .map(Impl::ImplBlock) + .map(|it| Impl::ImplBlock(it.into())) .map(|impl_| impl_.to_chalk(self.db)) .collect(); -- cgit v1.2.3 From e91ebfc752bdfa8fc20be6ea97a14aa6a4d897ae Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 12:13:07 +0300 Subject: Cleanup imports --- crates/ra_hir/src/ty/infer/coerce.rs | 2 +- crates/ra_hir/src/ty/infer/expr.rs | 5 ++--- crates/ra_hir/src/ty/infer/pat.rs | 10 +++++++--- crates/ra_hir/src/ty/infer/path.rs | 5 +++-- crates/ra_hir/src/ty/infer/unify.rs | 12 +++++++----- 5 files changed, 20 insertions(+), 14 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs index cf45ede7c..3fb5d8a83 100644 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ b/crates/ra_hir/src/ty/infer/coerce.rs @@ -7,6 +7,7 @@ use hir_def::{ lang_item::LangItemTarget, resolver::{HasResolver, Resolver}, + type_ref::Mutability, AdtId, }; use rustc_hash::FxHashMap; @@ -15,7 +16,6 @@ use test_utils::tested_by; use crate::{ db::HirDatabase, ty::{autoderef, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, - Mutability, }; use super::{InEnvironment, InferTy, InferenceContext, TypeVarValue}; diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index eb221d6bc..57f845dfa 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -5,22 +5,21 @@ use std::sync::Arc; use hir_def::{ builtin_type::Signedness, + expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, generics::GenericParams, path::{GenericArg, GenericArgs}, resolver::resolver_for_expr, AdtId, ContainerId, Lookup, StructFieldId, }; -use hir_expand::name; +use hir_expand::name::{self, Name}; use crate::{ db::HirDatabase, - expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, ty::{ autoderef, method_resolution, op, traits::InEnvironment, CallableDef, InferTy, IntTy, Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, }, - Name, }; use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; diff --git a/crates/ra_hir/src/ty/infer/pat.rs b/crates/ra_hir/src/ty/infer/pat.rs index 641d61e87..6dbf03eb2 100644 --- a/crates/ra_hir/src/ty/infer/pat.rs +++ b/crates/ra_hir/src/ty/infer/pat.rs @@ -3,14 +3,18 @@ use std::iter::repeat; use std::sync::Arc; +use hir_def::{ + expr::{BindingAnnotation, Pat, PatId, RecordFieldPat}, + path::Path, + type_ref::Mutability, +}; +use hir_expand::name::Name; use test_utils::tested_by; use super::{BindingMode, InferenceContext}; use crate::{ db::HirDatabase, - expr::{BindingAnnotation, Pat, PatId, RecordFieldPat}, - ty::{Mutability, Substs, Ty, TypeCtor, TypeWalk}, - Name, Path, + ty::{Substs, Ty, TypeCtor, TypeWalk}, }; impl<'a, D: HirDatabase> InferenceContext<'a, D> { diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs index be2067dd4..8d8b56d2d 100644 --- a/crates/ra_hir/src/ty/infer/path.rs +++ b/crates/ra_hir/src/ty/infer/path.rs @@ -1,14 +1,15 @@ //! Path expression resolution. use hir_def::{ - path::PathSegment, + path::{Path, PathSegment}, resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs}, }; +use hir_expand::name::Name; use crate::{ db::HirDatabase, ty::{method_resolution, Substs, Ty, TypeWalk, ValueTyDefId}, - AssocItem, Container, Function, Name, Path, + AssocItem, Container, Function, }; use super::{ExprOrPatId, InferenceContext, TraitRef}; diff --git a/crates/ra_hir/src/ty/infer/unify.rs b/crates/ra_hir/src/ty/infer/unify.rs index 64d9394cf..e27bb2f82 100644 --- a/crates/ra_hir/src/ty/infer/unify.rs +++ b/crates/ra_hir/src/ty/infer/unify.rs @@ -1,12 +1,14 @@ //! Unification and canonicalization logic. use super::{InferenceContext, Obligation}; -use crate::db::HirDatabase; -use crate::ty::{ - Canonical, InEnvironment, InferTy, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, - TypeWalk, +use crate::{ + db::HirDatabase, + ty::{ + Canonical, InEnvironment, InferTy, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, + TypeWalk, + }, + util::make_mut_slice, }; -use crate::util::make_mut_slice; impl<'a, D: HirDatabase> InferenceContext<'a, D> { pub(super) fn canonicalizer<'b>(&'b mut self) -> Canonicalizer<'a, 'b, D> -- cgit v1.2.3 From 825049bc6247f6d596910cd99f76f891d5435a86 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 12:31:40 +0300 Subject: Decouple --- crates/ra_hir/src/source_binder.rs | 8 +-- crates/ra_hir/src/ty/infer.rs | 6 +-- crates/ra_hir/src/ty/infer/path.rs | 86 ++++++++++++++++++------------- crates/ra_hir/src/ty/method_resolution.rs | 10 ++-- crates/ra_hir_def/src/lib.rs | 10 ++++ 5 files changed, 71 insertions(+), 49 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 9f3e6c43f..b9d3a1713 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -11,7 +11,7 @@ use hir_def::{ expr::{ExprId, PatId}, path::known, resolver::{self, resolver_for_scope, HasResolver, Resolver, TypeNs, ValueNs}, - DefWithBodyId, + AssocItemId, DefWithBodyId, }; use hir_expand::{ hygiene::Hygiene, name::AsName, AstId, HirFileId, MacroCallId, MacroFileKind, Source, @@ -380,7 +380,7 @@ impl SourceAnalyzer { name, method_resolution::LookupMode::MethodCall, |ty, it| match it { - AssocItem::Function(f) => callback(ty, f), + AssocItemId::FunctionId(f) => callback(ty, f.into()), _ => None, }, ) @@ -391,7 +391,7 @@ impl SourceAnalyzer { db: &impl HirDatabase, ty: &Type, name: Option<&Name>, - callback: impl FnMut(&Ty, AssocItem) -> Option, + mut callback: impl FnMut(&Ty, AssocItem) -> Option, ) -> Option { // There should be no inference vars in types passed here // FIXME check that? @@ -403,7 +403,7 @@ impl SourceAnalyzer { &self.resolver, name, method_resolution::LookupMode::Path, - callback, + |ty, it| callback(ty, it.into()), ) } diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index beb2efb7a..db9a8c9d1 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -26,7 +26,7 @@ use hir_def::{ path::known, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{Mutability, TypeRef}, - AdtId, DefWithBodyId, + AdtId, AssocItemId, DefWithBodyId, }; use hir_expand::{diagnostics::DiagnosticSink, name}; use ra_arena::map::ArenaMap; @@ -255,8 +255,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { self.result.variant_resolutions.insert(id, variant); } - fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: AssocItem) { - self.result.assoc_resolutions.insert(id, item); + fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: AssocItemId) { + self.result.assoc_resolutions.insert(id, item.into()); } fn write_pat_ty(&mut self, pat: PatId, ty: Ty) { diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs index 8d8b56d2d..09ff79728 100644 --- a/crates/ra_hir/src/ty/infer/path.rs +++ b/crates/ra_hir/src/ty/infer/path.rs @@ -2,14 +2,14 @@ use hir_def::{ path::{Path, PathSegment}, - resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs}, + resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, + AssocItemId, ContainerId, Lookup, }; use hir_expand::name::Name; use crate::{ db::HirDatabase, ty::{method_resolution, Substs, Ty, TypeWalk, ValueTyDefId}, - AssocItem, Container, Function, }; use super::{ExprOrPatId, InferenceContext, TraitRef}; @@ -143,31 +143,35 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { id: ExprOrPatId, ) -> Option<(ValueNs, Option)> { let trait_ = trait_ref.trait_; - let item = - self.db.trait_data(trait_).items.iter().map(|(_name, id)| (*id).into()).find_map( - |item| match item { - AssocItem::Function(func) => { - if segment.name == func.name(self.db) { - Some(AssocItem::Function(func)) - } else { - None - } + let item = self + .db + .trait_data(trait_) + .items + .iter() + .map(|(_name, id)| (*id).into()) + .find_map(|item| match item { + AssocItemId::FunctionId(func) => { + if segment.name == self.db.function_data(func).name { + Some(AssocItemId::FunctionId(func)) + } else { + None } + } - AssocItem::Const(konst) => { - if konst.name(self.db).map_or(false, |n| n == segment.name) { - Some(AssocItem::Const(konst)) - } else { - None - } + AssocItemId::ConstId(konst) => { + if self.db.const_data(konst).name.as_ref().map_or(false, |n| n == &segment.name) + { + Some(AssocItemId::ConstId(konst)) + } else { + None } - AssocItem::TypeAlias(_) => None, - }, - )?; + } + AssocItemId::TypeAliasId(_) => None, + })?; let def = match item { - AssocItem::Function(f) => ValueNs::FunctionId(f.id), - AssocItem::Const(c) => ValueNs::ConstId(c.id), - AssocItem::TypeAlias(_) => unreachable!(), + AssocItemId::FunctionId(f) => ValueNs::FunctionId(f), + AssocItemId::ConstId(c) => ValueNs::ConstId(c), + AssocItemId::TypeAliasId(_) => unreachable!(), }; let substs = Substs::build_for_def(self.db, item) .use_parent_substs(&trait_ref.substs) @@ -197,16 +201,18 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Some(name), method_resolution::LookupMode::Path, move |_ty, item| { - let def = match item { - AssocItem::Function(f) => ValueNs::FunctionId(f.id), - AssocItem::Const(c) => ValueNs::ConstId(c.id), - AssocItem::TypeAlias(_) => unreachable!(), + let (def, container) = match item { + AssocItemId::FunctionId(f) => { + (ValueNs::FunctionId(f), f.lookup(self.db).container) + } + AssocItemId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db).container), + AssocItemId::TypeAliasId(_) => unreachable!(), }; - let substs = match item.container(self.db) { - Container::ImplBlock(_) => self.find_self_types(&def, ty.clone()), - Container::Trait(t) => { + let substs = match container { + ContainerId::ImplId(_) => self.find_self_types(&def, ty.clone()), + ContainerId::TraitId(trait_) => { // we're picking this method - let trait_substs = Substs::build_for_def(self.db, t.id) + let trait_substs = Substs::build_for_def(self.db, trait_) .push(ty.clone()) .fill(std::iter::repeat_with(|| self.new_type_var())) .build(); @@ -215,29 +221,35 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { .fill_with_params() .build(); self.obligations.push(super::Obligation::Trait(TraitRef { - trait_: t.id, + trait_, substs: trait_substs, })); Some(substs) } + ContainerId::ModuleId(_) => None, }; - self.write_assoc_resolution(id, item); + self.write_assoc_resolution(id, item.into()); Some((def, substs)) }, ) } fn find_self_types(&self, def: &ValueNs, actual_def_ty: Ty) -> Option { - if let ValueNs::FunctionId(func) = def { - let func = Function::from(*func); + if let ValueNs::FunctionId(func) = *def { // We only do the infer if parent has generic params - let gen = self.db.generic_params(func.id.into()); + let gen = self.db.generic_params(func.into()); if gen.count_parent_params() == 0 { return None; } - let impl_block = func.impl_block(self.db)?.target_ty(self.db); + let impl_id = match func.lookup(self.db).container { + ContainerId::ImplId(it) => it, + _ => return None, + }; + let resolver = impl_id.resolver(self.db); + let impl_data = self.db.impl_data(impl_id); + let impl_block = Ty::from_hir(self.db, &resolver, &impl_data.target_type); let impl_block_substs = impl_block.substs()?; let actual_substs = actual_def_ty.substs()?; diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 7df2649c9..02e81fb34 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -18,7 +18,7 @@ use crate::{ db::HirDatabase, ty::primitive::{FloatBitness, Uncertain}, ty::{utils::all_super_traits, Ty, TypeCtor}, - AssocItem, Function, + Function, }; use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; @@ -157,7 +157,7 @@ pub(crate) fn lookup_method( ) -> Option<(Ty, Function)> { iterate_method_candidates(ty, db, resolver, Some(name), LookupMode::MethodCall, |ty, f| match f { - AssocItem::Function(f) => Some((ty.clone(), f)), + AssocItemId::FunctionId(f) => Some((ty.clone(), f.into())), _ => None, }) } @@ -183,7 +183,7 @@ pub(crate) fn iterate_method_candidates( resolver: &Resolver, name: Option<&Name>, mode: LookupMode, - mut callback: impl FnMut(&Ty, AssocItem) -> Option, + mut callback: impl FnMut(&Ty, AssocItemId) -> Option, ) -> Option { let krate = resolver.krate()?; match mode { @@ -239,7 +239,7 @@ fn iterate_trait_method_candidates( resolver: &Resolver, name: Option<&Name>, mode: LookupMode, - mut callback: impl FnMut(&Ty, AssocItem) -> Option, + mut callback: impl FnMut(&Ty, AssocItemId) -> Option, ) -> Option { let krate = resolver.krate()?; // FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that) @@ -285,7 +285,7 @@ fn iterate_inherent_methods( name: Option<&Name>, mode: LookupMode, krate: CrateId, - mut callback: impl FnMut(&Ty, AssocItem) -> Option, + mut callback: impl FnMut(&Ty, AssocItemId) -> Option, ) -> Option { for krate in ty.value.def_crates(db, krate)? { let impls = db.impls_in_crate(krate); diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 274dd1467..ea3f00bb3 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -398,6 +398,16 @@ impl_froms!( ConstId ); +impl From for GenericDefId { + fn from(item: AssocItemId) -> Self { + match item { + AssocItemId::FunctionId(f) => f.into(), + AssocItemId::ConstId(c) => c.into(), + AssocItemId::TypeAliasId(t) => t.into(), + } + } +} + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum AttrDefId { ModuleId(ModuleId), -- cgit v1.2.3 From 1fe9656ba4fdb1369153d4f0f6a7c8bfea6bfe08 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 12:47:18 +0300 Subject: Decouple --- crates/ra_hir/src/ty/traits.rs | 4 +-- crates/ra_hir/src/ty/traits/chalk.rs | 52 +++++++++++++++++++++--------------- 2 files changed, 32 insertions(+), 24 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs index 93cb32869..76189a60b 100644 --- a/crates/ra_hir/src/ty/traits.rs +++ b/crates/ra_hir/src/ty/traits.rs @@ -8,7 +8,7 @@ use ra_db::{impl_intern_key, salsa, CrateId}; use ra_prof::profile; use rustc_hash::FxHashSet; -use crate::{db::HirDatabase, ImplBlock}; +use crate::db::HirDatabase; use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; @@ -302,7 +302,7 @@ pub struct ClosureFnTraitImplData { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Impl { /// A normal impl from an impl block. - ImplBlock(ImplBlock), + ImplBlock(ImplId), /// Closure types implement the Fn traits synthetically. ClosureFnTraitImpl(ClosureFnTraitImplData), } diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs index 7b2e530a2..67ac5422c 100644 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ b/crates/ra_hir/src/ty/traits/chalk.rs @@ -4,15 +4,15 @@ use std::sync::Arc; use log::debug; use chalk_ir::{ - cast::Cast, family::ChalkIr, Identifier, ImplId, Parameter, PlaceholderIndex, TypeId, - TypeKindId, TypeName, UniverseIndex, + cast::Cast, family::ChalkIr, Identifier, Parameter, PlaceholderIndex, TypeId, TypeKindId, + TypeName, UniverseIndex, }; use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; use ra_db::CrateId; use hir_def::{ - lang_item::LangItemTarget, resolver::HasResolver, AstItemDef, ContainerId, GenericDefId, - Lookup, TraitId, TypeAliasId, + lang_item::LangItemTarget, resolver::HasResolver, AssocItemId, AstItemDef, ContainerId, + GenericDefId, ImplId, Lookup, TraitId, TypeAliasId, }; use hir_expand::name; @@ -23,7 +23,6 @@ use crate::{ db::HirDatabase, ty::display::HirDisplay, ty::{ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, - ImplBlock, }; /// This represents a trait whose name we could not resolve. @@ -435,14 +434,14 @@ where fn struct_datum(&self, struct_id: chalk_ir::StructId) -> Arc> { self.db.struct_datum(self.krate, struct_id) } - fn impl_datum(&self, impl_id: ImplId) -> Arc> { + fn impl_datum(&self, impl_id: chalk_ir::ImplId) -> Arc> { self.db.impl_datum(self.krate, impl_id) } fn impls_for_trait( &self, trait_id: chalk_ir::TraitId, parameters: &[Parameter], - ) -> Vec { + ) -> Vec { debug!("impls_for_trait {:?}", trait_id); if trait_id == UNKNOWN_TRAIT { return Vec::new(); @@ -614,7 +613,7 @@ pub(crate) fn struct_datum_query( pub(crate) fn impl_datum_query( db: &impl HirDatabase, krate: CrateId, - impl_id: ImplId, + impl_id: chalk_ir::ImplId, ) -> Arc> { let _p = ra_prof::profile("impl_datum"); debug!("impl_datum {:?}", impl_id); @@ -629,23 +628,31 @@ pub(crate) fn impl_datum_query( fn impl_block_datum( db: &impl HirDatabase, krate: CrateId, + chalk_id: chalk_ir::ImplId, impl_id: ImplId, - impl_block: ImplBlock, ) -> Option>> { - let generic_params = db.generic_params(impl_block.id.into()); + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + + // `CoerseUnsized` has one generic parameter for the target type. + let trait_ref = + TraitRef::from_hir(db, &resolver, impl_data.target_trait.as_ref()?, Some(target_ty))?; + + let generic_params = db.generic_params(impl_id.into()); let bound_vars = Substs::bound_vars(&generic_params); - let trait_ref = impl_block.target_trait_ref(db)?.subst(&bound_vars); + let trait_ref = trait_ref.subst(&bound_vars); let trait_ = trait_ref.trait_; - let impl_type = if impl_block.krate(db).crate_id == krate { + let impl_type = if impl_id.module(db).krate == krate { chalk_rust_ir::ImplType::Local } else { chalk_rust_ir::ImplType::External }; - let where_clauses = convert_where_clauses(db, impl_block.id.into(), &bound_vars); - let negative = impl_block.is_negative(db); + let where_clauses = convert_where_clauses(db, impl_id.into(), &bound_vars); + let negative = impl_data.is_negative; debug!( "impl {:?}: {}{} where {:?}", - impl_id, + chalk_id, if negative { "!" } else { "" }, trait_ref.display(db), where_clauses @@ -660,18 +667,19 @@ fn impl_block_datum( let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses }; let trait_data = db.trait_data(trait_); - let associated_ty_value_ids = impl_block - .items(db) - .into_iter() + let associated_ty_value_ids = impl_data + .items + .iter() .filter_map(|item| match item { - crate::AssocItem::TypeAlias(type_alias) => Some(type_alias), + AssocItemId::TypeAliasId(type_alias) => Some(*type_alias), _ => None, }) - .filter(|type_alias| { + .filter(|&type_alias| { // don't include associated types that don't exist in the trait - trait_data.associated_type_by_name(&type_alias.name(db)).is_some() + let name = &db.type_alias_data(type_alias).name; + trait_data.associated_type_by_name(name).is_some() }) - .map(|type_alias| AssocTyValue::TypeAlias(type_alias.id).to_chalk(db)) + .map(|type_alias| AssocTyValue::TypeAlias(type_alias).to_chalk(db)) .collect(); debug!("impl_datum: {:?}", impl_datum_bound); let impl_datum = ImplDatum { -- cgit v1.2.3 From d569869f7a8c7a4c23b14fadbef63d4dbc949bcd Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 15:56:20 +0300 Subject: Decouple more --- crates/ra_hir/src/code_model.rs | 2 +- crates/ra_hir/src/source_binder.rs | 6 ++-- crates/ra_hir/src/ty/infer.rs | 54 ++++++++++++++++--------------- crates/ra_hir/src/ty/infer/expr.rs | 12 +++---- crates/ra_hir/src/ty/method_resolution.rs | 7 ++-- 5 files changed, 41 insertions(+), 40 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 54da937ea..6d71bde92 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -647,7 +647,7 @@ impl Function { pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { let infer = self.infer(db); - infer.add_diagnostics(db, self, sink); + infer.add_diagnostics(db, self.id, sink); let mut validator = ExprValidator::new(self, infer, sink); validator.validate_body(db); } diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index b9d3a1713..0de36abd1 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -214,17 +214,17 @@ impl SourceAnalyzer { pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option { let expr_id = self.expr_id(&call.clone().into())?; - self.infer.as_ref()?.method_resolution(expr_id) + self.infer.as_ref()?.method_resolution(expr_id).map(Function::from) } pub fn resolve_field(&self, field: &ast::FieldExpr) -> Option { let expr_id = self.expr_id(&field.clone().into())?; - self.infer.as_ref()?.field_resolution(expr_id) + self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into()) } pub fn resolve_record_field(&self, field: &ast::RecordField) -> Option { let expr_id = self.expr_id(&field.expr()?)?; - self.infer.as_ref()?.record_field_resolution(expr_id) + self.infer.as_ref()?.record_field_resolution(expr_id).map(|it| it.into()) } pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option { diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index db9a8c9d1..7b6dfd61b 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -22,11 +22,13 @@ use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; use rustc_hash::FxHashMap; use hir_def::{ + body::Body, data::{ConstData, FunctionData}, - path::known, + expr::{BindingAnnotation, ExprId, PatId}, + path::{known, Path}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{Mutability, TypeRef}, - AdtId, AssocItemId, DefWithBodyId, + AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TypeAliasId, }; use hir_expand::{diagnostics::DiagnosticSink, name}; use ra_arena::map::ArenaMap; @@ -34,16 +36,14 @@ use ra_prof::profile; use test_utils::tested_by; use super::{ + primitive::{FloatTy, IntTy}, traits::{Guidance, Obligation, ProjectionPredicate, Solution}, ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, }; use crate::{ - code_model::TypeAlias, - db::HirDatabase, - expr::{BindingAnnotation, Body, ExprId, PatId}, - ty::infer::diagnostics::InferenceDiagnostic, - AssocItem, DefWithBody, FloatTy, Function, IntTy, Path, StructField, VariantDef, + db::HirDatabase, ty::infer::diagnostics::InferenceDiagnostic, AssocItem, DefWithBody, + VariantDef, }; macro_rules! ty_app { @@ -121,11 +121,11 @@ pub struct TypeMismatch { #[derive(Clone, PartialEq, Eq, Debug, Default)] pub struct InferenceResult { /// For each method call expr, records the function it resolves to. - method_resolutions: FxHashMap, + method_resolutions: FxHashMap, /// For each field access expr, records the field it resolves to. - field_resolutions: FxHashMap, + field_resolutions: FxHashMap, /// For each field in record literal, records the field it resolves to. - record_field_resolutions: FxHashMap, + record_field_resolutions: FxHashMap, /// For each struct literal, records the variant it resolves to. variant_resolutions: FxHashMap, /// For each associated item record what it resolves to @@ -137,13 +137,13 @@ pub struct InferenceResult { } impl InferenceResult { - pub fn method_resolution(&self, expr: ExprId) -> Option { + pub fn method_resolution(&self, expr: ExprId) -> Option { self.method_resolutions.get(&expr).copied() } - pub fn field_resolution(&self, expr: ExprId) -> Option { + pub fn field_resolution(&self, expr: ExprId) -> Option { self.field_resolutions.get(&expr).copied() } - pub fn record_field_resolution(&self, expr: ExprId) -> Option { + pub fn record_field_resolution(&self, expr: ExprId) -> Option { self.record_field_resolutions.get(&expr).copied() } pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option { @@ -164,7 +164,7 @@ impl InferenceResult { pub(crate) fn add_diagnostics( &self, db: &impl HirDatabase, - owner: Function, + owner: FunctionId, sink: &mut DiagnosticSink, ) { self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink)) @@ -243,11 +243,11 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { self.result.type_of_expr.insert(expr, ty); } - fn write_method_resolution(&mut self, expr: ExprId, func: Function) { + fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId) { self.result.method_resolutions.insert(expr, func); } - fn write_field_resolution(&mut self, expr: ExprId, field: StructField) { + fn write_field_resolution(&mut self, expr: ExprId, field: StructFieldId) { self.result.field_resolutions.insert(expr, field); } @@ -557,22 +557,22 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { self.infer_expr(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); } - fn resolve_into_iter_item(&self) -> Option { + fn resolve_into_iter_item(&self) -> Option { let path = known::std_iter_into_iterator(); let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name::ITEM_TYPE).map(TypeAlias::from) + self.db.trait_data(trait_).associated_type_by_name(&name::ITEM_TYPE) } - fn resolve_ops_try_ok(&self) -> Option { + fn resolve_ops_try_ok(&self) -> Option { let path = known::std_ops_try(); let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name::OK_TYPE).map(TypeAlias::from) + self.db.trait_data(trait_).associated_type_by_name(&name::OK_TYPE) } - fn resolve_future_future_output(&self) -> Option { + fn resolve_future_future_output(&self) -> Option { let path = known::std_future_future(); let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name::OUTPUT_TYPE).map(TypeAlias::from) + self.db.trait_data(trait_).associated_type_by_name(&name::OUTPUT_TYPE) } fn resolve_boxed_box(&self) -> Option { @@ -696,9 +696,10 @@ impl Expectation { } mod diagnostics { + use hir_def::{expr::ExprId, FunctionId, HasSource, Lookup}; use hir_expand::diagnostics::DiagnosticSink; - use crate::{db::HirDatabase, diagnostics::NoSuchField, expr::ExprId, Function, HasSource}; + use crate::{db::HirDatabase, diagnostics::NoSuchField}; #[derive(Debug, PartialEq, Eq, Clone)] pub(super) enum InferenceDiagnostic { @@ -709,13 +710,14 @@ mod diagnostics { pub(super) fn add_to( &self, db: &impl HirDatabase, - owner: Function, + owner: FunctionId, sink: &mut DiagnosticSink, ) { match self { InferenceDiagnostic::NoSuchField { expr, field } => { - let file = owner.source(db).file_id; - let field = owner.body_source_map(db).field_syntax(*expr, *field); + let file = owner.lookup(db).source(db).file_id; + let (_, source_map) = db.body_with_source_map(owner.into()); + let field = source_map.field_syntax(*expr, *field); sink.push(NoSuchField { file, field }) } } diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index 57f845dfa..d9ea6da42 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -100,7 +100,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let projection = ProjectionPredicate { ty: pat_ty.clone(), projection_ty: ProjectionTy { - associated_ty: into_iter_item_alias.id, + associated_ty: into_iter_item_alias, parameters: Substs::single(iterable_ty), }, }; @@ -230,7 +230,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { } }); if let Some(field_def) = field_def { - self.result.record_field_resolutions.insert(field.expr, field_def); + self.result.record_field_resolutions.insert(field.expr, field_def.into()); } let field_ty = field_def .map_or(Ty::Unknown, |it| field_types[it.id].clone()) @@ -262,7 +262,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { self.db.struct_data(s).variant_data.field(name).map(|local_id| { let field = StructFieldId { parent: s.into(), local_id }.into(); self.write_field_resolution(tgt_expr, field); - self.db.field_types(s.into())[field.id] + self.db.field_types(s.into())[field.local_id] .clone() .subst(&a_ty.parameters) }) @@ -285,7 +285,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let projection = ProjectionPredicate { ty: ty.clone(), projection_ty: ProjectionTy { - associated_ty: future_future_output_alias.id, + associated_ty: future_future_output_alias, parameters: Substs::single(inner_ty), }, }; @@ -304,7 +304,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let projection = ProjectionPredicate { ty: ty.clone(), projection_ty: ProjectionTy { - associated_ty: ops_try_ok_alias.id, + associated_ty: ops_try_ok_alias, parameters: Substs::single(inner_ty), }, }; @@ -557,7 +557,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { Some((ty, func)) => { let ty = canonicalized_receiver.decanonicalize_ty(ty); self.write_method_resolution(tgt_expr, func); - (ty, self.db.value_ty(func.id.into()), Some(self.db.generic_params(func.id.into()))) + (ty, self.db.value_ty(func.into()), Some(self.db.generic_params(func.into()))) } None => (receiver_ty, Ty::Unknown, None), }; diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs index 02e81fb34..5cc249855 100644 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ b/crates/ra_hir/src/ty/method_resolution.rs @@ -7,7 +7,7 @@ use std::sync::Arc; use arrayvec::ArrayVec; use hir_def::{ lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, type_ref::Mutability, - AssocItemId, AstItemDef, HasModule, ImplId, TraitId, + AssocItemId, AstItemDef, FunctionId, HasModule, ImplId, TraitId, }; use hir_expand::name::Name; use ra_db::CrateId; @@ -18,7 +18,6 @@ use crate::{ db::HirDatabase, ty::primitive::{FloatBitness, Uncertain}, ty::{utils::all_super_traits, Ty, TypeCtor}, - Function, }; use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; @@ -154,10 +153,10 @@ pub(crate) fn lookup_method( db: &impl HirDatabase, name: &Name, resolver: &Resolver, -) -> Option<(Ty, Function)> { +) -> Option<(Ty, FunctionId)> { iterate_method_candidates(ty, db, resolver, Some(name), LookupMode::MethodCall, |ty, f| match f { - AssocItemId::FunctionId(f) => Some((ty.clone(), f.into())), + AssocItemId::FunctionId(f) => Some((ty.clone(), f)), _ => None, }) } -- cgit v1.2.3 From 17680f6060be1abe8f021538aeff0a95e9c569da Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 16:02:33 +0300 Subject: More decoupling --- crates/ra_hir/src/code_model.rs | 10 +++++----- crates/ra_hir/src/db.rs | 17 +++++++---------- crates/ra_hir/src/source_binder.rs | 6 +++--- crates/ra_hir/src/ty/infer.rs | 27 ++++++++++++--------------- 4 files changed, 27 insertions(+), 33 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 6d71bde92..0e987240a 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -618,7 +618,7 @@ impl Function { } pub fn infer(self, db: &impl HirDatabase) -> Arc { - db.infer(self.into()) + db.infer(self.id.into()) } /// The containing impl block, if this is a method. @@ -672,7 +672,7 @@ impl Const { } pub fn infer(self, db: &impl HirDatabase) -> Arc { - db.infer(self.into()) + db.infer(self.id.into()) } /// The containing impl block, if this is a type alias. @@ -715,7 +715,7 @@ impl Static { } pub fn infer(self, db: &impl HirDatabase) -> Arc { - db.infer(self.into()) + db.infer(self.id.into()) } } @@ -908,9 +908,9 @@ impl Local { } pub fn ty(self, db: &impl HirDatabase) -> Type { - let infer = db.infer(self.parent); - let ty = infer[self.pat_id].clone(); let def = DefWithBodyId::from(self.parent); + let infer = db.infer(def); + let ty = infer[self.pat_id].clone(); let resolver = def.resolver(db); let krate = def.module(db).krate; let environment = TraitEnvironment::lower(db, &resolver); diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index a5bfef91f..e192c8f47 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -2,18 +2,15 @@ use std::sync::Arc; -use hir_def::{GenericDefId, ImplId, LocalStructFieldId, TraitId, VariantId}; +use hir_def::{DefWithBodyId, GenericDefId, ImplId, LocalStructFieldId, TraitId, VariantId}; use ra_arena::map::ArenaMap; use ra_db::{salsa, CrateId}; -use crate::{ - ty::{ - method_resolution::CrateImplBlocks, - traits::{AssocTyValue, Impl}, - CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, - ValueTyDefId, - }, - DefWithBody, +use crate::ty::{ + method_resolution::CrateImplBlocks, + traits::{AssocTyValue, Impl}, + CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, + ValueTyDefId, }; pub use hir_def::db::{ @@ -32,7 +29,7 @@ pub use hir_expand::db::{ #[salsa::requires(salsa::Database)] pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::ty::infer_query)] - fn infer(&self, def: DefWithBody) -> Arc; + fn infer(&self, def: DefWithBodyId) -> Arc; #[salsa::invoke(crate::ty::ty_query)] fn ty(&self, def: TyDefId) -> Ty; diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 0de36abd1..c85e38a0d 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -168,7 +168,7 @@ impl SourceAnalyzer { resolver, body_owner: Some(def), body_source_map: Some(source_map), - infer: Some(db.infer(def)), + infer: Some(db.infer(def.into())), scopes: Some(scopes), file_id: node.file_id, } @@ -297,13 +297,13 @@ impl SourceAnalyzer { if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { let expr_id = self.expr_id(&path_expr.into())?; if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_expr(expr_id) { - return Some(PathResolution::AssocItem(assoc)); + return Some(PathResolution::AssocItem(assoc.into())); } } if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { let pat_id = self.pat_id(&path_pat.into())?; if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { - return Some(PathResolution::AssocItem(assoc)); + return Some(PathResolution::AssocItem(assoc.into())); } } // This must be a normal source file rather than macro file. diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index 7b6dfd61b..1eca4883d 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -41,10 +41,7 @@ use super::{ ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, }; -use crate::{ - db::HirDatabase, ty::infer::diagnostics::InferenceDiagnostic, AssocItem, DefWithBody, - VariantDef, -}; +use crate::{db::HirDatabase, ty::infer::diagnostics::InferenceDiagnostic, VariantDef}; macro_rules! ty_app { ($ctor:pat, $param:pat) => { @@ -62,15 +59,15 @@ mod pat; mod coerce; /// The entry point of type inference. -pub fn infer_query(db: &impl HirDatabase, def: DefWithBody) -> Arc { +pub fn infer_query(db: &impl HirDatabase, def: DefWithBodyId) -> Arc { let _p = profile("infer_query"); - let resolver = DefWithBodyId::from(def).resolver(db); + let resolver = def.resolver(db); let mut ctx = InferenceContext::new(db, def, resolver); - match &def { - DefWithBody::Const(c) => ctx.collect_const(&db.const_data(c.id)), - DefWithBody::Function(f) => ctx.collect_fn(&db.function_data(f.id)), - DefWithBody::Static(s) => ctx.collect_const(&db.static_data(s.id)), + match def { + DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)), + DefWithBodyId::FunctionId(f) => ctx.collect_fn(&db.function_data(f)), + DefWithBodyId::StaticId(s) => ctx.collect_const(&db.static_data(s)), } ctx.infer_body(); @@ -129,7 +126,7 @@ pub struct InferenceResult { /// For each struct literal, records the variant it resolves to. variant_resolutions: FxHashMap, /// For each associated item record what it resolves to - assoc_resolutions: FxHashMap, + assoc_resolutions: FxHashMap, diagnostics: Vec, pub(super) type_of_expr: ArenaMap, pub(super) type_of_pat: ArenaMap, @@ -152,10 +149,10 @@ impl InferenceResult { pub fn variant_resolution_for_pat(&self, id: PatId) -> Option { self.variant_resolutions.get(&id.into()).copied() } - pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option { + pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option { self.assoc_resolutions.get(&id.into()).copied() } - pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option { + pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option { self.assoc_resolutions.get(&id.into()).copied() } pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> { @@ -191,7 +188,7 @@ impl Index for InferenceResult { #[derive(Clone, Debug)] struct InferenceContext<'a, D: HirDatabase> { db: &'a D, - owner: DefWithBody, + owner: DefWithBodyId, body: Arc, resolver: Resolver, var_unification_table: InPlaceUnificationTable, @@ -209,7 +206,7 @@ struct InferenceContext<'a, D: HirDatabase> { } impl<'a, D: HirDatabase> InferenceContext<'a, D> { - fn new(db: &'a D, owner: DefWithBody, resolver: Resolver) -> Self { + fn new(db: &'a D, owner: DefWithBodyId, resolver: Resolver) -> Self { InferenceContext { result: InferenceResult::default(), var_unification_table: InPlaceUnificationTable::new(), -- cgit v1.2.3 From 9fa46ff5c67bd1809cc748f6fc0e93d7c9be3fdb Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 16:25:01 +0300 Subject: Use Id for variats --- crates/ra_hir/src/code_model.rs | 8 -------- crates/ra_hir/src/source_binder.rs | 4 ++-- crates/ra_hir/src/ty/infer.rs | 18 +++++++++--------- crates/ra_hir/src/ty/infer/expr.rs | 34 +++++++++++++++++++--------------- crates/ra_hir/src/ty/infer/pat.rs | 20 +++++++++++--------- crates/ra_hir/src/ty/lower.rs | 14 ++++++-------- crates/ra_hir/src/ty/utils.rs | 14 +++++++++++++- 7 files changed, 60 insertions(+), 52 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 0e987240a..52ad4e5d1 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -534,14 +534,6 @@ impl VariantDef { } } - pub(crate) fn field(self, db: &impl HirDatabase, name: &Name) -> Option { - match self { - VariantDef::Struct(it) => it.field(db, name), - VariantDef::Union(it) => it.field(db, name), - VariantDef::EnumVariant(it) => it.field(db, name), - } - } - pub fn module(self, db: &impl HirDatabase) -> Module { match self { VariantDef::Struct(it) => it.module(db), diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index c85e38a0d..05f5bca57 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -229,12 +229,12 @@ impl SourceAnalyzer { pub fn resolve_record_literal(&self, record_lit: &ast::RecordLit) -> Option { let expr_id = self.expr_id(&record_lit.clone().into())?; - self.infer.as_ref()?.variant_resolution_for_expr(expr_id) + self.infer.as_ref()?.variant_resolution_for_expr(expr_id).map(|it| it.into()) } pub fn resolve_record_pattern(&self, record_pat: &ast::RecordPat) -> Option { let pat_id = self.pat_id(&record_pat.clone().into())?; - self.infer.as_ref()?.variant_resolution_for_pat(pat_id) + self.infer.as_ref()?.variant_resolution_for_pat(pat_id).map(|it| it.into()) } pub fn resolve_macro_call( diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs index 1eca4883d..59e4e5f36 100644 --- a/crates/ra_hir/src/ty/infer.rs +++ b/crates/ra_hir/src/ty/infer.rs @@ -28,7 +28,7 @@ use hir_def::{ path::{known, Path}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{Mutability, TypeRef}, - AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TypeAliasId, + AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TypeAliasId, VariantId, }; use hir_expand::{diagnostics::DiagnosticSink, name}; use ra_arena::map::ArenaMap; @@ -41,7 +41,7 @@ use super::{ ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, }; -use crate::{db::HirDatabase, ty::infer::diagnostics::InferenceDiagnostic, VariantDef}; +use crate::{db::HirDatabase, ty::infer::diagnostics::InferenceDiagnostic}; macro_rules! ty_app { ($ctor:pat, $param:pat) => { @@ -124,7 +124,7 @@ pub struct InferenceResult { /// For each field in record literal, records the field it resolves to. record_field_resolutions: FxHashMap, /// For each struct literal, records the variant it resolves to. - variant_resolutions: FxHashMap, + variant_resolutions: FxHashMap, /// For each associated item record what it resolves to assoc_resolutions: FxHashMap, diagnostics: Vec, @@ -143,10 +143,10 @@ impl InferenceResult { pub fn record_field_resolution(&self, expr: ExprId) -> Option { self.record_field_resolutions.get(&expr).copied() } - pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option { + pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option { self.variant_resolutions.get(&id.into()).copied() } - pub fn variant_resolution_for_pat(&self, id: PatId) -> Option { + pub fn variant_resolution_for_pat(&self, id: PatId) -> Option { self.variant_resolutions.get(&id.into()).copied() } pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option { @@ -248,7 +248,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { self.result.field_resolutions.insert(expr, field); } - fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantDef) { + fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantId) { self.result.variant_resolutions.insert(id, variant); } @@ -511,7 +511,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { }) } - fn resolve_variant(&mut self, path: Option<&Path>) -> (Ty, Option) { + fn resolve_variant(&mut self, path: Option<&Path>) -> (Ty, Option) { let path = match path { Some(path) => path, None => return (Ty::Unknown, None), @@ -524,13 +524,13 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let substs = Ty::substs_from_path(self.db, resolver, path, strukt.into()); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.apply_substs(substs)); - (ty, Some(VariantDef::Struct(strukt.into()))) + (ty, Some(strukt.into())) } Some(TypeNs::EnumVariantId(var)) => { let substs = Ty::substs_from_path(self.db, resolver, path, var.into()); let ty = self.db.ty(var.parent.into()); let ty = self.insert_type_vars(ty.apply_substs(substs)); - (ty, Some(VariantDef::EnumVariant(var.into()))) + (ty, Some(var.into())) } Some(_) | None => (Ty::Unknown, None), } diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs index d9ea6da42..f9ededa23 100644 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ b/crates/ra_hir/src/ty/infer/expr.rs @@ -16,9 +16,9 @@ use hir_expand::name::{self, Name}; use crate::{ db::HirDatabase, ty::{ - autoderef, method_resolution, op, traits::InEnvironment, CallableDef, InferTy, IntTy, - Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, - TypeWalk, Uncertain, + autoderef, method_resolution, op, traits::InEnvironment, utils::variant_data, CallableDef, + InferTy, IntTy, Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, + TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, }, }; @@ -218,22 +218,26 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let substs = ty.substs().unwrap_or_else(Substs::empty); let field_types = def_id.map(|it| self.db.field_types(it.into())).unwrap_or_default(); + let variant_data = def_id.map(|it| variant_data(self.db, it)); for (field_idx, field) in fields.iter().enumerate() { - let field_def = def_id.and_then(|it| match it.field(self.db, &field.name) { - Some(field) => Some(field), - None => { - self.push_diagnostic(InferenceDiagnostic::NoSuchField { - expr: tgt_expr, - field: field_idx, - }); - None - } - }); + let field_def = + variant_data.as_ref().and_then(|it| match it.field(&field.name) { + Some(local_id) => { + Some(StructFieldId { parent: def_id.unwrap(), local_id }) + } + None => { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + expr: tgt_expr, + field: field_idx, + }); + None + } + }); if let Some(field_def) = field_def { - self.result.record_field_resolutions.insert(field.expr, field_def.into()); + self.result.record_field_resolutions.insert(field.expr, field_def); } let field_ty = field_def - .map_or(Ty::Unknown, |it| field_types[it.id].clone()) + .map_or(Ty::Unknown, |it| field_types[it.local_id].clone()) .subst(&substs); self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); } diff --git a/crates/ra_hir/src/ty/infer/pat.rs b/crates/ra_hir/src/ty/infer/pat.rs index 6dbf03eb2..a14774607 100644 --- a/crates/ra_hir/src/ty/infer/pat.rs +++ b/crates/ra_hir/src/ty/infer/pat.rs @@ -14,7 +14,7 @@ use test_utils::tested_by; use super::{BindingMode, InferenceContext}; use crate::{ db::HirDatabase, - ty::{Substs, Ty, TypeCtor, TypeWalk}, + ty::{utils::variant_data, Substs, Ty, TypeCtor, TypeWalk}, }; impl<'a, D: HirDatabase> InferenceContext<'a, D> { @@ -26,16 +26,18 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { default_bm: BindingMode, ) -> Ty { let (ty, def) = self.resolve_variant(path); - + let var_data = def.map(|it| variant_data(self.db, it)); self.unify(&ty, expected); let substs = ty.substs().unwrap_or_else(Substs::empty); let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default(); + for (i, &subpat) in subpats.iter().enumerate() { - let expected_ty = def - .and_then(|d| d.field(self.db, &Name::new_tuple_field(i))) - .map_or(Ty::Unknown, |field| field_tys[field.id].clone()) + let expected_ty = var_data + .as_ref() + .and_then(|d| d.field(&Name::new_tuple_field(i))) + .map_or(Ty::Unknown, |field| field_tys[field].clone()) .subst(&substs); let expected_ty = self.normalize_associated_types_in(expected_ty); self.infer_pat(subpat, &expected_ty, default_bm); @@ -53,6 +55,7 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { id: PatId, ) -> Ty { let (ty, def) = self.resolve_variant(path); + let var_data = def.map(|it| variant_data(self.db, it)); if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); } @@ -63,10 +66,9 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default(); for subpat in subpats { - let matching_field = def.and_then(|it| it.field(self.db, &subpat.name)); - let expected_ty = matching_field - .map_or(Ty::Unknown, |field| field_tys[field.id].clone()) - .subst(&substs); + let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name)); + let expected_ty = + matching_field.map_or(Ty::Unknown, |field| field_tys[field].clone()).subst(&substs); let expected_ty = self.normalize_associated_types_in(expected_ty); self.infer_pat(subpat.pat, &expected_ty, default_bm); } diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 1c0f71adc..d33b50794 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -28,7 +28,7 @@ use crate::{ db::HirDatabase, ty::{ primitive::{FloatTy, IntTy}, - utils::{all_super_traits, associated_type_by_name_including_super_traits}, + utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, }, util::make_mut_slice, Adt, Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Path, Static, Struct, Trait, @@ -514,13 +514,11 @@ pub(crate) fn field_types_query( db: &impl HirDatabase, variant_id: VariantId, ) -> Arc> { - let (resolver, var_data) = match variant_id { - VariantId::StructId(it) => (it.resolver(db), db.struct_data(it).variant_data.clone()), - VariantId::UnionId(it) => (it.resolver(db), db.union_data(it).variant_data.clone()), - VariantId::EnumVariantId(it) => ( - it.parent.resolver(db), - db.enum_data(it.parent).variants[it.local_id].variant_data.clone(), - ), + let var_data = variant_data(db, variant_id); + let resolver = match variant_id { + VariantId::StructId(it) => it.resolver(db), + VariantId::UnionId(it) => it.resolver(db), + VariantId::EnumVariantId(it) => it.parent.resolver(db), }; let mut res = ArenaMap::default(); for (field_id, field_data) in var_data.fields().iter() { diff --git a/crates/ra_hir/src/ty/utils.rs b/crates/ra_hir/src/ty/utils.rs index 80ffceb4b..f82e6ac9b 100644 --- a/crates/ra_hir/src/ty/utils.rs +++ b/crates/ra_hir/src/ty/utils.rs @@ -1,11 +1,13 @@ //! Helper functions for working with def, which don't need to be a separate //! query, but can't be computed directly from `*Data` (ie, which need a `db`). +use std::sync::Arc; use hir_def::{ + adt::VariantData, db::DefDatabase, resolver::{HasResolver, TypeNs}, type_ref::TypeRef, - TraitId, TypeAliasId, + TraitId, TypeAliasId, VariantId, }; use hir_expand::name::{self, Name}; @@ -61,3 +63,13 @@ pub(super) fn associated_type_by_name_including_super_traits( .into_iter() .find_map(|t| db.trait_data(t).associated_type_by_name(name)) } + +pub(super) fn variant_data(db: &impl DefDatabase, var: VariantId) -> Arc { + match var { + VariantId::StructId(it) => db.struct_data(it).variant_data.clone(), + VariantId::UnionId(it) => db.union_data(it).variant_data.clone(), + VariantId::EnumVariantId(it) => { + db.enum_data(it.parent).variants[it.local_id].variant_data.clone() + } + } +} -- cgit v1.2.3 From d6e8f27488f8bf4ae7024b9b7a9c4797c2b52b12 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 16:29:11 +0300 Subject: Cleanup imports --- crates/ra_hir/src/ty/lower.rs | 4 ++-- crates/ra_hir/src/ty/op.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index d33b50794..d776b6cd0 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use hir_def::{ builtin_type::BuiltinType, generics::WherePredicate, - path::{GenericArg, PathSegment}, + path::{GenericArg, Path, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, AdtId, AstItemDef, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, @@ -31,7 +31,7 @@ use crate::{ utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, }, util::make_mut_slice, - Adt, Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Path, Static, Struct, Trait, + Adt, Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Static, Struct, Trait, TypeAlias, Union, }; diff --git a/crates/ra_hir/src/ty/op.rs b/crates/ra_hir/src/ty/op.rs index bcfa3a6a2..80d4111a0 100644 --- a/crates/ra_hir/src/ty/op.rs +++ b/crates/ra_hir/src/ty/op.rs @@ -1,8 +1,8 @@ //! FIXME: write short doc here +use hir_def::expr::{BinaryOp, CmpOp}; use super::{InferTy, Ty, TypeCtor}; use crate::{ - expr::{BinaryOp, CmpOp}, ty::ApplicationTy, }; -- cgit v1.2.3 From 12501fcdd02fec9d43dfd810d65e927ddebb1b56 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 16:39:58 +0300 Subject: Remove TypableDef --- crates/ra_hir/src/from_id.rs | 60 +++---------------------------------------- crates/ra_hir/src/ty.rs | 2 +- crates/ra_hir/src/ty/lower.rs | 39 +--------------------------- crates/ra_hir/src/ty/op.rs | 4 +-- 4 files changed, 7 insertions(+), 98 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/from_id.rs b/crates/ra_hir/src/from_id.rs index 38daa5e59..e96a18d12 100644 --- a/crates/ra_hir/src/from_id.rs +++ b/crates/ra_hir/src/from_id.rs @@ -4,13 +4,13 @@ //! are splitting the hir. use hir_def::{ - AdtId, AssocItemId, AttrDefId, ConstId, DefWithBodyId, EnumId, EnumVariantId, FunctionId, - GenericDefId, ModuleDefId, StaticId, StructFieldId, StructId, TypeAliasId, UnionId, VariantId, + AdtId, AssocItemId, AttrDefId, DefWithBodyId, EnumVariantId, GenericDefId, ModuleDefId, + StructFieldId, VariantId, }; use crate::{ - ty::TypableDef, Adt, AssocItem, AttrDef, Const, Crate, DefWithBody, EnumVariant, Function, - GenericDef, ModuleDef, Static, StructField, TypeAlias, VariantDef, + Adt, AssocItem, AttrDef, Crate, DefWithBody, EnumVariant, GenericDef, ModuleDef, StructField, + VariantDef, }; impl From for Crate { @@ -137,58 +137,6 @@ impl From for GenericDefId { } } -impl From for TypableDef { - fn from(id: AdtId) -> Self { - Adt::from(id).into() - } -} - -impl From for TypableDef { - fn from(id: StructId) -> Self { - AdtId::StructId(id).into() - } -} - -impl From for TypableDef { - fn from(id: UnionId) -> Self { - AdtId::UnionId(id).into() - } -} - -impl From for TypableDef { - fn from(id: EnumId) -> Self { - AdtId::EnumId(id).into() - } -} - -impl From for TypableDef { - fn from(id: EnumVariantId) -> Self { - EnumVariant::from(id).into() - } -} - -impl From for TypableDef { - fn from(id: TypeAliasId) -> Self { - TypeAlias::from(id).into() - } -} - -impl From for TypableDef { - fn from(id: FunctionId) -> Self { - Function::from(id).into() - } -} -impl From for TypableDef { - fn from(id: ConstId) -> Self { - Const::from(id).into() - } -} -impl From for TypableDef { - fn from(id: StaticId) -> Self { - Static::from(id).into() - } -} - impl From for GenericDefId { fn from(id: Adt) -> Self { match id { diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index a26776b26..e4ba8afa6 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -38,7 +38,7 @@ pub use lower::CallableDef; pub(crate) use lower::{ callable_item_sig, field_types_query, generic_defaults_query, generic_predicates_for_param_query, generic_predicates_query, ty_query, value_ty_query, - TyDefId, TypableDef, ValueTyDefId, + TyDefId, ValueTyDefId, }; pub(crate) use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index d776b6cd0..5dce2f342 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -31,8 +31,7 @@ use crate::{ utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, }, util::make_mut_slice, - Adt, Const, Enum, EnumVariant, Function, ImplBlock, ModuleDef, Static, Struct, Trait, - TypeAlias, Union, + ImplBlock, Trait, }; impl Ty { @@ -693,42 +692,6 @@ fn type_for_type_alias(db: &impl HirDatabase, t: TypeAliasId) -> Ty { inner.subst(&substs) } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum TypableDef { - Function(Function), - Adt(Adt), - EnumVariant(EnumVariant), - TypeAlias(TypeAlias), - Const(Const), - Static(Static), - BuiltinType(BuiltinType), -} -impl_froms!( - TypableDef: Function, - Adt(Struct, Enum, Union), - EnumVariant, - TypeAlias, - Const, - Static, - BuiltinType -); - -impl From for Option { - fn from(def: ModuleDef) -> Option { - let res = match def { - ModuleDef::Function(f) => f.into(), - ModuleDef::Adt(adt) => adt.into(), - ModuleDef::EnumVariant(v) => v.into(), - ModuleDef::TypeAlias(t) => t.into(), - ModuleDef::Const(v) => v.into(), - ModuleDef::Static(v) => v.into(), - ModuleDef::BuiltinType(t) => t.into(), - ModuleDef::Module(_) | ModuleDef::Trait(_) => return None, - }; - Some(res) - } -} - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum CallableDef { FunctionId(FunctionId), diff --git a/crates/ra_hir/src/ty/op.rs b/crates/ra_hir/src/ty/op.rs index 80d4111a0..cc6e244f4 100644 --- a/crates/ra_hir/src/ty/op.rs +++ b/crates/ra_hir/src/ty/op.rs @@ -2,9 +2,7 @@ use hir_def::expr::{BinaryOp, CmpOp}; use super::{InferTy, Ty, TypeCtor}; -use crate::{ - ty::ApplicationTy, -}; +use crate::ty::ApplicationTy; pub(super) fn binary_op_return_ty(op: BinaryOp, rhs_ty: Ty) -> Ty { match op { -- cgit v1.2.3 From 368653081558ab389c6543d6b5027859e26beb3b Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 16:48:48 +0300 Subject: Decouple --- crates/ra_hir/src/ty/lower.rs | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs index 5dce2f342..2d447f1ea 100644 --- a/crates/ra_hir/src/ty/lower.rs +++ b/crates/ra_hir/src/ty/lower.rs @@ -31,7 +31,6 @@ use crate::{ utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, }, util::make_mut_slice, - ImplBlock, Trait, }; impl Ty { @@ -148,13 +147,8 @@ impl Ty { ) -> Ty { let ty = match resolution { TypeNs::TraitId(trait_) => { - let trait_ref = TraitRef::from_resolved_path( - db, - resolver, - trait_.into(), - resolved_segment, - None, - ); + let trait_ref = + TraitRef::from_resolved_path(db, resolver, trait_, resolved_segment, None); return if remaining_segments.len() == 1 { let segment = &remaining_segments[0]; let associated_ty = associated_type_by_name_including_super_traits( @@ -187,7 +181,11 @@ impl Ty { let name = resolved_segment.name.clone(); Ty::Param { idx, name } } - TypeNs::SelfType(impl_block) => ImplBlock::from(impl_block).target_ty(db), + TypeNs::SelfType(impl_id) => { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + Ty::from_hir(db, &resolver, &impl_data.target_type) + } TypeNs::AdtSelfType(adt) => db.ty(adt.into()), TypeNs::AdtId(it) => Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()), @@ -250,14 +248,11 @@ impl Ty { GenericPredicate::Implemented(tr) if tr.self_ty() == &self_ty => Some(tr.trait_), _ => None, }); - let traits = traits_from_env.flat_map(|t| all_super_traits(db, t)).map(Trait::from); + let traits = traits_from_env.flat_map(|t| all_super_traits(db, t)); for t in traits { - if let Some(associated_ty) = db.trait_data(t.id).associated_type_by_name(&segment.name) - { - let substs = Substs::build_for_def(db, t.id) - .push(self_ty.clone()) - .fill_with_unknown() - .build(); + if let Some(associated_ty) = db.trait_data(t).associated_type_by_name(&segment.name) { + let substs = + Substs::build_for_def(db, t).push(self_ty.clone()).fill_with_unknown().build(); // FIXME handle type parameters on the segment return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); } @@ -391,7 +386,7 @@ impl TraitRef { pub(super) fn from_resolved_path( db: &impl HirDatabase, resolver: &Resolver, - resolved: Trait, + resolved: TraitId, segment: &PathSegment, explicit_self_ty: Option, ) -> Self { @@ -399,7 +394,7 @@ impl TraitRef { if let Some(self_ty) = explicit_self_ty { make_mut_slice(&mut substs.0)[0] = self_ty; } - TraitRef { trait_: resolved.id, substs } + TraitRef { trait_: resolved, substs } } pub(crate) fn from_hir( @@ -419,11 +414,11 @@ impl TraitRef { db: &impl HirDatabase, resolver: &Resolver, segment: &PathSegment, - resolved: Trait, + resolved: TraitId, ) -> Substs { let has_self_param = segment.args_and_bindings.as_ref().map(|a| a.has_self_type).unwrap_or(false); - substs_from_path_segment(db, resolver, segment, Some(resolved.id.into()), !has_self_param) + substs_from_path_segment(db, resolver, segment, Some(resolved.into()), !has_self_param) } pub(crate) fn for_trait(db: &impl HirDatabase, trait_: TraitId) -> TraitRef { -- cgit v1.2.3 From a87579500a2c35597071efd0ad6983927f0c1815 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 17:46:02 +0300 Subject: Move Ty --- crates/ra_hir/src/code_model.rs | 47 +- crates/ra_hir/src/db.rs | 117 +- crates/ra_hir/src/diagnostics.rs | 91 +- crates/ra_hir/src/expr.rs | 146 - crates/ra_hir/src/lib.rs | 9 +- crates/ra_hir/src/marks.rs | 9 - crates/ra_hir/src/source_binder.rs | 22 +- crates/ra_hir/src/test_db.rs | 123 - crates/ra_hir/src/ty.rs | 1111 +------ crates/ra_hir/src/ty/autoderef.rs | 108 - crates/ra_hir/src/ty/display.rs | 93 - crates/ra_hir/src/ty/infer.rs | 723 ----- crates/ra_hir/src/ty/infer/coerce.rs | 357 --- crates/ra_hir/src/ty/infer/expr.rs | 689 ---- crates/ra_hir/src/ty/infer/pat.rs | 189 -- crates/ra_hir/src/ty/infer/path.rs | 273 -- crates/ra_hir/src/ty/infer/unify.rs | 166 - crates/ra_hir/src/ty/lower.rs | 755 ----- crates/ra_hir/src/ty/method_resolution.rs | 362 --- crates/ra_hir/src/ty/op.rs | 50 - crates/ra_hir/src/ty/primitive.rs | 3 - crates/ra_hir/src/ty/tests.rs | 4896 ---------------------------- crates/ra_hir/src/ty/tests/coercion.rs | 369 --- crates/ra_hir/src/ty/tests/never_type.rs | 246 -- crates/ra_hir/src/ty/traits.rs | 328 -- crates/ra_hir/src/ty/traits/chalk.rs | 906 ------ crates/ra_hir/src/ty/utils.rs | 75 - crates/ra_hir/src/util.rs | 12 - crates/ra_hir_def/src/lib.rs | 2 +- crates/ra_hir_ty/Cargo.toml | 1 + crates/ra_hir_ty/src/autoderef.rs | 108 + crates/ra_hir_ty/src/db.rs | 116 + crates/ra_hir_ty/src/diagnostics.rs | 91 + crates/ra_hir_ty/src/display.rs | 93 + crates/ra_hir_ty/src/expr.rs | 151 + crates/ra_hir_ty/src/infer.rs | 723 +++++ crates/ra_hir_ty/src/infer/coerce.rs | 354 ++ crates/ra_hir_ty/src/infer/expr.rs | 686 ++++ crates/ra_hir_ty/src/infer/pat.rs | 186 ++ crates/ra_hir_ty/src/infer/path.rs | 270 ++ crates/ra_hir_ty/src/infer/unify.rs | 162 + crates/ra_hir_ty/src/lib.rs | 1134 ++++++- crates/ra_hir_ty/src/lower.rs | 753 +++++ crates/ra_hir_ty/src/marks.rs | 9 + crates/ra_hir_ty/src/method_resolution.rs | 363 +++ crates/ra_hir_ty/src/op.rs | 50 + crates/ra_hir_ty/src/test_db.rs | 144 + crates/ra_hir_ty/src/tests.rs | 4958 +++++++++++++++++++++++++++++ crates/ra_hir_ty/src/tests/coercion.rs | 369 +++ crates/ra_hir_ty/src/tests/never_type.rs | 246 ++ crates/ra_hir_ty/src/traits.rs | 328 ++ crates/ra_hir_ty/src/traits/chalk.rs | 906 ++++++ crates/ra_hir_ty/src/utils.rs | 84 + crates/ra_ide_api/src/impls.rs | 17 +- 54 files changed, 12332 insertions(+), 12247 deletions(-) delete mode 100644 crates/ra_hir/src/expr.rs delete mode 100644 crates/ra_hir/src/marks.rs delete mode 100644 crates/ra_hir/src/test_db.rs delete mode 100644 crates/ra_hir/src/ty/autoderef.rs delete mode 100644 crates/ra_hir/src/ty/display.rs delete mode 100644 crates/ra_hir/src/ty/infer.rs delete mode 100644 crates/ra_hir/src/ty/infer/coerce.rs delete mode 100644 crates/ra_hir/src/ty/infer/expr.rs delete mode 100644 crates/ra_hir/src/ty/infer/pat.rs delete mode 100644 crates/ra_hir/src/ty/infer/path.rs delete mode 100644 crates/ra_hir/src/ty/infer/unify.rs delete mode 100644 crates/ra_hir/src/ty/lower.rs delete mode 100644 crates/ra_hir/src/ty/method_resolution.rs delete mode 100644 crates/ra_hir/src/ty/op.rs delete mode 100644 crates/ra_hir/src/ty/primitive.rs delete mode 100644 crates/ra_hir/src/ty/tests.rs delete mode 100644 crates/ra_hir/src/ty/tests/coercion.rs delete mode 100644 crates/ra_hir/src/ty/tests/never_type.rs delete mode 100644 crates/ra_hir/src/ty/traits.rs delete mode 100644 crates/ra_hir/src/ty/traits/chalk.rs delete mode 100644 crates/ra_hir/src/ty/utils.rs delete mode 100644 crates/ra_hir/src/util.rs create mode 100644 crates/ra_hir_ty/src/autoderef.rs create mode 100644 crates/ra_hir_ty/src/db.rs create mode 100644 crates/ra_hir_ty/src/diagnostics.rs create mode 100644 crates/ra_hir_ty/src/display.rs create mode 100644 crates/ra_hir_ty/src/expr.rs create mode 100644 crates/ra_hir_ty/src/infer.rs create mode 100644 crates/ra_hir_ty/src/infer/coerce.rs create mode 100644 crates/ra_hir_ty/src/infer/expr.rs create mode 100644 crates/ra_hir_ty/src/infer/pat.rs create mode 100644 crates/ra_hir_ty/src/infer/path.rs create mode 100644 crates/ra_hir_ty/src/infer/unify.rs create mode 100644 crates/ra_hir_ty/src/lower.rs create mode 100644 crates/ra_hir_ty/src/marks.rs create mode 100644 crates/ra_hir_ty/src/method_resolution.rs create mode 100644 crates/ra_hir_ty/src/op.rs create mode 100644 crates/ra_hir_ty/src/test_db.rs create mode 100644 crates/ra_hir_ty/src/tests.rs create mode 100644 crates/ra_hir_ty/src/tests/coercion.rs create mode 100644 crates/ra_hir_ty/src/tests/never_type.rs create mode 100644 crates/ra_hir_ty/src/traits.rs create mode 100644 crates/ra_hir_ty/src/traits/chalk.rs create mode 100644 crates/ra_hir_ty/src/utils.rs (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 52ad4e5d1..87c78d98e 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -6,8 +6,10 @@ use std::sync::Arc; use hir_def::{ adt::VariantData, + body::{Body, BodySourceMap}, builtin_type::BuiltinType, docs::Documentation, + expr::{BindingAnnotation, Pat, PatId}, per_ns::PerNs, resolver::HasResolver, type_ref::{Mutability, TypeRef}, @@ -20,12 +22,12 @@ use hir_expand::{ name::{self, AsName}, AstId, MacroDefId, }; +use hir_ty::expr::ExprValidator; use ra_db::{CrateId, Edition, FileId, FilePosition}; use ra_syntax::{ast, AstNode, SyntaxNode}; use crate::{ db::{DefDatabase, HirDatabase}, - expr::{BindingAnnotation, Body, BodySourceMap, ExprValidator, Pat, PatId}, ty::display::HirFormatter, ty::{ self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TyDefId, TypeCtor, @@ -353,8 +355,8 @@ impl Struct { .map(|(id, _)| StructField { parent: self.into(), id }) } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.ty(self.id.into()) + pub fn ty(self, db: &impl HirDatabase) -> Type { + Type::from_def(db, self.id.module(db).krate, self.id) } pub fn constructor_ty(self, db: &impl HirDatabase) -> Ty { @@ -380,8 +382,8 @@ impl Union { Module { id: self.id.module(db) } } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.ty(self.id.into()) + pub fn ty(self, db: &impl HirDatabase) -> Type { + Type::from_def(db, self.id.module(db).krate, self.id) } pub fn fields(self, db: &impl HirDatabase) -> Vec { @@ -441,8 +443,8 @@ impl Enum { .map(|(id, _)| EnumVariant { parent: self, id }) } - pub fn ty(self, db: &impl HirDatabase) -> Ty { - db.ty(self.id.into()) + pub fn ty(self, db: &impl HirDatabase) -> Type { + Type::from_def(db, self.id.module(db).krate, self.id) } } @@ -640,7 +642,7 @@ impl Function { pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { let infer = self.infer(db); infer.add_diagnostics(db, self.id, sink); - let mut validator = ExprValidator::new(self, infer, sink); + let mut validator = ExprValidator::new(self.id, infer, sink); validator.validate_body(db); } } @@ -946,13 +948,12 @@ impl ImplBlock { db.impl_data(self.id).target_type.clone() } - pub fn target_ty(&self, db: &impl HirDatabase) -> Ty { - Ty::from_hir(db, &self.id.resolver(db), &self.target_type(db)) - } - - pub fn target_trait_ref(&self, db: &impl HirDatabase) -> Option { - let target_ty = self.target_ty(db); - TraitRef::from_hir(db, &self.id.resolver(db), &self.target_trait(db)?, Some(target_ty)) + pub fn target_ty(&self, db: &impl HirDatabase) -> Type { + let impl_data = db.impl_data(self.id); + let resolver = self.id.resolver(db); + let environment = TraitEnvironment::lower(db, &resolver); + let ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + Type { krate: self.id.module(db).krate, ty: InEnvironment { value: ty, environment } } } pub fn items(&self, db: &impl DefDatabase) -> Vec { @@ -1130,6 +1131,22 @@ impl Type { Some(adt.into()) } + // FIXME: provide required accessors such that it becomes implementable from outside. + pub fn is_equal_for_find_impls(&self, other: &Type) -> bool { + match (&self.ty.value, &other.ty.value) { + (Ty::Apply(a_original_ty), Ty::Apply(ty::ApplicationTy { ctor, parameters })) => { + match ctor { + TypeCtor::Ref(..) => match parameters.as_single() { + Ty::Apply(a_ty) => a_original_ty.ctor == a_ty.ctor, + _ => false, + }, + _ => a_original_ty.ctor == *ctor, + } + } + _ => false, + } + } + fn derived(&self, ty: Ty) -> Type { Type { krate: self.krate, diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index e192c8f47..bfae3660b 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -1,18 +1,5 @@ //! FIXME: write short doc here -use std::sync::Arc; - -use hir_def::{DefWithBodyId, GenericDefId, ImplId, LocalStructFieldId, TraitId, VariantId}; -use ra_arena::map::ArenaMap; -use ra_db::{salsa, CrateId}; - -use crate::ty::{ - method_resolution::CrateImplBlocks, - traits::{AssocTyValue, Impl}, - CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, - ValueTyDefId, -}; - pub use hir_def::db::{ BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQuery, CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, ExprScopesQuery, @@ -24,104 +11,12 @@ pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, MacroArgQuery, MacroDefQuery, MacroExpandQuery, ParseMacroQuery, }; - -#[salsa::query_group(HirDatabaseStorage)] -#[salsa::requires(salsa::Database)] -pub trait HirDatabase: DefDatabase { - #[salsa::invoke(crate::ty::infer_query)] - fn infer(&self, def: DefWithBodyId) -> Arc; - - #[salsa::invoke(crate::ty::ty_query)] - fn ty(&self, def: TyDefId) -> Ty; - - #[salsa::invoke(crate::ty::value_ty_query)] - fn value_ty(&self, def: ValueTyDefId) -> Ty; - - #[salsa::invoke(crate::ty::field_types_query)] - fn field_types(&self, var: VariantId) -> Arc>; - - #[salsa::invoke(crate::ty::callable_item_sig)] - fn callable_item_signature(&self, def: CallableDef) -> FnSig; - - #[salsa::invoke(crate::ty::generic_predicates_for_param_query)] - fn generic_predicates_for_param( - &self, - def: GenericDefId, - param_idx: u32, - ) -> Arc<[GenericPredicate]>; - - #[salsa::invoke(crate::ty::generic_predicates_query)] - fn generic_predicates(&self, def: GenericDefId) -> Arc<[GenericPredicate]>; - - #[salsa::invoke(crate::ty::generic_defaults_query)] - fn generic_defaults(&self, def: GenericDefId) -> Substs; - - #[salsa::invoke(crate::ty::method_resolution::CrateImplBlocks::impls_in_crate_query)] - fn impls_in_crate(&self, krate: CrateId) -> Arc; - - #[salsa::invoke(crate::ty::traits::impls_for_trait_query)] - fn impls_for_trait(&self, krate: CrateId, trait_: TraitId) -> Arc<[ImplId]>; - - /// This provides the Chalk trait solver instance. Because Chalk always - /// works from a specific crate, this query is keyed on the crate; and - /// because Chalk does its own internal caching, the solver is wrapped in a - /// Mutex and the query does an untracked read internally, to make sure the - /// cached state is thrown away when input facts change. - #[salsa::invoke(crate::ty::traits::trait_solver_query)] - fn trait_solver(&self, krate: CrateId) -> crate::ty::traits::TraitSolver; - - // Interned IDs for Chalk integration - #[salsa::interned] - fn intern_type_ctor(&self, type_ctor: TypeCtor) -> crate::ty::TypeCtorId; - #[salsa::interned] - fn intern_chalk_impl(&self, impl_: Impl) -> crate::ty::traits::GlobalImplId; - #[salsa::interned] - fn intern_assoc_ty_value( - &self, - assoc_ty_value: AssocTyValue, - ) -> crate::ty::traits::AssocTyValueId; - - #[salsa::invoke(crate::ty::traits::chalk::associated_ty_data_query)] - fn associated_ty_data( - &self, - id: chalk_ir::TypeId, - ) -> Arc>; - - #[salsa::invoke(crate::ty::traits::chalk::trait_datum_query)] - fn trait_datum( - &self, - krate: CrateId, - trait_id: chalk_ir::TraitId, - ) -> Arc>; - - #[salsa::invoke(crate::ty::traits::chalk::struct_datum_query)] - fn struct_datum( - &self, - krate: CrateId, - struct_id: chalk_ir::StructId, - ) -> Arc>; - - #[salsa::invoke(crate::ty::traits::chalk::impl_datum_query)] - fn impl_datum( - &self, - krate: CrateId, - impl_id: chalk_ir::ImplId, - ) -> Arc>; - - #[salsa::invoke(crate::ty::traits::chalk::associated_ty_value_query)] - fn associated_ty_value( - &self, - krate: CrateId, - id: chalk_rust_ir::AssociatedTyValueId, - ) -> Arc>; - - #[salsa::invoke(crate::ty::traits::trait_solve_query)] - fn trait_solve( - &self, - krate: CrateId, - goal: crate::ty::Canonical>, - ) -> Option; -} +pub use hir_ty::db::{ + AssociatedTyDataQuery, CallableItemSignatureQuery, FieldTypesQuery, GenericDefaultsQuery, + GenericPredicatesQuery, HirDatabase, HirDatabaseStorage, ImplDatumQuery, ImplsForTraitQuery, + ImplsInCrateQuery, InferQuery, StructDatumQuery, TraitDatumQuery, TraitSolveQuery, TyQuery, + ValueTyQuery, +}; #[test] fn hir_database_is_object_safe() { diff --git a/crates/ra_hir/src/diagnostics.rs b/crates/ra_hir/src/diagnostics.rs index 6db499e06..a9040ea3d 100644 --- a/crates/ra_hir/src/diagnostics.rs +++ b/crates/ra_hir/src/diagnostics.rs @@ -1,93 +1,4 @@ //! FIXME: write short doc here - -use std::any::Any; - -use hir_expand::HirFileId; -use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; - -use crate::{db::AstDatabase, Name, Source}; - pub use hir_def::diagnostics::UnresolvedModule; pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; - -#[derive(Debug)] -pub struct NoSuchField { - pub file: HirFileId, - pub field: AstPtr, -} - -impl Diagnostic for NoSuchField { - fn message(&self) -> String { - "no such field".to_string() - } - - fn source(&self) -> Source { - Source { file_id: self.file, value: self.field.into() } - } - - fn as_any(&self) -> &(dyn Any + Send + 'static) { - self - } -} - -#[derive(Debug)] -pub struct MissingFields { - pub file: HirFileId, - pub field_list: AstPtr, - pub missed_fields: Vec, -} - -impl Diagnostic for MissingFields { - fn message(&self) -> String { - use std::fmt::Write; - let mut message = String::from("Missing structure fields:\n"); - for field in &self.missed_fields { - write!(message, "- {}\n", field).unwrap(); - } - message - } - fn source(&self) -> Source { - Source { file_id: self.file, value: self.field_list.into() } - } - fn as_any(&self) -> &(dyn Any + Send + 'static) { - self - } -} - -impl AstDiagnostic for MissingFields { - type AST = ast::RecordFieldList; - - fn ast(&self, db: &impl AstDatabase) -> Self::AST { - let root = db.parse_or_expand(self.source().file_id).unwrap(); - let node = self.source().value.to_node(&root); - ast::RecordFieldList::cast(node).unwrap() - } -} - -#[derive(Debug)] -pub struct MissingOkInTailExpr { - pub file: HirFileId, - pub expr: AstPtr, -} - -impl Diagnostic for MissingOkInTailExpr { - fn message(&self) -> String { - "wrap return expression in Ok".to_string() - } - fn source(&self) -> Source { - Source { file_id: self.file, value: self.expr.into() } - } - fn as_any(&self) -> &(dyn Any + Send + 'static) { - self - } -} - -impl AstDiagnostic for MissingOkInTailExpr { - type AST = ast::Expr; - - fn ast(&self, db: &impl AstDatabase) -> Self::AST { - let root = db.parse_or_expand(self.file).unwrap(); - let node = self.source().value.to_node(&root); - ast::Expr::cast(node).unwrap() - } -} +pub use hir_ty::diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField}; diff --git a/crates/ra_hir/src/expr.rs b/crates/ra_hir/src/expr.rs deleted file mode 100644 index 5c82c23d6..000000000 --- a/crates/ra_hir/src/expr.rs +++ /dev/null @@ -1,146 +0,0 @@ -//! FIXME: write short doc here - -use std::sync::Arc; - -use hir_def::{path::known, resolver::HasResolver, AdtId}; -use hir_expand::diagnostics::DiagnosticSink; -use ra_syntax::ast; -use ra_syntax::AstPtr; -use rustc_hash::FxHashSet; - -use crate::{ - db::HirDatabase, - diagnostics::{MissingFields, MissingOkInTailExpr}, - ty::{ApplicationTy, InferenceResult, Ty, TypeCtor}, - Function, Name, Path, Struct, -}; - -pub use hir_def::{ - body::{ - scope::{ExprScopes, ScopeEntry, ScopeId}, - Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource, - }, - expr::{ - ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, - MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, - }, -}; - -pub(crate) struct ExprValidator<'a, 'b: 'a> { - func: Function, - infer: Arc, - sink: &'a mut DiagnosticSink<'b>, -} - -impl<'a, 'b> ExprValidator<'a, 'b> { - pub(crate) fn new( - func: Function, - infer: Arc, - sink: &'a mut DiagnosticSink<'b>, - ) -> ExprValidator<'a, 'b> { - ExprValidator { func, infer, sink } - } - - pub(crate) fn validate_body(&mut self, db: &impl HirDatabase) { - let body = self.func.body(db); - - for e in body.exprs.iter() { - if let (id, Expr::RecordLit { path, fields, spread }) = e { - self.validate_record_literal(id, path, fields, *spread, db); - } - } - - let body_expr = &body[body.body_expr]; - if let Expr::Block { statements: _, tail: Some(t) } = body_expr { - self.validate_results_in_tail_expr(body.body_expr, *t, db); - } - } - - fn validate_record_literal( - &mut self, - id: ExprId, - _path: &Option, - fields: &[RecordLitField], - spread: Option, - db: &impl HirDatabase, - ) { - if spread.is_some() { - return; - } - - let struct_def = match self.infer[id].as_adt() { - Some((AdtId::StructId(s), _)) => Struct::from(s), - _ => return, - }; - - let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); - let missed_fields: Vec = struct_def - .fields(db) - .iter() - .filter_map(|f| { - let name = f.name(db); - if lit_fields.contains(&name) { - None - } else { - Some(name) - } - }) - .collect(); - if missed_fields.is_empty() { - return; - } - let source_map = self.func.body_source_map(db); - - if let Some(source_ptr) = source_map.expr_syntax(id) { - if let Some(expr) = source_ptr.value.a() { - let root = source_ptr.file_syntax(db); - if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) { - if let Some(field_list) = record_lit.record_field_list() { - self.sink.push(MissingFields { - file: source_ptr.file_id, - field_list: AstPtr::new(&field_list), - missed_fields, - }) - } - } - } - } - } - - fn validate_results_in_tail_expr( - &mut self, - body_id: ExprId, - id: ExprId, - db: &impl HirDatabase, - ) { - // the mismatch will be on the whole block currently - let mismatch = match self.infer.type_mismatch_for_expr(body_id) { - Some(m) => m, - None => return, - }; - - let std_result_path = known::std_result_result(); - - let resolver = self.func.id.resolver(db); - let std_result_enum = match resolver.resolve_known_enum(db, &std_result_path) { - Some(it) => it, - _ => return, - }; - - let std_result_ctor = TypeCtor::Adt(AdtId::EnumId(std_result_enum)); - let params = match &mismatch.expected { - Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters, - _ => return, - }; - - if params.len() == 2 && ¶ms[0] == &mismatch.actual { - let source_map = self.func.body_source_map(db); - - if let Some(source_ptr) = source_map.expr_syntax(id) { - if let Some(expr) = source_ptr.value.a() { - self.sink.push(MissingOkInTailExpr { file: source_ptr.file_id, expr }); - } - } - } - } -} diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index b88e4c745..3c12c61f0 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs @@ -32,20 +32,13 @@ pub mod db; pub mod source_binder; mod ty; -mod expr; pub mod diagnostics; -mod util; mod from_id; mod code_model; pub mod from_source; -#[cfg(test)] -mod test_db; -#[cfg(test)] -mod marks; - pub use crate::{ code_model::{ src::HasSource, Adt, AssocItem, AttrDef, Const, Container, Crate, CrateDependency, @@ -53,7 +46,6 @@ pub use crate::{ HasAttrs, ImplBlock, Import, Local, MacroDef, Module, ModuleDef, ModuleSource, ScopeDef, Static, Struct, StructField, Trait, Type, TypeAlias, Union, VariantDef, }, - expr::ExprScopes, from_source::FromSource, source_binder::{PathResolution, ScopeEntryWithSyntax, SourceAnalyzer}, ty::{ @@ -64,6 +56,7 @@ pub use crate::{ }; pub use hir_def::{ + body::scope::ExprScopes, builtin_type::BuiltinType, docs::Documentation, path::{Path, PathKind}, diff --git a/crates/ra_hir/src/marks.rs b/crates/ra_hir/src/marks.rs deleted file mode 100644 index 0f754eb9c..000000000 --- a/crates/ra_hir/src/marks.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! See test_utils/src/marks.rs - -test_utils::marks!( - type_var_cycles_resolve_completely - type_var_cycles_resolve_as_possible - type_var_resolves_to_int_var - match_ergonomics_ref - coerce_merge_fail_fallback -); diff --git a/crates/ra_hir/src/source_binder.rs b/crates/ra_hir/src/source_binder.rs index 05f5bca57..76c493f1a 100644 --- a/crates/ra_hir/src/source_binder.rs +++ b/crates/ra_hir/src/source_binder.rs @@ -8,6 +8,10 @@ use std::sync::Arc; use hir_def::{ + body::{ + scope::{ExprScopes, ScopeId}, + BodySourceMap, + }, expr::{ExprId, PatId}, path::known, resolver::{self, resolver_for_scope, HasResolver, Resolver, TypeNs, ValueNs}, @@ -25,7 +29,6 @@ use ra_syntax::{ use crate::{ db::HirDatabase, - expr::{BodySourceMap, ExprScopes, ScopeId}, ty::{ method_resolution::{self, implements_trait}, InEnvironment, TraitEnvironment, Ty, @@ -91,7 +94,7 @@ pub struct SourceAnalyzer { body_owner: Option, body_source_map: Option>, infer: Option>, - scopes: Option>, + scopes: Option>, } #[derive(Debug, Clone, PartialEq, Eq)] @@ -455,21 +458,6 @@ impl SourceAnalyzer { macro_file_kind: to_macro_file_kind(macro_call.value), }) } - - #[cfg(test)] - pub(crate) fn body_source_map(&self) -> Arc { - self.body_source_map.clone().unwrap() - } - - #[cfg(test)] - pub(crate) fn inference_result(&self) -> Arc { - self.infer.clone().unwrap() - } - - #[cfg(test)] - pub(crate) fn analyzed_declaration(&self) -> Option { - self.body_owner - } } fn scope_for( diff --git a/crates/ra_hir/src/test_db.rs b/crates/ra_hir/src/test_db.rs deleted file mode 100644 index a2071f71c..000000000 --- a/crates/ra_hir/src/test_db.rs +++ /dev/null @@ -1,123 +0,0 @@ -//! Database used for testing `hir`. - -use std::{panic, sync::Arc}; - -use hir_def::{db::DefDatabase, ModuleId}; -use hir_expand::diagnostics::DiagnosticSink; -use parking_lot::Mutex; -use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, SourceDatabase}; - -use crate::{db, debug::HirDebugHelper}; - -#[salsa::database( - ra_db::SourceDatabaseExtStorage, - ra_db::SourceDatabaseStorage, - db::InternDatabaseStorage, - db::AstDatabaseStorage, - db::DefDatabaseStorage, - db::HirDatabaseStorage -)] -#[derive(Debug, Default)] -pub struct TestDB { - events: Mutex>>>, - runtime: salsa::Runtime, -} - -impl salsa::Database for TestDB { - fn salsa_runtime(&self) -> &salsa::Runtime { - &self.runtime - } - - fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { - &mut self.runtime - } - - fn salsa_event(&self, event: impl Fn() -> salsa::Event) { - let mut events = self.events.lock(); - if let Some(events) = &mut *events { - events.push(event()); - } - } -} - -impl salsa::ParallelDatabase for TestDB { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(TestDB { - events: Default::default(), - runtime: self.runtime.snapshot(self), - }) - } -} - -impl panic::RefUnwindSafe for TestDB {} - -impl FileLoader for TestDB { - fn file_text(&self, file_id: FileId) -> Arc { - FileLoaderDelegate(self).file_text(file_id) - } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) - } - fn relevant_crates(&self, file_id: FileId) -> Arc> { - FileLoaderDelegate(self).relevant_crates(file_id) - } -} - -// FIXME: improve `WithFixture` to bring useful hir debugging back -impl HirDebugHelper for TestDB { - fn crate_name(&self, _krate: CrateId) -> Option { - None - } - - fn file_path(&self, _file_id: FileId) -> Option { - None - } -} - -impl TestDB { - pub fn diagnostics(&self) -> String { - let mut buf = String::new(); - let crate_graph = self.crate_graph(); - for krate in crate_graph.iter().next() { - let crate_def_map = self.crate_def_map(krate); - for (module_id, _) in crate_def_map.modules.iter() { - let module_id = ModuleId { krate, module_id }; - let module = crate::Module::from(module_id); - module.diagnostics( - self, - &mut DiagnosticSink::new(|d| { - buf += &format!("{:?}: {}\n", d.syntax_node(self).text(), d.message()); - }), - ) - } - } - buf - } -} - -impl TestDB { - pub fn log(&self, f: impl FnOnce()) -> Vec> { - *self.events.lock() = Some(Vec::new()); - f(); - self.events.lock().take().unwrap() - } - - pub fn log_executed(&self, f: impl FnOnce()) -> Vec { - let events = self.log(f); - events - .into_iter() - .filter_map(|e| match e.kind { - // This pretty horrible, but `Debug` is the only way to inspect - // QueryDescriptor at the moment. - salsa::EventKind::WillExecute { database_key } => { - Some(format!("{:?}", database_key)) - } - _ => None, - }) - .collect() - } -} diff --git a/crates/ra_hir/src/ty.rs b/crates/ra_hir/src/ty.rs index e4ba8afa6..4ed69c00d 100644 --- a/crates/ra_hir/src/ty.rs +++ b/crates/ra_hir/src/ty.rs @@ -1,1113 +1,4 @@ //! The type system. We currently use this to infer types for completion, hover //! information and various assists. -mod autoderef; -pub(crate) mod primitive; -pub(crate) mod traits; -pub(crate) mod method_resolution; -mod op; -mod lower; -mod infer; -pub(crate) mod display; -pub(crate) mod utils; - -#[cfg(test)] -mod tests; - -use std::ops::Deref; -use std::sync::Arc; -use std::{fmt, iter, mem}; - -use hir_def::{ - expr::ExprId, generics::GenericParams, type_ref::Mutability, AdtId, ContainerId, DefWithBodyId, - GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, -}; -use hir_expand::name::Name; -use ra_db::{impl_intern_key, salsa, CrateId}; - -use crate::{ - db::HirDatabase, - ty::primitive::{FloatTy, IntTy, Uncertain}, - util::make_mut_slice, -}; -use display::{HirDisplay, HirFormatter}; - -pub(crate) use autoderef::autoderef; -pub(crate) use infer::{infer_query, InferTy, InferenceResult}; -pub use lower::CallableDef; -pub(crate) use lower::{ - callable_item_sig, field_types_query, generic_defaults_query, - generic_predicates_for_param_query, generic_predicates_query, ty_query, value_ty_query, - TyDefId, ValueTyDefId, -}; -pub(crate) use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; - -/// A type constructor or type name: this might be something like the primitive -/// type `bool`, a struct like `Vec`, or things like function pointers or -/// tuples. -#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] -pub enum TypeCtor { - /// The primitive boolean type. Written as `bool`. - Bool, - - /// The primitive character type; holds a Unicode scalar value - /// (a non-surrogate code point). Written as `char`. - Char, - - /// A primitive integer type. For example, `i32`. - Int(Uncertain), - - /// A primitive floating-point type. For example, `f64`. - Float(Uncertain), - - /// Structures, enumerations and unions. - Adt(AdtId), - - /// The pointee of a string slice. Written as `str`. - Str, - - /// The pointee of an array slice. Written as `[T]`. - Slice, - - /// An array with the given length. Written as `[T; n]`. - Array, - - /// A raw pointer. Written as `*mut T` or `*const T` - RawPtr(Mutability), - - /// A reference; a pointer with an associated lifetime. Written as - /// `&'a mut T` or `&'a T`. - Ref(Mutability), - - /// The anonymous type of a function declaration/definition. Each - /// function has a unique type, which is output (for a function - /// named `foo` returning an `i32`) as `fn() -> i32 {foo}`. - /// - /// This includes tuple struct / enum variant constructors as well. - /// - /// For example the type of `bar` here: - /// - /// ``` - /// fn foo() -> i32 { 1 } - /// let bar = foo; // bar: fn() -> i32 {foo} - /// ``` - FnDef(CallableDef), - - /// A pointer to a function. Written as `fn() -> i32`. - /// - /// For example the type of `bar` here: - /// - /// ``` - /// fn foo() -> i32 { 1 } - /// let bar: fn() -> i32 = foo; - /// ``` - FnPtr { num_args: u16 }, - - /// The never type `!`. - Never, - - /// A tuple type. For example, `(i32, bool)`. - Tuple { cardinality: u16 }, - - /// Represents an associated item like `Iterator::Item`. This is used - /// when we have tried to normalize a projection like `T::Item` but - /// couldn't find a better representation. In that case, we generate - /// an **application type** like `(Iterator::Item)`. - AssociatedType(TypeAliasId), - - /// The type of a specific closure. - /// - /// The closure signature is stored in a `FnPtr` type in the first type - /// parameter. - Closure { def: DefWithBodyId, expr: ExprId }, -} - -/// This exists just for Chalk, because Chalk just has a single `StructId` where -/// we have different kinds of ADTs, primitive types and special type -/// constructors like tuples and function pointers. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct TypeCtorId(salsa::InternId); -impl_intern_key!(TypeCtorId); - -impl TypeCtor { - pub fn num_ty_params(self, db: &impl HirDatabase) -> usize { - match self { - TypeCtor::Bool - | TypeCtor::Char - | TypeCtor::Int(_) - | TypeCtor::Float(_) - | TypeCtor::Str - | TypeCtor::Never => 0, - TypeCtor::Slice - | TypeCtor::Array - | TypeCtor::RawPtr(_) - | TypeCtor::Ref(_) - | TypeCtor::Closure { .. } // 1 param representing the signature of the closure - => 1, - TypeCtor::Adt(adt) => { - let generic_params = db.generic_params(AdtId::from(adt).into()); - generic_params.count_params_including_parent() - } - TypeCtor::FnDef(callable) => { - let generic_params = db.generic_params(callable.into()); - generic_params.count_params_including_parent() - } - TypeCtor::AssociatedType(type_alias) => { - let generic_params = db.generic_params(type_alias.into()); - generic_params.count_params_including_parent() - } - TypeCtor::FnPtr { num_args } => num_args as usize + 1, - TypeCtor::Tuple { cardinality } => cardinality as usize, - } - } - - pub fn krate(self, db: &impl HirDatabase) -> Option { - match self { - TypeCtor::Bool - | TypeCtor::Char - | TypeCtor::Int(_) - | TypeCtor::Float(_) - | TypeCtor::Str - | TypeCtor::Never - | TypeCtor::Slice - | TypeCtor::Array - | TypeCtor::RawPtr(_) - | TypeCtor::Ref(_) - | TypeCtor::FnPtr { .. } - | TypeCtor::Tuple { .. } => None, - // Closure's krate is irrelevant for coherence I would think? - TypeCtor::Closure { .. } => None, - TypeCtor::Adt(adt) => Some(adt.module(db).krate), - TypeCtor::FnDef(callable) => Some(callable.krate(db)), - TypeCtor::AssociatedType(type_alias) => Some(type_alias.lookup(db).module(db).krate), - } - } - - pub fn as_generic_def(self) -> Option { - match self { - TypeCtor::Bool - | TypeCtor::Char - | TypeCtor::Int(_) - | TypeCtor::Float(_) - | TypeCtor::Str - | TypeCtor::Never - | TypeCtor::Slice - | TypeCtor::Array - | TypeCtor::RawPtr(_) - | TypeCtor::Ref(_) - | TypeCtor::FnPtr { .. } - | TypeCtor::Tuple { .. } - | TypeCtor::Closure { .. } => None, - TypeCtor::Adt(adt) => Some(adt.into()), - TypeCtor::FnDef(callable) => Some(callable.into()), - TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()), - } - } -} - -/// A nominal type with (maybe 0) type parameters. This might be a primitive -/// type like `bool`, a struct, tuple, function pointer, reference or -/// several other things. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub struct ApplicationTy { - pub ctor: TypeCtor, - pub parameters: Substs, -} - -/// A "projection" type corresponds to an (unnormalized) -/// projection like `>::Foo`. Note that the -/// trait and all its parameters are fully known. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub struct ProjectionTy { - pub associated_ty: TypeAliasId, - pub parameters: Substs, -} - -impl ProjectionTy { - pub fn trait_ref(&self, db: &impl HirDatabase) -> TraitRef { - TraitRef { trait_: self.trait_(db).into(), substs: self.parameters.clone() } - } - - fn trait_(&self, db: &impl HirDatabase) -> TraitId { - match self.associated_ty.lookup(db).container { - ContainerId::TraitId(it) => it, - _ => panic!("projection ty without parent trait"), - } - } -} - -impl TypeWalk for ProjectionTy { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.parameters.walk(f); - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - self.parameters.walk_mut_binders(f, binders); - } -} - -/// A type. -/// -/// See also the `TyKind` enum in rustc (librustc/ty/sty.rs), which represents -/// the same thing (but in a different way). -/// -/// This should be cheap to clone. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub enum Ty { - /// A nominal type with (maybe 0) type parameters. This might be a primitive - /// type like `bool`, a struct, tuple, function pointer, reference or - /// several other things. - Apply(ApplicationTy), - - /// A "projection" type corresponds to an (unnormalized) - /// projection like `>::Foo`. Note that the - /// trait and all its parameters are fully known. - Projection(ProjectionTy), - - /// A type parameter; for example, `T` in `fn f(x: T) {} - Param { - /// The index of the parameter (starting with parameters from the - /// surrounding impl, then the current function). - idx: u32, - /// The name of the parameter, for displaying. - // FIXME get rid of this - name: Name, - }, - - /// A bound type variable. Used during trait resolution to represent Chalk - /// variables, and in `Dyn` and `Opaque` bounds to represent the `Self` type. - Bound(u32), - - /// A type variable used during type checking. Not to be confused with a - /// type parameter. - Infer(InferTy), - - /// A trait object (`dyn Trait` or bare `Trait` in pre-2018 Rust). - /// - /// The predicates are quantified over the `Self` type, i.e. `Ty::Bound(0)` - /// represents the `Self` type inside the bounds. This is currently - /// implicit; Chalk has the `Binders` struct to make it explicit, but it - /// didn't seem worth the overhead yet. - Dyn(Arc<[GenericPredicate]>), - - /// An opaque type (`impl Trait`). - /// - /// The predicates are quantified over the `Self` type; see `Ty::Dyn` for - /// more. - Opaque(Arc<[GenericPredicate]>), - - /// A placeholder for a type which could not be computed; this is propagated - /// to avoid useless error messages. Doubles as a placeholder where type - /// variables are inserted before type checking, since we want to try to - /// infer a better type here anyway -- for the IDE use case, we want to try - /// to infer as much as possible even in the presence of type errors. - Unknown, -} - -/// A list of substitutions for generic parameters. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub struct Substs(Arc<[Ty]>); - -impl TypeWalk for Substs { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - for t in self.0.iter() { - t.walk(f); - } - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - for t in make_mut_slice(&mut self.0) { - t.walk_mut_binders(f, binders); - } - } -} - -impl Substs { - pub fn empty() -> Substs { - Substs(Arc::new([])) - } - - pub fn single(ty: Ty) -> Substs { - Substs(Arc::new([ty])) - } - - pub fn prefix(&self, n: usize) -> Substs { - Substs(self.0[..std::cmp::min(self.0.len(), n)].into()) - } - - pub fn as_single(&self) -> &Ty { - if self.0.len() != 1 { - panic!("expected substs of len 1, got {:?}", self); - } - &self.0[0] - } - - /// Return Substs that replace each parameter by itself (i.e. `Ty::Param`). - pub fn identity(generic_params: &GenericParams) -> Substs { - Substs( - generic_params - .params_including_parent() - .into_iter() - .map(|p| Ty::Param { idx: p.idx, name: p.name.clone() }) - .collect(), - ) - } - - /// Return Substs that replace each parameter by a bound variable. - pub fn bound_vars(generic_params: &GenericParams) -> Substs { - Substs( - generic_params - .params_including_parent() - .into_iter() - .map(|p| Ty::Bound(p.idx)) - .collect(), - ) - } - - pub fn build_for_def(db: &impl HirDatabase, def: impl Into) -> SubstsBuilder { - let def = def.into(); - let params = db.generic_params(def); - let param_count = params.count_params_including_parent(); - Substs::builder(param_count) - } - - pub fn build_for_generics(generic_params: &GenericParams) -> SubstsBuilder { - Substs::builder(generic_params.count_params_including_parent()) - } - - pub fn build_for_type_ctor(db: &impl HirDatabase, type_ctor: TypeCtor) -> SubstsBuilder { - Substs::builder(type_ctor.num_ty_params(db)) - } - - fn builder(param_count: usize) -> SubstsBuilder { - SubstsBuilder { vec: Vec::with_capacity(param_count), param_count } - } -} - -#[derive(Debug, Clone)] -pub struct SubstsBuilder { - vec: Vec, - param_count: usize, -} - -impl SubstsBuilder { - pub fn build(self) -> Substs { - assert_eq!(self.vec.len(), self.param_count); - Substs(self.vec.into()) - } - - pub fn push(mut self, ty: Ty) -> Self { - self.vec.push(ty); - self - } - - fn remaining(&self) -> usize { - self.param_count - self.vec.len() - } - - pub fn fill_with_bound_vars(self, starting_from: u32) -> Self { - self.fill((starting_from..).map(Ty::Bound)) - } - - pub fn fill_with_params(self) -> Self { - let start = self.vec.len() as u32; - self.fill((start..).map(|idx| Ty::Param { idx, name: Name::missing() })) - } - - pub fn fill_with_unknown(self) -> Self { - self.fill(iter::repeat(Ty::Unknown)) - } - - pub fn fill(mut self, filler: impl Iterator) -> Self { - self.vec.extend(filler.take(self.remaining())); - assert_eq!(self.remaining(), 0); - self - } - - pub fn use_parent_substs(mut self, parent_substs: &Substs) -> Self { - assert!(self.vec.is_empty()); - assert!(parent_substs.len() <= self.param_count); - self.vec.extend(parent_substs.iter().cloned()); - self - } -} - -impl Deref for Substs { - type Target = [Ty]; - - fn deref(&self) -> &[Ty] { - &self.0 - } -} - -/// A trait with type parameters. This includes the `Self`, so this represents a concrete type implementing the trait. -/// Name to be bikeshedded: TraitBound? TraitImplements? -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub struct TraitRef { - /// FIXME name? - pub trait_: TraitId, - pub substs: Substs, -} - -impl TraitRef { - pub fn self_ty(&self) -> &Ty { - &self.substs[0] - } -} - -impl TypeWalk for TraitRef { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.substs.walk(f); - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - self.substs.walk_mut_binders(f, binders); - } -} - -/// Like `generics::WherePredicate`, but with resolved types: A condition on the -/// parameters of a generic item. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum GenericPredicate { - /// The given trait needs to be implemented for its type parameters. - Implemented(TraitRef), - /// An associated type bindings like in `Iterator`. - Projection(ProjectionPredicate), - /// We couldn't resolve the trait reference. (If some type parameters can't - /// be resolved, they will just be Unknown). - Error, -} - -impl GenericPredicate { - pub fn is_error(&self) -> bool { - match self { - GenericPredicate::Error => true, - _ => false, - } - } - - pub fn is_implemented(&self) -> bool { - match self { - GenericPredicate::Implemented(_) => true, - _ => false, - } - } - - pub fn trait_ref(&self, db: &impl HirDatabase) -> Option { - match self { - GenericPredicate::Implemented(tr) => Some(tr.clone()), - GenericPredicate::Projection(proj) => Some(proj.projection_ty.trait_ref(db)), - GenericPredicate::Error => None, - } - } -} - -impl TypeWalk for GenericPredicate { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - match self { - GenericPredicate::Implemented(trait_ref) => trait_ref.walk(f), - GenericPredicate::Projection(projection_pred) => projection_pred.walk(f), - GenericPredicate::Error => {} - } - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - match self { - GenericPredicate::Implemented(trait_ref) => trait_ref.walk_mut_binders(f, binders), - GenericPredicate::Projection(projection_pred) => { - projection_pred.walk_mut_binders(f, binders) - } - GenericPredicate::Error => {} - } - } -} - -/// Basically a claim (currently not validated / checked) that the contained -/// type / trait ref contains no inference variables; any inference variables it -/// contained have been replaced by bound variables, and `num_vars` tells us how -/// many there are. This is used to erase irrelevant differences between types -/// before using them in queries. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct Canonical { - pub value: T, - pub num_vars: usize, -} - -/// A function signature as seen by type inference: Several parameter types and -/// one return type. -#[derive(Clone, PartialEq, Eq, Debug)] -pub struct FnSig { - params_and_return: Arc<[Ty]>, -} - -impl FnSig { - pub fn from_params_and_return(mut params: Vec, ret: Ty) -> FnSig { - params.push(ret); - FnSig { params_and_return: params.into() } - } - - pub fn from_fn_ptr_substs(substs: &Substs) -> FnSig { - FnSig { params_and_return: Arc::clone(&substs.0) } - } - - pub fn params(&self) -> &[Ty] { - &self.params_and_return[0..self.params_and_return.len() - 1] - } - - pub fn ret(&self) -> &Ty { - &self.params_and_return[self.params_and_return.len() - 1] - } -} - -impl TypeWalk for FnSig { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - for t in self.params_and_return.iter() { - t.walk(f); - } - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - for t in make_mut_slice(&mut self.params_and_return) { - t.walk_mut_binders(f, binders); - } - } -} - -impl Ty { - pub fn simple(ctor: TypeCtor) -> Ty { - Ty::Apply(ApplicationTy { ctor, parameters: Substs::empty() }) - } - pub fn apply_one(ctor: TypeCtor, param: Ty) -> Ty { - Ty::Apply(ApplicationTy { ctor, parameters: Substs::single(param) }) - } - pub fn apply(ctor: TypeCtor, parameters: Substs) -> Ty { - Ty::Apply(ApplicationTy { ctor, parameters }) - } - pub fn unit() -> Self { - Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) - } - - pub fn as_reference(&self) -> Option<(&Ty, Mutability)> { - match self { - Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(mutability), parameters }) => { - Some((parameters.as_single(), *mutability)) - } - _ => None, - } - } - - pub fn as_adt(&self) -> Option<(AdtId, &Substs)> { - match self { - Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(adt_def), parameters }) => { - Some((*adt_def, parameters)) - } - _ => None, - } - } - - pub fn as_tuple(&self) -> Option<&Substs> { - match self { - Ty::Apply(ApplicationTy { ctor: TypeCtor::Tuple { .. }, parameters }) => { - Some(parameters) - } - _ => None, - } - } - - pub fn as_callable(&self) -> Option<(CallableDef, &Substs)> { - match self { - Ty::Apply(ApplicationTy { ctor: TypeCtor::FnDef(callable_def), parameters }) => { - Some((*callable_def, parameters)) - } - _ => None, - } - } - - fn builtin_deref(&self) -> Option { - match self { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Ref(..) => Some(Ty::clone(a_ty.parameters.as_single())), - TypeCtor::RawPtr(..) => Some(Ty::clone(a_ty.parameters.as_single())), - _ => None, - }, - _ => None, - } - } - - fn callable_sig(&self, db: &impl HirDatabase) -> Option { - match self { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::FnPtr { .. } => Some(FnSig::from_fn_ptr_substs(&a_ty.parameters)), - TypeCtor::FnDef(def) => { - let sig = db.callable_item_signature(def); - Some(sig.subst(&a_ty.parameters)) - } - TypeCtor::Closure { .. } => { - let sig_param = &a_ty.parameters[0]; - sig_param.callable_sig(db) - } - _ => None, - }, - _ => None, - } - } - - /// If this is a type with type parameters (an ADT or function), replaces - /// the `Substs` for these type parameters with the given ones. (So e.g. if - /// `self` is `Option<_>` and the substs contain `u32`, we'll have - /// `Option` afterwards.) - pub fn apply_substs(self, substs: Substs) -> Ty { - match self { - Ty::Apply(ApplicationTy { ctor, parameters: previous_substs }) => { - assert_eq!(previous_substs.len(), substs.len()); - Ty::Apply(ApplicationTy { ctor, parameters: substs }) - } - _ => self, - } - } - - /// Returns the type parameters of this type if it has some (i.e. is an ADT - /// or function); so if `self` is `Option`, this returns the `u32`. - pub fn substs(&self) -> Option { - match self { - Ty::Apply(ApplicationTy { parameters, .. }) => Some(parameters.clone()), - _ => None, - } - } - - /// If this is an `impl Trait` or `dyn Trait`, returns that trait. - pub fn inherent_trait(&self) -> Option { - match self { - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { - predicates.iter().find_map(|pred| match pred { - GenericPredicate::Implemented(tr) => Some(tr.trait_), - _ => None, - }) - } - _ => None, - } - } -} - -/// This allows walking structures that contain types to do something with those -/// types, similar to Chalk's `Fold` trait. -pub trait TypeWalk { - fn walk(&self, f: &mut impl FnMut(&Ty)); - fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { - self.walk_mut_binders(&mut |ty, _binders| f(ty), 0); - } - /// Walk the type, counting entered binders. - /// - /// `Ty::Bound` variables use DeBruijn indexing, which means that 0 refers - /// to the innermost binder, 1 to the next, etc.. So when we want to - /// substitute a certain bound variable, we can't just walk the whole type - /// and blindly replace each instance of a certain index; when we 'enter' - /// things that introduce new bound variables, we have to keep track of - /// that. Currently, the only thing that introduces bound variables on our - /// side are `Ty::Dyn` and `Ty::Opaque`, which each introduce a bound - /// variable for the self type. - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize); - - fn fold(mut self, f: &mut impl FnMut(Ty) -> Ty) -> Self - where - Self: Sized, - { - self.walk_mut(&mut |ty_mut| { - let ty = mem::replace(ty_mut, Ty::Unknown); - *ty_mut = f(ty); - }); - self - } - - /// Replaces type parameters in this type using the given `Substs`. (So e.g. - /// if `self` is `&[T]`, where type parameter T has index 0, and the - /// `Substs` contain `u32` at index 0, we'll have `&[u32]` afterwards.) - fn subst(self, substs: &Substs) -> Self - where - Self: Sized, - { - self.fold(&mut |ty| match ty { - Ty::Param { idx, name } => { - substs.get(idx as usize).cloned().unwrap_or(Ty::Param { idx, name }) - } - ty => ty, - }) - } - - /// Substitutes `Ty::Bound` vars (as opposed to type parameters). - fn subst_bound_vars(mut self, substs: &Substs) -> Self - where - Self: Sized, - { - self.walk_mut_binders( - &mut |ty, binders| match ty { - &mut Ty::Bound(idx) => { - if idx as usize >= binders && (idx as usize - binders) < substs.len() { - *ty = substs.0[idx as usize - binders].clone(); - } - } - _ => {} - }, - 0, - ); - self - } - - /// Shifts up `Ty::Bound` vars by `n`. - fn shift_bound_vars(self, n: i32) -> Self - where - Self: Sized, - { - self.fold(&mut |ty| match ty { - Ty::Bound(idx) => { - assert!(idx as i32 >= -n); - Ty::Bound((idx as i32 + n) as u32) - } - ty => ty, - }) - } -} - -impl TypeWalk for Ty { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - match self { - Ty::Apply(a_ty) => { - for t in a_ty.parameters.iter() { - t.walk(f); - } - } - Ty::Projection(p_ty) => { - for t in p_ty.parameters.iter() { - t.walk(f); - } - } - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { - for p in predicates.iter() { - p.walk(f); - } - } - Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} - } - f(self); - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - match self { - Ty::Apply(a_ty) => { - a_ty.parameters.walk_mut_binders(f, binders); - } - Ty::Projection(p_ty) => { - p_ty.parameters.walk_mut_binders(f, binders); - } - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { - for p in make_mut_slice(predicates) { - p.walk_mut_binders(f, binders + 1); - } - } - Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} - } - f(self, binders); - } -} - -impl HirDisplay for &Ty { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - HirDisplay::hir_fmt(*self, f) - } -} - -impl HirDisplay for ApplicationTy { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - if f.should_truncate() { - return write!(f, "…"); - } - - match self.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 => { - let t = self.parameters.as_single(); - write!(f, "[{}]", t.display(f.db))?; - } - TypeCtor::Array => { - let t = self.parameters.as_single(); - write!(f, "[{};_]", t.display(f.db))?; - } - TypeCtor::RawPtr(m) => { - let t = self.parameters.as_single(); - write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?; - } - TypeCtor::Ref(m) => { - let t = self.parameters.as_single(); - write!(f, "&{}{}", m.as_keyword_for_ref(), t.display(f.db))?; - } - TypeCtor::Never => write!(f, "!")?, - TypeCtor::Tuple { .. } => { - let ts = &self.parameters; - if ts.len() == 1 { - write!(f, "({},)", ts[0].display(f.db))?; - } else { - write!(f, "(")?; - f.write_joined(&*ts.0, ", ")?; - write!(f, ")")?; - } - } - TypeCtor::FnPtr { .. } => { - let sig = FnSig::from_fn_ptr_substs(&self.parameters); - write!(f, "fn(")?; - f.write_joined(sig.params(), ", ")?; - write!(f, ") -> {}", sig.ret().display(f.db))?; - } - TypeCtor::FnDef(def) => { - let sig = f.db.callable_item_signature(def); - let name = match def { - CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), - CallableDef::StructId(s) => { - f.db.struct_data(s).name.clone().unwrap_or_else(Name::missing) - } - CallableDef::EnumVariantId(e) => { - let enum_data = f.db.enum_data(e.parent); - enum_data.variants[e.local_id].name.clone().unwrap_or_else(Name::missing) - } - }; - match def { - CallableDef::FunctionId(_) => write!(f, "fn {}", name)?, - CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => { - write!(f, "{}", name)? - } - } - if self.parameters.len() > 0 { - write!(f, "<")?; - f.write_joined(&*self.parameters.0, ", ")?; - write!(f, ">")?; - } - write!(f, "(")?; - f.write_joined(sig.params(), ", ")?; - write!(f, ") -> {}", sig.ret().display(f.db))?; - } - TypeCtor::Adt(def_id) => { - let name = match def_id { - AdtId::StructId(it) => f.db.struct_data(it).name.clone(), - AdtId::UnionId(it) => f.db.union_data(it).name.clone(), - AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), - } - .unwrap_or_else(Name::missing); - write!(f, "{}", name)?; - if self.parameters.len() > 0 { - write!(f, "<")?; - f.write_joined(&*self.parameters.0, ", ")?; - write!(f, ">")?; - } - } - TypeCtor::AssociatedType(type_alias) => { - let trait_ = match type_alias.lookup(f.db).container { - ContainerId::TraitId(it) => it, - _ => panic!("not an associated type"), - }; - let trait_name = f.db.trait_data(trait_).name.clone().unwrap_or_else(Name::missing); - let name = f.db.type_alias_data(type_alias).name.clone(); - write!(f, "{}::{}", trait_name, name)?; - if self.parameters.len() > 0 { - write!(f, "<")?; - f.write_joined(&*self.parameters.0, ", ")?; - write!(f, ">")?; - } - } - TypeCtor::Closure { .. } => { - let sig = self.parameters[0] - .callable_sig(f.db) - .expect("first closure parameter should contain signature"); - write!(f, "|")?; - f.write_joined(sig.params(), ", ")?; - write!(f, "| -> {}", sig.ret().display(f.db))?; - } - } - Ok(()) - } -} - -impl HirDisplay for ProjectionTy { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - if f.should_truncate() { - return write!(f, "…"); - } - - let trait_name = - f.db.trait_data(self.trait_(f.db)).name.clone().unwrap_or_else(Name::missing); - write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; - if self.parameters.len() > 1 { - write!(f, "<")?; - f.write_joined(&self.parameters[1..], ", ")?; - write!(f, ">")?; - } - write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?; - Ok(()) - } -} - -impl HirDisplay for Ty { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - if f.should_truncate() { - return write!(f, "…"); - } - - match self { - Ty::Apply(a_ty) => a_ty.hir_fmt(f)?, - Ty::Projection(p_ty) => p_ty.hir_fmt(f)?, - Ty::Param { name, .. } => write!(f, "{}", name)?, - Ty::Bound(idx) => write!(f, "?{}", idx)?, - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { - match self { - Ty::Dyn(_) => write!(f, "dyn ")?, - Ty::Opaque(_) => write!(f, "impl ")?, - _ => unreachable!(), - }; - // Note: This code is written to produce nice results (i.e. - // corresponding to surface Rust) for types that can occur in - // actual Rust. It will have weird results if the predicates - // aren't as expected (i.e. self types = $0, projection - // predicates for a certain trait come after the Implemented - // predicate for that trait). - let mut first = true; - let mut angle_open = false; - for p in predicates.iter() { - match p { - GenericPredicate::Implemented(trait_ref) => { - if angle_open { - write!(f, ">")?; - } - if !first { - write!(f, " + ")?; - } - // We assume that the self type is $0 (i.e. the - // existential) here, which is the only thing that's - // possible in actual Rust, and hence don't print it - write!( - f, - "{}", - f.db.trait_data(trait_ref.trait_) - .name - .clone() - .unwrap_or_else(Name::missing) - )?; - if trait_ref.substs.len() > 1 { - write!(f, "<")?; - f.write_joined(&trait_ref.substs[1..], ", ")?; - // there might be assoc type bindings, so we leave the angle brackets open - angle_open = true; - } - } - GenericPredicate::Projection(projection_pred) => { - // in types in actual Rust, these will always come - // after the corresponding Implemented predicate - if angle_open { - write!(f, ", ")?; - } else { - write!(f, "<")?; - angle_open = true; - } - let name = - f.db.type_alias_data(projection_pred.projection_ty.associated_ty) - .name - .clone(); - write!(f, "{} = ", name)?; - projection_pred.ty.hir_fmt(f)?; - } - GenericPredicate::Error => { - if angle_open { - // impl Trait - write!(f, ", ")?; - } else if !first { - // impl Trait + {error} - write!(f, " + ")?; - } - p.hir_fmt(f)?; - } - } - first = false; - } - if angle_open { - write!(f, ">")?; - } - } - Ty::Unknown => write!(f, "{{unknown}}")?, - Ty::Infer(..) => write!(f, "_")?, - } - Ok(()) - } -} - -impl TraitRef { - fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result { - if f.should_truncate() { - return write!(f, "…"); - } - - self.substs[0].hir_fmt(f)?; - if use_as { - write!(f, " as ")?; - } else { - write!(f, ": ")?; - } - write!(f, "{}", f.db.trait_data(self.trait_).name.clone().unwrap_or_else(Name::missing))?; - if self.substs.len() > 1 { - write!(f, "<")?; - f.write_joined(&self.substs[1..], ", ")?; - write!(f, ">")?; - } - Ok(()) - } -} - -impl HirDisplay for TraitRef { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - self.hir_fmt_ext(f, false) - } -} - -impl HirDisplay for &GenericPredicate { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - HirDisplay::hir_fmt(*self, f) - } -} - -impl HirDisplay for GenericPredicate { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - if f.should_truncate() { - return write!(f, "…"); - } - - match self { - GenericPredicate::Implemented(trait_ref) => trait_ref.hir_fmt(f)?, - GenericPredicate::Projection(projection_pred) => { - write!(f, "<")?; - projection_pred.projection_ty.trait_ref(f.db).hir_fmt_ext(f, true)?; - write!( - f, - ">::{} = {}", - f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name, - projection_pred.ty.display(f.db) - )?; - } - GenericPredicate::Error => write!(f, "{{error}}")?, - } - Ok(()) - } -} - -impl HirDisplay for Obligation { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { - match self { - Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)), - Obligation::Projection(proj) => write!( - f, - "Normalize({} => {})", - proj.projection_ty.display(f.db), - proj.ty.display(f.db) - ), - } - } -} +pub use hir_ty::*; diff --git a/crates/ra_hir/src/ty/autoderef.rs b/crates/ra_hir/src/ty/autoderef.rs deleted file mode 100644 index ae68234ac..000000000 --- a/crates/ra_hir/src/ty/autoderef.rs +++ /dev/null @@ -1,108 +0,0 @@ -//! In certain situations, rust automatically inserts derefs as necessary: for -//! example, field accesses `foo.bar` still work when `foo` is actually a -//! reference to a type with the field `bar`. This is an approximation of the -//! logic in rustc (which lives in librustc_typeck/check/autoderef.rs). - -use std::iter::successors; - -use hir_def::lang_item::LangItemTarget; -use hir_expand::name; -use log::{info, warn}; -use ra_db::CrateId; - -use crate::db::HirDatabase; - -use super::{ - traits::{InEnvironment, Solution}, - Canonical, Substs, Ty, TypeWalk, -}; - -const AUTODEREF_RECURSION_LIMIT: usize = 10; - -pub(crate) fn autoderef<'a>( - db: &'a impl HirDatabase, - krate: Option, - ty: InEnvironment>, -) -> impl Iterator> + 'a { - let InEnvironment { value: ty, environment } = ty; - successors(Some(ty), move |ty| { - deref(db, krate?, InEnvironment { value: ty, environment: environment.clone() }) - }) - .take(AUTODEREF_RECURSION_LIMIT) -} - -pub(crate) fn deref( - db: &impl HirDatabase, - krate: CrateId, - ty: InEnvironment<&Canonical>, -) -> Option> { - if let Some(derefed) = ty.value.value.builtin_deref() { - Some(Canonical { value: derefed, num_vars: ty.value.num_vars }) - } else { - deref_by_trait(db, krate, ty) - } -} - -fn deref_by_trait( - db: &impl HirDatabase, - krate: CrateId, - ty: InEnvironment<&Canonical>, -) -> Option> { - let deref_trait = match db.lang_item(krate.into(), "deref".into())? { - LangItemTarget::TraitId(it) => it, - _ => return None, - }; - let target = db.trait_data(deref_trait).associated_type_by_name(&name::TARGET_TYPE)?; - - let generic_params = db.generic_params(target.into()); - if generic_params.count_params_including_parent() != 1 { - // the Target type + Deref trait should only have one generic parameter, - // namely Deref's Self type - return None; - } - - // FIXME make the Canonical handling nicer - - let parameters = Substs::build_for_generics(&generic_params) - .push(ty.value.value.clone().shift_bound_vars(1)) - .build(); - - let projection = super::traits::ProjectionPredicate { - ty: Ty::Bound(0), - projection_ty: super::ProjectionTy { associated_ty: target, parameters }, - }; - - let obligation = super::Obligation::Projection(projection); - - let in_env = InEnvironment { value: obligation, environment: ty.environment }; - - let canonical = super::Canonical { num_vars: 1 + ty.value.num_vars, value: in_env }; - - let solution = db.trait_solve(krate.into(), canonical)?; - - match &solution { - Solution::Unique(vars) => { - // FIXME: vars may contain solutions for any inference variables - // that happened to be inside ty. To correctly handle these, we - // would have to pass the solution up to the inference context, but - // that requires a larger refactoring (especially if the deref - // happens during method resolution). So for the moment, we just - // check that we're not in the situation we're we would actually - // need to handle the values of the additional variables, i.e. - // they're just being 'passed through'. In the 'standard' case where - // we have `impl Deref for Foo { Target = T }`, that should be - // the case. - for i in 1..vars.0.num_vars { - if vars.0.value[i] != Ty::Bound((i - 1) as u32) { - warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.value, solution); - return None; - } - } - Some(Canonical { value: vars.0.value[0].clone(), num_vars: vars.0.num_vars }) - } - Solution::Ambig(_) => { - info!("Ambiguous solution for derefing {:?}: {:?}", ty.value, solution); - None - } - } -} diff --git a/crates/ra_hir/src/ty/display.rs b/crates/ra_hir/src/ty/display.rs deleted file mode 100644 index 9bb3ece6c..000000000 --- a/crates/ra_hir/src/ty/display.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! FIXME: write short doc here - -use std::fmt; - -use crate::db::HirDatabase; - -pub struct HirFormatter<'a, 'b, DB> { - pub db: &'a DB, - fmt: &'a mut fmt::Formatter<'b>, - buf: String, - curr_size: usize, - max_size: Option, -} - -pub trait HirDisplay { - fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result; - - fn display<'a, DB>(&'a self, db: &'a DB) -> HirDisplayWrapper<'a, DB, Self> - where - Self: Sized, - { - HirDisplayWrapper(db, self, None) - } - - fn display_truncated<'a, DB>( - &'a self, - db: &'a DB, - max_size: Option, - ) -> HirDisplayWrapper<'a, DB, Self> - where - Self: Sized, - { - HirDisplayWrapper(db, self, max_size) - } -} - -impl<'a, 'b, DB> HirFormatter<'a, 'b, DB> -where - DB: HirDatabase, -{ - pub fn write_joined( - &mut self, - iter: impl IntoIterator, - sep: &str, - ) -> fmt::Result { - let mut first = true; - for e in iter { - if !first { - write!(self, "{}", sep)?; - } - first = false; - e.hir_fmt(self)?; - } - Ok(()) - } - - /// This allows using the `write!` macro directly with a `HirFormatter`. - pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - // We write to a buffer first to track output size - self.buf.clear(); - fmt::write(&mut self.buf, args)?; - self.curr_size += self.buf.len(); - - // Then we write to the internal formatter from the buffer - self.fmt.write_str(&self.buf) - } - - pub fn should_truncate(&self) -> bool { - if let Some(max_size) = self.max_size { - self.curr_size >= max_size - } else { - false - } - } -} - -pub struct HirDisplayWrapper<'a, DB, T>(&'a DB, &'a T, Option); - -impl<'a, DB, T> fmt::Display for HirDisplayWrapper<'a, DB, T> -where - DB: HirDatabase, - T: HirDisplay, -{ - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.1.hir_fmt(&mut HirFormatter { - db: self.0, - fmt: f, - buf: String::with_capacity(20), - curr_size: 0, - max_size: self.2, - }) - } -} diff --git a/crates/ra_hir/src/ty/infer.rs b/crates/ra_hir/src/ty/infer.rs deleted file mode 100644 index 59e4e5f36..000000000 --- a/crates/ra_hir/src/ty/infer.rs +++ /dev/null @@ -1,723 +0,0 @@ -//! Type inference, i.e. the process of walking through the code and determining -//! the type of each expression and pattern. -//! -//! For type inference, compare the implementations in rustc (the various -//! check_* methods in librustc_typeck/check/mod.rs are a good entry point) and -//! IntelliJ-Rust (org.rust.lang.core.types.infer). Our entry point for -//! inference here is the `infer` function, which infers the types of all -//! expressions in a given function. -//! -//! During inference, types (i.e. the `Ty` struct) can contain type 'variables' -//! which represent currently unknown types; as we walk through the expressions, -//! we might determine that certain variables need to be equal to each other, or -//! to certain types. To record this, we use the union-find implementation from -//! the `ena` crate, which is extracted from rustc. - -use std::borrow::Cow; -use std::mem; -use std::ops::Index; -use std::sync::Arc; - -use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; -use rustc_hash::FxHashMap; - -use hir_def::{ - body::Body, - data::{ConstData, FunctionData}, - expr::{BindingAnnotation, ExprId, PatId}, - path::{known, Path}, - resolver::{HasResolver, Resolver, TypeNs}, - type_ref::{Mutability, TypeRef}, - AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TypeAliasId, VariantId, -}; -use hir_expand::{diagnostics::DiagnosticSink, name}; -use ra_arena::map::ArenaMap; -use ra_prof::profile; -use test_utils::tested_by; - -use super::{ - primitive::{FloatTy, IntTy}, - traits::{Guidance, Obligation, ProjectionPredicate, Solution}, - ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, - TypeWalk, Uncertain, -}; -use crate::{db::HirDatabase, ty::infer::diagnostics::InferenceDiagnostic}; - -macro_rules! ty_app { - ($ctor:pat, $param:pat) => { - crate::ty::Ty::Apply(crate::ty::ApplicationTy { ctor: $ctor, parameters: $param }) - }; - ($ctor:pat) => { - ty_app!($ctor, _) - }; -} - -mod unify; -mod path; -mod expr; -mod pat; -mod coerce; - -/// The entry point of type inference. -pub fn infer_query(db: &impl HirDatabase, def: DefWithBodyId) -> Arc { - let _p = profile("infer_query"); - let resolver = def.resolver(db); - let mut ctx = InferenceContext::new(db, def, resolver); - - match def { - DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)), - DefWithBodyId::FunctionId(f) => ctx.collect_fn(&db.function_data(f)), - DefWithBodyId::StaticId(s) => ctx.collect_const(&db.static_data(s)), - } - - ctx.infer_body(); - - Arc::new(ctx.resolve_all()) -} - -#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] -enum ExprOrPatId { - ExprId(ExprId), - PatId(PatId), -} - -impl_froms!(ExprOrPatId: ExprId, PatId); - -/// Binding modes inferred for patterns. -/// https://doc.rust-lang.org/reference/patterns.html#binding-modes -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -enum BindingMode { - Move, - Ref(Mutability), -} - -impl BindingMode { - pub fn convert(annotation: BindingAnnotation) -> BindingMode { - match annotation { - BindingAnnotation::Unannotated | BindingAnnotation::Mutable => BindingMode::Move, - BindingAnnotation::Ref => BindingMode::Ref(Mutability::Shared), - BindingAnnotation::RefMut => BindingMode::Ref(Mutability::Mut), - } - } -} - -impl Default for BindingMode { - fn default() -> Self { - BindingMode::Move - } -} - -/// A mismatch between an expected and an inferred type. -#[derive(Clone, PartialEq, Eq, Debug, Hash)] -pub struct TypeMismatch { - pub expected: Ty, - pub actual: Ty, -} - -/// The result of type inference: A mapping from expressions and patterns to types. -#[derive(Clone, PartialEq, Eq, Debug, Default)] -pub struct InferenceResult { - /// For each method call expr, records the function it resolves to. - method_resolutions: FxHashMap, - /// For each field access expr, records the field it resolves to. - field_resolutions: FxHashMap, - /// For each field in record literal, records the field it resolves to. - record_field_resolutions: FxHashMap, - /// For each struct literal, records the variant it resolves to. - variant_resolutions: FxHashMap, - /// For each associated item record what it resolves to - assoc_resolutions: FxHashMap, - diagnostics: Vec, - pub(super) type_of_expr: ArenaMap, - pub(super) type_of_pat: ArenaMap, - pub(super) type_mismatches: ArenaMap, -} - -impl InferenceResult { - pub fn method_resolution(&self, expr: ExprId) -> Option { - self.method_resolutions.get(&expr).copied() - } - pub fn field_resolution(&self, expr: ExprId) -> Option { - self.field_resolutions.get(&expr).copied() - } - pub fn record_field_resolution(&self, expr: ExprId) -> Option { - self.record_field_resolutions.get(&expr).copied() - } - pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option { - self.variant_resolutions.get(&id.into()).copied() - } - pub fn variant_resolution_for_pat(&self, id: PatId) -> Option { - self.variant_resolutions.get(&id.into()).copied() - } - pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option { - self.assoc_resolutions.get(&id.into()).copied() - } - pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option { - self.assoc_resolutions.get(&id.into()).copied() - } - pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> { - self.type_mismatches.get(expr) - } - pub(crate) fn add_diagnostics( - &self, - db: &impl HirDatabase, - owner: FunctionId, - sink: &mut DiagnosticSink, - ) { - self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink)) - } -} - -impl Index for InferenceResult { - type Output = Ty; - - fn index(&self, expr: ExprId) -> &Ty { - self.type_of_expr.get(expr).unwrap_or(&Ty::Unknown) - } -} - -impl Index for InferenceResult { - type Output = Ty; - - fn index(&self, pat: PatId) -> &Ty { - self.type_of_pat.get(pat).unwrap_or(&Ty::Unknown) - } -} - -/// The inference context contains all information needed during type inference. -#[derive(Clone, Debug)] -struct InferenceContext<'a, D: HirDatabase> { - db: &'a D, - owner: DefWithBodyId, - body: Arc, - resolver: Resolver, - var_unification_table: InPlaceUnificationTable, - trait_env: Arc, - obligations: Vec, - result: InferenceResult, - /// The return type of the function being inferred. - return_ty: Ty, - - /// Impls of `CoerceUnsized` used in coercion. - /// (from_ty_ctor, to_ty_ctor) => coerce_generic_index - // FIXME: Use trait solver for this. - // Chalk seems unable to work well with builtin impl of `Unsize` now. - coerce_unsized_map: FxHashMap<(TypeCtor, TypeCtor), usize>, -} - -impl<'a, D: HirDatabase> InferenceContext<'a, D> { - fn new(db: &'a D, owner: DefWithBodyId, resolver: Resolver) -> Self { - InferenceContext { - result: InferenceResult::default(), - var_unification_table: InPlaceUnificationTable::new(), - obligations: Vec::default(), - return_ty: Ty::Unknown, // set in collect_fn_signature - trait_env: TraitEnvironment::lower(db, &resolver), - coerce_unsized_map: Self::init_coerce_unsized_map(db, &resolver), - db, - owner, - body: db.body(owner.into()), - resolver, - } - } - - fn resolve_all(mut self) -> InferenceResult { - // FIXME resolve obligations as well (use Guidance if necessary) - let mut result = mem::replace(&mut self.result, InferenceResult::default()); - let mut tv_stack = Vec::new(); - for ty in result.type_of_expr.values_mut() { - let resolved = self.resolve_ty_completely(&mut tv_stack, mem::replace(ty, Ty::Unknown)); - *ty = resolved; - } - for ty in result.type_of_pat.values_mut() { - let resolved = self.resolve_ty_completely(&mut tv_stack, mem::replace(ty, Ty::Unknown)); - *ty = resolved; - } - result - } - - fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) { - self.result.type_of_expr.insert(expr, ty); - } - - fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId) { - self.result.method_resolutions.insert(expr, func); - } - - fn write_field_resolution(&mut self, expr: ExprId, field: StructFieldId) { - self.result.field_resolutions.insert(expr, field); - } - - fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantId) { - self.result.variant_resolutions.insert(id, variant); - } - - fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: AssocItemId) { - self.result.assoc_resolutions.insert(id, item.into()); - } - - fn write_pat_ty(&mut self, pat: PatId, ty: Ty) { - self.result.type_of_pat.insert(pat, ty); - } - - fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) { - self.result.diagnostics.push(diagnostic); - } - - fn make_ty(&mut self, type_ref: &TypeRef) -> Ty { - let ty = Ty::from_hir( - self.db, - // FIXME use right resolver for block - &self.resolver, - type_ref, - ); - let ty = self.insert_type_vars(ty); - self.normalize_associated_types_in(ty) - } - - fn unify_substs(&mut self, substs1: &Substs, substs2: &Substs, depth: usize) -> bool { - substs1.0.iter().zip(substs2.0.iter()).all(|(t1, t2)| self.unify_inner(t1, t2, depth)) - } - - fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool { - self.unify_inner(ty1, ty2, 0) - } - - fn unify_inner(&mut self, ty1: &Ty, ty2: &Ty, depth: usize) -> bool { - if depth > 1000 { - // prevent stackoverflows - panic!("infinite recursion in unification"); - } - if ty1 == ty2 { - return true; - } - // try to resolve type vars first - let ty1 = self.resolve_ty_shallow(ty1); - let ty2 = self.resolve_ty_shallow(ty2); - match (&*ty1, &*ty2) { - (Ty::Apply(a_ty1), Ty::Apply(a_ty2)) if a_ty1.ctor == a_ty2.ctor => { - self.unify_substs(&a_ty1.parameters, &a_ty2.parameters, depth + 1) - } - _ => self.unify_inner_trivial(&ty1, &ty2), - } - } - - fn unify_inner_trivial(&mut self, ty1: &Ty, ty2: &Ty) -> bool { - match (ty1, ty2) { - (Ty::Unknown, _) | (_, Ty::Unknown) => true, - - (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2))) - | (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2))) - | (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2))) - | ( - Ty::Infer(InferTy::MaybeNeverTypeVar(tv1)), - Ty::Infer(InferTy::MaybeNeverTypeVar(tv2)), - ) => { - // both type vars are unknown since we tried to resolve them - self.var_unification_table.union(*tv1, *tv2); - true - } - - // The order of MaybeNeverTypeVar matters here. - // Unifying MaybeNeverTypeVar and TypeVar will let the latter become MaybeNeverTypeVar. - // Unifying MaybeNeverTypeVar and other concrete type will let the former become it. - (Ty::Infer(InferTy::TypeVar(tv)), other) - | (other, Ty::Infer(InferTy::TypeVar(tv))) - | (Ty::Infer(InferTy::MaybeNeverTypeVar(tv)), other) - | (other, Ty::Infer(InferTy::MaybeNeverTypeVar(tv))) - | (Ty::Infer(InferTy::IntVar(tv)), other @ ty_app!(TypeCtor::Int(_))) - | (other @ ty_app!(TypeCtor::Int(_)), Ty::Infer(InferTy::IntVar(tv))) - | (Ty::Infer(InferTy::FloatVar(tv)), other @ ty_app!(TypeCtor::Float(_))) - | (other @ ty_app!(TypeCtor::Float(_)), Ty::Infer(InferTy::FloatVar(tv))) => { - // the type var is unknown since we tried to resolve it - self.var_unification_table.union_value(*tv, TypeVarValue::Known(other.clone())); - true - } - - _ => false, - } - } - - fn new_type_var(&mut self) -> Ty { - Ty::Infer(InferTy::TypeVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) - } - - fn new_integer_var(&mut self) -> Ty { - Ty::Infer(InferTy::IntVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) - } - - fn new_float_var(&mut self) -> Ty { - Ty::Infer(InferTy::FloatVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) - } - - fn new_maybe_never_type_var(&mut self) -> Ty { - Ty::Infer(InferTy::MaybeNeverTypeVar( - self.var_unification_table.new_key(TypeVarValue::Unknown), - )) - } - - /// Replaces Ty::Unknown by a new type var, so we can maybe still infer it. - fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { - match ty { - Ty::Unknown => self.new_type_var(), - Ty::Apply(ApplicationTy { ctor: TypeCtor::Int(Uncertain::Unknown), .. }) => { - self.new_integer_var() - } - Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(Uncertain::Unknown), .. }) => { - self.new_float_var() - } - _ => ty, - } - } - - fn insert_type_vars(&mut self, ty: Ty) -> Ty { - ty.fold(&mut |ty| self.insert_type_vars_shallow(ty)) - } - - fn resolve_obligations_as_possible(&mut self) { - let obligations = mem::replace(&mut self.obligations, Vec::new()); - for obligation in obligations { - let in_env = InEnvironment::new(self.trait_env.clone(), obligation.clone()); - let canonicalized = self.canonicalizer().canonicalize_obligation(in_env); - let solution = self - .db - .trait_solve(self.resolver.krate().unwrap().into(), canonicalized.value.clone()); - - match solution { - Some(Solution::Unique(substs)) => { - canonicalized.apply_solution(self, substs.0); - } - Some(Solution::Ambig(Guidance::Definite(substs))) => { - canonicalized.apply_solution(self, substs.0); - self.obligations.push(obligation); - } - Some(_) => { - // FIXME use this when trying to resolve everything at the end - self.obligations.push(obligation); - } - None => { - // FIXME obligation cannot be fulfilled => diagnostic - } - }; - } - } - - /// Resolves the type as far as currently possible, replacing type variables - /// by their known types. All types returned by the infer_* functions should - /// be resolved as far as possible, i.e. contain no type variables with - /// known type. - fn resolve_ty_as_possible(&mut self, tv_stack: &mut Vec, ty: Ty) -> Ty { - self.resolve_obligations_as_possible(); - - ty.fold(&mut |ty| match ty { - Ty::Infer(tv) => { - let inner = tv.to_inner(); - if tv_stack.contains(&inner) { - tested_by!(type_var_cycles_resolve_as_possible); - // recursive type - return tv.fallback_value(); - } - if let Some(known_ty) = - self.var_unification_table.inlined_probe_value(inner).known() - { - // known_ty may contain other variables that are known by now - tv_stack.push(inner); - let result = self.resolve_ty_as_possible(tv_stack, known_ty.clone()); - tv_stack.pop(); - result - } else { - ty - } - } - _ => ty, - }) - } - - /// If `ty` is a type variable with known type, returns that type; - /// otherwise, return ty. - fn resolve_ty_shallow<'b>(&mut self, ty: &'b Ty) -> Cow<'b, Ty> { - let mut ty = Cow::Borrowed(ty); - // The type variable could resolve to a int/float variable. Hence try - // resolving up to three times; each type of variable shouldn't occur - // more than once - for i in 0..3 { - if i > 0 { - tested_by!(type_var_resolves_to_int_var); - } - match &*ty { - Ty::Infer(tv) => { - let inner = tv.to_inner(); - match self.var_unification_table.inlined_probe_value(inner).known() { - Some(known_ty) => { - // The known_ty can't be a type var itself - ty = Cow::Owned(known_ty.clone()); - } - _ => return ty, - } - } - _ => return ty, - } - } - log::error!("Inference variable still not resolved: {:?}", ty); - ty - } - - /// Recurses through the given type, normalizing associated types mentioned - /// in it by replacing them by type variables and registering obligations to - /// resolve later. This should be done once for every type we get from some - /// type annotation (e.g. from a let type annotation, field type or function - /// call). `make_ty` handles this already, but e.g. for field types we need - /// to do it as well. - fn normalize_associated_types_in(&mut self, ty: Ty) -> Ty { - let ty = self.resolve_ty_as_possible(&mut vec![], ty); - ty.fold(&mut |ty| match ty { - Ty::Projection(proj_ty) => self.normalize_projection_ty(proj_ty), - _ => ty, - }) - } - - fn normalize_projection_ty(&mut self, proj_ty: ProjectionTy) -> Ty { - let var = self.new_type_var(); - let predicate = ProjectionPredicate { projection_ty: proj_ty, ty: var.clone() }; - let obligation = Obligation::Projection(predicate); - self.obligations.push(obligation); - var - } - - /// Resolves the type completely; type variables without known type are - /// replaced by Ty::Unknown. - fn resolve_ty_completely(&mut self, tv_stack: &mut Vec, ty: Ty) -> Ty { - ty.fold(&mut |ty| match ty { - Ty::Infer(tv) => { - let inner = tv.to_inner(); - if tv_stack.contains(&inner) { - tested_by!(type_var_cycles_resolve_completely); - // recursive type - return tv.fallback_value(); - } - if let Some(known_ty) = - self.var_unification_table.inlined_probe_value(inner).known() - { - // known_ty may contain other variables that are known by now - tv_stack.push(inner); - let result = self.resolve_ty_completely(tv_stack, known_ty.clone()); - tv_stack.pop(); - result - } else { - tv.fallback_value() - } - } - _ => ty, - }) - } - - fn resolve_variant(&mut self, path: Option<&Path>) -> (Ty, Option) { - let path = match path { - Some(path) => path, - None => return (Ty::Unknown, None), - }; - let resolver = &self.resolver; - // FIXME: this should resolve assoc items as well, see this example: - // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 - match resolver.resolve_path_in_type_ns_fully(self.db, &path) { - Some(TypeNs::AdtId(AdtId::StructId(strukt))) => { - let substs = Ty::substs_from_path(self.db, resolver, path, strukt.into()); - let ty = self.db.ty(strukt.into()); - let ty = self.insert_type_vars(ty.apply_substs(substs)); - (ty, Some(strukt.into())) - } - Some(TypeNs::EnumVariantId(var)) => { - let substs = Ty::substs_from_path(self.db, resolver, path, var.into()); - let ty = self.db.ty(var.parent.into()); - let ty = self.insert_type_vars(ty.apply_substs(substs)); - (ty, Some(var.into())) - } - Some(_) | None => (Ty::Unknown, None), - } - } - - fn collect_const(&mut self, data: &ConstData) { - self.return_ty = self.make_ty(&data.type_ref); - } - - fn collect_fn(&mut self, data: &FunctionData) { - let body = Arc::clone(&self.body); // avoid borrow checker problem - for (type_ref, pat) in data.params.iter().zip(body.params.iter()) { - let ty = self.make_ty(type_ref); - - self.infer_pat(*pat, &ty, BindingMode::default()); - } - self.return_ty = self.make_ty(&data.ret_type); - } - - fn infer_body(&mut self) { - self.infer_expr(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); - } - - fn resolve_into_iter_item(&self) -> Option { - let path = known::std_iter_into_iterator(); - let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name::ITEM_TYPE) - } - - fn resolve_ops_try_ok(&self) -> Option { - let path = known::std_ops_try(); - let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name::OK_TYPE) - } - - fn resolve_future_future_output(&self) -> Option { - let path = known::std_future_future(); - let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; - self.db.trait_data(trait_).associated_type_by_name(&name::OUTPUT_TYPE) - } - - fn resolve_boxed_box(&self) -> Option { - let path = known::std_boxed_box(); - let struct_ = self.resolver.resolve_known_struct(self.db, &path)?; - Some(struct_.into()) - } -} - -/// The ID of a type variable. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct TypeVarId(pub(super) u32); - -impl UnifyKey for TypeVarId { - type Value = TypeVarValue; - - fn index(&self) -> u32 { - self.0 - } - - fn from_index(i: u32) -> Self { - TypeVarId(i) - } - - fn tag() -> &'static str { - "TypeVarId" - } -} - -/// The value of a type variable: either we already know the type, or we don't -/// know it yet. -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum TypeVarValue { - Known(Ty), - Unknown, -} - -impl TypeVarValue { - fn known(&self) -> Option<&Ty> { - match self { - TypeVarValue::Known(ty) => Some(ty), - TypeVarValue::Unknown => None, - } - } -} - -impl UnifyValue for TypeVarValue { - type Error = NoError; - - fn unify_values(value1: &Self, value2: &Self) -> Result { - match (value1, value2) { - // We should never equate two type variables, both of which have - // known types. Instead, we recursively equate those types. - (TypeVarValue::Known(t1), TypeVarValue::Known(t2)) => panic!( - "equating two type variables, both of which have known types: {:?} and {:?}", - t1, t2 - ), - - // If one side is known, prefer that one. - (TypeVarValue::Known(..), TypeVarValue::Unknown) => Ok(value1.clone()), - (TypeVarValue::Unknown, TypeVarValue::Known(..)) => Ok(value2.clone()), - - (TypeVarValue::Unknown, TypeVarValue::Unknown) => Ok(TypeVarValue::Unknown), - } - } -} - -/// The kinds of placeholders we need during type inference. There's separate -/// values for general types, and for integer and float variables. The latter -/// two are used for inference of literal values (e.g. `100` could be one of -/// several integer types). -#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] -pub enum InferTy { - TypeVar(TypeVarId), - IntVar(TypeVarId), - FloatVar(TypeVarId), - MaybeNeverTypeVar(TypeVarId), -} - -impl InferTy { - fn to_inner(self) -> TypeVarId { - match self { - InferTy::TypeVar(ty) - | InferTy::IntVar(ty) - | InferTy::FloatVar(ty) - | InferTy::MaybeNeverTypeVar(ty) => ty, - } - } - - fn fallback_value(self) -> Ty { - match self { - InferTy::TypeVar(..) => Ty::Unknown, - InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::i32()))), - InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(Uncertain::Known(FloatTy::f64()))), - InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never), - } - } -} - -/// When inferring an expression, we propagate downward whatever type hint we -/// are able in the form of an `Expectation`. -#[derive(Clone, PartialEq, Eq, Debug)] -struct Expectation { - ty: Ty, - // FIXME: In some cases, we need to be aware whether the expectation is that - // the type match exactly what we passed, or whether it just needs to be - // coercible to the expected type. See Expectation::rvalue_hint in rustc. -} - -impl Expectation { - /// The expectation that the type of the expression needs to equal the given - /// type. - fn has_type(ty: Ty) -> Self { - Expectation { ty } - } - - /// This expresses no expectation on the type. - fn none() -> Self { - Expectation { ty: Ty::Unknown } - } -} - -mod diagnostics { - use hir_def::{expr::ExprId, FunctionId, HasSource, Lookup}; - use hir_expand::diagnostics::DiagnosticSink; - - use crate::{db::HirDatabase, diagnostics::NoSuchField}; - - #[derive(Debug, PartialEq, Eq, Clone)] - pub(super) enum InferenceDiagnostic { - NoSuchField { expr: ExprId, field: usize }, - } - - impl InferenceDiagnostic { - pub(super) fn add_to( - &self, - db: &impl HirDatabase, - owner: FunctionId, - sink: &mut DiagnosticSink, - ) { - match self { - InferenceDiagnostic::NoSuchField { expr, field } => { - let file = owner.lookup(db).source(db).file_id; - let (_, source_map) = db.body_with_source_map(owner.into()); - let field = source_map.field_syntax(*expr, *field); - sink.push(NoSuchField { file, field }) - } - } - } - } -} diff --git a/crates/ra_hir/src/ty/infer/coerce.rs b/crates/ra_hir/src/ty/infer/coerce.rs deleted file mode 100644 index 3fb5d8a83..000000000 --- a/crates/ra_hir/src/ty/infer/coerce.rs +++ /dev/null @@ -1,357 +0,0 @@ -//! Coercion logic. Coercions are certain type conversions that can implicitly -//! happen in certain places, e.g. weakening `&mut` to `&` or deref coercions -//! like going from `&Vec` to `&[T]`. -//! -//! See: https://doc.rust-lang.org/nomicon/coercions.html - -use hir_def::{ - lang_item::LangItemTarget, - resolver::{HasResolver, Resolver}, - type_ref::Mutability, - AdtId, -}; -use rustc_hash::FxHashMap; -use test_utils::tested_by; - -use crate::{ - db::HirDatabase, - ty::{autoderef, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, -}; - -use super::{InEnvironment, InferTy, InferenceContext, TypeVarValue}; - -impl<'a, D: HirDatabase> InferenceContext<'a, D> { - /// Unify two types, but may coerce the first one to the second one - /// using "implicit coercion rules" if needed. - pub(super) fn coerce(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool { - let from_ty = self.resolve_ty_shallow(from_ty).into_owned(); - let to_ty = self.resolve_ty_shallow(to_ty); - self.coerce_inner(from_ty, &to_ty) - } - - /// Merge two types from different branches, with possible implicit coerce. - /// - /// Note that it is only possible that one type are coerced to another. - /// Coercing both types to another least upper bound type is not possible in rustc, - /// which will simply result in "incompatible types" error. - pub(super) fn coerce_merge_branch<'t>(&mut self, ty1: &Ty, ty2: &Ty) -> Ty { - if self.coerce(ty1, ty2) { - ty2.clone() - } else if self.coerce(ty2, ty1) { - ty1.clone() - } else { - tested_by!(coerce_merge_fail_fallback); - // For incompatible types, we use the latter one as result - // to be better recovery for `if` without `else`. - ty2.clone() - } - } - - pub(super) fn init_coerce_unsized_map( - db: &'a D, - resolver: &Resolver, - ) -> FxHashMap<(TypeCtor, TypeCtor), usize> { - let krate = resolver.krate().unwrap(); - let impls = match db.lang_item(krate.into(), "coerce_unsized".into()) { - Some(LangItemTarget::TraitId(trait_)) => { - db.impls_for_trait(krate.into(), trait_.into()) - } - _ => return FxHashMap::default(), - }; - - impls - .iter() - .filter_map(|&impl_id| { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - - // `CoerseUnsized` has one generic parameter for the target type. - let trait_ref = TraitRef::from_hir( - db, - &resolver, - impl_data.target_trait.as_ref()?, - Some(target_ty), - )?; - let cur_from_ty = trait_ref.substs.0.get(0)?; - let cur_to_ty = trait_ref.substs.0.get(1)?; - - match (&cur_from_ty, cur_to_ty) { - (ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => { - // FIXME: We return the first non-equal bound as the type parameter to coerce to unsized type. - // This works for smart-pointer-like coercion, which covers all impls from std. - st1.iter().zip(st2.iter()).enumerate().find_map(|(i, (ty1, ty2))| { - match (ty1, ty2) { - (Ty::Param { idx: p1, .. }, Ty::Param { idx: p2, .. }) - if p1 != p2 => - { - Some(((*ctor1, *ctor2), i)) - } - _ => None, - } - }) - } - _ => None, - } - }) - .collect() - } - - fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool { - match (&from_ty, to_ty) { - // Never type will make type variable to fallback to Never Type instead of Unknown. - (ty_app!(TypeCtor::Never), Ty::Infer(InferTy::TypeVar(tv))) => { - let var = self.new_maybe_never_type_var(); - self.var_unification_table.union_value(*tv, TypeVarValue::Known(var)); - return true; - } - (ty_app!(TypeCtor::Never), _) => return true, - - // Trivial cases, this should go after `never` check to - // avoid infer result type to be never - _ => { - if self.unify_inner_trivial(&from_ty, &to_ty) { - return true; - } - } - } - - // Pointer weakening and function to pointer - match (&mut from_ty, to_ty) { - // `*mut T`, `&mut T, `&T`` -> `*const T` - // `&mut T` -> `&T` - // `&mut T` -> `*mut T` - (ty_app!(c1@TypeCtor::RawPtr(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared))) - | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared))) - | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::Ref(Mutability::Shared))) - | (ty_app!(c1@TypeCtor::Ref(Mutability::Mut)), ty_app!(c2@TypeCtor::RawPtr(_))) => { - *c1 = *c2; - } - - // Illegal mutablity conversion - ( - ty_app!(TypeCtor::RawPtr(Mutability::Shared)), - ty_app!(TypeCtor::RawPtr(Mutability::Mut)), - ) - | ( - ty_app!(TypeCtor::Ref(Mutability::Shared)), - ty_app!(TypeCtor::Ref(Mutability::Mut)), - ) => return false, - - // `{function_type}` -> `fn()` - (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnPtr { .. })) => { - match from_ty.callable_sig(self.db) { - None => return false, - Some(sig) => { - let num_args = sig.params_and_return.len() as u16 - 1; - from_ty = - Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return)); - } - } - } - - _ => {} - } - - if let Some(ret) = self.try_coerce_unsized(&from_ty, &to_ty) { - return ret; - } - - // Auto Deref if cannot coerce - match (&from_ty, to_ty) { - // FIXME: DerefMut - (ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2)) => { - self.unify_autoderef_behind_ref(&st1[0], &st2[0]) - } - - // Otherwise, normal unify - _ => self.unify(&from_ty, to_ty), - } - } - - /// Coerce a type using `from_ty: CoerceUnsized` - /// - /// See: https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html - fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> Option { - let (ctor1, st1, ctor2, st2) = match (from_ty, to_ty) { - (ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => (ctor1, st1, ctor2, st2), - _ => return None, - }; - - let coerce_generic_index = *self.coerce_unsized_map.get(&(*ctor1, *ctor2))?; - - // Check `Unsize` first - match self.check_unsize_and_coerce( - st1.0.get(coerce_generic_index)?, - st2.0.get(coerce_generic_index)?, - 0, - ) { - Some(true) => {} - ret => return ret, - } - - let ret = st1 - .iter() - .zip(st2.iter()) - .enumerate() - .filter(|&(idx, _)| idx != coerce_generic_index) - .all(|(_, (ty1, ty2))| self.unify(ty1, ty2)); - - Some(ret) - } - - /// Check if `from_ty: Unsize`, and coerce to `to_ty` if it holds. - /// - /// It should not be directly called. It is only used by `try_coerce_unsized`. - /// - /// See: https://doc.rust-lang.org/nightly/std/marker/trait.Unsize.html - fn check_unsize_and_coerce(&mut self, from_ty: &Ty, to_ty: &Ty, depth: usize) -> Option { - if depth > 1000 { - panic!("Infinite recursion in coercion"); - } - - match (&from_ty, &to_ty) { - // `[T; N]` -> `[T]` - (ty_app!(TypeCtor::Array, st1), ty_app!(TypeCtor::Slice, st2)) => { - Some(self.unify(&st1[0], &st2[0])) - } - - // `T` -> `dyn Trait` when `T: Trait` - (_, Ty::Dyn(_)) => { - // FIXME: Check predicates - Some(true) - } - - // `(..., T)` -> `(..., U)` when `T: Unsize` - ( - ty_app!(TypeCtor::Tuple { cardinality: len1 }, st1), - ty_app!(TypeCtor::Tuple { cardinality: len2 }, st2), - ) => { - if len1 != len2 || *len1 == 0 { - return None; - } - - match self.check_unsize_and_coerce( - st1.last().unwrap(), - st2.last().unwrap(), - depth + 1, - ) { - Some(true) => {} - ret => return ret, - } - - let ret = st1[..st1.len() - 1] - .iter() - .zip(&st2[..st2.len() - 1]) - .all(|(ty1, ty2)| self.unify(ty1, ty2)); - - Some(ret) - } - - // Foo<..., T, ...> is Unsize> if: - // - T: Unsize - // - Foo is a struct - // - Only the last field of Foo has a type involving T - // - T is not part of the type of any other fields - // - Bar: Unsize>, if the last field of Foo has type Bar - ( - ty_app!(TypeCtor::Adt(AdtId::StructId(struct1)), st1), - ty_app!(TypeCtor::Adt(AdtId::StructId(struct2)), st2), - ) if struct1 == struct2 => { - let field_tys = self.db.field_types((*struct1).into()); - let struct_data = self.db.struct_data(*struct1); - - let mut fields = struct_data.variant_data.fields().iter(); - let (last_field_id, _data) = fields.next_back()?; - - // Get the generic parameter involved in the last field. - let unsize_generic_index = { - let mut index = None; - let mut multiple_param = false; - field_tys[last_field_id].walk(&mut |ty| match ty { - &Ty::Param { idx, .. } => { - if index.is_none() { - index = Some(idx); - } else if Some(idx) != index { - multiple_param = true; - } - } - _ => {} - }); - - if multiple_param { - return None; - } - index? - }; - - // Check other fields do not involve it. - let mut multiple_used = false; - fields.for_each(|(field_id, _data)| { - field_tys[field_id].walk(&mut |ty| match ty { - &Ty::Param { idx, .. } if idx == unsize_generic_index => { - multiple_used = true - } - _ => {} - }) - }); - if multiple_used { - return None; - } - - let unsize_generic_index = unsize_generic_index as usize; - - // Check `Unsize` first - match self.check_unsize_and_coerce( - st1.get(unsize_generic_index)?, - st2.get(unsize_generic_index)?, - depth + 1, - ) { - Some(true) => {} - ret => return ret, - } - - // Then unify other parameters - let ret = st1 - .iter() - .zip(st2.iter()) - .enumerate() - .filter(|&(idx, _)| idx != unsize_generic_index) - .all(|(_, (ty1, ty2))| self.unify(ty1, ty2)); - - Some(ret) - } - - _ => None, - } - } - - /// Unify `from_ty` to `to_ty` with optional auto Deref - /// - /// Note that the parameters are already stripped the outer reference. - fn unify_autoderef_behind_ref(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool { - let canonicalized = self.canonicalizer().canonicalize_ty(from_ty.clone()); - let to_ty = self.resolve_ty_shallow(&to_ty); - // FIXME: Auto DerefMut - for derefed_ty in autoderef::autoderef( - self.db, - self.resolver.krate(), - InEnvironment { - value: canonicalized.value.clone(), - environment: self.trait_env.clone(), - }, - ) { - let derefed_ty = canonicalized.decanonicalize_ty(derefed_ty.value); - match (&*self.resolve_ty_shallow(&derefed_ty), &*to_ty) { - // Stop when constructor matches. - (ty_app!(from_ctor, st1), ty_app!(to_ctor, st2)) if from_ctor == to_ctor => { - // It will not recurse to `coerce`. - return self.unify_substs(st1, st2, 0); - } - _ => {} - } - } - - false - } -} diff --git a/crates/ra_hir/src/ty/infer/expr.rs b/crates/ra_hir/src/ty/infer/expr.rs deleted file mode 100644 index f9ededa23..000000000 --- a/crates/ra_hir/src/ty/infer/expr.rs +++ /dev/null @@ -1,689 +0,0 @@ -//! Type inference for expressions. - -use std::iter::{repeat, repeat_with}; -use std::sync::Arc; - -use hir_def::{ - builtin_type::Signedness, - expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, - generics::GenericParams, - path::{GenericArg, GenericArgs}, - resolver::resolver_for_expr, - AdtId, ContainerId, Lookup, StructFieldId, -}; -use hir_expand::name::{self, Name}; - -use crate::{ - db::HirDatabase, - ty::{ - autoderef, method_resolution, op, traits::InEnvironment, utils::variant_data, CallableDef, - InferTy, IntTy, Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, - TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, - }, -}; - -use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; - -impl<'a, D: HirDatabase> InferenceContext<'a, D> { - pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(tgt_expr, expected); - let could_unify = self.unify(&ty, &expected.ty); - if !could_unify { - self.result.type_mismatches.insert( - tgt_expr, - TypeMismatch { expected: expected.ty.clone(), actual: ty.clone() }, - ); - } - let ty = self.resolve_ty_as_possible(&mut vec![], ty); - ty - } - - /// Infer type of expression with possibly implicit coerce to the expected type. - /// Return the type after possible coercion. - fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { - let ty = self.infer_expr_inner(expr, &expected); - let ty = if !self.coerce(&ty, &expected.ty) { - self.result - .type_mismatches - .insert(expr, TypeMismatch { expected: expected.ty.clone(), actual: ty.clone() }); - // Return actual type when type mismatch. - // This is needed for diagnostic when return type mismatch. - ty - } else if expected.ty == Ty::Unknown { - ty - } else { - expected.ty.clone() - }; - - self.resolve_ty_as_possible(&mut vec![], ty) - } - - fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { - let body = Arc::clone(&self.body); // avoid borrow checker problem - let ty = match &body[tgt_expr] { - Expr::Missing => Ty::Unknown, - Expr::If { condition, then_branch, else_branch } => { - // if let is desugared to match, so this is always simple if - self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); - - let then_ty = self.infer_expr_inner(*then_branch, &expected); - let else_ty = match else_branch { - Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), - None => Ty::unit(), - }; - - self.coerce_merge_branch(&then_ty, &else_ty) - } - Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), - Expr::TryBlock { body } => { - let _inner = self.infer_expr(*body, expected); - // FIXME should be std::result::Result<{inner}, _> - Ty::Unknown - } - Expr::Loop { body } => { - self.infer_expr(*body, &Expectation::has_type(Ty::unit())); - // FIXME handle break with value - Ty::simple(TypeCtor::Never) - } - Expr::While { condition, body } => { - // while let is desugared to a match loop, so this is always simple while - self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); - self.infer_expr(*body, &Expectation::has_type(Ty::unit())); - Ty::unit() - } - Expr::For { iterable, body, pat } => { - let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); - - let pat_ty = match self.resolve_into_iter_item() { - Some(into_iter_item_alias) => { - let pat_ty = self.new_type_var(); - let projection = ProjectionPredicate { - ty: pat_ty.clone(), - projection_ty: ProjectionTy { - associated_ty: into_iter_item_alias, - parameters: Substs::single(iterable_ty), - }, - }; - self.obligations.push(Obligation::Projection(projection)); - self.resolve_ty_as_possible(&mut vec![], pat_ty) - } - None => Ty::Unknown, - }; - - self.infer_pat(*pat, &pat_ty, BindingMode::default()); - self.infer_expr(*body, &Expectation::has_type(Ty::unit())); - Ty::unit() - } - Expr::Lambda { body, args, arg_types } => { - assert_eq!(args.len(), arg_types.len()); - - let mut sig_tys = Vec::new(); - - for (arg_pat, arg_type) in args.iter().zip(arg_types.iter()) { - let expected = if let Some(type_ref) = arg_type { - self.make_ty(type_ref) - } else { - Ty::Unknown - }; - let arg_ty = self.infer_pat(*arg_pat, &expected, BindingMode::default()); - sig_tys.push(arg_ty); - } - - // add return type - let ret_ty = self.new_type_var(); - sig_tys.push(ret_ty.clone()); - let sig_ty = Ty::apply( - TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, - Substs(sig_tys.into()), - ); - let closure_ty = Ty::apply_one( - TypeCtor::Closure { def: self.owner.into(), expr: tgt_expr }, - sig_ty, - ); - - // Eagerly try to relate the closure type with the expected - // type, otherwise we often won't have enough information to - // infer the body. - self.coerce(&closure_ty, &expected.ty); - - self.infer_expr(*body, &Expectation::has_type(ret_ty)); - closure_ty - } - Expr::Call { callee, args } => { - let callee_ty = self.infer_expr(*callee, &Expectation::none()); - let (param_tys, ret_ty) = match callee_ty.callable_sig(self.db) { - Some(sig) => (sig.params().to_vec(), sig.ret().clone()), - None => { - // Not callable - // FIXME: report an error - (Vec::new(), Ty::Unknown) - } - }; - self.register_obligations_for_call(&callee_ty); - self.check_call_arguments(args, ¶m_tys); - let ret_ty = self.normalize_associated_types_in(ret_ty); - ret_ty - } - Expr::MethodCall { receiver, args, method_name, generic_args } => self - .infer_method_call(tgt_expr, *receiver, &args, &method_name, generic_args.as_ref()), - Expr::Match { expr, arms } => { - let input_ty = self.infer_expr(*expr, &Expectation::none()); - - let mut result_ty = self.new_maybe_never_type_var(); - - for arm in arms { - for &pat in &arm.pats { - let _pat_ty = self.infer_pat(pat, &input_ty, BindingMode::default()); - } - if let Some(guard_expr) = arm.guard { - self.infer_expr( - guard_expr, - &Expectation::has_type(Ty::simple(TypeCtor::Bool)), - ); - } - - let arm_ty = self.infer_expr_inner(arm.expr, &expected); - result_ty = self.coerce_merge_branch(&result_ty, &arm_ty); - } - - result_ty - } - Expr::Path(p) => { - // FIXME this could be more efficient... - let resolver = resolver_for_expr(self.db, self.owner.into(), tgt_expr); - self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) - } - Expr::Continue => Ty::simple(TypeCtor::Never), - Expr::Break { expr } => { - if let Some(expr) = expr { - // FIXME handle break with value - self.infer_expr(*expr, &Expectation::none()); - } - Ty::simple(TypeCtor::Never) - } - Expr::Return { expr } => { - if let Some(expr) = expr { - self.infer_expr(*expr, &Expectation::has_type(self.return_ty.clone())); - } - Ty::simple(TypeCtor::Never) - } - Expr::RecordLit { path, fields, spread } => { - let (ty, def_id) = self.resolve_variant(path.as_ref()); - if let Some(variant) = def_id { - self.write_variant_resolution(tgt_expr.into(), variant); - } - - self.unify(&ty, &expected.ty); - - let substs = ty.substs().unwrap_or_else(Substs::empty); - let field_types = - def_id.map(|it| self.db.field_types(it.into())).unwrap_or_default(); - let variant_data = def_id.map(|it| variant_data(self.db, it)); - for (field_idx, field) in fields.iter().enumerate() { - let field_def = - variant_data.as_ref().and_then(|it| match it.field(&field.name) { - Some(local_id) => { - Some(StructFieldId { parent: def_id.unwrap(), local_id }) - } - None => { - self.push_diagnostic(InferenceDiagnostic::NoSuchField { - expr: tgt_expr, - field: field_idx, - }); - None - } - }); - if let Some(field_def) = field_def { - self.result.record_field_resolutions.insert(field.expr, field_def); - } - let field_ty = field_def - .map_or(Ty::Unknown, |it| field_types[it.local_id].clone()) - .subst(&substs); - self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); - } - if let Some(expr) = spread { - self.infer_expr(*expr, &Expectation::has_type(ty.clone())); - } - ty - } - Expr::Field { expr, name } => { - let receiver_ty = self.infer_expr(*expr, &Expectation::none()); - let canonicalized = self.canonicalizer().canonicalize_ty(receiver_ty); - let ty = autoderef::autoderef( - self.db, - self.resolver.krate(), - InEnvironment { - value: canonicalized.value.clone(), - environment: self.trait_env.clone(), - }, - ) - .find_map(|derefed_ty| match canonicalized.decanonicalize_ty(derefed_ty.value) { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Tuple { .. } => name - .as_tuple_index() - .and_then(|idx| a_ty.parameters.0.get(idx).cloned()), - TypeCtor::Adt(AdtId::StructId(s)) => { - self.db.struct_data(s).variant_data.field(name).map(|local_id| { - let field = StructFieldId { parent: s.into(), local_id }.into(); - self.write_field_resolution(tgt_expr, field); - self.db.field_types(s.into())[field.local_id] - .clone() - .subst(&a_ty.parameters) - }) - } - // FIXME: - TypeCtor::Adt(AdtId::UnionId(_)) => None, - _ => None, - }, - _ => None, - }) - .unwrap_or(Ty::Unknown); - let ty = self.insert_type_vars(ty); - self.normalize_associated_types_in(ty) - } - Expr::Await { expr } => { - let inner_ty = self.infer_expr(*expr, &Expectation::none()); - let ty = match self.resolve_future_future_output() { - Some(future_future_output_alias) => { - let ty = self.new_type_var(); - let projection = ProjectionPredicate { - ty: ty.clone(), - projection_ty: ProjectionTy { - associated_ty: future_future_output_alias, - parameters: Substs::single(inner_ty), - }, - }; - self.obligations.push(Obligation::Projection(projection)); - self.resolve_ty_as_possible(&mut vec![], ty) - } - None => Ty::Unknown, - }; - ty - } - Expr::Try { expr } => { - let inner_ty = self.infer_expr(*expr, &Expectation::none()); - let ty = match self.resolve_ops_try_ok() { - Some(ops_try_ok_alias) => { - let ty = self.new_type_var(); - let projection = ProjectionPredicate { - ty: ty.clone(), - projection_ty: ProjectionTy { - associated_ty: ops_try_ok_alias, - parameters: Substs::single(inner_ty), - }, - }; - self.obligations.push(Obligation::Projection(projection)); - self.resolve_ty_as_possible(&mut vec![], ty) - } - None => Ty::Unknown, - }; - ty - } - Expr::Cast { expr, type_ref } => { - let _inner_ty = self.infer_expr(*expr, &Expectation::none()); - let cast_ty = self.make_ty(type_ref); - // FIXME check the cast... - cast_ty - } - Expr::Ref { expr, mutability } => { - let expectation = - if let Some((exp_inner, exp_mutability)) = &expected.ty.as_reference() { - if *exp_mutability == Mutability::Mut && *mutability == Mutability::Shared { - // FIXME: throw type error - expected mut reference but found shared ref, - // which cannot be coerced - } - Expectation::has_type(Ty::clone(exp_inner)) - } else { - Expectation::none() - }; - // FIXME reference coercions etc. - let inner_ty = self.infer_expr(*expr, &expectation); - Ty::apply_one(TypeCtor::Ref(*mutability), inner_ty) - } - Expr::Box { expr } => { - let inner_ty = self.infer_expr(*expr, &Expectation::none()); - if let Some(box_) = self.resolve_boxed_box() { - Ty::apply_one(TypeCtor::Adt(box_), inner_ty) - } else { - Ty::Unknown - } - } - Expr::UnaryOp { expr, op } => { - let inner_ty = self.infer_expr(*expr, &Expectation::none()); - match op { - UnaryOp::Deref => match self.resolver.krate() { - Some(krate) => { - let canonicalized = self.canonicalizer().canonicalize_ty(inner_ty); - match autoderef::deref( - self.db, - krate, - InEnvironment { - value: &canonicalized.value, - environment: self.trait_env.clone(), - }, - ) { - Some(derefed_ty) => { - canonicalized.decanonicalize_ty(derefed_ty.value) - } - None => Ty::Unknown, - } - } - None => Ty::Unknown, - }, - UnaryOp::Neg => { - match &inner_ty { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Int(Uncertain::Unknown) - | TypeCtor::Int(Uncertain::Known(IntTy { - signedness: Signedness::Signed, - .. - })) - | TypeCtor::Float(..) => inner_ty, - _ => Ty::Unknown, - }, - Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => { - inner_ty - } - // FIXME: resolve ops::Neg trait - _ => Ty::Unknown, - } - } - UnaryOp::Not => { - match &inner_ty { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Bool | TypeCtor::Int(_) => inner_ty, - _ => Ty::Unknown, - }, - Ty::Infer(InferTy::IntVar(..)) => inner_ty, - // FIXME: resolve ops::Not trait for inner_ty - _ => Ty::Unknown, - } - } - } - } - Expr::BinaryOp { lhs, rhs, op } => match op { - Some(op) => { - let lhs_expectation = match op { - BinaryOp::LogicOp(..) => Expectation::has_type(Ty::simple(TypeCtor::Bool)), - _ => Expectation::none(), - }; - let lhs_ty = self.infer_expr(*lhs, &lhs_expectation); - // FIXME: find implementation of trait corresponding to operation - // symbol and resolve associated `Output` type - let rhs_expectation = op::binary_op_rhs_expectation(*op, lhs_ty); - let rhs_ty = self.infer_expr(*rhs, &Expectation::has_type(rhs_expectation)); - - // FIXME: similar as above, return ty is often associated trait type - op::binary_op_return_ty(*op, rhs_ty) - } - _ => Ty::Unknown, - }, - Expr::Index { base, index } => { - let _base_ty = self.infer_expr(*base, &Expectation::none()); - let _index_ty = self.infer_expr(*index, &Expectation::none()); - // FIXME: use `std::ops::Index::Output` to figure out the real return type - Ty::Unknown - } - Expr::Tuple { exprs } => { - let mut tys = match &expected.ty { - ty_app!(TypeCtor::Tuple { .. }, st) => st - .iter() - .cloned() - .chain(repeat_with(|| self.new_type_var())) - .take(exprs.len()) - .collect::>(), - _ => (0..exprs.len()).map(|_| self.new_type_var()).collect(), - }; - - for (expr, ty) in exprs.iter().zip(tys.iter_mut()) { - self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone())); - } - - Ty::apply(TypeCtor::Tuple { cardinality: tys.len() as u16 }, Substs(tys.into())) - } - Expr::Array(array) => { - let elem_ty = match &expected.ty { - ty_app!(TypeCtor::Array, st) | ty_app!(TypeCtor::Slice, st) => { - st.as_single().clone() - } - _ => self.new_type_var(), - }; - - match array { - Array::ElementList(items) => { - for expr in items.iter() { - self.infer_expr_coerce(*expr, &Expectation::has_type(elem_ty.clone())); - } - } - Array::Repeat { initializer, repeat } => { - self.infer_expr_coerce( - *initializer, - &Expectation::has_type(elem_ty.clone()), - ); - self.infer_expr( - *repeat, - &Expectation::has_type(Ty::simple(TypeCtor::Int(Uncertain::Known( - IntTy::usize(), - )))), - ); - } - } - - Ty::apply_one(TypeCtor::Array, elem_ty) - } - Expr::Literal(lit) => match lit { - Literal::Bool(..) => Ty::simple(TypeCtor::Bool), - Literal::String(..) => { - Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str)) - } - Literal::ByteString(..) => { - let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); - let slice_type = Ty::apply_one(TypeCtor::Slice, byte_type); - Ty::apply_one(TypeCtor::Ref(Mutability::Shared), slice_type) - } - Literal::Char(..) => Ty::simple(TypeCtor::Char), - Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), - Literal::Float(_v, ty) => Ty::simple(TypeCtor::Float((*ty).into())), - }, - }; - // use a new type variable if we got Ty::Unknown here - let ty = self.insert_type_vars_shallow(ty); - let ty = self.resolve_ty_as_possible(&mut vec![], ty); - self.write_expr_ty(tgt_expr, ty.clone()); - ty - } - - fn infer_block( - &mut self, - statements: &[Statement], - tail: Option, - expected: &Expectation, - ) -> Ty { - let mut diverges = false; - for stmt in statements { - match stmt { - Statement::Let { pat, type_ref, initializer } => { - let decl_ty = - type_ref.as_ref().map(|tr| self.make_ty(tr)).unwrap_or(Ty::Unknown); - - // Always use the declared type when specified - let mut ty = decl_ty.clone(); - - if let Some(expr) = initializer { - let actual_ty = - self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())); - if decl_ty == Ty::Unknown { - ty = actual_ty; - } - } - - let ty = self.resolve_ty_as_possible(&mut vec![], ty); - self.infer_pat(*pat, &ty, BindingMode::default()); - } - Statement::Expr(expr) => { - if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) { - diverges = true; - } - } - } - } - - let ty = if let Some(expr) = tail { - self.infer_expr_coerce(expr, expected) - } else { - self.coerce(&Ty::unit(), &expected.ty); - Ty::unit() - }; - if diverges { - Ty::simple(TypeCtor::Never) - } else { - ty - } - } - - fn infer_method_call( - &mut self, - tgt_expr: ExprId, - receiver: ExprId, - args: &[ExprId], - method_name: &Name, - generic_args: Option<&GenericArgs>, - ) -> Ty { - let receiver_ty = self.infer_expr(receiver, &Expectation::none()); - let canonicalized_receiver = self.canonicalizer().canonicalize_ty(receiver_ty.clone()); - let resolved = method_resolution::lookup_method( - &canonicalized_receiver.value, - self.db, - method_name, - &self.resolver, - ); - let (derefed_receiver_ty, method_ty, def_generics) = match resolved { - Some((ty, func)) => { - let ty = canonicalized_receiver.decanonicalize_ty(ty); - self.write_method_resolution(tgt_expr, func); - (ty, self.db.value_ty(func.into()), Some(self.db.generic_params(func.into()))) - } - None => (receiver_ty, Ty::Unknown, None), - }; - let substs = self.substs_for_method_call(def_generics, generic_args, &derefed_receiver_ty); - let method_ty = method_ty.apply_substs(substs); - let method_ty = self.insert_type_vars(method_ty); - self.register_obligations_for_call(&method_ty); - let (expected_receiver_ty, param_tys, ret_ty) = match method_ty.callable_sig(self.db) { - Some(sig) => { - if !sig.params().is_empty() { - (sig.params()[0].clone(), sig.params()[1..].to_vec(), sig.ret().clone()) - } else { - (Ty::Unknown, Vec::new(), sig.ret().clone()) - } - } - None => (Ty::Unknown, Vec::new(), Ty::Unknown), - }; - // Apply autoref so the below unification works correctly - // FIXME: return correct autorefs from lookup_method - let actual_receiver_ty = match expected_receiver_ty.as_reference() { - Some((_, mutability)) => Ty::apply_one(TypeCtor::Ref(mutability), derefed_receiver_ty), - _ => derefed_receiver_ty, - }; - self.unify(&expected_receiver_ty, &actual_receiver_ty); - - self.check_call_arguments(args, ¶m_tys); - let ret_ty = self.normalize_associated_types_in(ret_ty); - ret_ty - } - - fn check_call_arguments(&mut self, args: &[ExprId], param_tys: &[Ty]) { - // Quoting https://github.com/rust-lang/rust/blob/6ef275e6c3cb1384ec78128eceeb4963ff788dca/src/librustc_typeck/check/mod.rs#L3325 -- - // We do this in a pretty awful way: first we type-check any arguments - // that are not closures, then we type-check the closures. This is so - // that we have more information about the types of arguments when we - // type-check the functions. This isn't really the right way to do this. - for &check_closures in &[false, true] { - let param_iter = param_tys.iter().cloned().chain(repeat(Ty::Unknown)); - for (&arg, param_ty) in args.iter().zip(param_iter) { - let is_closure = match &self.body[arg] { - Expr::Lambda { .. } => true, - _ => false, - }; - - if is_closure != check_closures { - continue; - } - - let param_ty = self.normalize_associated_types_in(param_ty); - self.infer_expr_coerce(arg, &Expectation::has_type(param_ty.clone())); - } - } - } - - fn substs_for_method_call( - &mut self, - def_generics: Option>, - generic_args: Option<&GenericArgs>, - receiver_ty: &Ty, - ) -> Substs { - let (parent_param_count, param_count) = - def_generics.as_ref().map_or((0, 0), |g| (g.count_parent_params(), g.params.len())); - let mut substs = Vec::with_capacity(parent_param_count + param_count); - // Parent arguments are unknown, except for the receiver type - if let Some(parent_generics) = def_generics.and_then(|p| p.parent_params.clone()) { - for param in &parent_generics.params { - if param.name == name::SELF_TYPE { - substs.push(receiver_ty.clone()); - } else { - substs.push(Ty::Unknown); - } - } - } - // handle provided type arguments - if let Some(generic_args) = generic_args { - // if args are provided, it should be all of them, but we can't rely on that - for arg in generic_args.args.iter().take(param_count) { - match arg { - GenericArg::Type(type_ref) => { - let ty = self.make_ty(type_ref); - substs.push(ty); - } - } - } - }; - let supplied_params = substs.len(); - for _ in supplied_params..parent_param_count + param_count { - substs.push(Ty::Unknown); - } - assert_eq!(substs.len(), parent_param_count + param_count); - Substs(substs.into()) - } - - fn register_obligations_for_call(&mut self, callable_ty: &Ty) { - if let Ty::Apply(a_ty) = callable_ty { - if let TypeCtor::FnDef(def) = a_ty.ctor { - let generic_predicates = self.db.generic_predicates(def.into()); - for predicate in generic_predicates.iter() { - let predicate = predicate.clone().subst(&a_ty.parameters); - if let Some(obligation) = Obligation::from_predicate(predicate) { - self.obligations.push(obligation); - } - } - // add obligation for trait implementation, if this is a trait method - match def { - CallableDef::FunctionId(f) => { - if let ContainerId::TraitId(trait_) = f.lookup(self.db).container { - // construct a TraitDef - let substs = a_ty.parameters.prefix( - self.db - .generic_params(trait_.into()) - .count_params_including_parent(), - ); - self.obligations.push(Obligation::Trait(TraitRef { - trait_: trait_.into(), - substs, - })); - } - } - CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {} - } - } - } - } -} diff --git a/crates/ra_hir/src/ty/infer/pat.rs b/crates/ra_hir/src/ty/infer/pat.rs deleted file mode 100644 index a14774607..000000000 --- a/crates/ra_hir/src/ty/infer/pat.rs +++ /dev/null @@ -1,189 +0,0 @@ -//! Type inference for patterns. - -use std::iter::repeat; -use std::sync::Arc; - -use hir_def::{ - expr::{BindingAnnotation, Pat, PatId, RecordFieldPat}, - path::Path, - type_ref::Mutability, -}; -use hir_expand::name::Name; -use test_utils::tested_by; - -use super::{BindingMode, InferenceContext}; -use crate::{ - db::HirDatabase, - ty::{utils::variant_data, Substs, Ty, TypeCtor, TypeWalk}, -}; - -impl<'a, D: HirDatabase> InferenceContext<'a, D> { - fn infer_tuple_struct_pat( - &mut self, - path: Option<&Path>, - subpats: &[PatId], - expected: &Ty, - default_bm: BindingMode, - ) -> Ty { - let (ty, def) = self.resolve_variant(path); - let var_data = def.map(|it| variant_data(self.db, it)); - self.unify(&ty, expected); - - let substs = ty.substs().unwrap_or_else(Substs::empty); - - let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default(); - - for (i, &subpat) in subpats.iter().enumerate() { - let expected_ty = var_data - .as_ref() - .and_then(|d| d.field(&Name::new_tuple_field(i))) - .map_or(Ty::Unknown, |field| field_tys[field].clone()) - .subst(&substs); - let expected_ty = self.normalize_associated_types_in(expected_ty); - self.infer_pat(subpat, &expected_ty, default_bm); - } - - ty - } - - fn infer_record_pat( - &mut self, - path: Option<&Path>, - subpats: &[RecordFieldPat], - expected: &Ty, - default_bm: BindingMode, - id: PatId, - ) -> Ty { - let (ty, def) = self.resolve_variant(path); - let var_data = def.map(|it| variant_data(self.db, it)); - if let Some(variant) = def { - self.write_variant_resolution(id.into(), variant); - } - - self.unify(&ty, expected); - - let substs = ty.substs().unwrap_or_else(Substs::empty); - - let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default(); - for subpat in subpats { - let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name)); - let expected_ty = - matching_field.map_or(Ty::Unknown, |field| field_tys[field].clone()).subst(&substs); - let expected_ty = self.normalize_associated_types_in(expected_ty); - self.infer_pat(subpat.pat, &expected_ty, default_bm); - } - - ty - } - - pub(super) fn infer_pat( - &mut self, - pat: PatId, - mut expected: &Ty, - mut default_bm: BindingMode, - ) -> Ty { - let body = Arc::clone(&self.body); // avoid borrow checker problem - - let is_non_ref_pat = match &body[pat] { - Pat::Tuple(..) - | Pat::TupleStruct { .. } - | Pat::Record { .. } - | Pat::Range { .. } - | Pat::Slice { .. } => true, - // FIXME: Path/Lit might actually evaluate to ref, but inference is unimplemented. - Pat::Path(..) | Pat::Lit(..) => true, - Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Missing => false, - }; - if is_non_ref_pat { - while let Some((inner, mutability)) = expected.as_reference() { - expected = inner; - default_bm = match default_bm { - BindingMode::Move => BindingMode::Ref(mutability), - BindingMode::Ref(Mutability::Shared) => BindingMode::Ref(Mutability::Shared), - BindingMode::Ref(Mutability::Mut) => BindingMode::Ref(mutability), - } - } - } else if let Pat::Ref { .. } = &body[pat] { - tested_by!(match_ergonomics_ref); - // When you encounter a `&pat` pattern, reset to Move. - // This is so that `w` is by value: `let (_, &w) = &(1, &2);` - default_bm = BindingMode::Move; - } - - // Lose mutability. - let default_bm = default_bm; - let expected = expected; - - let ty = match &body[pat] { - Pat::Tuple(ref args) => { - let expectations = match expected.as_tuple() { - Some(parameters) => &*parameters.0, - _ => &[], - }; - let expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown)); - - let inner_tys = args - .iter() - .zip(expectations_iter) - .map(|(&pat, ty)| self.infer_pat(pat, ty, default_bm)) - .collect(); - - Ty::apply(TypeCtor::Tuple { cardinality: args.len() as u16 }, Substs(inner_tys)) - } - Pat::Ref { pat, mutability } => { - let expectation = match expected.as_reference() { - Some((inner_ty, exp_mut)) => { - if *mutability != exp_mut { - // FIXME: emit type error? - } - inner_ty - } - _ => &Ty::Unknown, - }; - let subty = self.infer_pat(*pat, expectation, default_bm); - Ty::apply_one(TypeCtor::Ref(*mutability), subty) - } - Pat::TupleStruct { path: p, args: subpats } => { - self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm) - } - Pat::Record { path: p, args: fields } => { - self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) - } - Pat::Path(path) => { - // FIXME use correct resolver for the surrounding expression - let resolver = self.resolver.clone(); - self.infer_path(&resolver, &path, pat.into()).unwrap_or(Ty::Unknown) - } - Pat::Bind { mode, name: _, subpat } => { - let mode = if mode == &BindingAnnotation::Unannotated { - default_bm - } else { - BindingMode::convert(*mode) - }; - let inner_ty = if let Some(subpat) = subpat { - self.infer_pat(*subpat, expected, default_bm) - } else { - expected.clone() - }; - let inner_ty = self.insert_type_vars_shallow(inner_ty); - - let bound_ty = match mode { - BindingMode::Ref(mutability) => { - Ty::apply_one(TypeCtor::Ref(mutability), inner_ty.clone()) - } - BindingMode::Move => inner_ty.clone(), - }; - let bound_ty = self.resolve_ty_as_possible(&mut vec![], bound_ty); - self.write_pat_ty(pat, bound_ty); - return inner_ty; - } - _ => Ty::Unknown, - }; - // use a new type variable if we got Ty::Unknown here - let ty = self.insert_type_vars_shallow(ty); - self.unify(&ty, expected); - let ty = self.resolve_ty_as_possible(&mut vec![], ty); - self.write_pat_ty(pat, ty.clone()); - ty - } -} diff --git a/crates/ra_hir/src/ty/infer/path.rs b/crates/ra_hir/src/ty/infer/path.rs deleted file mode 100644 index 09ff79728..000000000 --- a/crates/ra_hir/src/ty/infer/path.rs +++ /dev/null @@ -1,273 +0,0 @@ -//! Path expression resolution. - -use hir_def::{ - path::{Path, PathSegment}, - resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, - AssocItemId, ContainerId, Lookup, -}; -use hir_expand::name::Name; - -use crate::{ - db::HirDatabase, - ty::{method_resolution, Substs, Ty, TypeWalk, ValueTyDefId}, -}; - -use super::{ExprOrPatId, InferenceContext, TraitRef}; - -impl<'a, D: HirDatabase> InferenceContext<'a, D> { - pub(super) fn infer_path( - &mut self, - resolver: &Resolver, - path: &Path, - id: ExprOrPatId, - ) -> Option { - let ty = self.resolve_value_path(resolver, path, id)?; - let ty = self.insert_type_vars(ty); - let ty = self.normalize_associated_types_in(ty); - Some(ty) - } - - fn resolve_value_path( - &mut self, - resolver: &Resolver, - path: &Path, - id: ExprOrPatId, - ) -> Option { - let (value, self_subst) = if let crate::PathKind::Type(type_ref) = &path.kind { - if path.segments.is_empty() { - // This can't actually happen syntax-wise - return None; - } - let ty = self.make_ty(type_ref); - let remaining_segments_for_ty = &path.segments[..path.segments.len() - 1]; - let ty = Ty::from_type_relative_path(self.db, resolver, ty, remaining_segments_for_ty); - self.resolve_ty_assoc_item( - ty, - &path.segments.last().expect("path had at least one segment").name, - id, - )? - } else { - let value_or_partial = resolver.resolve_path_in_value_ns(self.db, &path)?; - - match value_or_partial { - ResolveValueResult::ValueNs(it) => (it, None), - ResolveValueResult::Partial(def, remaining_index) => { - self.resolve_assoc_item(def, path, remaining_index, id)? - } - } - }; - - let typable: ValueTyDefId = match value { - ValueNs::LocalBinding(pat) => { - let ty = self.result.type_of_pat.get(pat)?.clone(); - let ty = self.resolve_ty_as_possible(&mut vec![], ty); - return Some(ty); - } - ValueNs::FunctionId(it) => it.into(), - ValueNs::ConstId(it) => it.into(), - ValueNs::StaticId(it) => it.into(), - ValueNs::StructId(it) => it.into(), - ValueNs::EnumVariantId(it) => it.into(), - }; - - let mut ty = self.db.value_ty(typable); - if let Some(self_subst) = self_subst { - ty = ty.subst(&self_subst); - } - let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable); - let ty = ty.subst(&substs); - Some(ty) - } - - fn resolve_assoc_item( - &mut self, - def: TypeNs, - path: &Path, - remaining_index: usize, - id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { - assert!(remaining_index < path.segments.len()); - // there may be more intermediate segments between the resolved one and - // the end. Only the last segment needs to be resolved to a value; from - // the segments before that, we need to get either a type or a trait ref. - - let resolved_segment = &path.segments[remaining_index - 1]; - let remaining_segments = &path.segments[remaining_index..]; - let is_before_last = remaining_segments.len() == 1; - - match (def, is_before_last) { - (TypeNs::TraitId(trait_), true) => { - let segment = - remaining_segments.last().expect("there should be at least one segment here"); - let trait_ref = TraitRef::from_resolved_path( - self.db, - &self.resolver, - trait_.into(), - resolved_segment, - None, - ); - self.resolve_trait_assoc_item(trait_ref, segment, id) - } - (def, _) => { - // Either we already have a type (e.g. `Vec::new`), or we have a - // trait but it's not the last segment, so the next segment - // should resolve to an associated type of that trait (e.g. `::Item::default`) - let remaining_segments_for_ty = &remaining_segments[..remaining_segments.len() - 1]; - let ty = Ty::from_partly_resolved_hir_path( - self.db, - &self.resolver, - def, - resolved_segment, - remaining_segments_for_ty, - ); - if let Ty::Unknown = ty { - return None; - } - - let ty = self.insert_type_vars(ty); - let ty = self.normalize_associated_types_in(ty); - - let segment = - remaining_segments.last().expect("there should be at least one segment here"); - - self.resolve_ty_assoc_item(ty, &segment.name, id) - } - } - } - - fn resolve_trait_assoc_item( - &mut self, - trait_ref: TraitRef, - segment: &PathSegment, - id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { - let trait_ = trait_ref.trait_; - let item = self - .db - .trait_data(trait_) - .items - .iter() - .map(|(_name, id)| (*id).into()) - .find_map(|item| match item { - AssocItemId::FunctionId(func) => { - if segment.name == self.db.function_data(func).name { - Some(AssocItemId::FunctionId(func)) - } else { - None - } - } - - AssocItemId::ConstId(konst) => { - if self.db.const_data(konst).name.as_ref().map_or(false, |n| n == &segment.name) - { - Some(AssocItemId::ConstId(konst)) - } else { - None - } - } - AssocItemId::TypeAliasId(_) => None, - })?; - let def = match item { - AssocItemId::FunctionId(f) => ValueNs::FunctionId(f), - AssocItemId::ConstId(c) => ValueNs::ConstId(c), - AssocItemId::TypeAliasId(_) => unreachable!(), - }; - let substs = Substs::build_for_def(self.db, item) - .use_parent_substs(&trait_ref.substs) - .fill_with_params() - .build(); - - self.write_assoc_resolution(id, item); - Some((def, Some(substs))) - } - - fn resolve_ty_assoc_item( - &mut self, - ty: Ty, - name: &Name, - id: ExprOrPatId, - ) -> Option<(ValueNs, Option)> { - if let Ty::Unknown = ty { - return None; - } - - let canonical_ty = self.canonicalizer().canonicalize_ty(ty.clone()); - - method_resolution::iterate_method_candidates( - &canonical_ty.value, - self.db, - &self.resolver.clone(), - Some(name), - method_resolution::LookupMode::Path, - move |_ty, item| { - let (def, container) = match item { - AssocItemId::FunctionId(f) => { - (ValueNs::FunctionId(f), f.lookup(self.db).container) - } - AssocItemId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db).container), - AssocItemId::TypeAliasId(_) => unreachable!(), - }; - let substs = match container { - ContainerId::ImplId(_) => self.find_self_types(&def, ty.clone()), - ContainerId::TraitId(trait_) => { - // we're picking this method - let trait_substs = Substs::build_for_def(self.db, trait_) - .push(ty.clone()) - .fill(std::iter::repeat_with(|| self.new_type_var())) - .build(); - let substs = Substs::build_for_def(self.db, item) - .use_parent_substs(&trait_substs) - .fill_with_params() - .build(); - self.obligations.push(super::Obligation::Trait(TraitRef { - trait_, - substs: trait_substs, - })); - Some(substs) - } - ContainerId::ModuleId(_) => None, - }; - - self.write_assoc_resolution(id, item.into()); - Some((def, substs)) - }, - ) - } - - fn find_self_types(&self, def: &ValueNs, actual_def_ty: Ty) -> Option { - if let ValueNs::FunctionId(func) = *def { - // We only do the infer if parent has generic params - let gen = self.db.generic_params(func.into()); - if gen.count_parent_params() == 0 { - return None; - } - - let impl_id = match func.lookup(self.db).container { - ContainerId::ImplId(it) => it, - _ => return None, - }; - let resolver = impl_id.resolver(self.db); - let impl_data = self.db.impl_data(impl_id); - let impl_block = Ty::from_hir(self.db, &resolver, &impl_data.target_type); - let impl_block_substs = impl_block.substs()?; - let actual_substs = actual_def_ty.substs()?; - - let mut new_substs = vec![Ty::Unknown; gen.count_parent_params()]; - - // The following code *link up* the function actual parma type - // and impl_block type param index - impl_block_substs.iter().zip(actual_substs.iter()).for_each(|(param, pty)| { - if let Ty::Param { idx, .. } = param { - if let Some(s) = new_substs.get_mut(*idx as usize) { - *s = pty.clone(); - } - } - }); - - Some(Substs(new_substs.into())) - } else { - None - } - } -} diff --git a/crates/ra_hir/src/ty/infer/unify.rs b/crates/ra_hir/src/ty/infer/unify.rs deleted file mode 100644 index e27bb2f82..000000000 --- a/crates/ra_hir/src/ty/infer/unify.rs +++ /dev/null @@ -1,166 +0,0 @@ -//! Unification and canonicalization logic. - -use super::{InferenceContext, Obligation}; -use crate::{ - db::HirDatabase, - ty::{ - Canonical, InEnvironment, InferTy, ProjectionPredicate, ProjectionTy, Substs, TraitRef, Ty, - TypeWalk, - }, - util::make_mut_slice, -}; - -impl<'a, D: HirDatabase> InferenceContext<'a, D> { - pub(super) fn canonicalizer<'b>(&'b mut self) -> Canonicalizer<'a, 'b, D> - where - 'a: 'b, - { - Canonicalizer { ctx: self, free_vars: Vec::new(), var_stack: Vec::new() } - } -} - -pub(super) struct Canonicalizer<'a, 'b, D: HirDatabase> -where - 'a: 'b, -{ - ctx: &'b mut InferenceContext<'a, D>, - free_vars: Vec, - /// A stack of type variables that is used to detect recursive types (which - /// are an error, but we need to protect against them to avoid stack - /// overflows). - var_stack: Vec, -} - -pub(super) struct Canonicalized { - pub value: Canonical, - free_vars: Vec, -} - -impl<'a, 'b, D: HirDatabase> Canonicalizer<'a, 'b, D> -where - 'a: 'b, -{ - fn add(&mut self, free_var: InferTy) -> usize { - self.free_vars.iter().position(|&v| v == free_var).unwrap_or_else(|| { - let next_index = self.free_vars.len(); - self.free_vars.push(free_var); - next_index - }) - } - - fn do_canonicalize_ty(&mut self, ty: Ty) -> Ty { - ty.fold(&mut |ty| match ty { - Ty::Infer(tv) => { - let inner = tv.to_inner(); - if self.var_stack.contains(&inner) { - // recursive type - return tv.fallback_value(); - } - if let Some(known_ty) = - self.ctx.var_unification_table.inlined_probe_value(inner).known() - { - self.var_stack.push(inner); - let result = self.do_canonicalize_ty(known_ty.clone()); - self.var_stack.pop(); - result - } else { - let root = self.ctx.var_unification_table.find(inner); - let free_var = match tv { - InferTy::TypeVar(_) => InferTy::TypeVar(root), - InferTy::IntVar(_) => InferTy::IntVar(root), - InferTy::FloatVar(_) => InferTy::FloatVar(root), - InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root), - }; - let position = self.add(free_var); - Ty::Bound(position as u32) - } - } - _ => ty, - }) - } - - fn do_canonicalize_trait_ref(&mut self, mut trait_ref: TraitRef) -> TraitRef { - for ty in make_mut_slice(&mut trait_ref.substs.0) { - *ty = self.do_canonicalize_ty(ty.clone()); - } - trait_ref - } - - fn into_canonicalized(self, result: T) -> Canonicalized { - Canonicalized { - value: Canonical { value: result, num_vars: self.free_vars.len() }, - free_vars: self.free_vars, - } - } - - fn do_canonicalize_projection_ty(&mut self, mut projection_ty: ProjectionTy) -> ProjectionTy { - for ty in make_mut_slice(&mut projection_ty.parameters.0) { - *ty = self.do_canonicalize_ty(ty.clone()); - } - projection_ty - } - - fn do_canonicalize_projection_predicate( - &mut self, - projection: ProjectionPredicate, - ) -> ProjectionPredicate { - let ty = self.do_canonicalize_ty(projection.ty); - let projection_ty = self.do_canonicalize_projection_ty(projection.projection_ty); - - ProjectionPredicate { ty, projection_ty } - } - - // FIXME: add some point, we need to introduce a `Fold` trait that abstracts - // over all the things that can be canonicalized (like Chalk and rustc have) - - pub(crate) fn canonicalize_ty(mut self, ty: Ty) -> Canonicalized { - let result = self.do_canonicalize_ty(ty); - self.into_canonicalized(result) - } - - pub(crate) fn canonicalize_obligation( - mut self, - obligation: InEnvironment, - ) -> Canonicalized> { - let result = match obligation.value { - Obligation::Trait(tr) => Obligation::Trait(self.do_canonicalize_trait_ref(tr)), - Obligation::Projection(pr) => { - Obligation::Projection(self.do_canonicalize_projection_predicate(pr)) - } - }; - self.into_canonicalized(InEnvironment { - value: result, - environment: obligation.environment, - }) - } -} - -impl Canonicalized { - pub fn decanonicalize_ty(&self, mut ty: Ty) -> Ty { - ty.walk_mut_binders( - &mut |ty, binders| match ty { - &mut Ty::Bound(idx) => { - if idx as usize >= binders && (idx as usize - binders) < self.free_vars.len() { - *ty = Ty::Infer(self.free_vars[idx as usize - binders]); - } - } - _ => {} - }, - 0, - ); - ty - } - - pub fn apply_solution( - &self, - ctx: &mut InferenceContext<'_, impl HirDatabase>, - solution: Canonical>, - ) { - // the solution may contain new variables, which we need to convert to new inference vars - let new_vars = Substs((0..solution.num_vars).map(|_| ctx.new_type_var()).collect()); - for (i, ty) in solution.value.into_iter().enumerate() { - let var = self.free_vars[i]; - ctx.unify(&Ty::Infer(var), &ty.subst_bound_vars(&new_vars)); - } - } -} diff --git a/crates/ra_hir/src/ty/lower.rs b/crates/ra_hir/src/ty/lower.rs deleted file mode 100644 index 2d447f1ea..000000000 --- a/crates/ra_hir/src/ty/lower.rs +++ /dev/null @@ -1,755 +0,0 @@ -//! Methods for lowering the HIR to types. There are two main cases here: -//! -//! - Lowering a type reference like `&usize` or `Option` to a -//! type: The entry point for this is `Ty::from_hir`. -//! - Building the type for an item: This happens through the `type_for_def` query. -//! -//! This usually involves resolving names, collecting generic arguments etc. -use std::iter; -use std::sync::Arc; - -use hir_def::{ - builtin_type::BuiltinType, - generics::WherePredicate, - path::{GenericArg, Path, PathSegment}, - resolver::{HasResolver, Resolver, TypeNs}, - type_ref::{TypeBound, TypeRef}, - AdtId, AstItemDef, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, - LocalStructFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, UnionId, VariantId, -}; -use ra_arena::map::ArenaMap; -use ra_db::CrateId; - -use super::{ - FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, - Ty, TypeCtor, TypeWalk, -}; -use crate::{ - db::HirDatabase, - ty::{ - primitive::{FloatTy, IntTy}, - utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, - }, - util::make_mut_slice, -}; - -impl Ty { - pub(crate) fn from_hir(db: &impl HirDatabase, resolver: &Resolver, type_ref: &TypeRef) -> Self { - match type_ref { - TypeRef::Never => Ty::simple(TypeCtor::Never), - TypeRef::Tuple(inner) => { - let inner_tys: Arc<[Ty]> = - inner.iter().map(|tr| Ty::from_hir(db, resolver, tr)).collect(); - Ty::apply( - TypeCtor::Tuple { cardinality: inner_tys.len() as u16 }, - Substs(inner_tys), - ) - } - TypeRef::Path(path) => Ty::from_hir_path(db, resolver, path), - TypeRef::RawPtr(inner, mutability) => { - let inner_ty = Ty::from_hir(db, resolver, inner); - Ty::apply_one(TypeCtor::RawPtr(*mutability), inner_ty) - } - TypeRef::Array(inner) => { - let inner_ty = Ty::from_hir(db, resolver, inner); - Ty::apply_one(TypeCtor::Array, inner_ty) - } - TypeRef::Slice(inner) => { - let inner_ty = Ty::from_hir(db, resolver, inner); - Ty::apply_one(TypeCtor::Slice, inner_ty) - } - TypeRef::Reference(inner, mutability) => { - let inner_ty = Ty::from_hir(db, resolver, inner); - Ty::apply_one(TypeCtor::Ref(*mutability), inner_ty) - } - TypeRef::Placeholder => Ty::Unknown, - TypeRef::Fn(params) => { - let sig = Substs(params.iter().map(|tr| Ty::from_hir(db, resolver, tr)).collect()); - Ty::apply(TypeCtor::FnPtr { num_args: sig.len() as u16 - 1 }, sig) - } - TypeRef::DynTrait(bounds) => { - let self_ty = Ty::Bound(0); - let predicates = bounds - .iter() - .flat_map(|b| { - GenericPredicate::from_type_bound(db, resolver, b, self_ty.clone()) - }) - .collect(); - Ty::Dyn(predicates) - } - TypeRef::ImplTrait(bounds) => { - let self_ty = Ty::Bound(0); - let predicates = bounds - .iter() - .flat_map(|b| { - GenericPredicate::from_type_bound(db, resolver, b, self_ty.clone()) - }) - .collect(); - Ty::Opaque(predicates) - } - TypeRef::Error => Ty::Unknown, - } - } - - /// This is only for `generic_predicates_for_param`, where we can't just - /// lower the self types of the predicates since that could lead to cycles. - /// So we just check here if the `type_ref` resolves to a generic param, and which. - fn from_hir_only_param( - db: &impl HirDatabase, - resolver: &Resolver, - type_ref: &TypeRef, - ) -> Option { - let path = match type_ref { - TypeRef::Path(path) => path, - _ => return None, - }; - if let crate::PathKind::Type(_) = &path.kind { - return None; - } - if path.segments.len() > 1 { - return None; - } - let resolution = match resolver.resolve_path_in_type_ns(db, path) { - Some((it, None)) => it, - _ => return None, - }; - if let TypeNs::GenericParam(idx) = resolution { - Some(idx) - } else { - None - } - } - - pub(crate) fn from_type_relative_path( - db: &impl HirDatabase, - resolver: &Resolver, - ty: Ty, - remaining_segments: &[PathSegment], - ) -> Ty { - if remaining_segments.len() == 1 { - // resolve unselected assoc types - let segment = &remaining_segments[0]; - Ty::select_associated_type(db, resolver, ty, segment) - } else if remaining_segments.len() > 1 { - // FIXME report error (ambiguous associated type) - Ty::Unknown - } else { - ty - } - } - - pub(crate) fn from_partly_resolved_hir_path( - db: &impl HirDatabase, - resolver: &Resolver, - resolution: TypeNs, - resolved_segment: &PathSegment, - remaining_segments: &[PathSegment], - ) -> Ty { - let ty = match resolution { - TypeNs::TraitId(trait_) => { - let trait_ref = - TraitRef::from_resolved_path(db, resolver, trait_, resolved_segment, None); - return if remaining_segments.len() == 1 { - let segment = &remaining_segments[0]; - let associated_ty = associated_type_by_name_including_super_traits( - db, - trait_ref.trait_, - &segment.name, - ); - match associated_ty { - Some(associated_ty) => { - // FIXME handle type parameters on the segment - Ty::Projection(ProjectionTy { - associated_ty, - parameters: trait_ref.substs, - }) - } - None => { - // FIXME: report error (associated type not found) - Ty::Unknown - } - } - } else if remaining_segments.len() > 1 { - // FIXME report error (ambiguous associated type) - Ty::Unknown - } else { - Ty::Dyn(Arc::new([GenericPredicate::Implemented(trait_ref)])) - }; - } - TypeNs::GenericParam(idx) => { - // FIXME: maybe return name in resolution? - let name = resolved_segment.name.clone(); - Ty::Param { idx, name } - } - TypeNs::SelfType(impl_id) => { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - Ty::from_hir(db, &resolver, &impl_data.target_type) - } - TypeNs::AdtSelfType(adt) => db.ty(adt.into()), - - TypeNs::AdtId(it) => Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()), - TypeNs::BuiltinType(it) => { - Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()) - } - TypeNs::TypeAliasId(it) => { - Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()) - } - // FIXME: report error - TypeNs::EnumVariantId(_) => return Ty::Unknown, - }; - - Ty::from_type_relative_path(db, resolver, ty, remaining_segments) - } - - pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &Path) -> Ty { - // Resolve the path (in type namespace) - if let crate::PathKind::Type(type_ref) = &path.kind { - let ty = Ty::from_hir(db, resolver, &type_ref); - let remaining_segments = &path.segments[..]; - return Ty::from_type_relative_path(db, resolver, ty, remaining_segments); - } - let (resolution, remaining_index) = match resolver.resolve_path_in_type_ns(db, path) { - Some(it) => it, - None => return Ty::Unknown, - }; - let (resolved_segment, remaining_segments) = match remaining_index { - None => ( - path.segments.last().expect("resolved path has at least one element"), - &[] as &[PathSegment], - ), - Some(i) => (&path.segments[i - 1], &path.segments[i..]), - }; - Ty::from_partly_resolved_hir_path( - db, - resolver, - resolution, - resolved_segment, - remaining_segments, - ) - } - - fn select_associated_type( - db: &impl HirDatabase, - resolver: &Resolver, - self_ty: Ty, - segment: &PathSegment, - ) -> Ty { - let param_idx = match self_ty { - Ty::Param { idx, .. } => idx, - _ => return Ty::Unknown, // Error: Ambiguous associated type - }; - let def = match resolver.generic_def() { - Some(def) => def, - None => return Ty::Unknown, // this can't actually happen - }; - let predicates = db.generic_predicates_for_param(def.into(), param_idx); - let traits_from_env = predicates.iter().filter_map(|pred| match pred { - GenericPredicate::Implemented(tr) if tr.self_ty() == &self_ty => Some(tr.trait_), - _ => None, - }); - let traits = traits_from_env.flat_map(|t| all_super_traits(db, t)); - for t in traits { - if let Some(associated_ty) = db.trait_data(t).associated_type_by_name(&segment.name) { - let substs = - Substs::build_for_def(db, t).push(self_ty.clone()).fill_with_unknown().build(); - // FIXME handle type parameters on the segment - return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); - } - } - Ty::Unknown - } - - fn from_hir_path_inner( - db: &impl HirDatabase, - resolver: &Resolver, - segment: &PathSegment, - typable: TyDefId, - ) -> Ty { - let generic_def = match typable { - TyDefId::BuiltinType(_) => None, - TyDefId::AdtId(it) => Some(it.into()), - TyDefId::TypeAliasId(it) => Some(it.into()), - }; - let substs = substs_from_path_segment(db, resolver, segment, generic_def, false); - db.ty(typable).subst(&substs) - } - - /// Collect generic arguments from a path into a `Substs`. See also - /// `create_substs_for_ast_path` and `def_to_ty` in rustc. - pub(super) fn substs_from_path( - db: &impl HirDatabase, - resolver: &Resolver, - path: &Path, - // Note that we don't call `db.value_type(resolved)` here, - // `ValueTyDefId` is just a convenient way to pass generics and - // special-case enum variants - resolved: ValueTyDefId, - ) -> Substs { - let last = path.segments.last().expect("path should have at least one segment"); - let (segment, generic_def) = match resolved { - ValueTyDefId::FunctionId(it) => (last, Some(it.into())), - ValueTyDefId::StructId(it) => (last, Some(it.into())), - ValueTyDefId::ConstId(it) => (last, Some(it.into())), - ValueTyDefId::StaticId(_) => (last, None), - ValueTyDefId::EnumVariantId(var) => { - // the generic args for an enum variant may be either specified - // on the segment referring to the enum, or on the segment - // referring to the variant. So `Option::::None` and - // `Option::None::` are both allowed (though the former is - // preferred). See also `def_ids_for_path_segments` in rustc. - let len = path.segments.len(); - let segment = if len >= 2 && path.segments[len - 2].args_and_bindings.is_some() { - // Option::::None - &path.segments[len - 2] - } else { - // Option::None:: - last - }; - (segment, Some(var.parent.into())) - } - }; - substs_from_path_segment(db, resolver, segment, generic_def, false) - } -} - -pub(super) fn substs_from_path_segment( - db: &impl HirDatabase, - resolver: &Resolver, - segment: &PathSegment, - def_generic: Option, - add_self_param: bool, -) -> Substs { - let mut substs = Vec::new(); - let def_generics = def_generic.map(|def| db.generic_params(def.into())); - - let (parent_param_count, param_count) = - def_generics.map_or((0, 0), |g| (g.count_parent_params(), g.params.len())); - substs.extend(iter::repeat(Ty::Unknown).take(parent_param_count)); - if add_self_param { - // FIXME this add_self_param argument is kind of a hack: Traits have the - // Self type as an implicit first type parameter, but it can't be - // actually provided in the type arguments - // (well, actually sometimes it can, in the form of type-relative paths: `::default()`) - substs.push(Ty::Unknown); - } - if let Some(generic_args) = &segment.args_and_bindings { - // if args are provided, it should be all of them, but we can't rely on that - let self_param_correction = if add_self_param { 1 } else { 0 }; - let param_count = param_count - self_param_correction; - for arg in generic_args.args.iter().take(param_count) { - match arg { - GenericArg::Type(type_ref) => { - let ty = Ty::from_hir(db, resolver, type_ref); - substs.push(ty); - } - } - } - } - // add placeholders for args that were not provided - let supplied_params = substs.len(); - for _ in supplied_params..parent_param_count + param_count { - substs.push(Ty::Unknown); - } - assert_eq!(substs.len(), parent_param_count + param_count); - - // handle defaults - if let Some(def_generic) = def_generic { - let default_substs = db.generic_defaults(def_generic.into()); - assert_eq!(substs.len(), default_substs.len()); - - for (i, default_ty) in default_substs.iter().enumerate() { - if substs[i] == Ty::Unknown { - substs[i] = default_ty.clone(); - } - } - } - - Substs(substs.into()) -} - -impl TraitRef { - pub(crate) fn from_path( - db: &impl HirDatabase, - resolver: &Resolver, - path: &Path, - explicit_self_ty: Option, - ) -> Option { - let resolved = match resolver.resolve_path_in_type_ns_fully(db, &path)? { - TypeNs::TraitId(tr) => tr, - _ => return None, - }; - let segment = path.segments.last().expect("path should have at least one segment"); - Some(TraitRef::from_resolved_path(db, resolver, resolved.into(), segment, explicit_self_ty)) - } - - pub(super) fn from_resolved_path( - db: &impl HirDatabase, - resolver: &Resolver, - resolved: TraitId, - segment: &PathSegment, - explicit_self_ty: Option, - ) -> Self { - let mut substs = TraitRef::substs_from_path(db, resolver, segment, resolved); - if let Some(self_ty) = explicit_self_ty { - make_mut_slice(&mut substs.0)[0] = self_ty; - } - TraitRef { trait_: resolved, substs } - } - - pub(crate) fn from_hir( - db: &impl HirDatabase, - resolver: &Resolver, - type_ref: &TypeRef, - explicit_self_ty: Option, - ) -> Option { - let path = match type_ref { - TypeRef::Path(path) => path, - _ => return None, - }; - TraitRef::from_path(db, resolver, path, explicit_self_ty) - } - - fn substs_from_path( - db: &impl HirDatabase, - resolver: &Resolver, - segment: &PathSegment, - resolved: TraitId, - ) -> Substs { - let has_self_param = - segment.args_and_bindings.as_ref().map(|a| a.has_self_type).unwrap_or(false); - substs_from_path_segment(db, resolver, segment, Some(resolved.into()), !has_self_param) - } - - pub(crate) fn for_trait(db: &impl HirDatabase, trait_: TraitId) -> TraitRef { - let substs = Substs::identity(&db.generic_params(trait_.into())); - TraitRef { trait_, substs } - } - - pub(crate) fn from_type_bound( - db: &impl HirDatabase, - resolver: &Resolver, - bound: &TypeBound, - self_ty: Ty, - ) -> Option { - match bound { - TypeBound::Path(path) => TraitRef::from_path(db, resolver, path, Some(self_ty)), - TypeBound::Error => None, - } - } -} - -impl GenericPredicate { - pub(crate) fn from_where_predicate<'a>( - db: &'a impl HirDatabase, - resolver: &'a Resolver, - where_predicate: &'a WherePredicate, - ) -> impl Iterator + 'a { - let self_ty = Ty::from_hir(db, resolver, &where_predicate.type_ref); - GenericPredicate::from_type_bound(db, resolver, &where_predicate.bound, self_ty) - } - - pub(crate) fn from_type_bound<'a>( - db: &'a impl HirDatabase, - resolver: &'a Resolver, - bound: &'a TypeBound, - self_ty: Ty, - ) -> impl Iterator + 'a { - let trait_ref = TraitRef::from_type_bound(db, &resolver, bound, self_ty); - iter::once(trait_ref.clone().map_or(GenericPredicate::Error, GenericPredicate::Implemented)) - .chain( - trait_ref.into_iter().flat_map(move |tr| { - assoc_type_bindings_from_type_bound(db, resolver, bound, tr) - }), - ) - } -} - -fn assoc_type_bindings_from_type_bound<'a>( - db: &'a impl HirDatabase, - resolver: &'a Resolver, - bound: &'a TypeBound, - trait_ref: TraitRef, -) -> impl Iterator + 'a { - let last_segment = match bound { - TypeBound::Path(path) => path.segments.last(), - TypeBound::Error => None, - }; - last_segment - .into_iter() - .flat_map(|segment| segment.args_and_bindings.iter()) - .flat_map(|args_and_bindings| args_and_bindings.bindings.iter()) - .map(move |(name, type_ref)| { - let associated_ty = - associated_type_by_name_including_super_traits(db, trait_ref.trait_, &name); - let associated_ty = match associated_ty { - None => return GenericPredicate::Error, - Some(t) => t, - }; - let projection_ty = - ProjectionTy { associated_ty, parameters: trait_ref.substs.clone() }; - let ty = Ty::from_hir(db, resolver, type_ref); - let projection_predicate = ProjectionPredicate { projection_ty, ty }; - GenericPredicate::Projection(projection_predicate) - }) -} - -/// Build the signature of a callable item (function, struct or enum variant). -pub(crate) fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSig { - match def { - CallableDef::FunctionId(f) => fn_sig_for_fn(db, f), - CallableDef::StructId(s) => fn_sig_for_struct_constructor(db, s), - CallableDef::EnumVariantId(e) => fn_sig_for_enum_variant_constructor(db, e), - } -} - -/// Build the type of all specific fields of a struct or enum variant. -pub(crate) fn field_types_query( - db: &impl HirDatabase, - variant_id: VariantId, -) -> Arc> { - let var_data = variant_data(db, variant_id); - let resolver = match variant_id { - VariantId::StructId(it) => it.resolver(db), - VariantId::UnionId(it) => it.resolver(db), - VariantId::EnumVariantId(it) => it.parent.resolver(db), - }; - let mut res = ArenaMap::default(); - for (field_id, field_data) in var_data.fields().iter() { - res.insert(field_id, Ty::from_hir(db, &resolver, &field_data.type_ref)) - } - Arc::new(res) -} - -/// This query exists only to be used when resolving short-hand associated types -/// like `T::Item`. -/// -/// See the analogous query in rustc and its comment: -/// https://github.com/rust-lang/rust/blob/9150f844e2624eb013ec78ca08c1d416e6644026/src/librustc_typeck/astconv.rs#L46 -/// This is a query mostly to handle cycles somewhat gracefully; e.g. the -/// following bounds are disallowed: `T: Foo, U: Foo`, but -/// these are fine: `T: Foo, U: Foo<()>`. -pub(crate) fn generic_predicates_for_param_query( - db: &impl HirDatabase, - def: GenericDefId, - param_idx: u32, -) -> Arc<[GenericPredicate]> { - let resolver = def.resolver(db); - resolver - .where_predicates_in_scope() - // we have to filter out all other predicates *first*, before attempting to lower them - .filter(|pred| Ty::from_hir_only_param(db, &resolver, &pred.type_ref) == Some(param_idx)) - .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) - .collect() -} - -impl TraitEnvironment { - pub(crate) fn lower(db: &impl HirDatabase, resolver: &Resolver) -> Arc { - let predicates = resolver - .where_predicates_in_scope() - .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) - .collect::>(); - - Arc::new(TraitEnvironment { predicates }) - } -} - -/// Resolve the where clause(s) of an item with generics. -pub(crate) fn generic_predicates_query( - db: &impl HirDatabase, - def: GenericDefId, -) -> Arc<[GenericPredicate]> { - let resolver = def.resolver(db); - resolver - .where_predicates_in_scope() - .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) - .collect() -} - -/// Resolve the default type params from generics -pub(crate) fn generic_defaults_query(db: &impl HirDatabase, def: GenericDefId) -> Substs { - let resolver = def.resolver(db); - let generic_params = db.generic_params(def.into()); - - let defaults = generic_params - .params_including_parent() - .into_iter() - .map(|p| p.default.as_ref().map_or(Ty::Unknown, |t| Ty::from_hir(db, &resolver, t))) - .collect(); - - Substs(defaults) -} - -fn fn_sig_for_fn(db: &impl HirDatabase, def: FunctionId) -> FnSig { - let data = db.function_data(def); - let resolver = def.resolver(db); - let params = data.params.iter().map(|tr| Ty::from_hir(db, &resolver, tr)).collect::>(); - let ret = Ty::from_hir(db, &resolver, &data.ret_type); - FnSig::from_params_and_return(params, ret) -} - -/// Build the declared type of a function. This should not need to look at the -/// function body. -fn type_for_fn(db: &impl HirDatabase, def: FunctionId) -> Ty { - let generics = db.generic_params(def.into()); - let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.into()), substs) -} - -/// Build the declared type of a const. -fn type_for_const(db: &impl HirDatabase, def: ConstId) -> Ty { - let data = db.const_data(def); - let resolver = def.resolver(db); - - Ty::from_hir(db, &resolver, &data.type_ref) -} - -/// Build the declared type of a static. -fn type_for_static(db: &impl HirDatabase, def: StaticId) -> Ty { - let data = db.static_data(def); - let resolver = def.resolver(db); - - Ty::from_hir(db, &resolver, &data.type_ref) -} - -/// Build the declared type of a static. -fn type_for_builtin(def: BuiltinType) -> Ty { - Ty::simple(match def { - BuiltinType::Char => TypeCtor::Char, - BuiltinType::Bool => TypeCtor::Bool, - BuiltinType::Str => TypeCtor::Str, - BuiltinType::Int(t) => TypeCtor::Int(IntTy::from(t).into()), - BuiltinType::Float(t) => TypeCtor::Float(FloatTy::from(t).into()), - }) -} - -fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> FnSig { - let struct_data = db.struct_data(def.into()); - let fields = struct_data.variant_data.fields(); - let resolver = def.resolver(db); - let params = fields - .iter() - .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) - .collect::>(); - let ret = type_for_adt(db, def.into()); - FnSig::from_params_and_return(params, ret) -} - -/// Build the type of a tuple struct constructor. -fn type_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> Ty { - let struct_data = db.struct_data(def.into()); - if struct_data.variant_data.is_unit() { - return type_for_adt(db, def.into()); // Unit struct - } - let generics = db.generic_params(def.into()); - let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(def.into()), substs) -} - -fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> FnSig { - let enum_data = db.enum_data(def.parent); - let var_data = &enum_data.variants[def.local_id]; - let fields = var_data.variant_data.fields(); - let resolver = def.parent.resolver(db); - let params = fields - .iter() - .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) - .collect::>(); - let generics = db.generic_params(def.parent.into()); - let substs = Substs::identity(&generics); - let ret = type_for_adt(db, def.parent.into()).subst(&substs); - FnSig::from_params_and_return(params, ret) -} - -/// Build the type of a tuple enum variant constructor. -fn type_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> Ty { - let enum_data = db.enum_data(def.parent); - let var_data = &enum_data.variants[def.local_id].variant_data; - if var_data.is_unit() { - return type_for_adt(db, def.parent.into()); // Unit variant - } - let generics = db.generic_params(def.parent.into()); - let substs = Substs::identity(&generics); - Ty::apply(TypeCtor::FnDef(EnumVariantId::from(def).into()), substs) -} - -fn type_for_adt(db: &impl HirDatabase, adt: AdtId) -> Ty { - let generics = db.generic_params(adt.into()); - Ty::apply(TypeCtor::Adt(adt), Substs::identity(&generics)) -} - -fn type_for_type_alias(db: &impl HirDatabase, t: TypeAliasId) -> Ty { - let generics = db.generic_params(t.into()); - let resolver = t.resolver(db); - let type_ref = &db.type_alias_data(t).type_ref; - let substs = Substs::identity(&generics); - let inner = Ty::from_hir(db, &resolver, type_ref.as_ref().unwrap_or(&TypeRef::Error)); - inner.subst(&substs) -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum CallableDef { - FunctionId(FunctionId), - StructId(StructId), - EnumVariantId(EnumVariantId), -} -impl_froms!(CallableDef: FunctionId, StructId, EnumVariantId); - -impl CallableDef { - pub fn krate(self, db: &impl HirDatabase) -> CrateId { - match self { - CallableDef::FunctionId(f) => f.lookup(db).module(db).krate, - CallableDef::StructId(s) => s.module(db).krate, - CallableDef::EnumVariantId(e) => e.parent.module(db).krate, - } - } -} - -impl From for GenericDefId { - fn from(def: CallableDef) -> GenericDefId { - match def { - CallableDef::FunctionId(f) => f.into(), - CallableDef::StructId(s) => s.into(), - CallableDef::EnumVariantId(e) => e.into(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum TyDefId { - BuiltinType(BuiltinType), - AdtId(AdtId), - TypeAliasId(TypeAliasId), -} -impl_froms!(TyDefId: BuiltinType, AdtId(StructId, EnumId, UnionId), TypeAliasId); - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum ValueTyDefId { - FunctionId(FunctionId), - StructId(StructId), - EnumVariantId(EnumVariantId), - ConstId(ConstId), - StaticId(StaticId), -} -impl_froms!(ValueTyDefId: FunctionId, StructId, EnumVariantId, ConstId, StaticId); - -/// Build the declared type of an item. This depends on the namespace; e.g. for -/// `struct Foo(usize)`, we have two types: The type of the struct itself, and -/// the constructor function `(usize) -> Foo` which lives in the values -/// namespace. -pub(crate) fn ty_query(db: &impl HirDatabase, def: TyDefId) -> Ty { - match def { - TyDefId::BuiltinType(it) => type_for_builtin(it), - TyDefId::AdtId(it) => type_for_adt(db, it), - TyDefId::TypeAliasId(it) => type_for_type_alias(db, it), - } -} -pub(crate) fn value_ty_query(db: &impl HirDatabase, def: ValueTyDefId) -> Ty { - match def { - ValueTyDefId::FunctionId(it) => type_for_fn(db, it), - ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it), - ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it), - ValueTyDefId::ConstId(it) => type_for_const(db, it), - ValueTyDefId::StaticId(it) => type_for_static(db, it), - } -} diff --git a/crates/ra_hir/src/ty/method_resolution.rs b/crates/ra_hir/src/ty/method_resolution.rs deleted file mode 100644 index 5cc249855..000000000 --- a/crates/ra_hir/src/ty/method_resolution.rs +++ /dev/null @@ -1,362 +0,0 @@ -//! This module is concerned with finding methods that a given type provides. -//! For details about how this works in rustc, see the method lookup page in the -//! [rustc guide](https://rust-lang.github.io/rustc-guide/method-lookup.html) -//! and the corresponding code mostly in librustc_typeck/check/method/probe.rs. -use std::sync::Arc; - -use arrayvec::ArrayVec; -use hir_def::{ - lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, type_ref::Mutability, - AssocItemId, AstItemDef, FunctionId, HasModule, ImplId, TraitId, -}; -use hir_expand::name::Name; -use ra_db::CrateId; -use ra_prof::profile; -use rustc_hash::FxHashMap; - -use crate::{ - db::HirDatabase, - ty::primitive::{FloatBitness, Uncertain}, - ty::{utils::all_super_traits, Ty, TypeCtor}, -}; - -use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; - -/// This is used as a key for indexing impls. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum TyFingerprint { - Apply(TypeCtor), -} - -impl TyFingerprint { - /// Creates a TyFingerprint for looking up an impl. Only certain types can - /// have impls: if we have some `struct S`, we can have an `impl S`, but not - /// `impl &S`. Hence, this will return `None` for reference types and such. - fn for_impl(ty: &Ty) -> Option { - match ty { - Ty::Apply(a_ty) => Some(TyFingerprint::Apply(a_ty.ctor)), - _ => None, - } - } -} - -#[derive(Debug, PartialEq, Eq)] -pub struct CrateImplBlocks { - impls: FxHashMap>, - impls_by_trait: FxHashMap>, -} - -impl CrateImplBlocks { - pub(crate) fn impls_in_crate_query( - db: &impl HirDatabase, - krate: CrateId, - ) -> Arc { - let _p = profile("impls_in_crate_query"); - let mut res = - CrateImplBlocks { impls: FxHashMap::default(), impls_by_trait: FxHashMap::default() }; - - let crate_def_map = db.crate_def_map(krate); - for (_module_id, module_data) in crate_def_map.modules.iter() { - for &impl_id in module_data.impls.iter() { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - - match &impl_data.target_trait { - Some(trait_ref) => { - if let Some(tr) = - TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty)) - { - res.impls_by_trait.entry(tr.trait_).or_default().push(impl_id); - } - } - None => { - if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { - res.impls.entry(target_ty_fp).or_default().push(impl_id); - } - } - } - } - } - - Arc::new(res) - } - pub fn lookup_impl_blocks(&self, ty: &Ty) -> impl Iterator + '_ { - let fingerprint = TyFingerprint::for_impl(ty); - fingerprint.and_then(|f| self.impls.get(&f)).into_iter().flatten().copied() - } - - pub fn lookup_impl_blocks_for_trait(&self, tr: TraitId) -> impl Iterator + '_ { - self.impls_by_trait.get(&tr).into_iter().flatten().copied() - } - - pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { - self.impls.values().chain(self.impls_by_trait.values()).flatten().copied() - } -} - -impl Ty { - pub(crate) fn def_crates( - &self, - db: &impl HirDatabase, - cur_crate: CrateId, - ) -> Option> { - // Types like slice can have inherent impls in several crates, (core and alloc). - // The corresponding impls are marked with lang items, so we can use them to find the required crates. - macro_rules! lang_item_crate { - ($($name:expr),+ $(,)?) => {{ - let mut v = ArrayVec::<[LangItemTarget; 2]>::new(); - $( - v.extend(db.lang_item(cur_crate, $name.into())); - )+ - v - }}; - } - - let lang_item_targets = match self { - Ty::Apply(a_ty) => match a_ty.ctor { - TypeCtor::Adt(def_id) => { - return Some(std::iter::once(def_id.module(db).krate).collect()) - } - TypeCtor::Bool => lang_item_crate!("bool"), - TypeCtor::Char => lang_item_crate!("char"), - TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { - // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) - FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), - FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), - }, - TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), - TypeCtor::Str => lang_item_crate!("str_alloc", "str"), - TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), - TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), - TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"), - _ => return None, - }, - _ => return None, - }; - let res = lang_item_targets - .into_iter() - .filter_map(|it| match it { - LangItemTarget::ImplBlockId(it) => Some(it), - _ => None, - }) - .map(|it| it.module(db).krate) - .collect(); - Some(res) - } -} -/// Look up the method with the given name, returning the actual autoderefed -/// receiver type (but without autoref applied yet). -pub(crate) fn lookup_method( - ty: &Canonical, - db: &impl HirDatabase, - name: &Name, - resolver: &Resolver, -) -> Option<(Ty, FunctionId)> { - iterate_method_candidates(ty, db, resolver, Some(name), LookupMode::MethodCall, |ty, f| match f - { - AssocItemId::FunctionId(f) => Some((ty.clone(), f)), - _ => None, - }) -} - -/// Whether we're looking up a dotted method call (like `v.len()`) or a path -/// (like `Vec::new`). -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -pub enum LookupMode { - /// Looking up a method call like `v.len()`: We only consider candidates - /// that have a `self` parameter, and do autoderef. - MethodCall, - /// Looking up a path like `Vec::new` or `Vec::default`: We consider all - /// candidates including associated constants, but don't do autoderef. - Path, -} - -// This would be nicer if it just returned an iterator, but that runs into -// lifetime problems, because we need to borrow temp `CrateImplBlocks`. -// FIXME add a context type here? -pub(crate) fn iterate_method_candidates( - ty: &Canonical, - db: &impl HirDatabase, - resolver: &Resolver, - name: Option<&Name>, - mode: LookupMode, - mut callback: impl FnMut(&Ty, AssocItemId) -> Option, -) -> Option { - let krate = resolver.krate()?; - match mode { - LookupMode::MethodCall => { - // For method calls, rust first does any number of autoderef, and then one - // autoref (i.e. when the method takes &self or &mut self). We just ignore - // the autoref currently -- when we find a method matching the given name, - // we assume it fits. - - // Also note that when we've got a receiver like &S, even if the method we - // find in the end takes &self, we still do the autoderef step (just as - // rustc does an autoderef and then autoref again). - let environment = TraitEnvironment::lower(db, resolver); - let ty = InEnvironment { value: ty.clone(), environment }; - for derefed_ty in autoderef::autoderef(db, resolver.krate(), ty) { - if let Some(result) = - iterate_inherent_methods(&derefed_ty, db, name, mode, krate, &mut callback) - { - return Some(result); - } - if let Some(result) = iterate_trait_method_candidates( - &derefed_ty, - db, - resolver, - name, - mode, - &mut callback, - ) { - return Some(result); - } - } - } - LookupMode::Path => { - // No autoderef for path lookups - if let Some(result) = - iterate_inherent_methods(&ty, db, name, mode, krate.into(), &mut callback) - { - return Some(result); - } - if let Some(result) = - iterate_trait_method_candidates(&ty, db, resolver, name, mode, &mut callback) - { - return Some(result); - } - } - } - None -} - -fn iterate_trait_method_candidates( - ty: &Canonical, - db: &impl HirDatabase, - resolver: &Resolver, - name: Option<&Name>, - mode: LookupMode, - mut callback: impl FnMut(&Ty, AssocItemId) -> Option, -) -> Option { - let krate = resolver.krate()?; - // FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that) - let env = TraitEnvironment::lower(db, resolver); - // if ty is `impl Trait` or `dyn Trait`, the trait doesn't need to be in scope - let inherent_trait = ty.value.inherent_trait().into_iter(); - // if we have `T: Trait` in the param env, the trait doesn't need to be in scope - let traits_from_env = env - .trait_predicates_for_self_ty(&ty.value) - .map(|tr| tr.trait_) - .flat_map(|t| all_super_traits(db, t)); - let traits = - inherent_trait.chain(traits_from_env).chain(resolver.traits_in_scope(db).into_iter()); - 'traits: for t in traits { - let data = db.trait_data(t); - - // we'll be lazy about checking whether the type implements the - // trait, but if we find out it doesn't, we'll skip the rest of the - // iteration - let mut known_implemented = false; - for (_name, item) in data.items.iter() { - if !is_valid_candidate(db, name, mode, (*item).into()) { - continue; - } - if !known_implemented { - let goal = generic_implements_goal(db, env.clone(), t, ty.clone()); - if db.trait_solve(krate.into(), goal).is_none() { - continue 'traits; - } - } - known_implemented = true; - if let Some(result) = callback(&ty.value, (*item).into()) { - return Some(result); - } - } - } - None -} - -fn iterate_inherent_methods( - ty: &Canonical, - db: &impl HirDatabase, - name: Option<&Name>, - mode: LookupMode, - krate: CrateId, - mut callback: impl FnMut(&Ty, AssocItemId) -> Option, -) -> Option { - for krate in ty.value.def_crates(db, krate)? { - let impls = db.impls_in_crate(krate); - - for impl_block in impls.lookup_impl_blocks(&ty.value) { - for &item in db.impl_data(impl_block).items.iter() { - if !is_valid_candidate(db, name, mode, item) { - continue; - } - if let Some(result) = callback(&ty.value, item.into()) { - return Some(result); - } - } - } - } - None -} - -fn is_valid_candidate( - db: &impl HirDatabase, - name: Option<&Name>, - mode: LookupMode, - item: AssocItemId, -) -> bool { - match item { - AssocItemId::FunctionId(m) => { - let data = db.function_data(m); - name.map_or(true, |name| &data.name == name) - && (data.has_self_param || mode == LookupMode::Path) - } - AssocItemId::ConstId(c) => { - let data = db.const_data(c); - name.map_or(true, |name| data.name.as_ref() == Some(name)) && (mode == LookupMode::Path) - } - _ => false, - } -} - -pub(crate) fn implements_trait( - ty: &Canonical, - db: &impl HirDatabase, - resolver: &Resolver, - krate: CrateId, - trait_: TraitId, -) -> bool { - if ty.value.inherent_trait() == Some(trait_) { - // FIXME this is a bit of a hack, since Chalk should say the same thing - // anyway, but currently Chalk doesn't implement `dyn/impl Trait` yet - return true; - } - let env = TraitEnvironment::lower(db, resolver); - let goal = generic_implements_goal(db, env, trait_, ty.clone()); - let solution = db.trait_solve(krate.into(), goal); - - solution.is_some() -} - -/// This creates Substs for a trait with the given Self type and type variables -/// for all other parameters, to query Chalk with it. -fn generic_implements_goal( - db: &impl HirDatabase, - env: Arc, - trait_: TraitId, - self_ty: Canonical, -) -> Canonical> { - let num_vars = self_ty.num_vars; - let substs = super::Substs::build_for_def(db, trait_) - .push(self_ty.value) - .fill_with_bound_vars(num_vars as u32) - .build(); - let num_vars = substs.len() - 1 + self_ty.num_vars; - let trait_ref = TraitRef { trait_, substs }; - let obligation = super::Obligation::Trait(trait_ref); - Canonical { num_vars, value: InEnvironment::new(env, obligation) } -} diff --git a/crates/ra_hir/src/ty/op.rs b/crates/ra_hir/src/ty/op.rs deleted file mode 100644 index cc6e244f4..000000000 --- a/crates/ra_hir/src/ty/op.rs +++ /dev/null @@ -1,50 +0,0 @@ -//! FIXME: write short doc here -use hir_def::expr::{BinaryOp, CmpOp}; - -use super::{InferTy, Ty, TypeCtor}; -use crate::ty::ApplicationTy; - -pub(super) fn binary_op_return_ty(op: BinaryOp, rhs_ty: Ty) -> Ty { - match op { - BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => Ty::simple(TypeCtor::Bool), - BinaryOp::Assignment { .. } => Ty::unit(), - BinaryOp::ArithOp(_) => match rhs_ty { - Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { - TypeCtor::Int(..) | TypeCtor::Float(..) => rhs_ty, - _ => Ty::Unknown, - }, - Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => rhs_ty, - _ => Ty::Unknown, - }, - } -} - -pub(super) fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty { - match op { - BinaryOp::LogicOp(..) => Ty::simple(TypeCtor::Bool), - BinaryOp::Assignment { op: None } | BinaryOp::CmpOp(CmpOp::Eq { negated: _ }) => { - match lhs_ty { - Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { - TypeCtor::Int(..) - | TypeCtor::Float(..) - | TypeCtor::Str - | TypeCtor::Char - | TypeCtor::Bool => lhs_ty, - _ => Ty::Unknown, - }, - Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty, - _ => Ty::Unknown, - } - } - BinaryOp::CmpOp(CmpOp::Ord { .. }) - | BinaryOp::Assignment { op: Some(_) } - | BinaryOp::ArithOp(_) => match lhs_ty { - Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { - TypeCtor::Int(..) | TypeCtor::Float(..) => lhs_ty, - _ => Ty::Unknown, - }, - Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty, - _ => Ty::Unknown, - }, - } -} diff --git a/crates/ra_hir/src/ty/primitive.rs b/crates/ra_hir/src/ty/primitive.rs deleted file mode 100644 index 12dc96572..000000000 --- a/crates/ra_hir/src/ty/primitive.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! FIXME: write short doc here - -pub use hir_ty::primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain}; diff --git a/crates/ra_hir/src/ty/tests.rs b/crates/ra_hir/src/ty/tests.rs deleted file mode 100644 index 98eb863cb..000000000 --- a/crates/ra_hir/src/ty/tests.rs +++ /dev/null @@ -1,4896 +0,0 @@ -mod never_type; -mod coercion; - -use std::fmt::Write; -use std::sync::Arc; - -use insta::assert_snapshot; -use ra_db::{fixture::WithFixture, salsa::Database, FilePosition, SourceDatabase}; -use ra_syntax::{ - algo, - ast::{self, AstNode}, - SyntaxKind::*, -}; -use rustc_hash::FxHashSet; -use test_utils::covers; - -use crate::{ - expr::BodySourceMap, test_db::TestDB, ty::display::HirDisplay, ty::InferenceResult, Source, - SourceAnalyzer, -}; - -// These tests compare the inference results for all expressions in a file -// against snapshots of the expected results using insta. Use cargo-insta to -// update the snapshots. - -#[test] -fn cfg_impl_block() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:foo cfg:test -use foo::S as T; -struct S; - -#[cfg(test)] -impl S { - fn foo1(&self) -> i32 { 0 } -} - -#[cfg(not(test))] -impl S { - fn foo2(&self) -> i32 { 0 } -} - -fn test() { - let t = (S.foo1(), S.foo2(), T.foo3(), T.foo4()); - t<|>; -} - -//- /foo.rs crate:foo -struct S; - -#[cfg(not(test))] -impl S { - fn foo3(&self) -> i32 { 0 } -} - -#[cfg(test)] -impl S { - fn foo4(&self) -> i32 { 0 } -} -"#, - ); - assert_eq!("(i32, {unknown}, i32, {unknown})", type_at_pos(&db, pos)); -} - -#[test] -fn infer_await() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:std - -struct IntFuture; - -impl Future for IntFuture { - type Output = u64; -} - -fn test() { - let r = IntFuture; - let v = r.await; - v<|>; -} - -//- /std.rs crate:std -#[prelude_import] use future::*; -mod future { - trait Future { - type Output; - } -} - -"#, - ); - assert_eq!("u64", type_at_pos(&db, pos)); -} - -#[test] -fn infer_box() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:std - -fn test() { - let x = box 1; - let t = (x, box x, box &1, box [1]); - t<|>; -} - -//- /std.rs crate:std -#[prelude_import] use prelude::*; -mod prelude {} - -mod boxed { - pub struct Box { - inner: *mut T, - } -} - -"#, - ); - assert_eq!("(Box, Box>, Box<&i32>, Box<[i32;_]>)", type_at_pos(&db, pos)); -} - -#[test] -fn infer_adt_self() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs -enum Nat { Succ(Self), Demo(Nat), Zero } - -fn test() { - let foo: Nat = Nat::Zero; - if let Nat::Succ(x) = foo { - x<|> - } -} - -"#, - ); - assert_eq!("Nat", type_at_pos(&db, pos)); -} - -#[test] -fn infer_try() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:std - -fn test() { - let r: Result = Result::Ok(1); - let v = r?; - v<|>; -} - -//- /std.rs crate:std - -#[prelude_import] use ops::*; -mod ops { - trait Try { - type Ok; - type Error; - } -} - -#[prelude_import] use result::*; -mod result { - enum Result { - Ok(O), - Err(E) - } - - impl crate::ops::Try for Result { - type Ok = O; - type Error = E; - } -} - -"#, - ); - assert_eq!("i32", type_at_pos(&db, pos)); -} - -#[test] -fn infer_for_loop() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:std - -use std::collections::Vec; - -fn test() { - let v = Vec::new(); - v.push("foo"); - for x in v { - x<|>; - } -} - -//- /std.rs crate:std - -#[prelude_import] use iter::*; -mod iter { - trait IntoIterator { - type Item; - } -} - -mod collections { - struct Vec {} - impl Vec { - fn new() -> Self { Vec {} } - fn push(&mut self, t: T) { } - } - - impl crate::iter::IntoIterator for Vec { - type Item=T; - } -} -"#, - ); - assert_eq!("&str", type_at_pos(&db, pos)); -} - -#[test] -fn infer_while_let() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs -enum Option { Some(T), None } - -fn test() { - let foo: Option = None; - while let Option::Some(x) = foo { - <|>x - } -} - -"#, - ); - assert_eq!("f32", type_at_pos(&db, pos)); -} - -#[test] -fn infer_basics() { - assert_snapshot!( - infer(r#" -fn test(a: u32, b: isize, c: !, d: &str) { - a; - b; - c; - d; - 1usize; - 1isize; - "test"; - 1.0f32; -}"#), - @r###" - [9; 10) 'a': u32 - [17; 18) 'b': isize - [27; 28) 'c': ! - [33; 34) 'd': &str - [42; 121) '{ ...f32; }': ! - [48; 49) 'a': u32 - [55; 56) 'b': isize - [62; 63) 'c': ! - [69; 70) 'd': &str - [76; 82) '1usize': usize - [88; 94) '1isize': isize - [100; 106) '"test"': &str - [112; 118) '1.0f32': f32 - "### - ); -} - -#[test] -fn infer_let() { - assert_snapshot!( - infer(r#" -fn test() { - let a = 1isize; - let b: usize = 1; - let c = b; - let d: u32; - let e; - let f: i32 = e; -} -"#), - @r###" - [11; 118) '{ ...= e; }': () - [21; 22) 'a': isize - [25; 31) '1isize': isize - [41; 42) 'b': usize - [52; 53) '1': usize - [63; 64) 'c': usize - [67; 68) 'b': usize - [78; 79) 'd': u32 - [94; 95) 'e': i32 - [105; 106) 'f': i32 - [114; 115) 'e': i32 - "### - ); -} - -#[test] -fn infer_paths() { - assert_snapshot!( - infer(r#" -fn a() -> u32 { 1 } - -mod b { - fn c() -> u32 { 1 } -} - -fn test() { - a(); - b::c(); -} -"#), - @r###" - [15; 20) '{ 1 }': u32 - [17; 18) '1': u32 - [48; 53) '{ 1 }': u32 - [50; 51) '1': u32 - [67; 91) '{ ...c(); }': () - [73; 74) 'a': fn a() -> u32 - [73; 76) 'a()': u32 - [82; 86) 'b::c': fn c() -> u32 - [82; 88) 'b::c()': u32 - "### - ); -} - -#[test] -fn infer_path_type() { - assert_snapshot!( - infer(r#" -struct S; - -impl S { - fn foo() -> i32 { 1 } -} - -fn test() { - S::foo(); - ::foo(); -} -"#), - @r###" - [41; 46) '{ 1 }': i32 - [43; 44) '1': i32 - [60; 93) '{ ...o(); }': () - [66; 72) 'S::foo': fn foo() -> i32 - [66; 74) 'S::foo()': i32 - [80; 88) '::foo': fn foo() -> i32 - [80; 90) '::foo()': i32 - "### - ); -} - -#[test] -fn infer_slice_method() { - assert_snapshot!( - infer(r#" -#[lang = "slice"] -impl [T] { - fn foo(&self) -> T { - loop {} - } -} - -#[lang = "slice_alloc"] -impl [T] {} - -fn test() { - <[_]>::foo(b"foo"); -} -"#), - @r###" - [45; 49) 'self': &[T] - [56; 79) '{ ... }': T - [66; 73) 'loop {}': ! - [71; 73) '{}': () - [133; 160) '{ ...o"); }': () - [139; 149) '<[_]>::foo': fn foo(&[T]) -> T - [139; 157) '<[_]>:..."foo")': u8 - [150; 156) 'b"foo"': &[u8] - "### - ); -} - -#[test] -fn infer_struct() { - assert_snapshot!( - infer(r#" -struct A { - b: B, - c: C, -} -struct B; -struct C(usize); - -fn test() { - let c = C(1); - B; - let a: A = A { b: B, c: C(1) }; - a.b; - a.c; -} -"#), - @r###" - [72; 154) '{ ...a.c; }': () - [82; 83) 'c': C - [86; 87) 'C': C(usize) -> C - [86; 90) 'C(1)': C - [88; 89) '1': usize - [96; 97) 'B': B - [107; 108) 'a': A - [114; 133) 'A { b:...C(1) }': A - [121; 122) 'B': B - [127; 128) 'C': C(usize) -> C - [127; 131) 'C(1)': C - [129; 130) '1': usize - [139; 140) 'a': A - [139; 142) 'a.b': B - [148; 149) 'a': A - [148; 151) 'a.c': C - "### - ); -} - -#[test] -fn infer_enum() { - assert_snapshot!( - infer(r#" -enum E { - V1 { field: u32 }, - V2 -} -fn test() { - E::V1 { field: 1 }; - E::V2; -}"#), - @r###" - [48; 82) '{ E:...:V2; }': () - [52; 70) 'E::V1 ...d: 1 }': E - [67; 68) '1': u32 - [74; 79) 'E::V2': E - "### - ); -} - -#[test] -fn infer_refs() { - assert_snapshot!( - infer(r#" -fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) { - a; - *a; - &a; - &mut a; - b; - *b; - &b; - c; - *c; - d; - *d; -} -"#), - @r###" - [9; 10) 'a': &u32 - [18; 19) 'b': &mut u32 - [31; 32) 'c': *const u32 - [46; 47) 'd': *mut u32 - [59; 150) '{ ... *d; }': () - [65; 66) 'a': &u32 - [72; 74) '*a': u32 - [73; 74) 'a': &u32 - [80; 82) '&a': &&u32 - [81; 82) 'a': &u32 - [88; 94) '&mut a': &mut &u32 - [93; 94) 'a': &u32 - [100; 101) 'b': &mut u32 - [107; 109) '*b': u32 - [108; 109) 'b': &mut u32 - [115; 117) '&b': &&mut u32 - [116; 117) 'b': &mut u32 - [123; 124) 'c': *const u32 - [130; 132) '*c': u32 - [131; 132) 'c': *const u32 - [138; 139) 'd': *mut u32 - [145; 147) '*d': u32 - [146; 147) 'd': *mut u32 - "### - ); -} - -#[test] -fn infer_literals() { - assert_snapshot!( - infer(r##" -fn test() { - 5i32; - 5f32; - 5f64; - "hello"; - b"bytes"; - 'c'; - b'b'; - 3.14; - 5000; - false; - true; - r#" - //! doc - // non-doc - mod foo {} - "#; - br#"yolo"#; -} -"##), - @r###" - [11; 221) '{ ...o"#; }': () - [17; 21) '5i32': i32 - [27; 31) '5f32': f32 - [37; 41) '5f64': f64 - [47; 54) '"hello"': &str - [60; 68) 'b"bytes"': &[u8] - [74; 77) ''c'': char - [83; 87) 'b'b'': u8 - [93; 97) '3.14': f64 - [103; 107) '5000': i32 - [113; 118) 'false': bool - [124; 128) 'true': bool - [134; 202) 'r#" ... "#': &str - [208; 218) 'br#"yolo"#': &[u8] - "### - ); -} - -#[test] -fn infer_unary_op() { - assert_snapshot!( - infer(r#" -enum SomeType {} - -fn test(x: SomeType) { - let b = false; - let c = !b; - let a = 100; - let d: i128 = -a; - let e = -100; - let f = !!!true; - let g = !42; - let h = !10u32; - let j = !a; - -3.14; - !3; - -x; - !x; - -"hello"; - !"hello"; -} -"#), - @r###" - [27; 28) 'x': SomeType - [40; 272) '{ ...lo"; }': () - [50; 51) 'b': bool - [54; 59) 'false': bool - [69; 70) 'c': bool - [73; 75) '!b': bool - [74; 75) 'b': bool - [85; 86) 'a': i128 - [89; 92) '100': i128 - [102; 103) 'd': i128 - [112; 114) '-a': i128 - [113; 114) 'a': i128 - [124; 125) 'e': i32 - [128; 132) '-100': i32 - [129; 132) '100': i32 - [142; 143) 'f': bool - [146; 153) '!!!true': bool - [147; 153) '!!true': bool - [148; 153) '!true': bool - [149; 153) 'true': bool - [163; 164) 'g': i32 - [167; 170) '!42': i32 - [168; 170) '42': i32 - [180; 181) 'h': u32 - [184; 190) '!10u32': u32 - [185; 190) '10u32': u32 - [200; 201) 'j': i128 - [204; 206) '!a': i128 - [205; 206) 'a': i128 - [212; 217) '-3.14': f64 - [213; 217) '3.14': f64 - [223; 225) '!3': i32 - [224; 225) '3': i32 - [231; 233) '-x': {unknown} - [232; 233) 'x': SomeType - [239; 241) '!x': {unknown} - [240; 241) 'x': SomeType - [247; 255) '-"hello"': {unknown} - [248; 255) '"hello"': &str - [261; 269) '!"hello"': {unknown} - [262; 269) '"hello"': &str - "### - ); -} - -#[test] -fn infer_backwards() { - assert_snapshot!( - infer(r#" -fn takes_u32(x: u32) {} - -struct S { i32_field: i32 } - -fn test() -> &mut &f64 { - let a = unknown_function(); - takes_u32(a); - let b = unknown_function(); - S { i32_field: b }; - let c = unknown_function(); - &mut &c -} -"#), - @r###" - [14; 15) 'x': u32 - [22; 24) '{}': () - [78; 231) '{ ...t &c }': &mut &f64 - [88; 89) 'a': u32 - [92; 108) 'unknow...nction': {unknown} - [92; 110) 'unknow...tion()': u32 - [116; 125) 'takes_u32': fn takes_u32(u32) -> () - [116; 128) 'takes_u32(a)': () - [126; 127) 'a': u32 - [138; 139) 'b': i32 - [142; 158) 'unknow...nction': {unknown} - [142; 160) 'unknow...tion()': i32 - [166; 184) 'S { i3...d: b }': S - [181; 182) 'b': i32 - [194; 195) 'c': f64 - [198; 214) 'unknow...nction': {unknown} - [198; 216) 'unknow...tion()': f64 - [222; 229) '&mut &c': &mut &f64 - [227; 229) '&c': &f64 - [228; 229) 'c': f64 - "### - ); -} - -#[test] -fn infer_self() { - assert_snapshot!( - infer(r#" -struct S; - -impl S { - fn test(&self) { - self; - } - fn test2(self: &Self) { - self; - } - fn test3() -> Self { - S {} - } - fn test4() -> Self { - Self {} - } -} -"#), - @r###" - [34; 38) 'self': &S - [40; 61) '{ ... }': () - [50; 54) 'self': &S - [75; 79) 'self': &S - [88; 109) '{ ... }': () - [98; 102) 'self': &S - [133; 153) '{ ... }': S - [143; 147) 'S {}': S - [177; 200) '{ ... }': S - [187; 194) 'Self {}': S - "### - ); -} - -#[test] -fn infer_binary_op() { - assert_snapshot!( - infer(r#" -fn f(x: bool) -> i32 { - 0i32 -} - -fn test() -> bool { - let x = a && b; - let y = true || false; - let z = x == y; - let t = x != y; - let minus_forty: isize = -40isize; - let h = minus_forty <= CONST_2; - let c = f(z || y) + 5; - let d = b; - let g = minus_forty ^= i; - let ten: usize = 10; - let ten_is_eleven = ten == some_num; - - ten < 3 -} -"#), - @r###" - [6; 7) 'x': bool - [22; 34) '{ 0i32 }': i32 - [28; 32) '0i32': i32 - [54; 370) '{ ... < 3 }': bool - [64; 65) 'x': bool - [68; 69) 'a': bool - [68; 74) 'a && b': bool - [73; 74) 'b': bool - [84; 85) 'y': bool - [88; 92) 'true': bool - [88; 101) 'true || false': bool - [96; 101) 'false': bool - [111; 112) 'z': bool - [115; 116) 'x': bool - [115; 121) 'x == y': bool - [120; 121) 'y': bool - [131; 132) 't': bool - [135; 136) 'x': bool - [135; 141) 'x != y': bool - [140; 141) 'y': bool - [151; 162) 'minus_forty': isize - [172; 180) '-40isize': isize - [173; 180) '40isize': isize - [190; 191) 'h': bool - [194; 205) 'minus_forty': isize - [194; 216) 'minus_...ONST_2': bool - [209; 216) 'CONST_2': isize - [226; 227) 'c': i32 - [230; 231) 'f': fn f(bool) -> i32 - [230; 239) 'f(z || y)': i32 - [230; 243) 'f(z || y) + 5': i32 - [232; 233) 'z': bool - [232; 238) 'z || y': bool - [237; 238) 'y': bool - [242; 243) '5': i32 - [253; 254) 'd': {unknown} - [257; 258) 'b': {unknown} - [268; 269) 'g': () - [272; 283) 'minus_forty': isize - [272; 288) 'minus_...y ^= i': () - [287; 288) 'i': isize - [298; 301) 'ten': usize - [311; 313) '10': usize - [323; 336) 'ten_is_eleven': bool - [339; 342) 'ten': usize - [339; 354) 'ten == some_num': bool - [346; 354) 'some_num': usize - [361; 364) 'ten': usize - [361; 368) 'ten < 3': bool - [367; 368) '3': usize - "### - ); -} - -#[test] -fn infer_field_autoderef() { - assert_snapshot!( - infer(r#" -struct A { - b: B, -} -struct B; - -fn test1(a: A) { - let a1 = a; - a1.b; - let a2 = &a; - a2.b; - let a3 = &mut a; - a3.b; - let a4 = &&&&&&&a; - a4.b; - let a5 = &mut &&mut &&mut a; - a5.b; -} - -fn test2(a1: *const A, a2: *mut A) { - a1.b; - a2.b; -} -"#), - @r###" - [44; 45) 'a': A - [50; 213) '{ ...5.b; }': () - [60; 62) 'a1': A - [65; 66) 'a': A - [72; 74) 'a1': A - [72; 76) 'a1.b': B - [86; 88) 'a2': &A - [91; 93) '&a': &A - [92; 93) 'a': A - [99; 101) 'a2': &A - [99; 103) 'a2.b': B - [113; 115) 'a3': &mut A - [118; 124) '&mut a': &mut A - [123; 124) 'a': A - [130; 132) 'a3': &mut A - [130; 134) 'a3.b': B - [144; 146) 'a4': &&&&&&&A - [149; 157) '&&&&&&&a': &&&&&&&A - [150; 157) '&&&&&&a': &&&&&&A - [151; 157) '&&&&&a': &&&&&A - [152; 157) '&&&&a': &&&&A - [153; 157) '&&&a': &&&A - [154; 157) '&&a': &&A - [155; 157) '&a': &A - [156; 157) 'a': A - [163; 165) 'a4': &&&&&&&A - [163; 167) 'a4.b': B - [177; 179) 'a5': &mut &&mut &&mut A - [182; 200) '&mut &...&mut a': &mut &&mut &&mut A - [187; 200) '&&mut &&mut a': &&mut &&mut A - [188; 200) '&mut &&mut a': &mut &&mut A - [193; 200) '&&mut a': &&mut A - [194; 200) '&mut a': &mut A - [199; 200) 'a': A - [206; 208) 'a5': &mut &&mut &&mut A - [206; 210) 'a5.b': B - [224; 226) 'a1': *const A - [238; 240) 'a2': *mut A - [250; 273) '{ ...2.b; }': () - [256; 258) 'a1': *const A - [256; 260) 'a1.b': B - [266; 268) 'a2': *mut A - [266; 270) 'a2.b': B - "### - ); -} - -#[test] -fn infer_argument_autoderef() { - assert_snapshot!( - infer(r#" -#[lang = "deref"] -pub trait Deref { - type Target; - fn deref(&self) -> &Self::Target; -} - -struct A(T); - -impl A { - fn foo(&self) -> &T { - &self.0 - } -} - -struct B(T); - -impl Deref for B { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -fn test() { - let t = A::foo(&&B(B(A(42)))); -} -"#), - @r###" - [68; 72) 'self': &Self - [139; 143) 'self': &A - [151; 174) '{ ... }': &T - [161; 168) '&self.0': &T - [162; 166) 'self': &A - [162; 168) 'self.0': T - [255; 259) 'self': &B - [278; 301) '{ ... }': &T - [288; 295) '&self.0': &T - [289; 293) 'self': &B - [289; 295) 'self.0': T - [315; 353) '{ ...))); }': () - [325; 326) 't': &i32 - [329; 335) 'A::foo': fn foo(&A) -> &T - [329; 350) 'A::foo...42))))': &i32 - [336; 349) '&&B(B(A(42)))': &&B>> - [337; 349) '&B(B(A(42)))': &B>> - [338; 339) 'B': B>>(T) -> B - [338; 349) 'B(B(A(42)))': B>> - [340; 341) 'B': B>(T) -> B - [340; 348) 'B(A(42))': B> - [342; 343) 'A': A(T) -> A - [342; 347) 'A(42)': A - [344; 346) '42': i32 - "### - ); -} - -#[test] -fn infer_method_argument_autoderef() { - assert_snapshot!( - infer(r#" -#[lang = "deref"] -pub trait Deref { - type Target; - fn deref(&self) -> &Self::Target; -} - -struct A(*mut T); - -impl A { - fn foo(&self, x: &A) -> &T { - &*x.0 - } -} - -struct B(T); - -impl Deref for B { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -fn test(a: A) { - let t = A(0 as *mut _).foo(&&B(B(a))); -} -"#), - @r###" - [68; 72) 'self': &Self - [144; 148) 'self': &A - [150; 151) 'x': &A - [166; 187) '{ ... }': &T - [176; 181) '&*x.0': &T - [177; 181) '*x.0': T - [178; 179) 'x': &A - [178; 181) 'x.0': *mut T - [268; 272) 'self': &B - [291; 314) '{ ... }': &T - [301; 308) '&self.0': &T - [302; 306) 'self': &B - [302; 308) 'self.0': T - [326; 327) 'a': A - [337; 383) '{ ...))); }': () - [347; 348) 't': &i32 - [351; 352) 'A': A(*mut T) -> A - [351; 365) 'A(0 as *mut _)': A - [351; 380) 'A(0 as...B(a)))': &i32 - [353; 354) '0': i32 - [353; 364) '0 as *mut _': *mut i32 - [370; 379) '&&B(B(a))': &&B>> - [371; 379) '&B(B(a))': &B>> - [372; 373) 'B': B>>(T) -> B - [372; 379) 'B(B(a))': B>> - [374; 375) 'B': B>(T) -> B - [374; 378) 'B(a)': B> - [376; 377) 'a': A - "### - ); -} - -#[test] -fn bug_484() { - assert_snapshot!( - infer(r#" -fn test() { - let x = if true {}; -} -"#), - @r###" - [11; 37) '{ l... {}; }': () - [20; 21) 'x': () - [24; 34) 'if true {}': () - [27; 31) 'true': bool - [32; 34) '{}': () - "### - ); -} - -#[test] -fn infer_in_elseif() { - assert_snapshot!( - infer(r#" -struct Foo { field: i32 } -fn main(foo: Foo) { - if true { - - } else if false { - foo.field - } -} -"#), - @r###" - [35; 38) 'foo': Foo - [45; 109) '{ ... } }': () - [51; 107) 'if tru... }': () - [54; 58) 'true': bool - [59; 67) '{ }': () - [73; 107) 'if fal... }': () - [76; 81) 'false': bool - [82; 107) '{ ... }': i32 - [92; 95) 'foo': Foo - [92; 101) 'foo.field': i32 - "### - ) -} - -#[test] -fn infer_if_match_with_return() { - assert_snapshot!( - infer(r#" -fn foo() { - let _x1 = if true { - 1 - } else { - return; - }; - let _x2 = if true { - 2 - } else { - return - }; - let _x3 = match true { - true => 3, - _ => { - return; - } - }; - let _x4 = match true { - true => 4, - _ => return - }; -}"#), - @r###" - [10; 323) '{ ... }; }': () - [20; 23) '_x1': i32 - [26; 80) 'if tru... }': i32 - [29; 33) 'true': bool - [34; 51) '{ ... }': i32 - [44; 45) '1': i32 - [57; 80) '{ ... }': ! - [67; 73) 'return': ! - [90; 93) '_x2': i32 - [96; 149) 'if tru... }': i32 - [99; 103) 'true': bool - [104; 121) '{ ... }': i32 - [114; 115) '2': i32 - [127; 149) '{ ... }': ! - [137; 143) 'return': ! - [159; 162) '_x3': i32 - [165; 247) 'match ... }': i32 - [171; 175) 'true': bool - [186; 190) 'true': bool - [194; 195) '3': i32 - [205; 206) '_': bool - [210; 241) '{ ... }': ! - [224; 230) 'return': ! - [257; 260) '_x4': i32 - [263; 320) 'match ... }': i32 - [269; 273) 'true': bool - [284; 288) 'true': bool - [292; 293) '4': i32 - [303; 304) '_': bool - [308; 314) 'return': ! - "### - ) -} - -#[test] -fn infer_inherent_method() { - assert_snapshot!( - infer(r#" -struct A; - -impl A { - fn foo(self, x: u32) -> i32 {} -} - -mod b { - impl super::A { - fn bar(&self, x: u64) -> i64 {} - } -} - -fn test(a: A) { - a.foo(1); - (&a).bar(1); - a.bar(1); -} -"#), - @r###" - [32; 36) 'self': A - [38; 39) 'x': u32 - [53; 55) '{}': () - [103; 107) 'self': &A - [109; 110) 'x': u64 - [124; 126) '{}': () - [144; 145) 'a': A - [150; 198) '{ ...(1); }': () - [156; 157) 'a': A - [156; 164) 'a.foo(1)': i32 - [162; 163) '1': u32 - [170; 181) '(&a).bar(1)': i64 - [171; 173) '&a': &A - [172; 173) 'a': A - [179; 180) '1': u64 - [187; 188) 'a': A - [187; 195) 'a.bar(1)': i64 - [193; 194) '1': u64 - "### - ); -} - -#[test] -fn infer_inherent_method_str() { - assert_snapshot!( - infer(r#" -#[lang = "str"] -impl str { - fn foo(&self) -> i32 {} -} - -fn test() { - "foo".foo(); -} -"#), - @r###" - [40; 44) 'self': &str - [53; 55) '{}': () - [69; 89) '{ ...o(); }': () - [75; 80) '"foo"': &str - [75; 86) '"foo".foo()': i32 - "### - ); -} - -#[test] -fn infer_tuple() { - assert_snapshot!( - infer(r#" -fn test(x: &str, y: isize) { - let a: (u32, &str) = (1, "a"); - let b = (a, x); - let c = (y, x); - let d = (c, x); - let e = (1, "e"); - let f = (e, "d"); -} -"#), - @r###" - [9; 10) 'x': &str - [18; 19) 'y': isize - [28; 170) '{ ...d"); }': () - [38; 39) 'a': (u32, &str) - [55; 63) '(1, "a")': (u32, &str) - [56; 57) '1': u32 - [59; 62) '"a"': &str - [73; 74) 'b': ((u32, &str), &str) - [77; 83) '(a, x)': ((u32, &str), &str) - [78; 79) 'a': (u32, &str) - [81; 82) 'x': &str - [93; 94) 'c': (isize, &str) - [97; 103) '(y, x)': (isize, &str) - [98; 99) 'y': isize - [101; 102) 'x': &str - [113; 114) 'd': ((isize, &str), &str) - [117; 123) '(c, x)': ((isize, &str), &str) - [118; 119) 'c': (isize, &str) - [121; 122) 'x': &str - [133; 134) 'e': (i32, &str) - [137; 145) '(1, "e")': (i32, &str) - [138; 139) '1': i32 - [141; 144) '"e"': &str - [155; 156) 'f': ((i32, &str), &str) - [159; 167) '(e, "d")': ((i32, &str), &str) - [160; 161) 'e': (i32, &str) - [163; 166) '"d"': &str - "### - ); -} - -#[test] -fn infer_array() { - assert_snapshot!( - infer(r#" -fn test(x: &str, y: isize) { - let a = [x]; - let b = [a, a]; - let c = [b, b]; - - let d = [y, 1, 2, 3]; - let d = [1, y, 2, 3]; - let e = [y]; - let f = [d, d]; - let g = [e, e]; - - let h = [1, 2]; - let i = ["a", "b"]; - - let b = [a, ["b"]]; - let x: [u8; 0] = []; -} -"#), - @r###" - [9; 10) 'x': &str - [18; 19) 'y': isize - [28; 293) '{ ... []; }': () - [38; 39) 'a': [&str;_] - [42; 45) '[x]': [&str;_] - [43; 44) 'x': &str - [55; 56) 'b': [[&str;_];_] - [59; 65) '[a, a]': [[&str;_];_] - [60; 61) 'a': [&str;_] - [63; 64) 'a': [&str;_] - [75; 76) 'c': [[[&str;_];_];_] - [79; 85) '[b, b]': [[[&str;_];_];_] - [80; 81) 'b': [[&str;_];_] - [83; 84) 'b': [[&str;_];_] - [96; 97) 'd': [isize;_] - [100; 112) '[y, 1, 2, 3]': [isize;_] - [101; 102) 'y': isize - [104; 105) '1': isize - [107; 108) '2': isize - [110; 111) '3': isize - [122; 123) 'd': [isize;_] - [126; 138) '[1, y, 2, 3]': [isize;_] - [127; 128) '1': isize - [130; 131) 'y': isize - [133; 134) '2': isize - [136; 137) '3': isize - [148; 149) 'e': [isize;_] - [152; 155) '[y]': [isize;_] - [153; 154) 'y': isize - [165; 166) 'f': [[isize;_];_] - [169; 175) '[d, d]': [[isize;_];_] - [170; 171) 'd': [isize;_] - [173; 174) 'd': [isize;_] - [185; 186) 'g': [[isize;_];_] - [189; 195) '[e, e]': [[isize;_];_] - [190; 191) 'e': [isize;_] - [193; 194) 'e': [isize;_] - [206; 207) 'h': [i32;_] - [210; 216) '[1, 2]': [i32;_] - [211; 212) '1': i32 - [214; 215) '2': i32 - [226; 227) 'i': [&str;_] - [230; 240) '["a", "b"]': [&str;_] - [231; 234) '"a"': &str - [236; 239) '"b"': &str - [251; 252) 'b': [[&str;_];_] - [255; 265) '[a, ["b"]]': [[&str;_];_] - [256; 257) 'a': [&str;_] - [259; 264) '["b"]': [&str;_] - [260; 263) '"b"': &str - [275; 276) 'x': [u8;_] - [288; 290) '[]': [u8;_] - "### - ); -} - -#[test] -fn infer_pattern() { - assert_snapshot!( - infer(r#" -fn test(x: &i32) { - let y = x; - let &z = x; - let a = z; - let (c, d) = (1, "hello"); - - for (e, f) in some_iter { - let g = e; - } - - if let [val] = opt { - let h = val; - } - - let lambda = |a: u64, b, c: i32| { a + b; c }; - - let ref ref_to_x = x; - let mut mut_x = x; - let ref mut mut_ref_to_x = x; - let k = mut_ref_to_x; -} -"#), - @r###" - [9; 10) 'x': &i32 - [18; 369) '{ ...o_x; }': () - [28; 29) 'y': &i32 - [32; 33) 'x': &i32 - [43; 45) '&z': &i32 - [44; 45) 'z': i32 - [48; 49) 'x': &i32 - [59; 60) 'a': i32 - [63; 64) 'z': i32 - [74; 80) '(c, d)': (i32, &str) - [75; 76) 'c': i32 - [78; 79) 'd': &str - [83; 95) '(1, "hello")': (i32, &str) - [84; 85) '1': i32 - [87; 94) '"hello"': &str - [102; 152) 'for (e... }': () - [106; 112) '(e, f)': ({unknown}, {unknown}) - [107; 108) 'e': {unknown} - [110; 111) 'f': {unknown} - [116; 125) 'some_iter': {unknown} - [126; 152) '{ ... }': () - [140; 141) 'g': {unknown} - [144; 145) 'e': {unknown} - [158; 205) 'if let... }': () - [165; 170) '[val]': {unknown} - [173; 176) 'opt': {unknown} - [177; 205) '{ ... }': () - [191; 192) 'h': {unknown} - [195; 198) 'val': {unknown} - [215; 221) 'lambda': |u64, u64, i32| -> i32 - [224; 256) '|a: u6...b; c }': |u64, u64, i32| -> i32 - [225; 226) 'a': u64 - [233; 234) 'b': u64 - [236; 237) 'c': i32 - [244; 256) '{ a + b; c }': i32 - [246; 247) 'a': u64 - [246; 251) 'a + b': u64 - [250; 251) 'b': u64 - [253; 254) 'c': i32 - [267; 279) 'ref ref_to_x': &&i32 - [282; 283) 'x': &i32 - [293; 302) 'mut mut_x': &i32 - [305; 306) 'x': &i32 - [316; 336) 'ref mu...f_to_x': &mut &i32 - [339; 340) 'x': &i32 - [350; 351) 'k': &mut &i32 - [354; 366) 'mut_ref_to_x': &mut &i32 - "### - ); -} - -#[test] -fn infer_pattern_match_ergonomics() { - assert_snapshot!( - infer(r#" -struct A(T); - -fn test() { - let A(n) = &A(1); - let A(n) = &mut A(1); -} -"#), - @r###" - [28; 79) '{ ...(1); }': () - [38; 42) 'A(n)': A - [40; 41) 'n': &i32 - [45; 50) '&A(1)': &A - [46; 47) 'A': A(T) -> A - [46; 50) 'A(1)': A - [48; 49) '1': i32 - [60; 64) 'A(n)': A - [62; 63) 'n': &mut i32 - [67; 76) '&mut A(1)': &mut A - [72; 73) 'A': A(T) -> A - [72; 76) 'A(1)': A - [74; 75) '1': i32 - "### - ); -} - -#[test] -fn infer_pattern_match_ergonomics_ref() { - covers!(match_ergonomics_ref); - assert_snapshot!( - infer(r#" -fn test() { - let v = &(1, &2); - let (_, &w) = v; -} -"#), - @r###" - [11; 57) '{ ...= v; }': () - [21; 22) 'v': &(i32, &i32) - [25; 33) '&(1, &2)': &(i32, &i32) - [26; 33) '(1, &2)': (i32, &i32) - [27; 28) '1': i32 - [30; 32) '&2': &i32 - [31; 32) '2': i32 - [43; 50) '(_, &w)': (i32, &i32) - [44; 45) '_': i32 - [47; 49) '&w': &i32 - [48; 49) 'w': i32 - [53; 54) 'v': &(i32, &i32) - "### - ); -} - -#[test] -fn infer_adt_pattern() { - assert_snapshot!( - infer(r#" -enum E { - A { x: usize }, - B -} - -struct S(u32, E); - -fn test() { - let e = E::A { x: 3 }; - - let S(y, z) = foo; - let E::A { x: new_var } = e; - - match e { - E::A { x } => x, - E::B if foo => 1, - E::B => 10, - }; - - let ref d @ E::A { .. } = e; - d; -} -"#), - @r###" - [68; 289) '{ ... d; }': () - [78; 79) 'e': E - [82; 95) 'E::A { x: 3 }': E - [92; 93) '3': usize - [106; 113) 'S(y, z)': S - [108; 109) 'y': u32 - [111; 112) 'z': E - [116; 119) 'foo': S - [129; 148) 'E::A {..._var }': E - [139; 146) 'new_var': usize - [151; 152) 'e': E - [159; 245) 'match ... }': usize - [165; 166) 'e': E - [177; 187) 'E::A { x }': E - [184; 185) 'x': usize - [191; 192) 'x': usize - [202; 206) 'E::B': E - [210; 213) 'foo': bool - [217; 218) '1': usize - [228; 232) 'E::B': E - [236; 238) '10': usize - [256; 275) 'ref d ...{ .. }': &E - [264; 275) 'E::A { .. }': E - [278; 279) 'e': E - [285; 286) 'd': &E - "### - ); -} - -#[test] -fn infer_struct_generics() { - assert_snapshot!( - infer(r#" -struct A { - x: T, -} - -fn test(a1: A, i: i32) { - a1.x; - let a2 = A { x: i }; - a2.x; - let a3 = A:: { x: 1 }; - a3.x; -} -"#), - @r###" - [36; 38) 'a1': A - [48; 49) 'i': i32 - [56; 147) '{ ...3.x; }': () - [62; 64) 'a1': A - [62; 66) 'a1.x': u32 - [76; 78) 'a2': A - [81; 91) 'A { x: i }': A - [88; 89) 'i': i32 - [97; 99) 'a2': A - [97; 101) 'a2.x': i32 - [111; 113) 'a3': A - [116; 134) 'A:: - [131; 132) '1': i128 - [140; 142) 'a3': A - [140; 144) 'a3.x': i128 - "### - ); -} - -#[test] -fn infer_tuple_struct_generics() { - assert_snapshot!( - infer(r#" -struct A(T); -enum Option { Some(T), None } -use Option::*; - -fn test() { - A(42); - A(42u128); - Some("x"); - Option::Some("x"); - None; - let x: Option = None; -} -"#), - @r###" - [76; 184) '{ ...one; }': () - [82; 83) 'A': A(T) -> A - [82; 87) 'A(42)': A - [84; 86) '42': i32 - [93; 94) 'A': A(T) -> A - [93; 102) 'A(42u128)': A - [95; 101) '42u128': u128 - [108; 112) 'Some': Some<&str>(T) -> Option - [108; 117) 'Some("x")': Option<&str> - [113; 116) '"x"': &str - [123; 135) 'Option::Some': Some<&str>(T) -> Option - [123; 140) 'Option...e("x")': Option<&str> - [136; 139) '"x"': &str - [146; 150) 'None': Option<{unknown}> - [160; 161) 'x': Option - [177; 181) 'None': Option - "### - ); -} - -#[test] -fn infer_generics_in_patterns() { - assert_snapshot!( - infer(r#" -struct A { - x: T, -} - -enum Option { - Some(T), - None, -} - -fn test(a1: A, o: Option) { - let A { x: x2 } = a1; - let A:: { x: x3 } = A { x: 1 }; - match o { - Option::Some(t) => t, - _ => 1, - }; -} -"#), - @r###" - [79; 81) 'a1': A - [91; 92) 'o': Option - [107; 244) '{ ... }; }': () - [117; 128) 'A { x: x2 }': A - [124; 126) 'x2': u32 - [131; 133) 'a1': A - [143; 161) 'A:: - [157; 159) 'x3': i64 - [164; 174) 'A { x: 1 }': A - [171; 172) '1': i64 - [180; 241) 'match ... }': u64 - [186; 187) 'o': Option - [198; 213) 'Option::Some(t)': Option - [211; 212) 't': u64 - [217; 218) 't': u64 - [228; 229) '_': Option - [233; 234) '1': u64 - "### - ); -} - -#[test] -fn infer_function_generics() { - assert_snapshot!( - infer(r#" -fn id(t: T) -> T { t } - -fn test() { - id(1u32); - id::(1); - let x: u64 = id(1); -} -"#), - @r###" - [10; 11) 't': T - [21; 26) '{ t }': T - [23; 24) 't': T - [38; 98) '{ ...(1); }': () - [44; 46) 'id': fn id(T) -> T - [44; 52) 'id(1u32)': u32 - [47; 51) '1u32': u32 - [58; 68) 'id::': fn id(T) -> T - [58; 71) 'id::(1)': i128 - [69; 70) '1': i128 - [81; 82) 'x': u64 - [90; 92) 'id': fn id(T) -> T - [90; 95) 'id(1)': u64 - [93; 94) '1': u64 - "### - ); -} - -#[test] -fn infer_impl_generics() { - assert_snapshot!( - infer(r#" -struct A { - x: T1, - y: T2, -} -impl A { - fn x(self) -> X { - self.x - } - fn y(self) -> Y { - self.y - } - fn z(self, t: T) -> (X, Y, T) { - (self.x, self.y, t) - } -} - -fn test() -> i128 { - let a = A { x: 1u64, y: 1i64 }; - a.x(); - a.y(); - a.z(1i128); - a.z::(1); -} -"#), - @r###" - [74; 78) 'self': A - [85; 107) '{ ... }': X - [95; 99) 'self': A - [95; 101) 'self.x': X - [117; 121) 'self': A - [128; 150) '{ ... }': Y - [138; 142) 'self': A - [138; 144) 'self.y': Y - [163; 167) 'self': A - [169; 170) 't': T - [188; 223) '{ ... }': (X, Y, T) - [198; 217) '(self.....y, t)': (X, Y, T) - [199; 203) 'self': A - [199; 205) 'self.x': X - [207; 211) 'self': A - [207; 213) 'self.y': Y - [215; 216) 't': T - [245; 342) '{ ...(1); }': () - [255; 256) 'a': A - [259; 281) 'A { x:...1i64 }': A - [266; 270) '1u64': u64 - [275; 279) '1i64': i64 - [287; 288) 'a': A - [287; 292) 'a.x()': u64 - [298; 299) 'a': A - [298; 303) 'a.y()': i64 - [309; 310) 'a': A - [309; 319) 'a.z(1i128)': (u64, i64, i128) - [313; 318) '1i128': i128 - [325; 326) 'a': A - [325; 339) 'a.z::(1)': (u64, i64, u128) - [337; 338) '1': u128 - "### - ); -} - -#[test] -fn infer_impl_generics_with_autoderef() { - assert_snapshot!( - infer(r#" -enum Option { - Some(T), - None, -} -impl Option { - fn as_ref(&self) -> Option<&T> {} -} -fn test(o: Option) { - (&o).as_ref(); - o.as_ref(); -} -"#), - @r###" - [78; 82) 'self': &Option - [98; 100) '{}': () - [111; 112) 'o': Option - [127; 165) '{ ...f(); }': () - [133; 146) '(&o).as_ref()': Option<&u32> - [134; 136) '&o': &Option - [135; 136) 'o': Option - [152; 153) 'o': Option - [152; 162) 'o.as_ref()': Option<&u32> - "### - ); -} - -#[test] -fn infer_generic_chain() { - assert_snapshot!( - infer(r#" -struct A { - x: T, -} -impl A { - fn x(self) -> T2 { - self.x - } -} -fn id(t: T) -> T { t } - -fn test() -> i128 { - let x = 1; - let y = id(x); - let a = A { x: id(y) }; - let z = id(a.x); - let b = A { x: z }; - b.x() -} -"#), - @r###" - [53; 57) 'self': A - [65; 87) '{ ... }': T2 - [75; 79) 'self': A - [75; 81) 'self.x': T2 - [99; 100) 't': T - [110; 115) '{ t }': T - [112; 113) 't': T - [135; 261) '{ ....x() }': i128 - [146; 147) 'x': i128 - [150; 151) '1': i128 - [162; 163) 'y': i128 - [166; 168) 'id': fn id(T) -> T - [166; 171) 'id(x)': i128 - [169; 170) 'x': i128 - [182; 183) 'a': A - [186; 200) 'A { x: id(y) }': A - [193; 195) 'id': fn id(T) -> T - [193; 198) 'id(y)': i128 - [196; 197) 'y': i128 - [211; 212) 'z': i128 - [215; 217) 'id': fn id(T) -> T - [215; 222) 'id(a.x)': i128 - [218; 219) 'a': A - [218; 221) 'a.x': i128 - [233; 234) 'b': A - [237; 247) 'A { x: z }': A - [244; 245) 'z': i128 - [254; 255) 'b': A - [254; 259) 'b.x()': i128 - "### - ); -} - -#[test] -fn infer_associated_const() { - assert_snapshot!( - infer(r#" -struct Struct; - -impl Struct { - const FOO: u32 = 1; -} - -enum Enum {} - -impl Enum { - const BAR: u32 = 2; -} - -trait Trait { - const ID: u32; -} - -struct TraitTest; - -impl Trait for TraitTest { - const ID: u32 = 5; -} - -fn test() { - let x = Struct::FOO; - let y = Enum::BAR; - let z = TraitTest::ID; -} -"#), - @r###" - [52; 53) '1': u32 - [105; 106) '2': u32 - [213; 214) '5': u32 - [229; 307) '{ ...:ID; }': () - [239; 240) 'x': u32 - [243; 254) 'Struct::FOO': u32 - [264; 265) 'y': u32 - [268; 277) 'Enum::BAR': u32 - [287; 288) 'z': u32 - [291; 304) 'TraitTest::ID': u32 - "### - ); -} - -#[test] -fn infer_associated_method_struct() { - assert_snapshot!( - infer(r#" -struct A { x: u32 } - -impl A { - fn new() -> A { - A { x: 0 } - } -} -fn test() { - let a = A::new(); - a.x; -} -"#), - @r###" - [49; 75) '{ ... }': A - [59; 69) 'A { x: 0 }': A - [66; 67) '0': u32 - [88; 122) '{ ...a.x; }': () - [98; 99) 'a': A - [102; 108) 'A::new': fn new() -> A - [102; 110) 'A::new()': A - [116; 117) 'a': A - [116; 119) 'a.x': u32 - "### - ); -} - -#[test] -fn infer_associated_method_enum() { - assert_snapshot!( - infer(r#" -enum A { B, C } - -impl A { - pub fn b() -> A { - A::B - } - pub fn c() -> A { - A::C - } -} -fn test() { - let a = A::b(); - a; - let c = A::c(); - c; -} -"#), - @r###" - [47; 67) '{ ... }': A - [57; 61) 'A::B': A - [88; 108) '{ ... }': A - [98; 102) 'A::C': A - [121; 178) '{ ... c; }': () - [131; 132) 'a': A - [135; 139) 'A::b': fn b() -> A - [135; 141) 'A::b()': A - [147; 148) 'a': A - [158; 159) 'c': A - [162; 166) 'A::c': fn c() -> A - [162; 168) 'A::c()': A - [174; 175) 'c': A - "### - ); -} - -#[test] -fn infer_associated_method_with_modules() { - assert_snapshot!( - infer(r#" -mod a { - struct A; - impl A { pub fn thing() -> A { A {} }} -} - -mod b { - struct B; - impl B { pub fn thing() -> u32 { 99 }} - - mod c { - struct C; - impl C { pub fn thing() -> C { C {} }} - } -} -use b::c; - -fn test() { - let x = a::A::thing(); - let y = b::B::thing(); - let z = c::C::thing(); -} -"#), - @r###" - [56; 64) '{ A {} }': A - [58; 62) 'A {}': A - [126; 132) '{ 99 }': u32 - [128; 130) '99': u32 - [202; 210) '{ C {} }': C - [204; 208) 'C {}': C - [241; 325) '{ ...g(); }': () - [251; 252) 'x': A - [255; 266) 'a::A::thing': fn thing() -> A - [255; 268) 'a::A::thing()': A - [278; 279) 'y': u32 - [282; 293) 'b::B::thing': fn thing() -> u32 - [282; 295) 'b::B::thing()': u32 - [305; 306) 'z': C - [309; 320) 'c::C::thing': fn thing() -> C - [309; 322) 'c::C::thing()': C - "### - ); -} - -#[test] -fn infer_associated_method_generics() { - assert_snapshot!( - infer(r#" -struct Gen { - val: T -} - -impl Gen { - pub fn make(val: T) -> Gen { - Gen { val } - } -} - -fn test() { - let a = Gen::make(0u32); -} -"#), - @r###" - [64; 67) 'val': T - [82; 109) '{ ... }': Gen - [92; 103) 'Gen { val }': Gen - [98; 101) 'val': T - [123; 155) '{ ...32); }': () - [133; 134) 'a': Gen - [137; 146) 'Gen::make': fn make(T) -> Gen - [137; 152) 'Gen::make(0u32)': Gen - [147; 151) '0u32': u32 - "### - ); -} - -#[test] -fn infer_associated_method_generics_with_default_param() { - assert_snapshot!( - infer(r#" -struct Gen { - val: T -} - -impl Gen { - pub fn make() -> Gen { - loop { } - } -} - -fn test() { - let a = Gen::make(); -} -"#), - @r###" - [80; 104) '{ ... }': Gen - [90; 98) 'loop { }': ! - [95; 98) '{ }': () - [118; 146) '{ ...e(); }': () - [128; 129) 'a': Gen - [132; 141) 'Gen::make': fn make() -> Gen - [132; 143) 'Gen::make()': Gen - "### - ); -} - -#[test] -fn infer_associated_method_generics_with_default_tuple_param() { - let t = type_at( - r#" -//- /main.rs -struct Gen { - val: T -} - -impl Gen { - pub fn make() -> Gen { - loop { } - } -} - -fn test() { - let a = Gen::make(); - a.val<|>; -} -"#, - ); - assert_eq!(t, "()"); -} - -#[test] -fn infer_associated_method_generics_without_args() { - assert_snapshot!( - infer(r#" -struct Gen { - val: T -} - -impl Gen { - pub fn make() -> Gen { - loop { } - } -} - -fn test() { - let a = Gen::::make(); -} -"#), - @r###" - [76; 100) '{ ... }': Gen - [86; 94) 'loop { }': ! - [91; 94) '{ }': () - [114; 149) '{ ...e(); }': () - [124; 125) 'a': Gen - [128; 144) 'Gen::<...::make': fn make() -> Gen - [128; 146) 'Gen::<...make()': Gen - "### - ); -} - -#[test] -fn infer_associated_method_generics_2_type_params_without_args() { - assert_snapshot!( - infer(r#" -struct Gen { - val: T, - val2: U, -} - -impl Gen { - pub fn make() -> Gen { - loop { } - } -} - -fn test() { - let a = Gen::::make(); -} -"#), - @r###" - [102; 126) '{ ... }': Gen - [112; 120) 'loop { }': ! - [117; 120) '{ }': () - [140; 180) '{ ...e(); }': () - [150; 151) 'a': Gen - [154; 175) 'Gen::<...::make': fn make() -> Gen - [154; 177) 'Gen::<...make()': Gen - "### - ); -} - -#[test] -fn infer_type_alias() { - assert_snapshot!( - infer(r#" -struct A { x: X, y: Y } -type Foo = A; -type Bar = A; -type Baz = A; -fn test(x: Foo, y: Bar<&str>, z: Baz) { - x.x; - x.y; - y.x; - y.y; - z.x; - z.y; -} -"#), - @r###" - [116; 117) 'x': A - [124; 125) 'y': A<&str, u128> - [138; 139) 'z': A - [154; 211) '{ ...z.y; }': () - [160; 161) 'x': A - [160; 163) 'x.x': u32 - [169; 170) 'x': A - [169; 172) 'x.y': i128 - [178; 179) 'y': A<&str, u128> - [178; 181) 'y.x': &str - [187; 188) 'y': A<&str, u128> - [187; 190) 'y.y': u128 - [196; 197) 'z': A - [196; 199) 'z.x': u8 - [205; 206) 'z': A - [205; 208) 'z.y': i8 - "### - ) -} - -#[test] -#[should_panic] // we currently can't handle this -fn recursive_type_alias() { - assert_snapshot!( - infer(r#" -struct A {} -type Foo = Foo; -type Bar = A; -fn test(x: Foo) {} -"#), - @"" - ) -} - -#[test] -fn no_panic_on_field_of_enum() { - assert_snapshot!( - infer(r#" -enum X {} - -fn test(x: X) { - x.some_field; -} -"#), - @r###" - [20; 21) 'x': X - [26; 47) '{ ...eld; }': () - [32; 33) 'x': X - [32; 44) 'x.some_field': {unknown} - "### - ); -} - -#[test] -fn bug_585() { - assert_snapshot!( - infer(r#" -fn test() { - X {}; - match x { - A::B {} => (), - A::Y() => (), - } -} -"#), - @r###" - [11; 89) '{ ... } }': () - [17; 21) 'X {}': {unknown} - [27; 87) 'match ... }': () - [33; 34) 'x': {unknown} - [45; 52) 'A::B {}': {unknown} - [56; 58) '()': () - [68; 74) 'A::Y()': {unknown} - [78; 80) '()': () - "### - ); -} - -#[test] -fn bug_651() { - assert_snapshot!( - infer(r#" -fn quux() { - let y = 92; - 1 + y; -} -"#), - @r###" - [11; 41) '{ ...+ y; }': () - [21; 22) 'y': i32 - [25; 27) '92': i32 - [33; 34) '1': i32 - [33; 38) '1 + y': i32 - [37; 38) 'y': i32 - "### - ); -} - -#[test] -fn recursive_vars() { - covers!(type_var_cycles_resolve_completely); - covers!(type_var_cycles_resolve_as_possible); - assert_snapshot!( - infer(r#" -fn test() { - let y = unknown; - [y, &y]; -} -"#), - @r###" - [11; 48) '{ ...&y]; }': () - [21; 22) 'y': &{unknown} - [25; 32) 'unknown': &{unknown} - [38; 45) '[y, &y]': [&&{unknown};_] - [39; 40) 'y': &{unknown} - [42; 44) '&y': &&{unknown} - [43; 44) 'y': &{unknown} - "### - ); -} - -#[test] -fn recursive_vars_2() { - covers!(type_var_cycles_resolve_completely); - covers!(type_var_cycles_resolve_as_possible); - assert_snapshot!( - infer(r#" -fn test() { - let x = unknown; - let y = unknown; - [(x, y), (&y, &x)]; -} -"#), - @r###" - [11; 80) '{ ...x)]; }': () - [21; 22) 'x': &&{unknown} - [25; 32) 'unknown': &&{unknown} - [42; 43) 'y': &&{unknown} - [46; 53) 'unknown': &&{unknown} - [59; 77) '[(x, y..., &x)]': [(&&&{unknown}, &&&{unknown});_] - [60; 66) '(x, y)': (&&&{unknown}, &&&{unknown}) - [61; 62) 'x': &&{unknown} - [64; 65) 'y': &&{unknown} - [68; 76) '(&y, &x)': (&&&{unknown}, &&&{unknown}) - [69; 71) '&y': &&&{unknown} - [70; 71) 'y': &&{unknown} - [73; 75) '&x': &&&{unknown} - [74; 75) 'x': &&{unknown} - "### - ); -} - -#[test] -fn infer_type_param() { - assert_snapshot!( - infer(r#" -fn id(x: T) -> T { - x -} - -fn clone(x: &T) -> T { - *x -} - -fn test() { - let y = 10u32; - id(y); - let x: bool = clone(z); - id::(1); -} -"#), - @r###" - [10; 11) 'x': T - [21; 30) '{ x }': T - [27; 28) 'x': T - [44; 45) 'x': &T - [56; 66) '{ *x }': T - [62; 64) '*x': T - [63; 64) 'x': &T - [78; 158) '{ ...(1); }': () - [88; 89) 'y': u32 - [92; 97) '10u32': u32 - [103; 105) 'id': fn id(T) -> T - [103; 108) 'id(y)': u32 - [106; 107) 'y': u32 - [118; 119) 'x': bool - [128; 133) 'clone': fn clone(&T) -> T - [128; 136) 'clone(z)': bool - [134; 135) 'z': &bool - [142; 152) 'id::': fn id(T) -> T - [142; 155) 'id::(1)': i128 - [153; 154) '1': i128 - "### - ); -} - -#[test] -fn infer_std_crash_1() { - // caused stack overflow, taken from std - assert_snapshot!( - infer(r#" -enum Maybe { - Real(T), - Fake, -} - -fn write() { - match something_unknown { - Maybe::Real(ref mut something) => (), - } -} -"#), - @r###" - [54; 139) '{ ... } }': () - [60; 137) 'match ... }': () - [66; 83) 'someth...nknown': Maybe<{unknown}> - [94; 124) 'Maybe:...thing)': Maybe<{unknown}> - [106; 123) 'ref mu...ething': &mut {unknown} - [128; 130) '()': () - "### - ); -} - -#[test] -fn infer_std_crash_2() { - covers!(type_var_resolves_to_int_var); - // caused "equating two type variables, ...", taken from std - assert_snapshot!( - infer(r#" -fn test_line_buffer() { - &[0, b'\n', 1, b'\n']; -} -"#), - @r###" - [23; 53) '{ ...n']; }': () - [29; 50) '&[0, b...b'\n']': &[u8;_] - [30; 50) '[0, b'...b'\n']': [u8;_] - [31; 32) '0': u8 - [34; 39) 'b'\n'': u8 - [41; 42) '1': u8 - [44; 49) 'b'\n'': u8 - "### - ); -} - -#[test] -fn infer_std_crash_3() { - // taken from rustc - assert_snapshot!( - infer(r#" -pub fn compute() { - match nope!() { - SizeSkeleton::Pointer { non_zero: true, tail } => {} - } -} -"#), - @r###" - [18; 108) '{ ... } }': () - [24; 106) 'match ... }': () - [30; 37) 'nope!()': {unknown} - [48; 94) 'SizeSk...tail }': {unknown} - [82; 86) 'true': {unknown} - [88; 92) 'tail': {unknown} - [98; 100) '{}': () - "### - ); -} - -#[test] -fn infer_std_crash_4() { - // taken from rustc - assert_snapshot!( - infer(r#" -pub fn primitive_type() { - match *self { - BorrowedRef { type_: Primitive(p), ..} => {}, - } -} -"#), - @r###" - [25; 106) '{ ... } }': () - [31; 104) 'match ... }': () - [37; 42) '*self': {unknown} - [38; 42) 'self': {unknown} - [53; 91) 'Borrow...), ..}': {unknown} - [74; 86) 'Primitive(p)': {unknown} - [84; 85) 'p': {unknown} - [95; 97) '{}': () - "### - ); -} - -#[test] -fn infer_std_crash_5() { - // taken from rustc - assert_snapshot!( - infer(r#" -fn extra_compiler_flags() { - for content in doesnt_matter { - let name = if doesnt_matter { - first - } else { - &content - }; - - let content = if ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE.contains(&name) { - name - } else { - content - }; - } -} -"#), - @r###" - [27; 323) '{ ... } }': () - [33; 321) 'for co... }': () - [37; 44) 'content': &{unknown} - [48; 61) 'doesnt_matter': {unknown} - [62; 321) '{ ... }': () - [76; 80) 'name': &&{unknown} - [83; 167) 'if doe... }': &&{unknown} - [86; 99) 'doesnt_matter': bool - [100; 129) '{ ... }': &&{unknown} - [114; 119) 'first': &&{unknown} - [135; 167) '{ ... }': &&{unknown} - [149; 157) '&content': &&{unknown} - [150; 157) 'content': &{unknown} - [182; 189) 'content': &{unknown} - [192; 314) 'if ICE... }': &{unknown} - [195; 232) 'ICE_RE..._VALUE': {unknown} - [195; 248) 'ICE_RE...&name)': bool - [242; 247) '&name': &&&{unknown} - [243; 247) 'name': &&{unknown} - [249; 277) '{ ... }': &&{unknown} - [263; 267) 'name': &&{unknown} - [283; 314) '{ ... }': &{unknown} - [297; 304) 'content': &{unknown} - "### - ); -} - -#[test] -fn infer_nested_generics_crash() { - // another crash found typechecking rustc - assert_snapshot!( - infer(r#" -struct Canonical { - value: V, -} -struct QueryResponse { - value: V, -} -fn test(query_response: Canonical>) { - &query_response.value; -} -"#), - @r###" - [92; 106) 'query_response': Canonical> - [137; 167) '{ ...lue; }': () - [143; 164) '&query....value': &QueryResponse - [144; 158) 'query_response': Canonical> - [144; 164) 'query_....value': QueryResponse - "### - ); -} - -#[test] -fn bug_1030() { - assert_snapshot!(infer(r#" -struct HashSet; -struct FxHasher; -type FxHashSet = HashSet; - -impl HashSet { - fn default() -> HashSet {} -} - -pub fn main_loop() { - FxHashSet::default(); -} -"#), - @r###" - [144; 146) '{}': () - [169; 198) '{ ...t(); }': () - [175; 193) 'FxHash...efault': fn default<{unknown}, FxHasher>() -> HashSet - [175; 195) 'FxHash...ault()': HashSet<{unknown}, FxHasher> - "### - ); -} - -#[test] -fn cross_crate_associated_method_call() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:other_crate -fn test() { - let x = other_crate::foo::S::thing(); - x<|>; -} - -//- /lib.rs crate:other_crate -mod foo { - struct S; - impl S { - fn thing() -> i128 {} - } -} -"#, - ); - assert_eq!("i128", type_at_pos(&db, pos)); -} - -#[test] -fn infer_const() { - assert_snapshot!( - infer(r#" -struct Foo; -impl Foo { const ASSOC_CONST: u32 = 0; } -const GLOBAL_CONST: u32 = 101; -fn test() { - const LOCAL_CONST: u32 = 99; - let x = LOCAL_CONST; - let z = GLOBAL_CONST; - let id = Foo::ASSOC_CONST; -} -"#), - @r###" - [49; 50) '0': u32 - [80; 83) '101': u32 - [95; 213) '{ ...NST; }': () - [138; 139) 'x': {unknown} - [142; 153) 'LOCAL_CONST': {unknown} - [163; 164) 'z': u32 - [167; 179) 'GLOBAL_CONST': u32 - [189; 191) 'id': u32 - [194; 210) 'Foo::A..._CONST': u32 - "### - ); -} - -#[test] -fn infer_static() { - assert_snapshot!( - infer(r#" -static GLOBAL_STATIC: u32 = 101; -static mut GLOBAL_STATIC_MUT: u32 = 101; -fn test() { - static LOCAL_STATIC: u32 = 99; - static mut LOCAL_STATIC_MUT: u32 = 99; - let x = LOCAL_STATIC; - let y = LOCAL_STATIC_MUT; - let z = GLOBAL_STATIC; - let w = GLOBAL_STATIC_MUT; -} -"#), - @r###" - [29; 32) '101': u32 - [70; 73) '101': u32 - [85; 280) '{ ...MUT; }': () - [173; 174) 'x': {unknown} - [177; 189) 'LOCAL_STATIC': {unknown} - [199; 200) 'y': {unknown} - [203; 219) 'LOCAL_...IC_MUT': {unknown} - [229; 230) 'z': u32 - [233; 246) 'GLOBAL_STATIC': u32 - [256; 257) 'w': u32 - [260; 277) 'GLOBAL...IC_MUT': u32 - "### - ); -} - -#[test] -fn infer_trait_method_simple() { - // the trait implementation is intentionally incomplete -- it shouldn't matter - assert_snapshot!( - infer(r#" -trait Trait1 { - fn method(&self) -> u32; -} -struct S1; -impl Trait1 for S1 {} -trait Trait2 { - fn method(&self) -> i128; -} -struct S2; -impl Trait2 for S2 {} -fn test() { - S1.method(); // -> u32 - S2.method(); // -> i128 -} -"#), - @r###" - [31; 35) 'self': &Self - [110; 114) 'self': &Self - [170; 228) '{ ...i128 }': () - [176; 178) 'S1': S1 - [176; 187) 'S1.method()': u32 - [203; 205) 'S2': S2 - [203; 214) 'S2.method()': i128 - "### - ); -} - -#[test] -fn infer_trait_method_scoped() { - // the trait implementation is intentionally incomplete -- it shouldn't matter - assert_snapshot!( - infer(r#" -struct S; -mod foo { - pub trait Trait1 { - fn method(&self) -> u32; - } - impl Trait1 for super::S {} -} -mod bar { - pub trait Trait2 { - fn method(&self) -> i128; - } - impl Trait2 for super::S {} -} - -mod foo_test { - use super::S; - use super::foo::Trait1; - fn test() { - S.method(); // -> u32 - } -} - -mod bar_test { - use super::S; - use super::bar::Trait2; - fn test() { - S.method(); // -> i128 - } -} -"#), - @r###" - [63; 67) 'self': &Self - [169; 173) 'self': &Self - [300; 337) '{ ... }': () - [310; 311) 'S': S - [310; 320) 'S.method()': u32 - [416; 454) '{ ... }': () - [426; 427) 'S': S - [426; 436) 'S.method()': i128 - "### - ); -} - -#[test] -fn infer_trait_method_generic_1() { - // the trait implementation is intentionally incomplete -- it shouldn't matter - assert_snapshot!( - infer(r#" -trait Trait { - fn method(&self) -> T; -} -struct S; -impl Trait for S {} -fn test() { - S.method(); -} -"#), - @r###" - [33; 37) 'self': &Self - [92; 111) '{ ...d(); }': () - [98; 99) 'S': S - [98; 108) 'S.method()': u32 - "### - ); -} - -#[test] -fn infer_trait_method_generic_more_params() { - // the trait implementation is intentionally incomplete -- it shouldn't matter - assert_snapshot!( - infer(r#" -trait Trait { - fn method1(&self) -> (T1, T2, T3); - fn method2(&self) -> (T3, T2, T1); -} -struct S1; -impl Trait for S1 {} -struct S2; -impl Trait for S2 {} -fn test() { - S1.method1(); // u8, u16, u32 - S1.method2(); // u32, u16, u8 - S2.method1(); // i8, i16, {unknown} - S2.method2(); // {unknown}, i16, i8 -} -"#), - @r###" - [43; 47) 'self': &Self - [82; 86) 'self': &Self - [210; 361) '{ ..., i8 }': () - [216; 218) 'S1': S1 - [216; 228) 'S1.method1()': (u8, u16, u32) - [250; 252) 'S1': S1 - [250; 262) 'S1.method2()': (u32, u16, u8) - [284; 286) 'S2': S2 - [284; 296) 'S2.method1()': (i8, i16, {unknown}) - [324; 326) 'S2': S2 - [324; 336) 'S2.method2()': ({unknown}, i16, i8) - "### - ); -} - -#[test] -fn infer_trait_method_generic_2() { - // the trait implementation is intentionally incomplete -- it shouldn't matter - assert_snapshot!( - infer(r#" -trait Trait { - fn method(&self) -> T; -} -struct S(T); -impl Trait for S {} -fn test() { - S(1u32).method(); -} -"#), - @r###" - [33; 37) 'self': &Self - [102; 127) '{ ...d(); }': () - [108; 109) 'S': S(T) -> S - [108; 115) 'S(1u32)': S - [108; 124) 'S(1u32...thod()': u32 - [110; 114) '1u32': u32 - "### - ); -} - -#[test] -fn infer_trait_assoc_method() { - assert_snapshot!( - infer(r#" -trait Default { - fn default() -> Self; -} -struct S; -impl Default for S {} -fn test() { - let s1: S = Default::default(); - let s2 = S::default(); - let s3 = ::default(); -} -"#), - @r###" - [87; 193) '{ ...t(); }': () - [97; 99) 's1': S - [105; 121) 'Defaul...efault': fn default() -> Self - [105; 123) 'Defaul...ault()': S - [133; 135) 's2': S - [138; 148) 'S::default': fn default() -> Self - [138; 150) 'S::default()': S - [160; 162) 's3': S - [165; 188) '() -> Self - [165; 190) ' { - fn make() -> T; -} -struct S; -impl Trait for S {} -struct G; -impl Trait for G {} -fn test() { - let a = S::make(); - let b = G::::make(); - let c: f64 = G::make(); -} -"#), - @r###" - [127; 211) '{ ...e(); }': () - [137; 138) 'a': u32 - [141; 148) 'S::make': fn make() -> T - [141; 150) 'S::make()': u32 - [160; 161) 'b': u64 - [164; 178) 'G::::make': fn make, u64>() -> T - [164; 180) 'G::, f64>() -> T - [199; 208) 'G::make()': f64 - "### - ); -} - -#[test] -fn infer_trait_assoc_method_generics_2() { - assert_snapshot!( - infer(r#" -trait Trait { - fn make() -> (T, U); -} -struct S; -impl Trait for S {} -struct G; -impl Trait for G {} -fn test() { - let a = S::make::(); - let b: (_, i64) = S::make(); - let c = G::::make::(); - let d: (u32, _) = G::make::(); - let e: (u32, i64) = G::make(); -} -"#), - @r###" - [135; 313) '{ ...e(); }': () - [145; 146) 'a': (u32, i64) - [149; 163) 'S::make::': fn make() -> (T, U) - [149; 165) 'S::mak...i64>()': (u32, i64) - [175; 176) 'b': (u32, i64) - [189; 196) 'S::make': fn make() -> (T, U) - [189; 198) 'S::make()': (u32, i64) - [208; 209) 'c': (u32, i64) - [212; 233) 'G::': fn make, u32, i64>() -> (T, U) - [212; 235) 'G::()': (u32, i64) - [245; 246) 'd': (u32, i64) - [259; 273) 'G::make::': fn make, u32, i64>() -> (T, U) - [259; 275) 'G::mak...i64>()': (u32, i64) - [285; 286) 'e': (u32, i64) - [301; 308) 'G::make': fn make, u32, i64>() -> (T, U) - [301; 310) 'G::make()': (u32, i64) - "### - ); -} - -#[test] -fn infer_trait_assoc_method_generics_3() { - assert_snapshot!( - infer(r#" -trait Trait { - fn make() -> (Self, T); -} -struct S; -impl Trait for S {} -fn test() { - let a = S::make(); -} -"#), - @r###" - [101; 127) '{ ...e(); }': () - [111; 112) 'a': (S, i64) - [115; 122) 'S::make': fn make, i64>() -> (Self, T) - [115; 124) 'S::make()': (S, i64) - "### - ); -} - -#[test] -fn infer_trait_assoc_method_generics_4() { - assert_snapshot!( - infer(r#" -trait Trait { - fn make() -> (Self, T); -} -struct S; -impl Trait for S {} -impl Trait for S {} -fn test() { - let a: (S, _) = S::make(); - let b: (_, i32) = S::make(); -} -"#), - @r###" - [131; 203) '{ ...e(); }': () - [141; 142) 'a': (S, i64) - [158; 165) 'S::make': fn make, i64>() -> (Self, T) - [158; 167) 'S::make()': (S, i64) - [177; 178) 'b': (S, i32) - [191; 198) 'S::make': fn make, i32>() -> (Self, T) - [191; 200) 'S::make()': (S, i32) - "### - ); -} - -#[test] -fn infer_trait_assoc_method_generics_5() { - assert_snapshot!( - infer(r#" -trait Trait { - fn make() -> (Self, T, U); -} -struct S; -impl Trait for S {} -fn test() { - let a = >::make::(); - let b: (S, _, _) = Trait::::make::(); -} -"#), - @r###" - [107; 211) '{ ...>(); }': () - [117; 118) 'a': (S, i64, u8) - [121; 150) '': fn make, i64, u8>() -> (Self, T, U) - [121; 152) '()': (S, i64, u8) - [162; 163) 'b': (S, i64, u8) - [182; 206) 'Trait:...::': fn make, i64, u8>() -> (Self, T, U) - [182; 208) 'Trait:...()': (S, i64, u8) - "### - ); -} - -#[test] -fn infer_from_bound_1() { - assert_snapshot!( - infer(r#" -trait Trait {} -struct S(T); -impl Trait for S {} -fn foo>(t: T) {} -fn test() { - let s = S(unknown); - foo(s); -} -"#), - @r###" - [86; 87) 't': T - [92; 94) '{}': () - [105; 144) '{ ...(s); }': () - [115; 116) 's': S - [119; 120) 'S': S(T) -> S - [119; 129) 'S(unknown)': S - [121; 128) 'unknown': u32 - [135; 138) 'foo': fn foo>(T) -> () - [135; 141) 'foo(s)': () - [139; 140) 's': S - "### - ); -} - -#[test] -fn infer_from_bound_2() { - assert_snapshot!( - infer(r#" -trait Trait {} -struct S(T); -impl Trait for S {} -fn foo>(t: T) -> U {} -fn test() { - let s = S(unknown); - let x: u32 = foo(s); -} -"#), - @r###" - [87; 88) 't': T - [98; 100) '{}': () - [111; 163) '{ ...(s); }': () - [121; 122) 's': S - [125; 126) 'S': S(T) -> S - [125; 135) 'S(unknown)': S - [127; 134) 'unknown': u32 - [145; 146) 'x': u32 - [154; 157) 'foo': fn foo>(T) -> U - [154; 160) 'foo(s)': u32 - [158; 159) 's': S - "### - ); -} - -#[test] -fn infer_call_trait_method_on_generic_param_1() { - assert_snapshot!( - infer(r#" -trait Trait { - fn method(&self) -> u32; -} -fn test(t: T) { - t.method(); -} -"#), - @r###" - [30; 34) 'self': &Self - [64; 65) 't': T - [70; 89) '{ ...d(); }': () - [76; 77) 't': T - [76; 86) 't.method()': u32 - "### - ); -} - -#[test] -fn infer_call_trait_method_on_generic_param_2() { - assert_snapshot!( - infer(r#" -trait Trait { - fn method(&self) -> T; -} -fn test>(t: T) { - t.method(); -} -"#), - @r###" - [33; 37) 'self': &Self - [71; 72) 't': T - [77; 96) '{ ...d(); }': () - [83; 84) 't': T - [83; 93) 't.method()': [missing name] - "### - ); -} - -#[test] -fn infer_with_multiple_trait_impls() { - assert_snapshot!( - infer(r#" -trait Into { - fn into(self) -> T; -} -struct S; -impl Into for S {} -impl Into for S {} -fn test() { - let x: u32 = S.into(); - let y: u64 = S.into(); - let z = Into::::into(S); -} -"#), - @r###" - [29; 33) 'self': Self - [111; 202) '{ ...(S); }': () - [121; 122) 'x': u32 - [130; 131) 'S': S - [130; 138) 'S.into()': u32 - [148; 149) 'y': u64 - [157; 158) 'S': S - [157; 165) 'S.into()': u64 - [175; 176) 'z': u64 - [179; 196) 'Into::...::into': fn into(Self) -> T - [179; 199) 'Into::...nto(S)': u64 - [197; 198) 'S': S - "### - ); -} - -#[test] -fn infer_project_associated_type() { - // y, z, a don't yet work because of https://github.com/rust-lang/chalk/issues/234 - assert_snapshot!( - infer(r#" -trait Iterable { - type Item; -} -struct S; -impl Iterable for S { type Item = u32; } -fn test() { - let x: ::Item = 1; - let y: ::Item = no_matter; - let z: T::Item = no_matter; - let a: ::Item = no_matter; -} -"#), - @r###" - [108; 261) '{ ...ter; }': () - [118; 119) 'x': u32 - [145; 146) '1': u32 - [156; 157) 'y': {unknown} - [183; 192) 'no_matter': {unknown} - [202; 203) 'z': {unknown} - [215; 224) 'no_matter': {unknown} - [234; 235) 'a': {unknown} - [249; 258) 'no_matter': {unknown} - "### - ); -} - -#[test] -fn infer_return_associated_type() { - assert_snapshot!( - infer(r#" -trait Iterable { - type Item; -} -struct S; -impl Iterable for S { type Item = u32; } -fn foo1(t: T) -> T::Item {} -fn foo2(t: T) -> ::Item {} -fn foo3(t: T) -> ::Item {} -fn test() { - let x = foo1(S); - let y = foo2(S); - let z = foo3(S); -} -"#), - @r###" - [106; 107) 't': T - [123; 125) '{}': () - [147; 148) 't': T - [178; 180) '{}': () - [202; 203) 't': T - [221; 223) '{}': () - [234; 300) '{ ...(S); }': () - [244; 245) 'x': u32 - [248; 252) 'foo1': fn foo1(T) -> ::Item - [248; 255) 'foo1(S)': u32 - [253; 254) 'S': S - [265; 266) 'y': u32 - [269; 273) 'foo2': fn foo2(T) -> ::Item - [269; 276) 'foo2(S)': u32 - [274; 275) 'S': S - [286; 287) 'z': u32 - [290; 294) 'foo3': fn foo3(T) -> ::Item - [290; 297) 'foo3(S)': u32 - [295; 296) 'S': S - "### - ); -} - -#[test] -fn infer_associated_type_bound() { - assert_snapshot!( - infer(r#" -trait Iterable { - type Item; -} -fn test>() { - let y: T::Item = unknown; -} -"#), - @r###" - [67; 100) '{ ...own; }': () - [77; 78) 'y': {unknown} - [90; 97) 'unknown': {unknown} - "### - ); -} - -#[test] -fn infer_const_body() { - assert_snapshot!( - infer(r#" -const A: u32 = 1 + 1; -static B: u64 = { let x = 1; x }; -"#), - @r###" - [16; 17) '1': u32 - [16; 21) '1 + 1': u32 - [20; 21) '1': u32 - [39; 55) '{ let ...1; x }': u64 - [45; 46) 'x': u64 - [49; 50) '1': u64 - [52; 53) 'x': u64 - "### - ); -} - -#[test] -fn tuple_struct_fields() { - assert_snapshot!( - infer(r#" -struct S(i32, u64); -fn test() -> u64 { - let a = S(4, 6); - let b = a.0; - a.1 -} -"#), - @r###" - [38; 87) '{ ... a.1 }': u64 - [48; 49) 'a': S - [52; 53) 'S': S(i32, u64) -> S - [52; 59) 'S(4, 6)': S - [54; 55) '4': i32 - [57; 58) '6': u64 - [69; 70) 'b': i32 - [73; 74) 'a': S - [73; 76) 'a.0': i32 - [82; 83) 'a': S - [82; 85) 'a.1': u64 - "### - ); -} - -#[test] -fn tuple_struct_with_fn() { - assert_snapshot!( - infer(r#" -struct S(fn(u32) -> u64); -fn test() -> u64 { - let a = S(|i| 2*i); - let b = a.0(4); - a.0(2) -} -"#), - @r###" - [44; 102) '{ ...0(2) }': u64 - [54; 55) 'a': S - [58; 59) 'S': S(fn(u32) -> u64) -> S - [58; 68) 'S(|i| 2*i)': S - [60; 67) '|i| 2*i': |i32| -> i32 - [61; 62) 'i': i32 - [64; 65) '2': i32 - [64; 67) '2*i': i32 - [66; 67) 'i': i32 - [78; 79) 'b': u64 - [82; 83) 'a': S - [82; 85) 'a.0': fn(u32) -> u64 - [82; 88) 'a.0(4)': u64 - [86; 87) '4': u32 - [94; 95) 'a': S - [94; 97) 'a.0': fn(u32) -> u64 - [94; 100) 'a.0(2)': u64 - [98; 99) '2': u32 - "### - ); -} - -#[test] -fn indexing_arrays() { - assert_snapshot!( - infer("fn main() { &mut [9][2]; }"), - @r###" - [10; 26) '{ &mut...[2]; }': () - [12; 23) '&mut [9][2]': &mut {unknown} - [17; 20) '[9]': [i32;_] - [17; 23) '[9][2]': {unknown} - [18; 19) '9': i32 - [21; 22) '2': i32 - "### - ) -} - -#[test] -fn infer_macros_expanded() { - assert_snapshot!( - infer(r#" -struct Foo(Vec); - -macro_rules! foo { - ($($item:expr),*) => { - { - Foo(vec![$($item,)*]) - } - }; -} - -fn main() { - let x = foo!(1,2); -} -"#), - @r###" - ![0; 17) '{Foo(v...,2,])}': Foo - ![1; 4) 'Foo': Foo({unknown}) -> Foo - ![1; 16) 'Foo(vec![1,2,])': Foo - ![5; 15) 'vec![1,2,]': {unknown} - [156; 182) '{ ...,2); }': () - [166; 167) 'x': Foo - "### - ); -} - -#[test] -fn infer_legacy_textual_scoped_macros_expanded() { - assert_snapshot!( - infer(r#" -struct Foo(Vec); - -#[macro_use] -mod m { - macro_rules! foo { - ($($item:expr),*) => { - { - Foo(vec![$($item,)*]) - } - }; - } -} - -fn main() { - let x = foo!(1,2); - let y = crate::foo!(1,2); -} -"#), - @r###" - ![0; 17) '{Foo(v...,2,])}': Foo - ![1; 4) 'Foo': Foo({unknown}) -> Foo - ![1; 16) 'Foo(vec![1,2,])': Foo - ![5; 15) 'vec![1,2,]': {unknown} - [195; 251) '{ ...,2); }': () - [205; 206) 'x': Foo - [228; 229) 'y': {unknown} - [232; 248) 'crate:...!(1,2)': {unknown} - "### - ); -} - -#[test] -fn infer_path_qualified_macros_expanded() { - assert_snapshot!( - infer(r#" -#[macro_export] -macro_rules! foo { - () => { 42i32 } -} - -mod m { - pub use super::foo as bar; -} - -fn main() { - let x = crate::foo!(); - let y = m::bar!(); -} -"#), - @r###" - ![0; 5) '42i32': i32 - ![0; 5) '42i32': i32 - [111; 164) '{ ...!(); }': () - [121; 122) 'x': i32 - [148; 149) 'y': i32 - "### - ); -} - -#[test] -fn infer_type_value_macro_having_same_name() { - assert_snapshot!( - infer(r#" -#[macro_export] -macro_rules! foo { - () => { - mod foo { - pub use super::foo; - } - }; - ($x:tt) => { - $x - }; -} - -foo!(); - -fn foo() { - let foo = foo::foo!(42i32); -} -"#), - @r###" - ![0; 5) '42i32': i32 - [171; 206) '{ ...32); }': () - [181; 184) 'foo': i32 - "### - ); -} - -#[test] -fn processes_impls_generated_by_macros() { - let t = type_at( - r#" -//- /main.rs -macro_rules! m { - ($ident:ident) => (impl Trait for $ident {}) -} -trait Trait { fn foo(self) -> u128 {} } -struct S; -m!(S); -fn test() { S.foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[test] -fn infer_macro_with_dollar_crate_is_correct_in_expr() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:foo -fn test() { - let x = (foo::foo!(1), foo::foo!(2)); - x<|>; -} - -//- /lib.rs crate:foo -#[macro_export] -macro_rules! foo { - (1) => { $crate::bar!() }; - (2) => { 1 + $crate::baz() }; -} - -#[macro_export] -macro_rules! bar { - () => { 42 } -} - -pub fn baz() -> usize { 31usize } -"#, - ); - assert_eq!("(i32, usize)", type_at_pos(&db, pos)); -} - -#[ignore] -#[test] -fn method_resolution_trait_before_autoref() { - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl S { fn foo(&self) -> i8 { 0 } } -impl Trait for S { fn foo(self) -> u128 { 0 } } -fn test() { S.foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[ignore] -#[test] -fn method_resolution_by_value_before_autoref() { - let t = type_at( - r#" -//- /main.rs -trait Clone { fn clone(&self) -> Self; } -struct S; -impl Clone for S {} -impl Clone for &S {} -fn test() { (S.clone(), (&S).clone(), (&&S).clone())<|>; } -"#, - ); - assert_eq!(t, "(S, S, &S)"); -} - -#[test] -fn method_resolution_trait_before_autoderef() { - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl S { fn foo(self) -> i8 { 0 } } -impl Trait for &S { fn foo(self) -> u128 { 0 } } -fn test() { (&S).foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[test] -fn method_resolution_impl_before_trait() { - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl S { fn foo(self) -> i8 { 0 } } -impl Trait for S { fn foo(self) -> u128 { 0 } } -fn test() { S.foo()<|>; } -"#, - ); - assert_eq!(t, "i8"); -} - -#[test] -fn method_resolution_trait_autoderef() { - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl Trait for S { fn foo(self) -> u128 { 0 } } -fn test() { (&S).foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[test] -fn method_resolution_trait_from_prelude() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:other_crate -struct S; -impl Clone for S {} - -fn test() { - S.clone()<|>; -} - -//- /lib.rs crate:other_crate -#[prelude_import] use foo::*; - -mod foo { - trait Clone { - fn clone(&self) -> Self; - } -} -"#, - ); - assert_eq!("S", type_at_pos(&db, pos)); -} - -#[test] -fn method_resolution_where_clause_for_unknown_trait() { - // The blanket impl shouldn't apply because we can't even resolve UnknownTrait - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl Trait for T where T: UnknownTrait {} -fn test() { (&S).foo()<|>; } -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn method_resolution_where_clause_not_met() { - // The blanket impl shouldn't apply because we can't prove S: Clone - let t = type_at( - r#" -//- /main.rs -trait Clone {} -trait Trait { fn foo(self) -> u128; } -struct S; -impl Trait for T where T: Clone {} -fn test() { (&S).foo()<|>; } -"#, - ); - // This is also to make sure that we don't resolve to the foo method just - // because that's the only method named foo we can find, which would make - // the below tests not work - assert_eq!(t, "{unknown}"); -} - -#[test] -fn method_resolution_where_clause_inline_not_met() { - // The blanket impl shouldn't apply because we can't prove S: Clone - let t = type_at( - r#" -//- /main.rs -trait Clone {} -trait Trait { fn foo(self) -> u128; } -struct S; -impl Trait for T {} -fn test() { (&S).foo()<|>; } -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn method_resolution_where_clause_1() { - let t = type_at( - r#" -//- /main.rs -trait Clone {} -trait Trait { fn foo(self) -> u128; } -struct S; -impl Clone for S {} -impl Trait for T where T: Clone {} -fn test() { S.foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[test] -fn method_resolution_where_clause_2() { - let t = type_at( - r#" -//- /main.rs -trait Into { fn into(self) -> T; } -trait From { fn from(other: T) -> Self; } -struct S1; -struct S2; -impl From for S1 {} -impl Into for T where U: From {} -fn test() { S2.into()<|>; } -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn method_resolution_where_clause_inline() { - let t = type_at( - r#" -//- /main.rs -trait Into { fn into(self) -> T; } -trait From { fn from(other: T) -> Self; } -struct S1; -struct S2; -impl From for S1 {} -impl> Into for T {} -fn test() { S2.into()<|>; } -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn method_resolution_encountering_fn_type() { - type_at( - r#" -//- /main.rs -fn foo() {} -trait FnOnce { fn call(self); } -fn test() { foo.call()<|>; } -"#, - ); -} - -#[test] -fn method_resolution_slow() { - // this can get quite slow if we set the solver size limit too high - let t = type_at( - r#" -//- /main.rs -trait SendX {} - -struct S1; impl SendX for S1 {} -struct S2; impl SendX for S2 {} -struct U1; - -trait Trait { fn method(self); } - -struct X1 {} -impl SendX for X1 where A: SendX, B: SendX {} - -struct S {} - -trait FnX {} - -impl Trait for S where C: FnX, B: SendX {} - -fn test() { (S {}).method()<|>; } -"#, - ); - assert_eq!(t, "()"); -} - -#[test] -fn shadowing_primitive() { - let t = type_at( - r#" -//- /main.rs -struct i32; -struct Foo; - -impl i32 { fn foo(&self) -> Foo { Foo } } - -fn main() { - let x: i32 = i32; - x.foo()<|>; -}"#, - ); - assert_eq!(t, "Foo"); -} - -#[test] -fn deref_trait() { - let t = type_at( - r#" -//- /main.rs -#[lang = "deref"] -trait Deref { - type Target; - fn deref(&self) -> &Self::Target; -} - -struct Arc; -impl Deref for Arc { - type Target = T; -} - -struct S; -impl S { - fn foo(&self) -> u128 {} -} - -fn test(s: Arc) { - (*s, s.foo())<|>; -} -"#, - ); - assert_eq!(t, "(S, u128)"); -} - -#[test] -fn deref_trait_with_inference_var() { - let t = type_at( - r#" -//- /main.rs -#[lang = "deref"] -trait Deref { - type Target; - fn deref(&self) -> &Self::Target; -} - -struct Arc; -fn new_arc() -> Arc {} -impl Deref for Arc { - type Target = T; -} - -struct S; -fn foo(a: Arc) {} - -fn test() { - let a = new_arc(); - let b = (*a)<|>; - foo(a); -} -"#, - ); - assert_eq!(t, "S"); -} - -#[test] -fn deref_trait_infinite_recursion() { - let t = type_at( - r#" -//- /main.rs -#[lang = "deref"] -trait Deref { - type Target; - fn deref(&self) -> &Self::Target; -} - -struct S; - -impl Deref for S { - type Target = S; -} - -fn test(s: S) { - s.foo()<|>; -} -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn deref_trait_with_question_mark_size() { - let t = type_at( - r#" -//- /main.rs -#[lang = "deref"] -trait Deref { - type Target; - fn deref(&self) -> &Self::Target; -} - -struct Arc; -impl Deref for Arc { - type Target = T; -} - -struct S; -impl S { - fn foo(&self) -> u128 {} -} - -fn test(s: Arc) { - (*s, s.foo())<|>; -} -"#, - ); - assert_eq!(t, "(S, u128)"); -} - -#[test] -fn obligation_from_function_clause() { - let t = type_at( - r#" -//- /main.rs -struct S; - -trait Trait {} -impl Trait for S {} - -fn foo, U>(t: T) -> U {} - -fn test(s: S) { - foo(s)<|>; -} -"#, - ); - assert_eq!(t, "u32"); -} - -#[test] -fn obligation_from_method_clause() { - let t = type_at( - r#" -//- /main.rs -struct S; - -trait Trait {} -impl Trait for S {} - -struct O; -impl O { - fn foo, U>(&self, t: T) -> U {} -} - -fn test() { - O.foo(S)<|>; -} -"#, - ); - assert_eq!(t, "isize"); -} - -#[test] -fn obligation_from_self_method_clause() { - let t = type_at( - r#" -//- /main.rs -struct S; - -trait Trait {} -impl Trait for S {} - -impl S { - fn foo(&self) -> U where Self: Trait {} -} - -fn test() { - S.foo()<|>; -} -"#, - ); - assert_eq!(t, "i64"); -} - -#[test] -fn obligation_from_impl_clause() { - let t = type_at( - r#" -//- /main.rs -struct S; - -trait Trait {} -impl Trait<&str> for S {} - -struct O; -impl> O { - fn foo(&self) -> U {} -} - -fn test(o: O) { - o.foo()<|>; -} -"#, - ); - assert_eq!(t, "&str"); -} - -#[test] -fn generic_param_env_1() { - let t = type_at( - r#" -//- /main.rs -trait Clone {} -trait Trait { fn foo(self) -> u128; } -struct S; -impl Clone for S {} -impl Trait for T where T: Clone {} -fn test(t: T) { t.foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[test] -fn generic_param_env_1_not_met() { - let t = type_at( - r#" -//- /main.rs -trait Clone {} -trait Trait { fn foo(self) -> u128; } -struct S; -impl Clone for S {} -impl Trait for T where T: Clone {} -fn test(t: T) { t.foo()<|>; } -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn generic_param_env_2() { - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl Trait for S {} -fn test(t: T) { t.foo()<|>; } -"#, - ); - assert_eq!(t, "u128"); -} - -#[test] -fn generic_param_env_2_not_met() { - let t = type_at( - r#" -//- /main.rs -trait Trait { fn foo(self) -> u128; } -struct S; -impl Trait for S {} -fn test(t: T) { t.foo()<|>; } -"#, - ); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn generic_param_env_deref() { - let t = type_at( - r#" -//- /main.rs -#[lang = "deref"] -trait Deref { - type Target; -} -trait Trait {} -impl Deref for T where T: Trait { - type Target = i128; -} -fn test(t: T) { (*t)<|>; } -"#, - ); - assert_eq!(t, "i128"); -} - -#[test] -fn associated_type_placeholder() { - let t = type_at( - r#" -//- /main.rs -pub trait ApplyL { - type Out; -} - -pub struct RefMutL; - -impl ApplyL for RefMutL { - type Out = ::Out; -} - -fn test() { - let y: as ApplyL>::Out = no_matter; - y<|>; -} -"#, - ); - // inside the generic function, the associated type gets normalized to a placeholder `ApplL::Out` [https://rust-lang.github.io/rustc-guide/traits/associated-types.html#placeholder-associated-types]. - // FIXME: fix type parameter names going missing when going through Chalk - assert_eq!(t, "ApplyL::Out<[missing name]>"); -} - -#[test] -fn associated_type_placeholder_2() { - let t = type_at( - r#" -//- /main.rs -pub trait ApplyL { - type Out; -} -fn foo(t: T) -> ::Out; - -fn test(t: T) { - let y = foo(t); - y<|>; -} -"#, - ); - // FIXME here Chalk doesn't normalize the type to a placeholder. I think we - // need to add a rule like Normalize(::Out -> ApplyL::Out) - // to the trait env ourselves here; probably Chalk can't do this by itself. - // assert_eq!(t, "ApplyL::Out<[missing name]>"); - assert_eq!(t, "{unknown}"); -} - -#[test] -fn impl_trait() { - assert_snapshot!( - infer(r#" -trait Trait { - fn foo(&self) -> T; - fn foo2(&self) -> i64; -} -fn bar() -> impl Trait {} - -fn test(x: impl Trait, y: &impl Trait) { - x; - y; - let z = bar(); - x.foo(); - y.foo(); - z.foo(); - x.foo2(); - y.foo2(); - z.foo2(); -} -"#), - @r###" - [30; 34) 'self': &Self - [55; 59) 'self': &Self - [99; 101) '{}': () - [111; 112) 'x': impl Trait - [131; 132) 'y': &impl Trait - [152; 269) '{ ...2(); }': () - [158; 159) 'x': impl Trait - [165; 166) 'y': &impl Trait - [176; 177) 'z': impl Trait - [180; 183) 'bar': fn bar() -> impl Trait - [180; 185) 'bar()': impl Trait - [191; 192) 'x': impl Trait - [191; 198) 'x.foo()': u64 - [204; 205) 'y': &impl Trait - [204; 211) 'y.foo()': u64 - [217; 218) 'z': impl Trait - [217; 224) 'z.foo()': u64 - [230; 231) 'x': impl Trait - [230; 238) 'x.foo2()': i64 - [244; 245) 'y': &impl Trait - [244; 252) 'y.foo2()': i64 - [258; 259) 'z': impl Trait - [258; 266) 'z.foo2()': i64 - "### - ); -} - -#[test] -fn dyn_trait() { - assert_snapshot!( - infer(r#" -trait Trait { - fn foo(&self) -> T; - fn foo2(&self) -> i64; -} -fn bar() -> dyn Trait {} - -fn test(x: dyn Trait, y: &dyn Trait) { - x; - y; - let z = bar(); - x.foo(); - y.foo(); - z.foo(); - x.foo2(); - y.foo2(); - z.foo2(); -} -"#), - @r###" - [30; 34) 'self': &Self - [55; 59) 'self': &Self - [98; 100) '{}': () - [110; 111) 'x': dyn Trait - [129; 130) 'y': &dyn Trait - [149; 266) '{ ...2(); }': () - [155; 156) 'x': dyn Trait - [162; 163) 'y': &dyn Trait - [173; 174) 'z': dyn Trait - [177; 180) 'bar': fn bar() -> dyn Trait - [177; 182) 'bar()': dyn Trait - [188; 189) 'x': dyn Trait - [188; 195) 'x.foo()': u64 - [201; 202) 'y': &dyn Trait - [201; 208) 'y.foo()': u64 - [214; 215) 'z': dyn Trait - [214; 221) 'z.foo()': u64 - [227; 228) 'x': dyn Trait - [227; 235) 'x.foo2()': i64 - [241; 242) 'y': &dyn Trait - [241; 249) 'y.foo2()': i64 - [255; 256) 'z': dyn Trait - [255; 263) 'z.foo2()': i64 - "### - ); -} - -#[test] -fn dyn_trait_bare() { - assert_snapshot!( - infer(r#" -trait Trait { - fn foo(&self) -> u64; -} -fn bar() -> Trait {} - -fn test(x: Trait, y: &Trait) -> u64 { - x; - y; - let z = bar(); - x.foo(); - y.foo(); - z.foo(); -} -"#), - @r###" - [27; 31) 'self': &Self - [61; 63) '{}': () - [73; 74) 'x': dyn Trait - [83; 84) 'y': &dyn Trait - [101; 176) '{ ...o(); }': () - [107; 108) 'x': dyn Trait - [114; 115) 'y': &dyn Trait - [125; 126) 'z': dyn Trait - [129; 132) 'bar': fn bar() -> dyn Trait - [129; 134) 'bar()': dyn Trait - [140; 141) 'x': dyn Trait - [140; 147) 'x.foo()': u64 - [153; 154) 'y': &dyn Trait - [153; 160) 'y.foo()': u64 - [166; 167) 'z': dyn Trait - [166; 173) 'z.foo()': u64 - "### - ); -} - -#[test] -fn weird_bounds() { - assert_snapshot!( - infer(r#" -trait Trait {} -fn test() { - let a: impl Trait + 'lifetime = foo; - let b: impl 'lifetime = foo; - let b: impl (Trait) = foo; - let b: impl ('lifetime) = foo; - let d: impl ?Sized = foo; - let e: impl Trait + ?Sized = foo; -} -"#), - @r###" - [26; 237) '{ ...foo; }': () - [36; 37) 'a': impl Trait + {error} - [64; 67) 'foo': impl Trait + {error} - [77; 78) 'b': impl {error} - [97; 100) 'foo': impl {error} - [110; 111) 'b': impl Trait - [128; 131) 'foo': impl Trait - [141; 142) 'b': impl {error} - [163; 166) 'foo': impl {error} - [176; 177) 'd': impl {error} - [193; 196) 'foo': impl {error} - [206; 207) 'e': impl Trait + {error} - [231; 234) 'foo': impl Trait + {error} - "### - ); -} - -#[test] -fn assoc_type_bindings() { - assert_snapshot!( - infer(r#" -trait Trait { - type Type; -} - -fn get(t: T) -> ::Type {} -fn get2>(t: T) -> U {} -fn set>(t: T) -> T {t} - -struct S; -impl Trait for S { type Type = T; } - -fn test>(x: T, y: impl Trait) { - get(x); - get2(x); - get(y); - get2(y); - get(set(S)); - get2(set(S)); - get2(S::); -} -"#), - @r###" - [50; 51) 't': T - [78; 80) '{}': () - [112; 113) 't': T - [123; 125) '{}': () - [155; 156) 't': T - [166; 169) '{t}': T - [167; 168) 't': T - [257; 258) 'x': T - [263; 264) 'y': impl Trait - [290; 398) '{ ...r>); }': () - [296; 299) 'get': fn get(T) -> ::Type - [296; 302) 'get(x)': {unknown} - [300; 301) 'x': T - [308; 312) 'get2': fn get2<{unknown}, T>(T) -> U - [308; 315) 'get2(x)': {unknown} - [313; 314) 'x': T - [321; 324) 'get': fn get>(T) -> ::Type - [321; 327) 'get(y)': {unknown} - [325; 326) 'y': impl Trait - [333; 337) 'get2': fn get2<{unknown}, impl Trait>(T) -> U - [333; 340) 'get2(y)': {unknown} - [338; 339) 'y': impl Trait - [346; 349) 'get': fn get>(T) -> ::Type - [346; 357) 'get(set(S))': u64 - [350; 353) 'set': fn set>(T) -> T - [350; 356) 'set(S)': S - [354; 355) 'S': S - [363; 367) 'get2': fn get2>(T) -> U - [363; 375) 'get2(set(S))': u64 - [368; 371) 'set': fn set>(T) -> T - [368; 374) 'set(S)': S - [372; 373) 'S': S - [381; 385) 'get2': fn get2>(T) -> U - [381; 395) 'get2(S::)': str - [386; 394) 'S::': S - "### - ); -} - -#[test] -fn impl_trait_assoc_binding_projection_bug() { - let (db, pos) = TestDB::with_position( - r#" -//- /main.rs crate:main deps:std -pub trait Language { - type Kind; -} -pub enum RustLanguage {} -impl Language for RustLanguage { - type Kind = SyntaxKind; -} -struct SyntaxNode {} -fn foo() -> impl Iterator> {} - -trait Clone { - fn clone(&self) -> Self; -} - -fn api_walkthrough() { - for node in foo() { - node.clone()<|>; - } -} - -//- /std.rs crate:std -#[prelude_import] use iter::*; -mod iter { - trait IntoIterator { - type Item; - } - trait Iterator { - type Item; - } - impl IntoIterator for T { - type Item = ::Item; - } -} -"#, - ); - assert_eq!("{unknown}", type_at_pos(&db, pos)); -} - -#[test] -fn projection_eq_within_chalk() { - // std::env::set_var("CHALK_DEBUG", "1"); - assert_snapshot!( - infer(r#" -trait Trait1 { - type Type; -} -trait Trait2 { - fn foo(self) -> T; -} -impl Trait2 for U where U: Trait1 {} - -fn test>(x: T) { - x.foo(); -} -"#), - @r###" - [62; 66) 'self': Self - [164; 165) 'x': T - [170; 186) '{ ...o(); }': () - [176; 177) 'x': T - [176; 183) 'x.foo()': {unknown} - "### - ); -} - -#[test] -fn where_clause_trait_in_scope_for_method_resolution() { - let t = type_at( - r#" -//- /main.rs -mod foo { - trait Trait { - fn foo(&self) -> u32 {} - } -} - -fn test(x: T) { - x.foo()<|>; -} -"#, - ); - assert_eq!(t, "u32"); -} - -#[test] -fn super_trait_method_resolution() { - assert_snapshot!( - infer(r#" -mod foo { - trait SuperTrait { - fn foo(&self) -> u32 {} - } -} -trait Trait1: foo::SuperTrait {} -trait Trait2 where Self: foo::SuperTrait {} - -fn test(x: T, y: U) { - x.foo(); - y.foo(); -} -"#), - @r###" - [50; 54) 'self': &Self - [63; 65) '{}': () - [182; 183) 'x': T - [188; 189) 'y': U - [194; 223) '{ ...o(); }': () - [200; 201) 'x': T - [200; 207) 'x.foo()': u32 - [213; 214) 'y': U - [213; 220) 'y.foo()': u32 - "### - ); -} - -#[test] -fn super_trait_cycle() { - // This just needs to not crash - assert_snapshot!( - infer(r#" -trait A: B {} -trait B: A {} - -fn test(x: T) { - x.foo(); -} -"#), - @r###" - [44; 45) 'x': T - [50; 66) '{ ...o(); }': () - [56; 57) 'x': T - [56; 63) 'x.foo()': {unknown} - "### - ); -} - -#[test] -fn super_trait_assoc_type_bounds() { - assert_snapshot!( - infer(r#" -trait SuperTrait { type Type; } -trait Trait where Self: SuperTrait {} - -fn get2>(t: T) -> U {} -fn set>(t: T) -> T {t} - -struct S; -impl SuperTrait for S { type Type = T; } -impl Trait for S {} - -fn test() { - get2(set(S)); -} -"#), - @r###" - [103; 104) 't': T - [114; 116) '{}': () - [146; 147) 't': T - [157; 160) '{t}': T - [158; 159) 't': T - [259; 280) '{ ...S)); }': () - [265; 269) 'get2': fn get2>(T) -> U - [265; 277) 'get2(set(S))': u64 - [270; 273) 'set': fn set>(T) -> T - [270; 276) 'set(S)': S - [274; 275) 'S': S - "### - ); -} - -#[test] -fn fn_trait() { - assert_snapshot!( - infer(r#" -trait FnOnce { - type Output; - - fn call_once(self, args: Args) -> >::Output; -} - -fn test u128>(f: F) { - f.call_once((1, 2)); -} -"#), - @r###" - [57; 61) 'self': Self - [63; 67) 'args': Args - [150; 151) 'f': F - [156; 184) '{ ...2)); }': () - [162; 163) 'f': F - [162; 181) 'f.call...1, 2))': {unknown} - [174; 180) '(1, 2)': (u32, u64) - [175; 176) '1': u32 - [178; 179) '2': u64 - "### - ); -} - -#[test] -fn closure_1() { - assert_snapshot!( - infer(r#" -#[lang = "fn_once"] -trait FnOnce { - type Output; -} - -enum Option { Some(T), None } -impl Option { - fn map U>(self, f: F) -> Option {} -} - -fn test() { - let x = Option::Some(1u32); - x.map(|v| v + 1); - x.map(|_v| 1u64); - let y: Option = x.map(|_v| 1); -} -"#), - @r###" - [148; 152) 'self': Option - [154; 155) 'f': F - [173; 175) '{}': () - [189; 308) '{ ... 1); }': () - [199; 200) 'x': Option - [203; 215) 'Option::Some': Some(T) -> Option - [203; 221) 'Option...(1u32)': Option - [216; 220) '1u32': u32 - [227; 228) 'x': Option - [227; 243) 'x.map(...v + 1)': Option - [233; 242) '|v| v + 1': |u32| -> u32 - [234; 235) 'v': u32 - [237; 238) 'v': u32 - [237; 242) 'v + 1': u32 - [241; 242) '1': u32 - [249; 250) 'x': Option - [249; 265) 'x.map(... 1u64)': Option - [255; 264) '|_v| 1u64': |u32| -> u64 - [256; 258) '_v': u32 - [260; 264) '1u64': u64 - [275; 276) 'y': Option - [292; 293) 'x': Option - [292; 305) 'x.map(|_v| 1)': Option - [298; 304) '|_v| 1': |u32| -> i64 - [299; 301) '_v': u32 - [303; 304) '1': i64 - "### - ); -} - -#[test] -fn closure_2() { - assert_snapshot!( - infer(r#" -trait FnOnce { - type Output; -} - -fn test u64>(f: F) { - f(1); - let g = |v| v + 1; - g(1u64); - let h = |v| 1u128 + v; -} -"#), - @r###" - [73; 74) 'f': F - [79; 155) '{ ...+ v; }': () - [85; 86) 'f': F - [85; 89) 'f(1)': {unknown} - [87; 88) '1': i32 - [99; 100) 'g': |u64| -> i32 - [103; 112) '|v| v + 1': |u64| -> i32 - [104; 105) 'v': u64 - [107; 108) 'v': u64 - [107; 112) 'v + 1': i32 - [111; 112) '1': i32 - [118; 119) 'g': |u64| -> i32 - [118; 125) 'g(1u64)': i32 - [120; 124) '1u64': u64 - [135; 136) 'h': |u128| -> u128 - [139; 152) '|v| 1u128 + v': |u128| -> u128 - [140; 141) 'v': u128 - [143; 148) '1u128': u128 - [143; 152) '1u128 + v': u128 - [151; 152) 'v': u128 - "### - ); -} - -#[test] -fn closure_as_argument_inference_order() { - assert_snapshot!( - infer(r#" -#[lang = "fn_once"] -trait FnOnce { - type Output; -} - -fn foo1 U>(x: T, f: F) -> U {} -fn foo2 U>(f: F, x: T) -> U {} - -struct S; -impl S { - fn method(self) -> u64; - - fn foo1 U>(self, x: T, f: F) -> U {} - fn foo2 U>(self, f: F, x: T) -> U {} -} - -fn test() { - let x1 = foo1(S, |s| s.method()); - let x2 = foo2(|s| s.method(), S); - let x3 = S.foo1(S, |s| s.method()); - let x4 = S.foo2(|s| s.method(), S); -} -"#), - @r###" - [95; 96) 'x': T - [101; 102) 'f': F - [112; 114) '{}': () - [148; 149) 'f': F - [154; 155) 'x': T - [165; 167) '{}': () - [202; 206) 'self': S - [254; 258) 'self': S - [260; 261) 'x': T - [266; 267) 'f': F - [277; 279) '{}': () - [317; 321) 'self': S - [323; 324) 'f': F - [329; 330) 'x': T - [340; 342) '{}': () - [356; 515) '{ ... S); }': () - [366; 368) 'x1': u64 - [371; 375) 'foo1': fn foo1 u64>(T, F) -> U - [371; 394) 'foo1(S...hod())': u64 - [376; 377) 'S': S - [379; 393) '|s| s.method()': |S| -> u64 - [380; 381) 's': S - [383; 384) 's': S - [383; 393) 's.method()': u64 - [404; 406) 'x2': u64 - [409; 413) 'foo2': fn foo2 u64>(F, T) -> U - [409; 432) 'foo2(|...(), S)': u64 - [414; 428) '|s| s.method()': |S| -> u64 - [415; 416) 's': S - [418; 419) 's': S - [418; 428) 's.method()': u64 - [430; 431) 'S': S - [442; 444) 'x3': u64 - [447; 448) 'S': S - [447; 472) 'S.foo1...hod())': u64 - [454; 455) 'S': S - [457; 471) '|s| s.method()': |S| -> u64 - [458; 459) 's': S - [461; 462) 's': S - [461; 471) 's.method()': u64 - [482; 484) 'x4': u64 - [487; 488) 'S': S - [487; 512) 'S.foo2...(), S)': u64 - [494; 508) '|s| s.method()': |S| -> u64 - [495; 496) 's': S - [498; 499) 's': S - [498; 508) 's.method()': u64 - [510; 511) 'S': S - "### - ); -} - -#[test] -fn unselected_projection_in_trait_env_1() { - let t = type_at( - r#" -//- /main.rs -trait Trait { - type Item; -} - -trait Trait2 { - fn foo(&self) -> u32; -} - -fn test() where T::Item: Trait2 { - let x: T::Item = no_matter; - x.foo()<|>; -} -"#, - ); - assert_eq!(t, "u32"); -} - -#[test] -fn unselected_projection_in_trait_env_2() { - let t = type_at( - r#" -//- /main.rs -trait Trait { - type Item; -} - -trait Trait2 { - fn foo(&self) -> u32; -} - -fn test() where T::Item: Trait2, T: Trait, U: Trait<()> { - let x: T::Item = no_matter; - x.foo()<|>; -} -"#, - ); - assert_eq!(t, "u32"); -} - -#[test] -// FIXME this is currently a Salsa panic; it would be nicer if it just returned -// in Unknown, and we should be able to do that once Salsa allows us to handle -// the cycle. But at least it doesn't overflow for now. -#[should_panic] -fn unselected_projection_in_trait_env_cycle_1() { - let t = type_at( - r#" -//- /main.rs -trait Trait { - type Item; -} - -trait Trait2 {} - -fn test() where T: Trait2 { - let x: T::Item = no_matter<|>; -} -"#, - ); - // this is a legitimate cycle - assert_eq!(t, "{unknown}"); -} - -#[test] -// FIXME this is currently a Salsa panic; it would be nicer if it just returned -// in Unknown, and we should be able to do that once Salsa allows us to handle -// the cycle. But at least it doesn't overflow for now. -#[should_panic] -fn unselected_projection_in_trait_env_cycle_2() { - let t = type_at( - r#" -//- /main.rs -trait Trait { - type Item; -} - -fn test() where T: Trait, U: Trait { - let x: T::Item = no_matter<|>; -} -"#, - ); - // this is a legitimate cycle - assert_eq!(t, "{unknown}"); -} - -fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { - let file = db.parse(pos.file_id).ok().unwrap(); - let expr = algo::find_node_at_offset::(file.syntax(), pos.offset).unwrap(); - let analyzer = - SourceAnalyzer::new(db, Source::new(pos.file_id.into(), expr.syntax()), Some(pos.offset)); - let ty = analyzer.type_of(db, &expr).unwrap(); - ty.display(db).to_string() -} - -fn type_at(content: &str) -> String { - let (db, file_pos) = TestDB::with_position(content); - type_at_pos(&db, file_pos) -} - -fn infer(content: &str) -> String { - let (db, file_id) = TestDB::with_single_file(content); - let source_file = db.parse(file_id).ok().unwrap(); - - let mut acc = String::new(); - - let mut infer_def = |inference_result: Arc, - body_source_map: Arc| { - let mut types = Vec::new(); - - for (pat, ty) in inference_result.type_of_pat.iter() { - let syntax_ptr = match body_source_map.pat_syntax(pat) { - Some(sp) => { - sp.map(|ast| ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr())) - } - None => continue, - }; - types.push((syntax_ptr, ty)); - } - - for (expr, ty) in inference_result.type_of_expr.iter() { - let syntax_ptr = match body_source_map.expr_syntax(expr) { - Some(sp) => { - sp.map(|ast| ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr())) - } - None => continue, - }; - types.push((syntax_ptr, ty)); - } - - // sort ranges for consistency - types.sort_by_key(|(src_ptr, _)| { - (src_ptr.value.range().start(), src_ptr.value.range().end()) - }); - for (src_ptr, ty) in &types { - let node = src_ptr.value.to_node(&src_ptr.file_syntax(&db)); - - let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.clone()) { - (self_param.self_kw_token().text_range(), "self".to_string()) - } else { - (src_ptr.value.range(), node.text().to_string().replace("\n", " ")) - }; - let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" }; - write!( - acc, - "{}{} '{}': {}\n", - macro_prefix, - range, - ellipsize(text, 15), - ty.display(&db) - ) - .unwrap(); - } - }; - - let mut analyzed = FxHashSet::default(); - for node in source_file.syntax().descendants() { - if node.kind() == FN_DEF || node.kind() == CONST_DEF || node.kind() == STATIC_DEF { - let analyzer = SourceAnalyzer::new(&db, Source::new(file_id.into(), &node), None); - if analyzed.insert(analyzer.analyzed_declaration()) { - infer_def(analyzer.inference_result(), analyzer.body_source_map()); - } - } - } - - acc.truncate(acc.trim_end().len()); - acc -} - -fn ellipsize(mut text: String, max_len: usize) -> String { - if text.len() <= max_len { - return text; - } - let ellipsis = "..."; - let e_len = ellipsis.len(); - let mut prefix_len = (max_len - e_len) / 2; - while !text.is_char_boundary(prefix_len) { - prefix_len += 1; - } - let mut suffix_len = max_len - e_len - prefix_len; - while !text.is_char_boundary(text.len() - suffix_len) { - suffix_len += 1; - } - text.replace_range(prefix_len..text.len() - suffix_len, ellipsis); - text -} - -#[test] -fn typing_whitespace_inside_a_function_should_not_invalidate_types() { - let (mut db, pos) = TestDB::with_position( - " - //- /lib.rs - fn foo() -> i32 { - <|>1 + 1 - } - ", - ); - { - let file = db.parse(pos.file_id).ok().unwrap(); - let node = file.syntax().token_at_offset(pos.offset).right_biased().unwrap().parent(); - let events = db.log_executed(|| { - SourceAnalyzer::new(&db, Source::new(pos.file_id.into(), &node), None); - }); - assert!(format!("{:?}", events).contains("infer")) - } - - let new_text = " - fn foo() -> i32 { - 1 - + - 1 - } - " - .to_string(); - - db.query_mut(ra_db::FileTextQuery).set(pos.file_id, Arc::new(new_text)); - - { - let file = db.parse(pos.file_id).ok().unwrap(); - let node = file.syntax().token_at_offset(pos.offset).right_biased().unwrap().parent(); - let events = db.log_executed(|| { - SourceAnalyzer::new(&db, Source::new(pos.file_id.into(), &node), None); - }); - assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events) - } -} - -#[test] -fn no_such_field_diagnostics() { - let diagnostics = TestDB::with_files( - r" - //- /lib.rs - struct S { foo: i32, bar: () } - impl S { - fn new() -> S { - S { - foo: 92, - baz: 62, - } - } - } - ", - ) - .diagnostics(); - - assert_snapshot!(diagnostics, @r###" - "baz: 62": no such field - "{\n foo: 92,\n baz: 62,\n }": Missing structure fields: - - bar - "### - ); -} - -#[test] -fn infer_builtin_macros_line() { - assert_snapshot!( - infer(r#" -#[rustc_builtin_macro] -macro_rules! line {() => {}} - -fn main() { - let x = line!(); -} -"#), - @r###" - ![0; 1) '6': i32 - [64; 88) '{ ...!(); }': () - [74; 75) 'x': i32 - "### - ); -} - -#[test] -fn infer_builtin_macros_file() { - assert_snapshot!( - infer(r#" -#[rustc_builtin_macro] -macro_rules! file {() => {}} - -fn main() { - let x = file!(); -} -"#), - @r###" - ![0; 2) '""': &str - [64; 88) '{ ...!(); }': () - [74; 75) 'x': &str - "### - ); -} - -#[test] -fn infer_builtin_macros_column() { - assert_snapshot!( - infer(r#" -#[rustc_builtin_macro] -macro_rules! column {() => {}} - -fn main() { - let x = column!(); -} -"#), - @r###" - ![0; 2) '13': i32 - [66; 92) '{ ...!(); }': () - [76; 77) 'x': i32 - "### - ); -} diff --git a/crates/ra_hir/src/ty/tests/coercion.rs b/crates/ra_hir/src/ty/tests/coercion.rs deleted file mode 100644 index 1530fcc63..000000000 --- a/crates/ra_hir/src/ty/tests/coercion.rs +++ /dev/null @@ -1,369 +0,0 @@ -use insta::assert_snapshot; -use test_utils::covers; - -// Infer with some common definitions and impls. -fn infer(source: &str) -> String { - let defs = r#" - #[lang = "sized"] - pub trait Sized {} - #[lang = "unsize"] - pub trait Unsize {} - #[lang = "coerce_unsized"] - pub trait CoerceUnsized {} - - impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} - impl, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} - "#; - - // Append to the end to keep positions unchanged. - super::infer(&format!("{}{}", source, defs)) -} - -#[test] -fn infer_block_expr_type_mismatch() { - assert_snapshot!( - infer(r#" -fn test() { - let a: i32 = { 1i64 }; -} -"#), - @r###" - [11; 41) '{ ...4 }; }': () - [21; 22) 'a': i32 - [30; 38) '{ 1i64 }': i64 - [32; 36) '1i64': i64 - "###); -} - -#[test] -fn coerce_places() { - assert_snapshot!( - infer(r#" -struct S { a: T } - -fn f(_: &[T]) -> T { loop {} } -fn g(_: S<&[T]>) -> T { loop {} } - -fn gen() -> *mut [T; 2] { loop {} } -fn test1() -> *mut [U] { - gen() -} - -fn test2() { - let arr: &[u8; 1] = &[1]; - - let a: &[_] = arr; - let b = f(arr); - let c: &[_] = { arr }; - let d = g(S { a: arr }); - let e: [&[_]; 1] = [arr]; - let f: [&[_]; 2] = [arr; 2]; - let g: (&[_], &[_]) = (arr, arr); -} -"#), - @r###" - [31; 32) '_': &[T] - [45; 56) '{ loop {} }': T - [47; 54) 'loop {}': ! - [52; 54) '{}': () - [65; 66) '_': S<&[T]> - [82; 93) '{ loop {} }': T - [84; 91) 'loop {}': ! - [89; 91) '{}': () - [122; 133) '{ loop {} }': *mut [T;_] - [124; 131) 'loop {}': ! - [129; 131) '{}': () - [160; 173) '{ gen() }': *mut [U] - [166; 169) 'gen': fn gen() -> *mut [T;_] - [166; 171) 'gen()': *mut [U;_] - [186; 420) '{ ...rr); }': () - [196; 199) 'arr': &[u8;_] - [212; 216) '&[1]': &[u8;_] - [213; 216) '[1]': [u8;_] - [214; 215) '1': u8 - [227; 228) 'a': &[u8] - [237; 240) 'arr': &[u8;_] - [250; 251) 'b': u8 - [254; 255) 'f': fn f(&[T]) -> T - [254; 260) 'f(arr)': u8 - [256; 259) 'arr': &[u8;_] - [270; 271) 'c': &[u8] - [280; 287) '{ arr }': &[u8] - [282; 285) 'arr': &[u8;_] - [297; 298) 'd': u8 - [301; 302) 'g': fn g(S<&[T]>) -> T - [301; 316) 'g(S { a: arr })': u8 - [303; 315) 'S { a: arr }': S<&[u8]> - [310; 313) 'arr': &[u8;_] - [326; 327) 'e': [&[u8];_] - [341; 346) '[arr]': [&[u8];_] - [342; 345) 'arr': &[u8;_] - [356; 357) 'f': [&[u8];_] - [371; 379) '[arr; 2]': [&[u8];_] - [372; 375) 'arr': &[u8;_] - [377; 378) '2': usize - [389; 390) 'g': (&[u8], &[u8]) - [407; 417) '(arr, arr)': (&[u8], &[u8]) - [408; 411) 'arr': &[u8;_] - [413; 416) 'arr': &[u8;_] - "### - ); -} - -#[test] -fn infer_let_stmt_coerce() { - assert_snapshot!( - infer(r#" -fn test() { - let x: &[i32] = &[1]; -} -"#), - @r###" - [11; 40) '{ ...[1]; }': () - [21; 22) 'x': &[i32] - [33; 37) '&[1]': &[i32;_] - [34; 37) '[1]': [i32;_] - [35; 36) '1': i32 - "###); -} - -#[test] -fn infer_custom_coerce_unsized() { - assert_snapshot!( - infer(r#" -struct A(*const T); -struct B(*const T); -struct C { inner: *const T } - -impl, U: ?Sized> CoerceUnsized> for B {} -impl, U: ?Sized> CoerceUnsized> for C {} - -fn foo1(x: A<[T]>) -> A<[T]> { x } -fn foo2(x: B<[T]>) -> B<[T]> { x } -fn foo3(x: C<[T]>) -> C<[T]> { x } - -fn test(a: A<[u8; 2]>, b: B<[u8; 2]>, c: C<[u8; 2]>) { - let d = foo1(a); - let e = foo2(b); - let f = foo3(c); -} -"#), - @r###" - [258; 259) 'x': A<[T]> - [279; 284) '{ x }': A<[T]> - [281; 282) 'x': A<[T]> - [296; 297) 'x': B<[T]> - [317; 322) '{ x }': B<[T]> - [319; 320) 'x': B<[T]> - [334; 335) 'x': C<[T]> - [355; 360) '{ x }': C<[T]> - [357; 358) 'x': C<[T]> - [370; 371) 'a': A<[u8;_]> - [385; 386) 'b': B<[u8;_]> - [400; 401) 'c': C<[u8;_]> - [415; 481) '{ ...(c); }': () - [425; 426) 'd': A<[{unknown}]> - [429; 433) 'foo1': fn foo1<{unknown}>(A<[T]>) -> A<[T]> - [429; 436) 'foo1(a)': A<[{unknown}]> - [434; 435) 'a': A<[u8;_]> - [446; 447) 'e': B<[u8]> - [450; 454) 'foo2': fn foo2(B<[T]>) -> B<[T]> - [450; 457) 'foo2(b)': B<[u8]> - [455; 456) 'b': B<[u8;_]> - [467; 468) 'f': C<[u8]> - [471; 475) 'foo3': fn foo3(C<[T]>) -> C<[T]> - [471; 478) 'foo3(c)': C<[u8]> - [476; 477) 'c': C<[u8;_]> - "### - ); -} - -#[test] -fn infer_if_coerce() { - assert_snapshot!( - infer(r#" -fn foo(x: &[T]) -> &[T] { loop {} } -fn test() { - let x = if true { - foo(&[1]) - } else { - &[1] - }; -} -"#), - @r###" - [11; 12) 'x': &[T] - [28; 39) '{ loop {} }': &[T] - [30; 37) 'loop {}': ! - [35; 37) '{}': () - [50; 126) '{ ... }; }': () - [60; 61) 'x': &[i32] - [64; 123) 'if tru... }': &[i32] - [67; 71) 'true': bool - [72; 97) '{ ... }': &[i32] - [82; 85) 'foo': fn foo(&[T]) -> &[T] - [82; 91) 'foo(&[1])': &[i32] - [86; 90) '&[1]': &[i32;_] - [87; 90) '[1]': [i32;_] - [88; 89) '1': i32 - [103; 123) '{ ... }': &[i32;_] - [113; 117) '&[1]': &[i32;_] - [114; 117) '[1]': [i32;_] - [115; 116) '1': i32 - "### - ); -} - -#[test] -fn infer_if_else_coerce() { - assert_snapshot!( - infer(r#" -fn foo(x: &[T]) -> &[T] { loop {} } -fn test() { - let x = if true { - &[1] - } else { - foo(&[1]) - }; -} -"#), - @r###" - [11; 12) 'x': &[T] - [28; 39) '{ loop {} }': &[T] - [30; 37) 'loop {}': ! - [35; 37) '{}': () - [50; 126) '{ ... }; }': () - [60; 61) 'x': &[i32] - [64; 123) 'if tru... }': &[i32] - [67; 71) 'true': bool - [72; 92) '{ ... }': &[i32;_] - [82; 86) '&[1]': &[i32;_] - [83; 86) '[1]': [i32;_] - [84; 85) '1': i32 - [98; 123) '{ ... }': &[i32] - [108; 111) 'foo': fn foo(&[T]) -> &[T] - [108; 117) 'foo(&[1])': &[i32] - [112; 116) '&[1]': &[i32;_] - [113; 116) '[1]': [i32;_] - [114; 115) '1': i32 - "### - ); -} - -#[test] -fn infer_match_first_coerce() { - assert_snapshot!( - infer(r#" -fn foo(x: &[T]) -> &[T] { loop {} } -fn test(i: i32) { - let x = match i { - 2 => foo(&[2]), - 1 => &[1], - _ => &[3], - }; -} -"#), - @r###" - [11; 12) 'x': &[T] - [28; 39) '{ loop {} }': &[T] - [30; 37) 'loop {}': ! - [35; 37) '{}': () - [48; 49) 'i': i32 - [56; 150) '{ ... }; }': () - [66; 67) 'x': &[i32] - [70; 147) 'match ... }': &[i32] - [76; 77) 'i': i32 - [88; 89) '2': i32 - [93; 96) 'foo': fn foo(&[T]) -> &[T] - [93; 102) 'foo(&[2])': &[i32] - [97; 101) '&[2]': &[i32;_] - [98; 101) '[2]': [i32;_] - [99; 100) '2': i32 - [112; 113) '1': i32 - [117; 121) '&[1]': &[i32;_] - [118; 121) '[1]': [i32;_] - [119; 120) '1': i32 - [131; 132) '_': i32 - [136; 140) '&[3]': &[i32;_] - [137; 140) '[3]': [i32;_] - [138; 139) '3': i32 - "### - ); -} - -#[test] -fn infer_match_second_coerce() { - assert_snapshot!( - infer(r#" -fn foo(x: &[T]) -> &[T] { loop {} } -fn test(i: i32) { - let x = match i { - 1 => &[1], - 2 => foo(&[2]), - _ => &[3], - }; -} -"#), - @r###" - [11; 12) 'x': &[T] - [28; 39) '{ loop {} }': &[T] - [30; 37) 'loop {}': ! - [35; 37) '{}': () - [48; 49) 'i': i32 - [56; 150) '{ ... }; }': () - [66; 67) 'x': &[i32] - [70; 147) 'match ... }': &[i32] - [76; 77) 'i': i32 - [88; 89) '1': i32 - [93; 97) '&[1]': &[i32;_] - [94; 97) '[1]': [i32;_] - [95; 96) '1': i32 - [107; 108) '2': i32 - [112; 115) 'foo': fn foo(&[T]) -> &[T] - [112; 121) 'foo(&[2])': &[i32] - [116; 120) '&[2]': &[i32;_] - [117; 120) '[2]': [i32;_] - [118; 119) '2': i32 - [131; 132) '_': i32 - [136; 140) '&[3]': &[i32;_] - [137; 140) '[3]': [i32;_] - [138; 139) '3': i32 - "### - ); -} - -#[test] -fn coerce_merge_one_by_one1() { - covers!(coerce_merge_fail_fallback); - - assert_snapshot!( - infer(r#" -fn test() { - let t = &mut 1; - let x = match 1 { - 1 => t as *mut i32, - 2 => t as &i32, - _ => t as *const i32, - }; -} -"#), - @r###" - [11; 145) '{ ... }; }': () - [21; 22) 't': &mut i32 - [25; 31) '&mut 1': &mut i32 - [30; 31) '1': i32 - [41; 42) 'x': *const i32 - [45; 142) 'match ... }': *const i32 - [51; 52) '1': i32 - [63; 64) '1': i32 - [68; 69) 't': &mut i32 - [68; 81) 't as *mut i32': *mut i32 - [91; 92) '2': i32 - [96; 97) 't': &mut i32 - [96; 105) 't as &i32': &i32 - [115; 116) '_': i32 - [120; 121) 't': &mut i32 - [120; 135) 't as *const i32': *const i32 - "### - ); -} diff --git a/crates/ra_hir/src/ty/tests/never_type.rs b/crates/ra_hir/src/ty/tests/never_type.rs deleted file mode 100644 index c202f545a..000000000 --- a/crates/ra_hir/src/ty/tests/never_type.rs +++ /dev/null @@ -1,246 +0,0 @@ -use super::type_at; - -#[test] -fn infer_never1() { - let t = type_at( - r#" -//- /main.rs -fn test() { - let t = return; - t<|>; -} -"#, - ); - assert_eq!(t, "!"); -} - -#[test] -fn infer_never2() { - let t = type_at( - r#" -//- /main.rs -fn gen() -> T { loop {} } - -fn test() { - let a = gen(); - if false { a } else { loop {} }; - a<|>; -} -"#, - ); - assert_eq!(t, "!"); -} - -#[test] -fn infer_never3() { - let t = type_at( - r#" -//- /main.rs -fn gen() -> T { loop {} } - -fn test() { - let a = gen(); - if false { loop {} } else { a }; - a<|>; -} -"#, - ); - assert_eq!(t, "!"); -} - -#[test] -fn never_type_in_generic_args() { - let t = type_at( - r#" -//- /main.rs -enum Option { None, Some(T) } - -fn test() { - let a = if true { Option::None } else { Option::Some(return) }; - a<|>; -} -"#, - ); - assert_eq!(t, "Option"); -} - -#[test] -fn never_type_can_be_reinferred1() { - let t = type_at( - r#" -//- /main.rs -fn gen() -> T { loop {} } - -fn test() { - let a = gen(); - if false { loop {} } else { a }; - a<|>; - if false { a }; -} -"#, - ); - assert_eq!(t, "()"); -} - -#[test] -fn never_type_can_be_reinferred2() { - let t = type_at( - r#" -//- /main.rs -enum Option { None, Some(T) } - -fn test() { - let a = if true { Option::None } else { Option::Some(return) }; - a<|>; - match 42 { - 42 => a, - _ => Option::Some(42), - }; -} -"#, - ); - assert_eq!(t, "Option"); -} -#[test] -fn never_type_can_be_reinferred3() { - let t = type_at( - r#" -//- /main.rs -enum Option { None, Some(T) } - -fn test() { - let a = if true { Option::None } else { Option::Some(return) }; - a<|>; - match 42 { - 42 => a, - _ => Option::Some("str"), - }; -} -"#, - ); - assert_eq!(t, "Option<&str>"); -} - -#[test] -fn match_no_arm() { - let t = type_at( - r#" -//- /main.rs -enum Void {} - -fn test(a: Void) { - let t = match a {}; - t<|>; -} -"#, - ); - assert_eq!(t, "!"); -} - -#[test] -fn if_never() { - let t = type_at( - r#" -//- /main.rs -fn test() { - let i = if true { - loop {} - } else { - 3.0 - }; - i<|>; -} -"#, - ); - assert_eq!(t, "f64"); -} - -#[test] -fn if_else_never() { - let t = type_at( - r#" -//- /main.rs -fn test(input: bool) { - let i = if input { - 2.0 - } else { - return - }; - i<|>; -} -"#, - ); - assert_eq!(t, "f64"); -} - -#[test] -fn match_first_arm_never() { - let t = type_at( - r#" -//- /main.rs -fn test(a: i32) { - let i = match a { - 1 => return, - 2 => 2.0, - 3 => loop {}, - _ => 3.0, - }; - i<|>; -} -"#, - ); - assert_eq!(t, "f64"); -} - -#[test] -fn match_second_arm_never() { - let t = type_at( - r#" -//- /main.rs -fn test(a: i32) { - let i = match a { - 1 => 3.0, - 2 => loop {}, - 3 => 3.0, - _ => return, - }; - i<|>; -} -"#, - ); - assert_eq!(t, "f64"); -} - -#[test] -fn match_all_arms_never() { - let t = type_at( - r#" -//- /main.rs -fn test(a: i32) { - let i = match a { - 2 => return, - _ => loop {}, - }; - i<|>; -} -"#, - ); - assert_eq!(t, "!"); -} - -#[test] -fn match_no_never_arms() { - let t = type_at( - r#" -//- /main.rs -fn test(a: i32) { - let i = match a { - 2 => 2.0, - _ => 3.0, - }; - i<|>; -} -"#, - ); - assert_eq!(t, "f64"); -} diff --git a/crates/ra_hir/src/ty/traits.rs b/crates/ra_hir/src/ty/traits.rs deleted file mode 100644 index 76189a60b..000000000 --- a/crates/ra_hir/src/ty/traits.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! Trait solving using Chalk. -use std::sync::{Arc, Mutex}; - -use chalk_ir::{cast::Cast, family::ChalkIr}; -use hir_def::{expr::ExprId, DefWithBodyId, ImplId, TraitId, TypeAliasId}; -use log::debug; -use ra_db::{impl_intern_key, salsa, CrateId}; -use ra_prof::profile; -use rustc_hash::FxHashSet; - -use crate::db::HirDatabase; - -use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; - -use self::chalk::{from_chalk, ToChalk}; - -pub(crate) mod chalk; - -#[derive(Debug, Clone)] -pub struct TraitSolver { - krate: CrateId, - inner: Arc>>, -} - -/// We need eq for salsa -impl PartialEq for TraitSolver { - fn eq(&self, other: &TraitSolver) -> bool { - Arc::ptr_eq(&self.inner, &other.inner) - } -} - -impl Eq for TraitSolver {} - -impl TraitSolver { - fn solve( - &self, - db: &impl HirDatabase, - goal: &chalk_ir::UCanonical>>, - ) -> Option> { - let context = ChalkContext { db, krate: self.krate }; - debug!("solve goal: {:?}", goal); - let mut solver = match self.inner.lock() { - Ok(it) => it, - // Our cancellation works via unwinding, but, as chalk is not - // panic-safe, we need to make sure to propagate the cancellation. - // Ideally, we should also make chalk panic-safe. - Err(_) => ra_db::Canceled::throw(), - }; - let solution = solver.solve(&context, goal); - debug!("solve({:?}) => {:?}", goal, solution); - solution - } -} - -/// This controls the maximum size of types Chalk considers. If we set this too -/// high, we can run into slow edge cases; if we set it too low, Chalk won't -/// find some solutions. -const CHALK_SOLVER_MAX_SIZE: usize = 4; - -#[derive(Debug, Copy, Clone)] -struct ChalkContext<'a, DB> { - db: &'a DB, - krate: CrateId, -} - -pub(crate) fn trait_solver_query( - db: &(impl HirDatabase + salsa::Database), - krate: CrateId, -) -> TraitSolver { - db.salsa_runtime().report_untracked_read(); - // krate parameter is just so we cache a unique solver per crate - let solver_choice = chalk_solve::SolverChoice::SLG { max_size: CHALK_SOLVER_MAX_SIZE }; - debug!("Creating new solver for crate {:?}", krate); - TraitSolver { krate, inner: Arc::new(Mutex::new(solver_choice.into_solver())) } -} - -/// Collects impls for the given trait in the whole dependency tree of `krate`. -pub(crate) fn impls_for_trait_query( - db: &impl HirDatabase, - krate: CrateId, - trait_: TraitId, -) -> Arc<[ImplId]> { - let mut impls = FxHashSet::default(); - // We call the query recursively here. On the one hand, this means we can - // reuse results from queries for different crates; on the other hand, this - // will only ever get called for a few crates near the root of the tree (the - // ones the user is editing), so this may actually be a waste of memory. I'm - // doing it like this mainly for simplicity for now. - for dep in db.crate_graph().dependencies(krate) { - impls.extend(db.impls_for_trait(dep.crate_id, trait_).iter()); - } - let crate_impl_blocks = db.impls_in_crate(krate); - impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_)); - impls.into_iter().collect() -} - -/// A set of clauses that we assume to be true. E.g. if we are inside this function: -/// ```rust -/// fn foo(t: T) {} -/// ``` -/// we assume that `T: Default`. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct TraitEnvironment { - pub predicates: Vec, -} - -impl TraitEnvironment { - /// Returns trait refs with the given self type which are supposed to hold - /// in this trait env. E.g. if we are in `foo()`, this will - /// find that `T: SomeTrait` if we call it for `T`. - pub(crate) fn trait_predicates_for_self_ty<'a>( - &'a self, - ty: &'a Ty, - ) -> impl Iterator + 'a { - self.predicates.iter().filter_map(move |pred| match pred { - GenericPredicate::Implemented(tr) if tr.self_ty() == ty => Some(tr), - _ => None, - }) - } -} - -/// Something (usually a goal), along with an environment. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct InEnvironment { - pub environment: Arc, - pub value: T, -} - -impl InEnvironment { - pub fn new(environment: Arc, value: T) -> InEnvironment { - InEnvironment { environment, value } - } -} - -/// Something that needs to be proven (by Chalk) during type checking, e.g. that -/// a certain type implements a certain trait. Proving the Obligation might -/// result in additional information about inference variables. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum Obligation { - /// Prove that a certain type implements a trait (the type is the `Self` type - /// parameter to the `TraitRef`). - Trait(TraitRef), - Projection(ProjectionPredicate), -} - -impl Obligation { - pub fn from_predicate(predicate: GenericPredicate) -> Option { - match predicate { - GenericPredicate::Implemented(trait_ref) => Some(Obligation::Trait(trait_ref)), - GenericPredicate::Projection(projection_pred) => { - Some(Obligation::Projection(projection_pred)) - } - GenericPredicate::Error => None, - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct ProjectionPredicate { - pub projection_ty: ProjectionTy, - pub ty: Ty, -} - -impl TypeWalk for ProjectionPredicate { - fn walk(&self, f: &mut impl FnMut(&Ty)) { - self.projection_ty.walk(f); - self.ty.walk(f); - } - - fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { - self.projection_ty.walk_mut_binders(f, binders); - self.ty.walk_mut_binders(f, binders); - } -} - -/// Solve a trait goal using Chalk. -pub(crate) fn trait_solve_query( - db: &impl HirDatabase, - krate: CrateId, - goal: Canonical>, -) -> Option { - let _p = profile("trait_solve_query"); - debug!("trait_solve_query({})", goal.value.value.display(db)); - - if let Obligation::Projection(pred) = &goal.value.value { - if let Ty::Bound(_) = &pred.projection_ty.parameters[0] { - // Hack: don't ask Chalk to normalize with an unknown self type, it'll say that's impossible - return Some(Solution::Ambig(Guidance::Unknown)); - } - } - - let canonical = goal.to_chalk(db).cast(); - - // We currently don't deal with universes (I think / hope they're not yet - // relevant for our use cases?) - let u_canonical = chalk_ir::UCanonical { canonical, universes: 1 }; - let solution = db.trait_solver(krate).solve(db, &u_canonical); - solution.map(|solution| solution_from_chalk(db, solution)) -} - -fn solution_from_chalk( - db: &impl HirDatabase, - solution: chalk_solve::Solution, -) -> Solution { - let convert_subst = |subst: chalk_ir::Canonical>| { - let value = subst - .value - .parameters - .into_iter() - .map(|p| { - let ty = match p { - chalk_ir::Parameter(chalk_ir::ParameterKind::Ty(ty)) => from_chalk(db, ty), - chalk_ir::Parameter(chalk_ir::ParameterKind::Lifetime(_)) => unimplemented!(), - }; - ty - }) - .collect(); - let result = Canonical { value, num_vars: subst.binders.len() }; - SolutionVariables(result) - }; - match solution { - chalk_solve::Solution::Unique(constr_subst) => { - let subst = chalk_ir::Canonical { - value: constr_subst.value.subst, - binders: constr_subst.binders, - }; - Solution::Unique(convert_subst(subst)) - } - chalk_solve::Solution::Ambig(chalk_solve::Guidance::Definite(subst)) => { - Solution::Ambig(Guidance::Definite(convert_subst(subst))) - } - chalk_solve::Solution::Ambig(chalk_solve::Guidance::Suggested(subst)) => { - Solution::Ambig(Guidance::Suggested(convert_subst(subst))) - } - chalk_solve::Solution::Ambig(chalk_solve::Guidance::Unknown) => { - Solution::Ambig(Guidance::Unknown) - } - } -} - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct SolutionVariables(pub Canonical>); - -#[derive(Clone, Debug, PartialEq, Eq)] -/// A (possible) solution for a proposed goal. -pub enum Solution { - /// The goal indeed holds, and there is a unique value for all existential - /// variables. - Unique(SolutionVariables), - - /// The goal may be provable in multiple ways, but regardless we may have some guidance - /// for type inference. In this case, we don't return any lifetime - /// constraints, since we have not "committed" to any particular solution - /// yet. - Ambig(Guidance), -} - -#[derive(Clone, Debug, PartialEq, Eq)] -/// When a goal holds ambiguously (e.g., because there are multiple possible -/// solutions), we issue a set of *guidance* back to type inference. -pub enum Guidance { - /// The existential variables *must* have the given values if the goal is - /// ever to hold, but that alone isn't enough to guarantee the goal will - /// actually hold. - Definite(SolutionVariables), - - /// There are multiple plausible values for the existentials, but the ones - /// here are suggested as the preferred choice heuristically. These should - /// be used for inference fallback only. - Suggested(SolutionVariables), - - /// There's no useful information to feed back to type inference - Unknown, -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum FnTrait { - FnOnce, - FnMut, - Fn, -} - -impl FnTrait { - fn lang_item_name(self) -> &'static str { - match self { - FnTrait::FnOnce => "fn_once", - FnTrait::FnMut => "fn_mut", - FnTrait::Fn => "fn", - } - } -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ClosureFnTraitImplData { - def: DefWithBodyId, - expr: ExprId, - fn_trait: FnTrait, -} - -/// An impl. Usually this comes from an impl block, but some built-in types get -/// synthetic impls. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Impl { - /// A normal impl from an impl block. - ImplBlock(ImplId), - /// Closure types implement the Fn traits synthetically. - ClosureFnTraitImpl(ClosureFnTraitImplData), -} -/// This exists just for Chalk, because our ImplIds are only unique per module. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct GlobalImplId(salsa::InternId); -impl_intern_key!(GlobalImplId); - -/// An associated type value. Usually this comes from a `type` declaration -/// inside an impl block, but for built-in impls we have to synthesize it. -/// (We only need this because Chalk wants a unique ID for each of these.) -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum AssocTyValue { - /// A normal assoc type value from an impl block. - TypeAlias(TypeAliasId), - /// The output type of the Fn trait implementation. - ClosureFnTraitImplOutput(ClosureFnTraitImplData), -} -/// This exists just for Chalk, because it needs a unique ID for each associated -/// type value in an impl (even synthetic ones). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct AssocTyValueId(salsa::InternId); -impl_intern_key!(AssocTyValueId); diff --git a/crates/ra_hir/src/ty/traits/chalk.rs b/crates/ra_hir/src/ty/traits/chalk.rs deleted file mode 100644 index 67ac5422c..000000000 --- a/crates/ra_hir/src/ty/traits/chalk.rs +++ /dev/null @@ -1,906 +0,0 @@ -//! Conversion code from/to Chalk. -use std::sync::Arc; - -use log::debug; - -use chalk_ir::{ - cast::Cast, family::ChalkIr, Identifier, Parameter, PlaceholderIndex, TypeId, TypeKindId, - TypeName, UniverseIndex, -}; -use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; -use ra_db::CrateId; - -use hir_def::{ - lang_item::LangItemTarget, resolver::HasResolver, AssocItemId, AstItemDef, ContainerId, - GenericDefId, ImplId, Lookup, TraitId, TypeAliasId, -}; -use hir_expand::name; - -use ra_db::salsa::{InternId, InternKey}; - -use super::{AssocTyValue, Canonical, ChalkContext, Impl, Obligation}; -use crate::{ - db::HirDatabase, - ty::display::HirDisplay, - ty::{ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, -}; - -/// This represents a trait whose name we could not resolve. -const UNKNOWN_TRAIT: chalk_ir::TraitId = - chalk_ir::TraitId(chalk_ir::RawId { index: u32::max_value() }); - -pub(super) trait ToChalk { - type Chalk; - fn to_chalk(self, db: &impl HirDatabase) -> Self::Chalk; - fn from_chalk(db: &impl HirDatabase, chalk: Self::Chalk) -> Self; -} - -pub(super) fn from_chalk(db: &impl HirDatabase, chalk: ChalkT) -> T -where - T: ToChalk, -{ - T::from_chalk(db, chalk) -} - -impl ToChalk for Ty { - type Chalk = chalk_ir::Ty; - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Ty { - match self { - Ty::Apply(apply_ty) => { - let name = match apply_ty.ctor { - TypeCtor::AssociatedType(type_alias) => { - let type_id = type_alias.to_chalk(db); - TypeName::AssociatedType(type_id) - } - _ => { - // other TypeCtors get interned and turned into a chalk StructId - let struct_id = apply_ty.ctor.to_chalk(db); - TypeName::TypeKindId(struct_id.into()) - } - }; - let parameters = apply_ty.parameters.to_chalk(db); - chalk_ir::ApplicationTy { name, parameters }.cast().intern() - } - Ty::Projection(proj_ty) => { - let associated_ty_id = proj_ty.associated_ty.to_chalk(db); - let parameters = proj_ty.parameters.to_chalk(db); - chalk_ir::ProjectionTy { associated_ty_id, parameters }.cast().intern() - } - Ty::Param { idx, .. } => { - PlaceholderIndex { ui: UniverseIndex::ROOT, idx: idx as usize }.to_ty::() - } - Ty::Bound(idx) => chalk_ir::TyData::BoundVar(idx as usize).intern(), - Ty::Infer(_infer_ty) => panic!("uncanonicalized infer ty"), - Ty::Dyn(predicates) => { - let where_clauses = predicates.iter().cloned().map(|p| p.to_chalk(db)).collect(); - chalk_ir::TyData::Dyn(make_binders(where_clauses, 1)).intern() - } - Ty::Opaque(predicates) => { - let where_clauses = predicates.iter().cloned().map(|p| p.to_chalk(db)).collect(); - chalk_ir::TyData::Opaque(make_binders(where_clauses, 1)).intern() - } - Ty::Unknown => { - let parameters = Vec::new(); - let name = TypeName::Error; - chalk_ir::ApplicationTy { name, parameters }.cast().intern() - } - } - } - fn from_chalk(db: &impl HirDatabase, chalk: chalk_ir::Ty) -> Self { - match chalk.data().clone() { - chalk_ir::TyData::Apply(apply_ty) => { - // FIXME this is kind of hacky due to the fact that - // TypeName::Placeholder is a Ty::Param on our side - match apply_ty.name { - TypeName::TypeKindId(TypeKindId::StructId(struct_id)) => { - let ctor = from_chalk(db, struct_id); - let parameters = from_chalk(db, apply_ty.parameters); - Ty::Apply(ApplicationTy { ctor, parameters }) - } - TypeName::AssociatedType(type_id) => { - let ctor = TypeCtor::AssociatedType(from_chalk(db, type_id)); - let parameters = from_chalk(db, apply_ty.parameters); - Ty::Apply(ApplicationTy { ctor, parameters }) - } - TypeName::Error => Ty::Unknown, - // FIXME handle TypeKindId::Trait/Type here - TypeName::TypeKindId(_) => unimplemented!(), - TypeName::Placeholder(idx) => { - assert_eq!(idx.ui, UniverseIndex::ROOT); - Ty::Param { idx: idx.idx as u32, name: crate::Name::missing() } - } - } - } - chalk_ir::TyData::Projection(proj) => { - let associated_ty = from_chalk(db, proj.associated_ty_id); - let parameters = from_chalk(db, proj.parameters); - Ty::Projection(ProjectionTy { associated_ty, parameters }) - } - chalk_ir::TyData::ForAll(_) => unimplemented!(), - chalk_ir::TyData::BoundVar(idx) => Ty::Bound(idx as u32), - chalk_ir::TyData::InferenceVar(_iv) => Ty::Unknown, - chalk_ir::TyData::Dyn(where_clauses) => { - assert_eq!(where_clauses.binders.len(), 1); - let predicates = - where_clauses.value.into_iter().map(|c| from_chalk(db, c)).collect(); - Ty::Dyn(predicates) - } - chalk_ir::TyData::Opaque(where_clauses) => { - assert_eq!(where_clauses.binders.len(), 1); - let predicates = - where_clauses.value.into_iter().map(|c| from_chalk(db, c)).collect(); - Ty::Opaque(predicates) - } - } - } -} - -impl ToChalk for Substs { - type Chalk = Vec>; - - fn to_chalk(self, db: &impl HirDatabase) -> Vec> { - self.iter().map(|ty| ty.clone().to_chalk(db).cast()).collect() - } - - fn from_chalk(db: &impl HirDatabase, parameters: Vec>) -> Substs { - let tys = parameters - .into_iter() - .map(|p| match p { - chalk_ir::Parameter(chalk_ir::ParameterKind::Ty(ty)) => from_chalk(db, ty), - chalk_ir::Parameter(chalk_ir::ParameterKind::Lifetime(_)) => unimplemented!(), - }) - .collect(); - Substs(tys) - } -} - -impl ToChalk for TraitRef { - type Chalk = chalk_ir::TraitRef; - - fn to_chalk(self: TraitRef, db: &impl HirDatabase) -> chalk_ir::TraitRef { - let trait_id = self.trait_.to_chalk(db); - let parameters = self.substs.to_chalk(db); - chalk_ir::TraitRef { trait_id, parameters } - } - - fn from_chalk(db: &impl HirDatabase, trait_ref: chalk_ir::TraitRef) -> Self { - let trait_ = from_chalk(db, trait_ref.trait_id); - let substs = from_chalk(db, trait_ref.parameters); - TraitRef { trait_, substs } - } -} - -impl ToChalk for TraitId { - type Chalk = chalk_ir::TraitId; - - fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TraitId { - chalk_ir::TraitId(id_to_chalk(self)) - } - - fn from_chalk(_db: &impl HirDatabase, trait_id: chalk_ir::TraitId) -> TraitId { - id_from_chalk(trait_id.0) - } -} - -impl ToChalk for TypeCtor { - type Chalk = chalk_ir::StructId; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::StructId { - db.intern_type_ctor(self).into() - } - - fn from_chalk(db: &impl HirDatabase, struct_id: chalk_ir::StructId) -> TypeCtor { - db.lookup_intern_type_ctor(struct_id.into()) - } -} - -impl ToChalk for Impl { - type Chalk = chalk_ir::ImplId; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::ImplId { - db.intern_chalk_impl(self).into() - } - - fn from_chalk(db: &impl HirDatabase, impl_id: chalk_ir::ImplId) -> Impl { - db.lookup_intern_chalk_impl(impl_id.into()) - } -} - -impl ToChalk for TypeAliasId { - type Chalk = chalk_ir::TypeId; - - fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TypeId { - chalk_ir::TypeId(id_to_chalk(self)) - } - - fn from_chalk(_db: &impl HirDatabase, type_alias_id: chalk_ir::TypeId) -> TypeAliasId { - id_from_chalk(type_alias_id.0) - } -} - -impl ToChalk for AssocTyValue { - type Chalk = chalk_rust_ir::AssociatedTyValueId; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_rust_ir::AssociatedTyValueId { - db.intern_assoc_ty_value(self).into() - } - - fn from_chalk( - db: &impl HirDatabase, - assoc_ty_value_id: chalk_rust_ir::AssociatedTyValueId, - ) -> AssocTyValue { - db.lookup_intern_assoc_ty_value(assoc_ty_value_id.into()) - } -} - -impl ToChalk for GenericPredicate { - type Chalk = chalk_ir::QuantifiedWhereClause; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::QuantifiedWhereClause { - match self { - GenericPredicate::Implemented(trait_ref) => { - make_binders(chalk_ir::WhereClause::Implemented(trait_ref.to_chalk(db)), 0) - } - GenericPredicate::Projection(projection_pred) => make_binders( - chalk_ir::WhereClause::ProjectionEq(chalk_ir::ProjectionEq { - projection: projection_pred.projection_ty.to_chalk(db), - ty: projection_pred.ty.to_chalk(db), - }), - 0, - ), - GenericPredicate::Error => { - let impossible_trait_ref = chalk_ir::TraitRef { - trait_id: UNKNOWN_TRAIT, - parameters: vec![Ty::Unknown.to_chalk(db).cast()], - }; - make_binders(chalk_ir::WhereClause::Implemented(impossible_trait_ref), 0) - } - } - } - - fn from_chalk( - db: &impl HirDatabase, - where_clause: chalk_ir::QuantifiedWhereClause, - ) -> GenericPredicate { - match where_clause.value { - chalk_ir::WhereClause::Implemented(tr) => { - if tr.trait_id == UNKNOWN_TRAIT { - // FIXME we need an Error enum on the Chalk side to avoid this - return GenericPredicate::Error; - } - GenericPredicate::Implemented(from_chalk(db, tr)) - } - chalk_ir::WhereClause::ProjectionEq(projection_eq) => { - let projection_ty = from_chalk(db, projection_eq.projection); - let ty = from_chalk(db, projection_eq.ty); - GenericPredicate::Projection(super::ProjectionPredicate { projection_ty, ty }) - } - } - } -} - -impl ToChalk for ProjectionTy { - type Chalk = chalk_ir::ProjectionTy; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::ProjectionTy { - chalk_ir::ProjectionTy { - associated_ty_id: self.associated_ty.to_chalk(db), - parameters: self.parameters.to_chalk(db), - } - } - - fn from_chalk( - db: &impl HirDatabase, - projection_ty: chalk_ir::ProjectionTy, - ) -> ProjectionTy { - ProjectionTy { - associated_ty: from_chalk(db, projection_ty.associated_ty_id), - parameters: from_chalk(db, projection_ty.parameters), - } - } -} - -impl ToChalk for super::ProjectionPredicate { - type Chalk = chalk_ir::Normalize; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Normalize { - chalk_ir::Normalize { - projection: self.projection_ty.to_chalk(db), - ty: self.ty.to_chalk(db), - } - } - - fn from_chalk(_db: &impl HirDatabase, _normalize: chalk_ir::Normalize) -> Self { - unimplemented!() - } -} - -impl ToChalk for Obligation { - type Chalk = chalk_ir::DomainGoal; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::DomainGoal { - match self { - Obligation::Trait(tr) => tr.to_chalk(db).cast(), - Obligation::Projection(pr) => pr.to_chalk(db).cast(), - } - } - - fn from_chalk(_db: &impl HirDatabase, _goal: chalk_ir::DomainGoal) -> Self { - unimplemented!() - } -} - -impl ToChalk for Canonical -where - T: ToChalk, -{ - type Chalk = chalk_ir::Canonical; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Canonical { - let parameter = chalk_ir::ParameterKind::Ty(chalk_ir::UniverseIndex::ROOT); - let value = self.value.to_chalk(db); - let canonical = chalk_ir::Canonical { value, binders: vec![parameter; self.num_vars] }; - canonical - } - - fn from_chalk(db: &impl HirDatabase, canonical: chalk_ir::Canonical) -> Canonical { - Canonical { num_vars: canonical.binders.len(), value: from_chalk(db, canonical.value) } - } -} - -impl ToChalk for Arc { - type Chalk = chalk_ir::Environment; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Environment { - let mut clauses = Vec::new(); - for pred in &self.predicates { - if pred.is_error() { - // for env, we just ignore errors - continue; - } - let program_clause: chalk_ir::ProgramClause = pred.clone().to_chalk(db).cast(); - clauses.push(program_clause.into_from_env_clause()); - } - chalk_ir::Environment::new().add_clauses(clauses) - } - - fn from_chalk( - _db: &impl HirDatabase, - _env: chalk_ir::Environment, - ) -> Arc { - unimplemented!() - } -} - -impl ToChalk for super::InEnvironment -where - T::Chalk: chalk_ir::family::HasTypeFamily, -{ - type Chalk = chalk_ir::InEnvironment; - - fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::InEnvironment { - chalk_ir::InEnvironment { - environment: self.environment.to_chalk(db), - goal: self.value.to_chalk(db), - } - } - - fn from_chalk( - db: &impl HirDatabase, - in_env: chalk_ir::InEnvironment, - ) -> super::InEnvironment { - super::InEnvironment { - environment: from_chalk(db, in_env.environment), - value: from_chalk(db, in_env.goal), - } - } -} - -fn make_binders(value: T, num_vars: usize) -> chalk_ir::Binders { - chalk_ir::Binders { - value, - binders: std::iter::repeat(chalk_ir::ParameterKind::Ty(())).take(num_vars).collect(), - } -} - -fn convert_where_clauses( - db: &impl HirDatabase, - def: GenericDefId, - substs: &Substs, -) -> Vec> { - let generic_predicates = db.generic_predicates(def); - let mut result = Vec::with_capacity(generic_predicates.len()); - for pred in generic_predicates.iter() { - if pred.is_error() { - // HACK: Return just the single predicate (which is always false - // anyway), otherwise Chalk can easily get into slow situations - return vec![pred.clone().subst(substs).to_chalk(db)]; - } - result.push(pred.clone().subst(substs).to_chalk(db)); - } - result -} - -impl<'a, DB> chalk_solve::RustIrDatabase for ChalkContext<'a, DB> -where - DB: HirDatabase, -{ - fn associated_ty_data(&self, id: TypeId) -> Arc> { - self.db.associated_ty_data(id) - } - fn trait_datum(&self, trait_id: chalk_ir::TraitId) -> Arc> { - self.db.trait_datum(self.krate, trait_id) - } - fn struct_datum(&self, struct_id: chalk_ir::StructId) -> Arc> { - self.db.struct_datum(self.krate, struct_id) - } - fn impl_datum(&self, impl_id: chalk_ir::ImplId) -> Arc> { - self.db.impl_datum(self.krate, impl_id) - } - fn impls_for_trait( - &self, - trait_id: chalk_ir::TraitId, - parameters: &[Parameter], - ) -> Vec { - debug!("impls_for_trait {:?}", trait_id); - if trait_id == UNKNOWN_TRAIT { - return Vec::new(); - } - let trait_: TraitId = from_chalk(self.db, trait_id); - let mut result: Vec<_> = self - .db - .impls_for_trait(self.krate, trait_.into()) - .iter() - .copied() - .map(|it| Impl::ImplBlock(it.into())) - .map(|impl_| impl_.to_chalk(self.db)) - .collect(); - - let ty: Ty = from_chalk(self.db, parameters[0].assert_ty_ref().clone()); - if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Closure { def, expr }, .. }) = ty { - for &fn_trait in - [super::FnTrait::FnOnce, super::FnTrait::FnMut, super::FnTrait::Fn].iter() - { - if let Some(actual_trait) = get_fn_trait(self.db, self.krate, fn_trait) { - if trait_ == actual_trait { - let impl_ = super::ClosureFnTraitImplData { def, expr, fn_trait }; - result.push(Impl::ClosureFnTraitImpl(impl_).to_chalk(self.db)); - } - } - } - } - - debug!("impls_for_trait returned {} impls", result.len()); - result - } - fn impl_provided_for( - &self, - auto_trait_id: chalk_ir::TraitId, - struct_id: chalk_ir::StructId, - ) -> bool { - debug!("impl_provided_for {:?}, {:?}", auto_trait_id, struct_id); - false // FIXME - } - fn type_name(&self, _id: TypeKindId) -> Identifier { - unimplemented!() - } - fn associated_ty_value( - &self, - id: chalk_rust_ir::AssociatedTyValueId, - ) -> Arc> { - self.db.associated_ty_value(self.krate.into(), id) - } - fn custom_clauses(&self) -> Vec> { - vec![] - } - fn local_impls_to_coherence_check( - &self, - _trait_id: chalk_ir::TraitId, - ) -> Vec { - // We don't do coherence checking (yet) - unimplemented!() - } -} - -pub(crate) fn associated_ty_data_query( - db: &impl HirDatabase, - id: TypeId, -) -> Arc> { - debug!("associated_ty_data {:?}", id); - let type_alias: TypeAliasId = from_chalk(db, id); - let trait_ = match type_alias.lookup(db).container { - ContainerId::TraitId(t) => t, - _ => panic!("associated type not in trait"), - }; - let generic_params = db.generic_params(type_alias.into()); - let bound_data = chalk_rust_ir::AssociatedTyDatumBound { - // FIXME add bounds and where clauses - bounds: vec![], - where_clauses: vec![], - }; - let datum = AssociatedTyDatum { - trait_id: trait_.to_chalk(db), - id, - name: lalrpop_intern::intern(&db.type_alias_data(type_alias).name.to_string()), - binders: make_binders(bound_data, generic_params.count_params_including_parent()), - }; - Arc::new(datum) -} - -pub(crate) fn trait_datum_query( - db: &impl HirDatabase, - krate: CrateId, - trait_id: chalk_ir::TraitId, -) -> Arc> { - debug!("trait_datum {:?}", trait_id); - if trait_id == UNKNOWN_TRAIT { - let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses: Vec::new() }; - - let flags = chalk_rust_ir::TraitFlags { - auto: false, - marker: false, - upstream: true, - fundamental: false, - non_enumerable: true, - coinductive: false, - }; - return Arc::new(TraitDatum { - id: trait_id, - binders: make_binders(trait_datum_bound, 1), - flags, - associated_ty_ids: vec![], - }); - } - let trait_: TraitId = from_chalk(db, trait_id); - let trait_data = db.trait_data(trait_); - debug!("trait {:?} = {:?}", trait_id, trait_data.name); - let generic_params = db.generic_params(trait_.into()); - let bound_vars = Substs::bound_vars(&generic_params); - let flags = chalk_rust_ir::TraitFlags { - auto: trait_data.auto, - upstream: trait_.module(db).krate != krate, - non_enumerable: true, - coinductive: false, // only relevant for Chalk testing - // FIXME set these flags correctly - marker: false, - fundamental: false, - }; - let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); - let associated_ty_ids = - trait_data.associated_types().map(|type_alias| type_alias.to_chalk(db)).collect(); - let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses }; - let trait_datum = TraitDatum { - id: trait_id, - binders: make_binders(trait_datum_bound, bound_vars.len()), - flags, - associated_ty_ids, - }; - Arc::new(trait_datum) -} - -pub(crate) fn struct_datum_query( - db: &impl HirDatabase, - krate: CrateId, - struct_id: chalk_ir::StructId, -) -> Arc> { - debug!("struct_datum {:?}", struct_id); - let type_ctor: TypeCtor = from_chalk(db, struct_id); - debug!("struct {:?} = {:?}", struct_id, type_ctor); - let num_params = type_ctor.num_ty_params(db); - let upstream = type_ctor.krate(db) != Some(krate); - let where_clauses = type_ctor - .as_generic_def() - .map(|generic_def| { - let generic_params = db.generic_params(generic_def.into()); - let bound_vars = Substs::bound_vars(&generic_params); - convert_where_clauses(db, generic_def, &bound_vars) - }) - .unwrap_or_else(Vec::new); - let flags = chalk_rust_ir::StructFlags { - upstream, - // FIXME set fundamental flag correctly - fundamental: false, - }; - let struct_datum_bound = chalk_rust_ir::StructDatumBound { - fields: Vec::new(), // FIXME add fields (only relevant for auto traits) - where_clauses, - }; - let struct_datum = - StructDatum { id: struct_id, binders: make_binders(struct_datum_bound, num_params), flags }; - Arc::new(struct_datum) -} - -pub(crate) fn impl_datum_query( - db: &impl HirDatabase, - krate: CrateId, - impl_id: chalk_ir::ImplId, -) -> Arc> { - let _p = ra_prof::profile("impl_datum"); - debug!("impl_datum {:?}", impl_id); - let impl_: Impl = from_chalk(db, impl_id); - match impl_ { - Impl::ImplBlock(impl_block) => impl_block_datum(db, krate, impl_id, impl_block), - Impl::ClosureFnTraitImpl(data) => closure_fn_trait_impl_datum(db, krate, data), - } - .unwrap_or_else(invalid_impl_datum) -} - -fn impl_block_datum( - db: &impl HirDatabase, - krate: CrateId, - chalk_id: chalk_ir::ImplId, - impl_id: ImplId, -) -> Option>> { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - - // `CoerseUnsized` has one generic parameter for the target type. - let trait_ref = - TraitRef::from_hir(db, &resolver, impl_data.target_trait.as_ref()?, Some(target_ty))?; - - let generic_params = db.generic_params(impl_id.into()); - let bound_vars = Substs::bound_vars(&generic_params); - let trait_ref = trait_ref.subst(&bound_vars); - let trait_ = trait_ref.trait_; - let impl_type = if impl_id.module(db).krate == krate { - chalk_rust_ir::ImplType::Local - } else { - chalk_rust_ir::ImplType::External - }; - let where_clauses = convert_where_clauses(db, impl_id.into(), &bound_vars); - let negative = impl_data.is_negative; - debug!( - "impl {:?}: {}{} where {:?}", - chalk_id, - if negative { "!" } else { "" }, - trait_ref.display(db), - where_clauses - ); - let trait_ref = trait_ref.to_chalk(db); - - let polarity = if negative { - chalk_rust_ir::Polarity::Negative - } else { - chalk_rust_ir::Polarity::Positive - }; - - let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses }; - let trait_data = db.trait_data(trait_); - let associated_ty_value_ids = impl_data - .items - .iter() - .filter_map(|item| match item { - AssocItemId::TypeAliasId(type_alias) => Some(*type_alias), - _ => None, - }) - .filter(|&type_alias| { - // don't include associated types that don't exist in the trait - let name = &db.type_alias_data(type_alias).name; - trait_data.associated_type_by_name(name).is_some() - }) - .map(|type_alias| AssocTyValue::TypeAlias(type_alias).to_chalk(db)) - .collect(); - debug!("impl_datum: {:?}", impl_datum_bound); - let impl_datum = ImplDatum { - binders: make_binders(impl_datum_bound, bound_vars.len()), - impl_type, - polarity, - associated_ty_value_ids, - }; - Some(Arc::new(impl_datum)) -} - -fn invalid_impl_datum() -> Arc> { - let trait_ref = chalk_ir::TraitRef { - trait_id: UNKNOWN_TRAIT, - parameters: vec![chalk_ir::TyData::BoundVar(0).cast().intern().cast()], - }; - let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses: Vec::new() }; - let impl_datum = ImplDatum { - binders: make_binders(impl_datum_bound, 1), - impl_type: chalk_rust_ir::ImplType::External, - polarity: chalk_rust_ir::Polarity::Positive, - associated_ty_value_ids: Vec::new(), - }; - Arc::new(impl_datum) -} - -fn closure_fn_trait_impl_datum( - db: &impl HirDatabase, - krate: CrateId, - data: super::ClosureFnTraitImplData, -) -> Option>> { - // for some closure |X, Y| -> Z: - // impl Fn<(T, U)> for closure V> { Output = V } - - let trait_ = get_fn_trait(db, krate, data.fn_trait)?; // get corresponding fn trait - - // validate FnOnce trait, since we need it in the assoc ty value definition - // and don't want to return a valid value only to find out later that FnOnce - // is broken - let fn_once_trait = get_fn_trait(db, krate, super::FnTrait::FnOnce)?; - let _output = db.trait_data(fn_once_trait).associated_type_by_name(&name::OUTPUT_TYPE)?; - - let num_args: u16 = match &db.body(data.def.into())[data.expr] { - crate::expr::Expr::Lambda { args, .. } => args.len() as u16, - _ => { - log::warn!("closure for closure type {:?} not found", data); - 0 - } - }; - - let arg_ty = Ty::apply( - TypeCtor::Tuple { cardinality: num_args }, - Substs::builder(num_args as usize).fill_with_bound_vars(0).build(), - ); - let sig_ty = Ty::apply( - TypeCtor::FnPtr { num_args }, - Substs::builder(num_args as usize + 1).fill_with_bound_vars(0).build(), - ); - - let self_ty = Ty::apply_one(TypeCtor::Closure { def: data.def, expr: data.expr }, sig_ty); - - let trait_ref = TraitRef { - trait_: trait_.into(), - substs: Substs::build_for_def(db, trait_).push(self_ty).push(arg_ty).build(), - }; - - let output_ty_id = AssocTyValue::ClosureFnTraitImplOutput(data.clone()).to_chalk(db); - - let impl_type = chalk_rust_ir::ImplType::External; - - let impl_datum_bound = chalk_rust_ir::ImplDatumBound { - trait_ref: trait_ref.to_chalk(db), - where_clauses: Vec::new(), - }; - let impl_datum = ImplDatum { - binders: make_binders(impl_datum_bound, num_args as usize + 1), - impl_type, - polarity: chalk_rust_ir::Polarity::Positive, - associated_ty_value_ids: vec![output_ty_id], - }; - Some(Arc::new(impl_datum)) -} - -pub(crate) fn associated_ty_value_query( - db: &impl HirDatabase, - krate: CrateId, - id: chalk_rust_ir::AssociatedTyValueId, -) -> Arc> { - let data: AssocTyValue = from_chalk(db, id); - match data { - AssocTyValue::TypeAlias(type_alias) => { - type_alias_associated_ty_value(db, krate, type_alias) - } - AssocTyValue::ClosureFnTraitImplOutput(data) => { - closure_fn_trait_output_assoc_ty_value(db, krate, data) - } - } -} - -fn type_alias_associated_ty_value( - db: &impl HirDatabase, - _krate: CrateId, - type_alias: TypeAliasId, -) -> Arc> { - let type_alias_data = db.type_alias_data(type_alias); - let impl_id = match type_alias.lookup(db).container { - ContainerId::ImplId(it) => it, - _ => panic!("assoc ty value should be in impl"), - }; - - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - let target_trait = impl_data - .target_trait - .as_ref() - .and_then(|trait_ref| TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty))) - .expect("assoc ty value should not exist"); // we don't return any assoc ty values if the impl'd trait can't be resolved - - let assoc_ty = db - .trait_data(target_trait.trait_) - .associated_type_by_name(&type_alias_data.name) - .expect("assoc ty value should not exist"); // validated when building the impl data as well - let generic_params = db.generic_params(impl_id.into()); - let bound_vars = Substs::bound_vars(&generic_params); - let ty = db.ty(type_alias.into()).subst(&bound_vars); - let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: ty.to_chalk(db) }; - let value = chalk_rust_ir::AssociatedTyValue { - impl_id: Impl::ImplBlock(impl_id.into()).to_chalk(db), - associated_ty_id: assoc_ty.to_chalk(db), - value: make_binders(value_bound, bound_vars.len()), - }; - Arc::new(value) -} - -fn closure_fn_trait_output_assoc_ty_value( - db: &impl HirDatabase, - krate: CrateId, - data: super::ClosureFnTraitImplData, -) -> Arc> { - let impl_id = Impl::ClosureFnTraitImpl(data.clone()).to_chalk(db); - - let num_args: u16 = match &db.body(data.def.into())[data.expr] { - crate::expr::Expr::Lambda { args, .. } => args.len() as u16, - _ => { - log::warn!("closure for closure type {:?} not found", data); - 0 - } - }; - - let output_ty = Ty::Bound(num_args.into()); - - let fn_once_trait = - get_fn_trait(db, krate, super::FnTrait::FnOnce).expect("assoc ty value should not exist"); - - let output_ty_id = db - .trait_data(fn_once_trait) - .associated_type_by_name(&name::OUTPUT_TYPE) - .expect("assoc ty value should not exist"); - - let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: output_ty.to_chalk(db) }; - - let value = chalk_rust_ir::AssociatedTyValue { - associated_ty_id: output_ty_id.to_chalk(db), - impl_id, - value: make_binders(value_bound, num_args as usize + 1), - }; - Arc::new(value) -} - -fn get_fn_trait( - db: &impl HirDatabase, - krate: CrateId, - fn_trait: super::FnTrait, -) -> Option { - let target = db.lang_item(krate, fn_trait.lang_item_name().into())?; - match target { - LangItemTarget::TraitId(t) => Some(t), - _ => None, - } -} - -fn id_from_chalk(chalk_id: chalk_ir::RawId) -> T { - T::from_intern_id(InternId::from(chalk_id.index)) -} -fn id_to_chalk(salsa_id: T) -> chalk_ir::RawId { - chalk_ir::RawId { index: salsa_id.as_intern_id().as_u32() } -} - -impl From for crate::ty::TypeCtorId { - fn from(struct_id: chalk_ir::StructId) -> Self { - id_from_chalk(struct_id.0) - } -} - -impl From for chalk_ir::StructId { - fn from(type_ctor_id: crate::ty::TypeCtorId) -> Self { - chalk_ir::StructId(id_to_chalk(type_ctor_id)) - } -} - -impl From for crate::ty::traits::GlobalImplId { - fn from(impl_id: chalk_ir::ImplId) -> Self { - id_from_chalk(impl_id.0) - } -} - -impl From for chalk_ir::ImplId { - fn from(impl_id: crate::ty::traits::GlobalImplId) -> Self { - chalk_ir::ImplId(id_to_chalk(impl_id)) - } -} - -impl From for crate::ty::traits::AssocTyValueId { - fn from(id: chalk_rust_ir::AssociatedTyValueId) -> Self { - id_from_chalk(id.0) - } -} - -impl From for chalk_rust_ir::AssociatedTyValueId { - fn from(assoc_ty_value_id: crate::ty::traits::AssocTyValueId) -> Self { - chalk_rust_ir::AssociatedTyValueId(id_to_chalk(assoc_ty_value_id)) - } -} diff --git a/crates/ra_hir/src/ty/utils.rs b/crates/ra_hir/src/ty/utils.rs deleted file mode 100644 index f82e6ac9b..000000000 --- a/crates/ra_hir/src/ty/utils.rs +++ /dev/null @@ -1,75 +0,0 @@ -//! Helper functions for working with def, which don't need to be a separate -//! query, but can't be computed directly from `*Data` (ie, which need a `db`). -use std::sync::Arc; - -use hir_def::{ - adt::VariantData, - db::DefDatabase, - resolver::{HasResolver, TypeNs}, - type_ref::TypeRef, - TraitId, TypeAliasId, VariantId, -}; -use hir_expand::name::{self, Name}; - -// FIXME: this is wrong, b/c it can't express `trait T: PartialEq<()>`. -// We should return a `TraitREf` here. -fn direct_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { - let resolver = trait_.resolver(db); - // returning the iterator directly doesn't easily work because of - // lifetime problems, but since there usually shouldn't be more than a - // few direct traits this should be fine (we could even use some kind of - // SmallVec if performance is a concern) - db.generic_params(trait_.into()) - .where_predicates - .iter() - .filter_map(|pred| match &pred.type_ref { - TypeRef::Path(p) if p.as_ident() == Some(&name::SELF_TYPE) => pred.bound.as_path(), - _ => None, - }) - .filter_map(|path| match resolver.resolve_path_in_type_ns_fully(db, path) { - Some(TypeNs::TraitId(t)) => Some(t), - _ => None, - }) - .collect() -} - -/// Returns an iterator over the whole super trait hierarchy (including the -/// trait itself). -pub(super) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { - // we need to take care a bit here to avoid infinite loops in case of cycles - // (i.e. if we have `trait A: B; trait B: A;`) - let mut result = vec![trait_]; - let mut i = 0; - while i < result.len() { - let t = result[i]; - // yeah this is quadratic, but trait hierarchies should be flat - // enough that this doesn't matter - for tt in direct_super_traits(db, t) { - if !result.contains(&tt) { - result.push(tt); - } - } - i += 1; - } - result -} - -pub(super) fn associated_type_by_name_including_super_traits( - db: &impl DefDatabase, - trait_: TraitId, - name: &Name, -) -> Option { - all_super_traits(db, trait_) - .into_iter() - .find_map(|t| db.trait_data(t).associated_type_by_name(name)) -} - -pub(super) fn variant_data(db: &impl DefDatabase, var: VariantId) -> Arc { - match var { - VariantId::StructId(it) => db.struct_data(it).variant_data.clone(), - VariantId::UnionId(it) => db.union_data(it).variant_data.clone(), - VariantId::EnumVariantId(it) => { - db.enum_data(it.parent).variants[it.local_id].variant_data.clone() - } - } -} diff --git a/crates/ra_hir/src/util.rs b/crates/ra_hir/src/util.rs deleted file mode 100644 index 0095ee45d..000000000 --- a/crates/ra_hir/src/util.rs +++ /dev/null @@ -1,12 +0,0 @@ -//! Internal utility functions. - -use std::sync::Arc; - -/// Helper for mutating `Arc<[T]>` (i.e. `Arc::make_mut` for Arc slices). -/// The underlying values are cloned if there are other strong references. -pub(crate) fn make_mut_slice(a: &mut Arc<[T]>) -> &mut [T] { - if Arc::get_mut(a).is_none() { - *a = a.iter().cloned().collect(); - } - Arc::get_mut(a).unwrap() -} diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index ea3f00bb3..ddf464c60 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -27,7 +27,7 @@ pub mod body; pub mod resolver; mod trace; -mod nameres; +pub mod nameres; #[cfg(test)] mod test_db; diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 027b50865..199afff49 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -8,6 +8,7 @@ authors = ["rust-analyzer developers"] doctest = false [dependencies] +arrayvec = "0.5.1" log = "0.4.5" rustc-hash = "1.0" parking_lot = "0.10.0" diff --git a/crates/ra_hir_ty/src/autoderef.rs b/crates/ra_hir_ty/src/autoderef.rs new file mode 100644 index 000000000..9d1d4e48c --- /dev/null +++ b/crates/ra_hir_ty/src/autoderef.rs @@ -0,0 +1,108 @@ +//! In certain situations, rust automatically inserts derefs as necessary: for +//! example, field accesses `foo.bar` still work when `foo` is actually a +//! reference to a type with the field `bar`. This is an approximation of the +//! logic in rustc (which lives in librustc_typeck/check/autoderef.rs). + +use std::iter::successors; + +use hir_def::lang_item::LangItemTarget; +use hir_expand::name; +use log::{info, warn}; +use ra_db::CrateId; + +use crate::db::HirDatabase; + +use super::{ + traits::{InEnvironment, Solution}, + Canonical, Substs, Ty, TypeWalk, +}; + +const AUTODEREF_RECURSION_LIMIT: usize = 10; + +pub fn autoderef<'a>( + db: &'a impl HirDatabase, + krate: Option, + ty: InEnvironment>, +) -> impl Iterator> + 'a { + let InEnvironment { value: ty, environment } = ty; + successors(Some(ty), move |ty| { + deref(db, krate?, InEnvironment { value: ty, environment: environment.clone() }) + }) + .take(AUTODEREF_RECURSION_LIMIT) +} + +pub(crate) fn deref( + db: &impl HirDatabase, + krate: CrateId, + ty: InEnvironment<&Canonical>, +) -> Option> { + if let Some(derefed) = ty.value.value.builtin_deref() { + Some(Canonical { value: derefed, num_vars: ty.value.num_vars }) + } else { + deref_by_trait(db, krate, ty) + } +} + +fn deref_by_trait( + db: &impl HirDatabase, + krate: CrateId, + ty: InEnvironment<&Canonical>, +) -> Option> { + let deref_trait = match db.lang_item(krate.into(), "deref".into())? { + LangItemTarget::TraitId(it) => it, + _ => return None, + }; + let target = db.trait_data(deref_trait).associated_type_by_name(&name::TARGET_TYPE)?; + + let generic_params = db.generic_params(target.into()); + if generic_params.count_params_including_parent() != 1 { + // the Target type + Deref trait should only have one generic parameter, + // namely Deref's Self type + return None; + } + + // FIXME make the Canonical handling nicer + + let parameters = Substs::build_for_generics(&generic_params) + .push(ty.value.value.clone().shift_bound_vars(1)) + .build(); + + let projection = super::traits::ProjectionPredicate { + ty: Ty::Bound(0), + projection_ty: super::ProjectionTy { associated_ty: target, parameters }, + }; + + let obligation = super::Obligation::Projection(projection); + + let in_env = InEnvironment { value: obligation, environment: ty.environment }; + + let canonical = super::Canonical { num_vars: 1 + ty.value.num_vars, value: in_env }; + + let solution = db.trait_solve(krate.into(), canonical)?; + + match &solution { + Solution::Unique(vars) => { + // FIXME: vars may contain solutions for any inference variables + // that happened to be inside ty. To correctly handle these, we + // would have to pass the solution up to the inference context, but + // that requires a larger refactoring (especially if the deref + // happens during method resolution). So for the moment, we just + // check that we're not in the situation we're we would actually + // need to handle the values of the additional variables, i.e. + // they're just being 'passed through'. In the 'standard' case where + // we have `impl Deref for Foo { Target = T }`, that should be + // the case. + for i in 1..vars.0.num_vars { + if vars.0.value[i] != Ty::Bound((i - 1) as u32) { + warn!("complex solution for derefing {:?}: {:?}, ignoring", ty.value, solution); + return None; + } + } + Some(Canonical { value: vars.0.value[0].clone(), num_vars: vars.0.num_vars }) + } + Solution::Ambig(_) => { + info!("Ambiguous solution for derefing {:?}: {:?}", ty.value, solution); + None + } + } +} diff --git a/crates/ra_hir_ty/src/db.rs b/crates/ra_hir_ty/src/db.rs new file mode 100644 index 000000000..aa2659c4b --- /dev/null +++ b/crates/ra_hir_ty/src/db.rs @@ -0,0 +1,116 @@ +//! FIXME: write short doc here + +use std::sync::Arc; + +use hir_def::{ + db::DefDatabase, DefWithBodyId, GenericDefId, ImplId, LocalStructFieldId, TraitId, VariantId, +}; +use ra_arena::map::ArenaMap; +use ra_db::{salsa, CrateId}; + +use crate::{ + method_resolution::CrateImplBlocks, + traits::{AssocTyValue, Impl}, + CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, + ValueTyDefId, +}; + +#[salsa::query_group(HirDatabaseStorage)] +#[salsa::requires(salsa::Database)] +pub trait HirDatabase: DefDatabase { + #[salsa::invoke(crate::infer_query)] + fn infer(&self, def: DefWithBodyId) -> Arc; + + #[salsa::invoke(crate::lower::ty_query)] + fn ty(&self, def: TyDefId) -> Ty; + + #[salsa::invoke(crate::lower::value_ty_query)] + fn value_ty(&self, def: ValueTyDefId) -> Ty; + + #[salsa::invoke(crate::lower::field_types_query)] + fn field_types(&self, var: VariantId) -> Arc>; + + #[salsa::invoke(crate::callable_item_sig)] + fn callable_item_signature(&self, def: CallableDef) -> FnSig; + + #[salsa::invoke(crate::lower::generic_predicates_for_param_query)] + fn generic_predicates_for_param( + &self, + def: GenericDefId, + param_idx: u32, + ) -> Arc<[GenericPredicate]>; + + #[salsa::invoke(crate::lower::generic_predicates_query)] + fn generic_predicates(&self, def: GenericDefId) -> Arc<[GenericPredicate]>; + + #[salsa::invoke(crate::lower::generic_defaults_query)] + fn generic_defaults(&self, def: GenericDefId) -> Substs; + + #[salsa::invoke(crate::method_resolution::CrateImplBlocks::impls_in_crate_query)] + fn impls_in_crate(&self, krate: CrateId) -> Arc; + + #[salsa::invoke(crate::traits::impls_for_trait_query)] + fn impls_for_trait(&self, krate: CrateId, trait_: TraitId) -> Arc<[ImplId]>; + + /// This provides the Chalk trait solver instance. Because Chalk always + /// works from a specific crate, this query is keyed on the crate; and + /// because Chalk does its own internal caching, the solver is wrapped in a + /// Mutex and the query does an untracked read internally, to make sure the + /// cached state is thrown away when input facts change. + #[salsa::invoke(crate::traits::trait_solver_query)] + fn trait_solver(&self, krate: CrateId) -> crate::traits::TraitSolver; + + // Interned IDs for Chalk integration + #[salsa::interned] + fn intern_type_ctor(&self, type_ctor: TypeCtor) -> crate::TypeCtorId; + #[salsa::interned] + fn intern_chalk_impl(&self, impl_: Impl) -> crate::traits::GlobalImplId; + #[salsa::interned] + fn intern_assoc_ty_value(&self, assoc_ty_value: AssocTyValue) -> crate::traits::AssocTyValueId; + + #[salsa::invoke(crate::traits::chalk::associated_ty_data_query)] + fn associated_ty_data( + &self, + id: chalk_ir::TypeId, + ) -> Arc>; + + #[salsa::invoke(crate::traits::chalk::trait_datum_query)] + fn trait_datum( + &self, + krate: CrateId, + trait_id: chalk_ir::TraitId, + ) -> Arc>; + + #[salsa::invoke(crate::traits::chalk::struct_datum_query)] + fn struct_datum( + &self, + krate: CrateId, + struct_id: chalk_ir::StructId, + ) -> Arc>; + + #[salsa::invoke(crate::traits::chalk::impl_datum_query)] + fn impl_datum( + &self, + krate: CrateId, + impl_id: chalk_ir::ImplId, + ) -> Arc>; + + #[salsa::invoke(crate::traits::chalk::associated_ty_value_query)] + fn associated_ty_value( + &self, + krate: CrateId, + id: chalk_rust_ir::AssociatedTyValueId, + ) -> Arc>; + + #[salsa::invoke(crate::traits::trait_solve_query)] + fn trait_solve( + &self, + krate: CrateId, + goal: crate::Canonical>, + ) -> Option; +} + +#[test] +fn hir_database_is_object_safe() { + fn _assert_object_safe(_: &dyn HirDatabase) {} +} diff --git a/crates/ra_hir_ty/src/diagnostics.rs b/crates/ra_hir_ty/src/diagnostics.rs new file mode 100644 index 000000000..4a13fac23 --- /dev/null +++ b/crates/ra_hir_ty/src/diagnostics.rs @@ -0,0 +1,91 @@ +//! FIXME: write short doc here + +use std::any::Any; + +use hir_expand::{db::AstDatabase, name::Name, HirFileId, Source}; +use ra_syntax::{ast, AstNode, AstPtr, SyntaxNodePtr}; + +pub use hir_def::diagnostics::UnresolvedModule; +pub use hir_expand::diagnostics::{AstDiagnostic, Diagnostic, DiagnosticSink}; + +#[derive(Debug)] +pub struct NoSuchField { + pub file: HirFileId, + pub field: AstPtr, +} + +impl Diagnostic for NoSuchField { + fn message(&self) -> String { + "no such field".to_string() + } + + fn source(&self) -> Source { + Source { file_id: self.file, value: self.field.into() } + } + + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +#[derive(Debug)] +pub struct MissingFields { + pub file: HirFileId, + pub field_list: AstPtr, + pub missed_fields: Vec, +} + +impl Diagnostic for MissingFields { + fn message(&self) -> String { + use std::fmt::Write; + let mut message = String::from("Missing structure fields:\n"); + for field in &self.missed_fields { + write!(message, "- {}\n", field).unwrap(); + } + message + } + fn source(&self) -> Source { + Source { file_id: self.file, value: self.field_list.into() } + } + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +impl AstDiagnostic for MissingFields { + type AST = ast::RecordFieldList; + + fn ast(&self, db: &impl AstDatabase) -> Self::AST { + let root = db.parse_or_expand(self.source().file_id).unwrap(); + let node = self.source().value.to_node(&root); + ast::RecordFieldList::cast(node).unwrap() + } +} + +#[derive(Debug)] +pub struct MissingOkInTailExpr { + pub file: HirFileId, + pub expr: AstPtr, +} + +impl Diagnostic for MissingOkInTailExpr { + fn message(&self) -> String { + "wrap return expression in Ok".to_string() + } + fn source(&self) -> Source { + Source { file_id: self.file, value: self.expr.into() } + } + fn as_any(&self) -> &(dyn Any + Send + 'static) { + self + } +} + +impl AstDiagnostic for MissingOkInTailExpr { + type AST = ast::Expr; + + fn ast(&self, db: &impl AstDatabase) -> Self::AST { + let root = db.parse_or_expand(self.file).unwrap(); + let node = self.source().value.to_node(&root); + ast::Expr::cast(node).unwrap() + } +} diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs new file mode 100644 index 000000000..9bb3ece6c --- /dev/null +++ b/crates/ra_hir_ty/src/display.rs @@ -0,0 +1,93 @@ +//! FIXME: write short doc here + +use std::fmt; + +use crate::db::HirDatabase; + +pub struct HirFormatter<'a, 'b, DB> { + pub db: &'a DB, + fmt: &'a mut fmt::Formatter<'b>, + buf: String, + curr_size: usize, + max_size: Option, +} + +pub trait HirDisplay { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result; + + fn display<'a, DB>(&'a self, db: &'a DB) -> HirDisplayWrapper<'a, DB, Self> + where + Self: Sized, + { + HirDisplayWrapper(db, self, None) + } + + fn display_truncated<'a, DB>( + &'a self, + db: &'a DB, + max_size: Option, + ) -> HirDisplayWrapper<'a, DB, Self> + where + Self: Sized, + { + HirDisplayWrapper(db, self, max_size) + } +} + +impl<'a, 'b, DB> HirFormatter<'a, 'b, DB> +where + DB: HirDatabase, +{ + pub fn write_joined( + &mut self, + iter: impl IntoIterator, + sep: &str, + ) -> fmt::Result { + let mut first = true; + for e in iter { + if !first { + write!(self, "{}", sep)?; + } + first = false; + e.hir_fmt(self)?; + } + Ok(()) + } + + /// This allows using the `write!` macro directly with a `HirFormatter`. + pub fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + // We write to a buffer first to track output size + self.buf.clear(); + fmt::write(&mut self.buf, args)?; + self.curr_size += self.buf.len(); + + // Then we write to the internal formatter from the buffer + self.fmt.write_str(&self.buf) + } + + pub fn should_truncate(&self) -> bool { + if let Some(max_size) = self.max_size { + self.curr_size >= max_size + } else { + false + } + } +} + +pub struct HirDisplayWrapper<'a, DB, T>(&'a DB, &'a T, Option); + +impl<'a, DB, T> fmt::Display for HirDisplayWrapper<'a, DB, T> +where + DB: HirDatabase, + T: HirDisplay, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.1.hir_fmt(&mut HirFormatter { + db: self.0, + fmt: f, + buf: String::with_capacity(20), + curr_size: 0, + max_size: self.2, + }) + } +} diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs new file mode 100644 index 000000000..5c65f9370 --- /dev/null +++ b/crates/ra_hir_ty/src/expr.rs @@ -0,0 +1,151 @@ +//! FIXME: write short doc here + +use std::sync::Arc; + +use hir_def::{ + path::{known, Path}, + resolver::HasResolver, + AdtId, FunctionId, +}; +use hir_expand::{diagnostics::DiagnosticSink, name::Name}; +use ra_syntax::ast; +use ra_syntax::AstPtr; +use rustc_hash::FxHashSet; + +use crate::{ + db::HirDatabase, + diagnostics::{MissingFields, MissingOkInTailExpr}, + ApplicationTy, InferenceResult, Ty, TypeCtor, +}; + +pub use hir_def::{ + body::{ + scope::{ExprScopes, ScopeEntry, ScopeId}, + Body, BodySourceMap, ExprPtr, ExprSource, PatPtr, PatSource, + }, + expr::{ + ArithOp, Array, BinaryOp, BindingAnnotation, CmpOp, Expr, ExprId, Literal, LogicOp, + MatchArm, Ordering, Pat, PatId, RecordFieldPat, RecordLitField, Statement, UnaryOp, + }, +}; + +pub struct ExprValidator<'a, 'b: 'a> { + func: FunctionId, + infer: Arc, + sink: &'a mut DiagnosticSink<'b>, +} + +impl<'a, 'b> ExprValidator<'a, 'b> { + pub fn new( + func: FunctionId, + infer: Arc, + sink: &'a mut DiagnosticSink<'b>, + ) -> ExprValidator<'a, 'b> { + ExprValidator { func, infer, sink } + } + + pub fn validate_body(&mut self, db: &impl HirDatabase) { + let body = db.body(self.func.into()); + + for e in body.exprs.iter() { + if let (id, Expr::RecordLit { path, fields, spread }) = e { + self.validate_record_literal(id, path, fields, *spread, db); + } + } + + let body_expr = &body[body.body_expr]; + if let Expr::Block { statements: _, tail: Some(t) } = body_expr { + self.validate_results_in_tail_expr(body.body_expr, *t, db); + } + } + + fn validate_record_literal( + &mut self, + id: ExprId, + _path: &Option, + fields: &[RecordLitField], + spread: Option, + db: &impl HirDatabase, + ) { + if spread.is_some() { + return; + } + + let struct_def = match self.infer[id].as_adt() { + Some((AdtId::StructId(s), _)) => s, + _ => return, + }; + let struct_data = db.struct_data(struct_def); + + let lit_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect(); + let missed_fields: Vec = struct_data + .variant_data + .fields() + .iter() + .filter_map(|(_f, d)| { + let name = d.name.clone(); + if lit_fields.contains(&name) { + None + } else { + Some(name) + } + }) + .collect(); + if missed_fields.is_empty() { + return; + } + let (_, source_map) = db.body_with_source_map(self.func.into()); + + if let Some(source_ptr) = source_map.expr_syntax(id) { + if let Some(expr) = source_ptr.value.a() { + let root = source_ptr.file_syntax(db); + if let ast::Expr::RecordLit(record_lit) = expr.to_node(&root) { + if let Some(field_list) = record_lit.record_field_list() { + self.sink.push(MissingFields { + file: source_ptr.file_id, + field_list: AstPtr::new(&field_list), + missed_fields, + }) + } + } + } + } + } + + fn validate_results_in_tail_expr( + &mut self, + body_id: ExprId, + id: ExprId, + db: &impl HirDatabase, + ) { + // the mismatch will be on the whole block currently + let mismatch = match self.infer.type_mismatch_for_expr(body_id) { + Some(m) => m, + None => return, + }; + + let std_result_path = known::std_result_result(); + + let resolver = self.func.resolver(db); + let std_result_enum = match resolver.resolve_known_enum(db, &std_result_path) { + Some(it) => it, + _ => return, + }; + + let std_result_ctor = TypeCtor::Adt(AdtId::EnumId(std_result_enum)); + let params = match &mismatch.expected { + Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters, + _ => return, + }; + + if params.len() == 2 && ¶ms[0] == &mismatch.actual { + let (_, source_map) = db.body_with_source_map(self.func.into()); + + if let Some(source_ptr) = source_map.expr_syntax(id) { + if let Some(expr) = source_ptr.value.a() { + self.sink.push(MissingOkInTailExpr { file: source_ptr.file_id, expr }); + } + } + } + } +} diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs new file mode 100644 index 000000000..1e9f4b208 --- /dev/null +++ b/crates/ra_hir_ty/src/infer.rs @@ -0,0 +1,723 @@ +//! Type inference, i.e. the process of walking through the code and determining +//! the type of each expression and pattern. +//! +//! For type inference, compare the implementations in rustc (the various +//! check_* methods in librustc_typeck/check/mod.rs are a good entry point) and +//! IntelliJ-Rust (org.rust.lang.core.types.infer). Our entry point for +//! inference here is the `infer` function, which infers the types of all +//! expressions in a given function. +//! +//! During inference, types (i.e. the `Ty` struct) can contain type 'variables' +//! which represent currently unknown types; as we walk through the expressions, +//! we might determine that certain variables need to be equal to each other, or +//! to certain types. To record this, we use the union-find implementation from +//! the `ena` crate, which is extracted from rustc. + +use std::borrow::Cow; +use std::mem; +use std::ops::Index; +use std::sync::Arc; + +use ena::unify::{InPlaceUnificationTable, NoError, UnifyKey, UnifyValue}; +use rustc_hash::FxHashMap; + +use hir_def::{ + body::Body, + data::{ConstData, FunctionData}, + expr::{BindingAnnotation, ExprId, PatId}, + path::{known, Path}, + resolver::{HasResolver, Resolver, TypeNs}, + type_ref::{Mutability, TypeRef}, + AdtId, AssocItemId, DefWithBodyId, FunctionId, StructFieldId, TypeAliasId, VariantId, +}; +use hir_expand::{diagnostics::DiagnosticSink, name}; +use ra_arena::map::ArenaMap; +use ra_prof::profile; +use test_utils::tested_by; + +use super::{ + primitive::{FloatTy, IntTy}, + traits::{Guidance, Obligation, ProjectionPredicate, Solution}, + ApplicationTy, InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, + TypeWalk, Uncertain, +}; +use crate::{db::HirDatabase, infer::diagnostics::InferenceDiagnostic}; + +macro_rules! ty_app { + ($ctor:pat, $param:pat) => { + crate::Ty::Apply(crate::ApplicationTy { ctor: $ctor, parameters: $param }) + }; + ($ctor:pat) => { + ty_app!($ctor, _) + }; +} + +mod unify; +mod path; +mod expr; +mod pat; +mod coerce; + +/// The entry point of type inference. +pub fn infer_query(db: &impl HirDatabase, def: DefWithBodyId) -> Arc { + let _p = profile("infer_query"); + let resolver = def.resolver(db); + let mut ctx = InferenceContext::new(db, def, resolver); + + match def { + DefWithBodyId::ConstId(c) => ctx.collect_const(&db.const_data(c)), + DefWithBodyId::FunctionId(f) => ctx.collect_fn(&db.function_data(f)), + DefWithBodyId::StaticId(s) => ctx.collect_const(&db.static_data(s)), + } + + ctx.infer_body(); + + Arc::new(ctx.resolve_all()) +} + +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +enum ExprOrPatId { + ExprId(ExprId), + PatId(PatId), +} + +impl_froms!(ExprOrPatId: ExprId, PatId); + +/// Binding modes inferred for patterns. +/// https://doc.rust-lang.org/reference/patterns.html#binding-modes +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +enum BindingMode { + Move, + Ref(Mutability), +} + +impl BindingMode { + pub fn convert(annotation: BindingAnnotation) -> BindingMode { + match annotation { + BindingAnnotation::Unannotated | BindingAnnotation::Mutable => BindingMode::Move, + BindingAnnotation::Ref => BindingMode::Ref(Mutability::Shared), + BindingAnnotation::RefMut => BindingMode::Ref(Mutability::Mut), + } + } +} + +impl Default for BindingMode { + fn default() -> Self { + BindingMode::Move + } +} + +/// A mismatch between an expected and an inferred type. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct TypeMismatch { + pub expected: Ty, + pub actual: Ty, +} + +/// The result of type inference: A mapping from expressions and patterns to types. +#[derive(Clone, PartialEq, Eq, Debug, Default)] +pub struct InferenceResult { + /// For each method call expr, records the function it resolves to. + method_resolutions: FxHashMap, + /// For each field access expr, records the field it resolves to. + field_resolutions: FxHashMap, + /// For each field in record literal, records the field it resolves to. + record_field_resolutions: FxHashMap, + /// For each struct literal, records the variant it resolves to. + variant_resolutions: FxHashMap, + /// For each associated item record what it resolves to + assoc_resolutions: FxHashMap, + diagnostics: Vec, + pub type_of_expr: ArenaMap, + pub type_of_pat: ArenaMap, + pub(super) type_mismatches: ArenaMap, +} + +impl InferenceResult { + pub fn method_resolution(&self, expr: ExprId) -> Option { + self.method_resolutions.get(&expr).copied() + } + pub fn field_resolution(&self, expr: ExprId) -> Option { + self.field_resolutions.get(&expr).copied() + } + pub fn record_field_resolution(&self, expr: ExprId) -> Option { + self.record_field_resolutions.get(&expr).copied() + } + pub fn variant_resolution_for_expr(&self, id: ExprId) -> Option { + self.variant_resolutions.get(&id.into()).copied() + } + pub fn variant_resolution_for_pat(&self, id: PatId) -> Option { + self.variant_resolutions.get(&id.into()).copied() + } + pub fn assoc_resolutions_for_expr(&self, id: ExprId) -> Option { + self.assoc_resolutions.get(&id.into()).copied() + } + pub fn assoc_resolutions_for_pat(&self, id: PatId) -> Option { + self.assoc_resolutions.get(&id.into()).copied() + } + pub fn type_mismatch_for_expr(&self, expr: ExprId) -> Option<&TypeMismatch> { + self.type_mismatches.get(expr) + } + pub fn add_diagnostics( + &self, + db: &impl HirDatabase, + owner: FunctionId, + sink: &mut DiagnosticSink, + ) { + self.diagnostics.iter().for_each(|it| it.add_to(db, owner, sink)) + } +} + +impl Index for InferenceResult { + type Output = Ty; + + fn index(&self, expr: ExprId) -> &Ty { + self.type_of_expr.get(expr).unwrap_or(&Ty::Unknown) + } +} + +impl Index for InferenceResult { + type Output = Ty; + + fn index(&self, pat: PatId) -> &Ty { + self.type_of_pat.get(pat).unwrap_or(&Ty::Unknown) + } +} + +/// The inference context contains all information needed during type inference. +#[derive(Clone, Debug)] +struct InferenceContext<'a, D: HirDatabase> { + db: &'a D, + owner: DefWithBodyId, + body: Arc, + resolver: Resolver, + var_unification_table: InPlaceUnificationTable, + trait_env: Arc, + obligations: Vec, + result: InferenceResult, + /// The return type of the function being inferred. + return_ty: Ty, + + /// Impls of `CoerceUnsized` used in coercion. + /// (from_ty_ctor, to_ty_ctor) => coerce_generic_index + // FIXME: Use trait solver for this. + // Chalk seems unable to work well with builtin impl of `Unsize` now. + coerce_unsized_map: FxHashMap<(TypeCtor, TypeCtor), usize>, +} + +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + fn new(db: &'a D, owner: DefWithBodyId, resolver: Resolver) -> Self { + InferenceContext { + result: InferenceResult::default(), + var_unification_table: InPlaceUnificationTable::new(), + obligations: Vec::default(), + return_ty: Ty::Unknown, // set in collect_fn_signature + trait_env: TraitEnvironment::lower(db, &resolver), + coerce_unsized_map: Self::init_coerce_unsized_map(db, &resolver), + db, + owner, + body: db.body(owner.into()), + resolver, + } + } + + fn resolve_all(mut self) -> InferenceResult { + // FIXME resolve obligations as well (use Guidance if necessary) + let mut result = mem::replace(&mut self.result, InferenceResult::default()); + let mut tv_stack = Vec::new(); + for ty in result.type_of_expr.values_mut() { + let resolved = self.resolve_ty_completely(&mut tv_stack, mem::replace(ty, Ty::Unknown)); + *ty = resolved; + } + for ty in result.type_of_pat.values_mut() { + let resolved = self.resolve_ty_completely(&mut tv_stack, mem::replace(ty, Ty::Unknown)); + *ty = resolved; + } + result + } + + fn write_expr_ty(&mut self, expr: ExprId, ty: Ty) { + self.result.type_of_expr.insert(expr, ty); + } + + fn write_method_resolution(&mut self, expr: ExprId, func: FunctionId) { + self.result.method_resolutions.insert(expr, func); + } + + fn write_field_resolution(&mut self, expr: ExprId, field: StructFieldId) { + self.result.field_resolutions.insert(expr, field); + } + + fn write_variant_resolution(&mut self, id: ExprOrPatId, variant: VariantId) { + self.result.variant_resolutions.insert(id, variant); + } + + fn write_assoc_resolution(&mut self, id: ExprOrPatId, item: AssocItemId) { + self.result.assoc_resolutions.insert(id, item.into()); + } + + fn write_pat_ty(&mut self, pat: PatId, ty: Ty) { + self.result.type_of_pat.insert(pat, ty); + } + + fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) { + self.result.diagnostics.push(diagnostic); + } + + fn make_ty(&mut self, type_ref: &TypeRef) -> Ty { + let ty = Ty::from_hir( + self.db, + // FIXME use right resolver for block + &self.resolver, + type_ref, + ); + let ty = self.insert_type_vars(ty); + self.normalize_associated_types_in(ty) + } + + fn unify_substs(&mut self, substs1: &Substs, substs2: &Substs, depth: usize) -> bool { + substs1.0.iter().zip(substs2.0.iter()).all(|(t1, t2)| self.unify_inner(t1, t2, depth)) + } + + fn unify(&mut self, ty1: &Ty, ty2: &Ty) -> bool { + self.unify_inner(ty1, ty2, 0) + } + + fn unify_inner(&mut self, ty1: &Ty, ty2: &Ty, depth: usize) -> bool { + if depth > 1000 { + // prevent stackoverflows + panic!("infinite recursion in unification"); + } + if ty1 == ty2 { + return true; + } + // try to resolve type vars first + let ty1 = self.resolve_ty_shallow(ty1); + let ty2 = self.resolve_ty_shallow(ty2); + match (&*ty1, &*ty2) { + (Ty::Apply(a_ty1), Ty::Apply(a_ty2)) if a_ty1.ctor == a_ty2.ctor => { + self.unify_substs(&a_ty1.parameters, &a_ty2.parameters, depth + 1) + } + _ => self.unify_inner_trivial(&ty1, &ty2), + } + } + + fn unify_inner_trivial(&mut self, ty1: &Ty, ty2: &Ty) -> bool { + match (ty1, ty2) { + (Ty::Unknown, _) | (_, Ty::Unknown) => true, + + (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2))) + | (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2))) + | (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2))) + | ( + Ty::Infer(InferTy::MaybeNeverTypeVar(tv1)), + Ty::Infer(InferTy::MaybeNeverTypeVar(tv2)), + ) => { + // both type vars are unknown since we tried to resolve them + self.var_unification_table.union(*tv1, *tv2); + true + } + + // The order of MaybeNeverTypeVar matters here. + // Unifying MaybeNeverTypeVar and TypeVar will let the latter become MaybeNeverTypeVar. + // Unifying MaybeNeverTypeVar and other concrete type will let the former become it. + (Ty::Infer(InferTy::TypeVar(tv)), other) + | (other, Ty::Infer(InferTy::TypeVar(tv))) + | (Ty::Infer(InferTy::MaybeNeverTypeVar(tv)), other) + | (other, Ty::Infer(InferTy::MaybeNeverTypeVar(tv))) + | (Ty::Infer(InferTy::IntVar(tv)), other @ ty_app!(TypeCtor::Int(_))) + | (other @ ty_app!(TypeCtor::Int(_)), Ty::Infer(InferTy::IntVar(tv))) + | (Ty::Infer(InferTy::FloatVar(tv)), other @ ty_app!(TypeCtor::Float(_))) + | (other @ ty_app!(TypeCtor::Float(_)), Ty::Infer(InferTy::FloatVar(tv))) => { + // the type var is unknown since we tried to resolve it + self.var_unification_table.union_value(*tv, TypeVarValue::Known(other.clone())); + true + } + + _ => false, + } + } + + fn new_type_var(&mut self) -> Ty { + Ty::Infer(InferTy::TypeVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) + } + + fn new_integer_var(&mut self) -> Ty { + Ty::Infer(InferTy::IntVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) + } + + fn new_float_var(&mut self) -> Ty { + Ty::Infer(InferTy::FloatVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) + } + + fn new_maybe_never_type_var(&mut self) -> Ty { + Ty::Infer(InferTy::MaybeNeverTypeVar( + self.var_unification_table.new_key(TypeVarValue::Unknown), + )) + } + + /// Replaces Ty::Unknown by a new type var, so we can maybe still infer it. + fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { + match ty { + Ty::Unknown => self.new_type_var(), + Ty::Apply(ApplicationTy { ctor: TypeCtor::Int(Uncertain::Unknown), .. }) => { + self.new_integer_var() + } + Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(Uncertain::Unknown), .. }) => { + self.new_float_var() + } + _ => ty, + } + } + + fn insert_type_vars(&mut self, ty: Ty) -> Ty { + ty.fold(&mut |ty| self.insert_type_vars_shallow(ty)) + } + + fn resolve_obligations_as_possible(&mut self) { + let obligations = mem::replace(&mut self.obligations, Vec::new()); + for obligation in obligations { + let in_env = InEnvironment::new(self.trait_env.clone(), obligation.clone()); + let canonicalized = self.canonicalizer().canonicalize_obligation(in_env); + let solution = self + .db + .trait_solve(self.resolver.krate().unwrap().into(), canonicalized.value.clone()); + + match solution { + Some(Solution::Unique(substs)) => { + canonicalized.apply_solution(self, substs.0); + } + Some(Solution::Ambig(Guidance::Definite(substs))) => { + canonicalized.apply_solution(self, substs.0); + self.obligations.push(obligation); + } + Some(_) => { + // FIXME use this when trying to resolve everything at the end + self.obligations.push(obligation); + } + None => { + // FIXME obligation cannot be fulfilled => diagnostic + } + }; + } + } + + /// Resolves the type as far as currently possible, replacing type variables + /// by their known types. All types returned by the infer_* functions should + /// be resolved as far as possible, i.e. contain no type variables with + /// known type. + fn resolve_ty_as_possible(&mut self, tv_stack: &mut Vec, ty: Ty) -> Ty { + self.resolve_obligations_as_possible(); + + ty.fold(&mut |ty| match ty { + Ty::Infer(tv) => { + let inner = tv.to_inner(); + if tv_stack.contains(&inner) { + tested_by!(type_var_cycles_resolve_as_possible); + // recursive type + return tv.fallback_value(); + } + if let Some(known_ty) = + self.var_unification_table.inlined_probe_value(inner).known() + { + // known_ty may contain other variables that are known by now + tv_stack.push(inner); + let result = self.resolve_ty_as_possible(tv_stack, known_ty.clone()); + tv_stack.pop(); + result + } else { + ty + } + } + _ => ty, + }) + } + + /// If `ty` is a type variable with known type, returns that type; + /// otherwise, return ty. + fn resolve_ty_shallow<'b>(&mut self, ty: &'b Ty) -> Cow<'b, Ty> { + let mut ty = Cow::Borrowed(ty); + // The type variable could resolve to a int/float variable. Hence try + // resolving up to three times; each type of variable shouldn't occur + // more than once + for i in 0..3 { + if i > 0 { + tested_by!(type_var_resolves_to_int_var); + } + match &*ty { + Ty::Infer(tv) => { + let inner = tv.to_inner(); + match self.var_unification_table.inlined_probe_value(inner).known() { + Some(known_ty) => { + // The known_ty can't be a type var itself + ty = Cow::Owned(known_ty.clone()); + } + _ => return ty, + } + } + _ => return ty, + } + } + log::error!("Inference variable still not resolved: {:?}", ty); + ty + } + + /// Recurses through the given type, normalizing associated types mentioned + /// in it by replacing them by type variables and registering obligations to + /// resolve later. This should be done once for every type we get from some + /// type annotation (e.g. from a let type annotation, field type or function + /// call). `make_ty` handles this already, but e.g. for field types we need + /// to do it as well. + fn normalize_associated_types_in(&mut self, ty: Ty) -> Ty { + let ty = self.resolve_ty_as_possible(&mut vec![], ty); + ty.fold(&mut |ty| match ty { + Ty::Projection(proj_ty) => self.normalize_projection_ty(proj_ty), + _ => ty, + }) + } + + fn normalize_projection_ty(&mut self, proj_ty: ProjectionTy) -> Ty { + let var = self.new_type_var(); + let predicate = ProjectionPredicate { projection_ty: proj_ty, ty: var.clone() }; + let obligation = Obligation::Projection(predicate); + self.obligations.push(obligation); + var + } + + /// Resolves the type completely; type variables without known type are + /// replaced by Ty::Unknown. + fn resolve_ty_completely(&mut self, tv_stack: &mut Vec, ty: Ty) -> Ty { + ty.fold(&mut |ty| match ty { + Ty::Infer(tv) => { + let inner = tv.to_inner(); + if tv_stack.contains(&inner) { + tested_by!(type_var_cycles_resolve_completely); + // recursive type + return tv.fallback_value(); + } + if let Some(known_ty) = + self.var_unification_table.inlined_probe_value(inner).known() + { + // known_ty may contain other variables that are known by now + tv_stack.push(inner); + let result = self.resolve_ty_completely(tv_stack, known_ty.clone()); + tv_stack.pop(); + result + } else { + tv.fallback_value() + } + } + _ => ty, + }) + } + + fn resolve_variant(&mut self, path: Option<&Path>) -> (Ty, Option) { + let path = match path { + Some(path) => path, + None => return (Ty::Unknown, None), + }; + let resolver = &self.resolver; + // FIXME: this should resolve assoc items as well, see this example: + // https://play.rust-lang.org/?gist=087992e9e22495446c01c0d4e2d69521 + match resolver.resolve_path_in_type_ns_fully(self.db, &path) { + Some(TypeNs::AdtId(AdtId::StructId(strukt))) => { + let substs = Ty::substs_from_path(self.db, resolver, path, strukt.into()); + let ty = self.db.ty(strukt.into()); + let ty = self.insert_type_vars(ty.apply_substs(substs)); + (ty, Some(strukt.into())) + } + Some(TypeNs::EnumVariantId(var)) => { + let substs = Ty::substs_from_path(self.db, resolver, path, var.into()); + let ty = self.db.ty(var.parent.into()); + let ty = self.insert_type_vars(ty.apply_substs(substs)); + (ty, Some(var.into())) + } + Some(_) | None => (Ty::Unknown, None), + } + } + + fn collect_const(&mut self, data: &ConstData) { + self.return_ty = self.make_ty(&data.type_ref); + } + + fn collect_fn(&mut self, data: &FunctionData) { + let body = Arc::clone(&self.body); // avoid borrow checker problem + for (type_ref, pat) in data.params.iter().zip(body.params.iter()) { + let ty = self.make_ty(type_ref); + + self.infer_pat(*pat, &ty, BindingMode::default()); + } + self.return_ty = self.make_ty(&data.ret_type); + } + + fn infer_body(&mut self) { + self.infer_expr(self.body.body_expr, &Expectation::has_type(self.return_ty.clone())); + } + + fn resolve_into_iter_item(&self) -> Option { + let path = known::std_iter_into_iterator(); + let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; + self.db.trait_data(trait_).associated_type_by_name(&name::ITEM_TYPE) + } + + fn resolve_ops_try_ok(&self) -> Option { + let path = known::std_ops_try(); + let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; + self.db.trait_data(trait_).associated_type_by_name(&name::OK_TYPE) + } + + fn resolve_future_future_output(&self) -> Option { + let path = known::std_future_future(); + let trait_ = self.resolver.resolve_known_trait(self.db, &path)?; + self.db.trait_data(trait_).associated_type_by_name(&name::OUTPUT_TYPE) + } + + fn resolve_boxed_box(&self) -> Option { + let path = known::std_boxed_box(); + let struct_ = self.resolver.resolve_known_struct(self.db, &path)?; + Some(struct_.into()) + } +} + +/// The ID of a type variable. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct TypeVarId(pub(super) u32); + +impl UnifyKey for TypeVarId { + type Value = TypeVarValue; + + fn index(&self) -> u32 { + self.0 + } + + fn from_index(i: u32) -> Self { + TypeVarId(i) + } + + fn tag() -> &'static str { + "TypeVarId" + } +} + +/// The value of a type variable: either we already know the type, or we don't +/// know it yet. +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum TypeVarValue { + Known(Ty), + Unknown, +} + +impl TypeVarValue { + fn known(&self) -> Option<&Ty> { + match self { + TypeVarValue::Known(ty) => Some(ty), + TypeVarValue::Unknown => None, + } + } +} + +impl UnifyValue for TypeVarValue { + type Error = NoError; + + fn unify_values(value1: &Self, value2: &Self) -> Result { + match (value1, value2) { + // We should never equate two type variables, both of which have + // known types. Instead, we recursively equate those types. + (TypeVarValue::Known(t1), TypeVarValue::Known(t2)) => panic!( + "equating two type variables, both of which have known types: {:?} and {:?}", + t1, t2 + ), + + // If one side is known, prefer that one. + (TypeVarValue::Known(..), TypeVarValue::Unknown) => Ok(value1.clone()), + (TypeVarValue::Unknown, TypeVarValue::Known(..)) => Ok(value2.clone()), + + (TypeVarValue::Unknown, TypeVarValue::Unknown) => Ok(TypeVarValue::Unknown), + } + } +} + +/// The kinds of placeholders we need during type inference. There's separate +/// values for general types, and for integer and float variables. The latter +/// two are used for inference of literal values (e.g. `100` could be one of +/// several integer types). +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum InferTy { + TypeVar(TypeVarId), + IntVar(TypeVarId), + FloatVar(TypeVarId), + MaybeNeverTypeVar(TypeVarId), +} + +impl InferTy { + fn to_inner(self) -> TypeVarId { + match self { + InferTy::TypeVar(ty) + | InferTy::IntVar(ty) + | InferTy::FloatVar(ty) + | InferTy::MaybeNeverTypeVar(ty) => ty, + } + } + + fn fallback_value(self) -> Ty { + match self { + InferTy::TypeVar(..) => Ty::Unknown, + InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::i32()))), + InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(Uncertain::Known(FloatTy::f64()))), + InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never), + } + } +} + +/// When inferring an expression, we propagate downward whatever type hint we +/// are able in the form of an `Expectation`. +#[derive(Clone, PartialEq, Eq, Debug)] +struct Expectation { + ty: Ty, + // FIXME: In some cases, we need to be aware whether the expectation is that + // the type match exactly what we passed, or whether it just needs to be + // coercible to the expected type. See Expectation::rvalue_hint in rustc. +} + +impl Expectation { + /// The expectation that the type of the expression needs to equal the given + /// type. + fn has_type(ty: Ty) -> Self { + Expectation { ty } + } + + /// This expresses no expectation on the type. + fn none() -> Self { + Expectation { ty: Ty::Unknown } + } +} + +mod diagnostics { + use hir_def::{expr::ExprId, FunctionId, HasSource, Lookup}; + use hir_expand::diagnostics::DiagnosticSink; + + use crate::{db::HirDatabase, diagnostics::NoSuchField}; + + #[derive(Debug, PartialEq, Eq, Clone)] + pub(super) enum InferenceDiagnostic { + NoSuchField { expr: ExprId, field: usize }, + } + + impl InferenceDiagnostic { + pub(super) fn add_to( + &self, + db: &impl HirDatabase, + owner: FunctionId, + sink: &mut DiagnosticSink, + ) { + match self { + InferenceDiagnostic::NoSuchField { expr, field } => { + let file = owner.lookup(db).source(db).file_id; + let (_, source_map) = db.body_with_source_map(owner.into()); + let field = source_map.field_syntax(*expr, *field); + sink.push(NoSuchField { file, field }) + } + } + } + } +} diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs new file mode 100644 index 000000000..d66a21932 --- /dev/null +++ b/crates/ra_hir_ty/src/infer/coerce.rs @@ -0,0 +1,354 @@ +//! Coercion logic. Coercions are certain type conversions that can implicitly +//! happen in certain places, e.g. weakening `&mut` to `&` or deref coercions +//! like going from `&Vec` to `&[T]`. +//! +//! See: https://doc.rust-lang.org/nomicon/coercions.html + +use hir_def::{ + lang_item::LangItemTarget, + resolver::{HasResolver, Resolver}, + type_ref::Mutability, + AdtId, +}; +use rustc_hash::FxHashMap; +use test_utils::tested_by; + +use crate::{autoderef, db::HirDatabase, Substs, TraitRef, Ty, TypeCtor, TypeWalk}; + +use super::{InEnvironment, InferTy, InferenceContext, TypeVarValue}; + +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + /// Unify two types, but may coerce the first one to the second one + /// using "implicit coercion rules" if needed. + pub(super) fn coerce(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool { + let from_ty = self.resolve_ty_shallow(from_ty).into_owned(); + let to_ty = self.resolve_ty_shallow(to_ty); + self.coerce_inner(from_ty, &to_ty) + } + + /// Merge two types from different branches, with possible implicit coerce. + /// + /// Note that it is only possible that one type are coerced to another. + /// Coercing both types to another least upper bound type is not possible in rustc, + /// which will simply result in "incompatible types" error. + pub(super) fn coerce_merge_branch<'t>(&mut self, ty1: &Ty, ty2: &Ty) -> Ty { + if self.coerce(ty1, ty2) { + ty2.clone() + } else if self.coerce(ty2, ty1) { + ty1.clone() + } else { + tested_by!(coerce_merge_fail_fallback); + // For incompatible types, we use the latter one as result + // to be better recovery for `if` without `else`. + ty2.clone() + } + } + + pub(super) fn init_coerce_unsized_map( + db: &'a D, + resolver: &Resolver, + ) -> FxHashMap<(TypeCtor, TypeCtor), usize> { + let krate = resolver.krate().unwrap(); + let impls = match db.lang_item(krate.into(), "coerce_unsized".into()) { + Some(LangItemTarget::TraitId(trait_)) => { + db.impls_for_trait(krate.into(), trait_.into()) + } + _ => return FxHashMap::default(), + }; + + impls + .iter() + .filter_map(|&impl_id| { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + + // `CoerseUnsized` has one generic parameter for the target type. + let trait_ref = TraitRef::from_hir( + db, + &resolver, + impl_data.target_trait.as_ref()?, + Some(target_ty), + )?; + let cur_from_ty = trait_ref.substs.0.get(0)?; + let cur_to_ty = trait_ref.substs.0.get(1)?; + + match (&cur_from_ty, cur_to_ty) { + (ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => { + // FIXME: We return the first non-equal bound as the type parameter to coerce to unsized type. + // This works for smart-pointer-like coercion, which covers all impls from std. + st1.iter().zip(st2.iter()).enumerate().find_map(|(i, (ty1, ty2))| { + match (ty1, ty2) { + (Ty::Param { idx: p1, .. }, Ty::Param { idx: p2, .. }) + if p1 != p2 => + { + Some(((*ctor1, *ctor2), i)) + } + _ => None, + } + }) + } + _ => None, + } + }) + .collect() + } + + fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool { + match (&from_ty, to_ty) { + // Never type will make type variable to fallback to Never Type instead of Unknown. + (ty_app!(TypeCtor::Never), Ty::Infer(InferTy::TypeVar(tv))) => { + let var = self.new_maybe_never_type_var(); + self.var_unification_table.union_value(*tv, TypeVarValue::Known(var)); + return true; + } + (ty_app!(TypeCtor::Never), _) => return true, + + // Trivial cases, this should go after `never` check to + // avoid infer result type to be never + _ => { + if self.unify_inner_trivial(&from_ty, &to_ty) { + return true; + } + } + } + + // Pointer weakening and function to pointer + match (&mut from_ty, to_ty) { + // `*mut T`, `&mut T, `&T`` -> `*const T` + // `&mut T` -> `&T` + // `&mut T` -> `*mut T` + (ty_app!(c1@TypeCtor::RawPtr(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared))) + | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::RawPtr(Mutability::Shared))) + | (ty_app!(c1@TypeCtor::Ref(_)), ty_app!(c2@TypeCtor::Ref(Mutability::Shared))) + | (ty_app!(c1@TypeCtor::Ref(Mutability::Mut)), ty_app!(c2@TypeCtor::RawPtr(_))) => { + *c1 = *c2; + } + + // Illegal mutablity conversion + ( + ty_app!(TypeCtor::RawPtr(Mutability::Shared)), + ty_app!(TypeCtor::RawPtr(Mutability::Mut)), + ) + | ( + ty_app!(TypeCtor::Ref(Mutability::Shared)), + ty_app!(TypeCtor::Ref(Mutability::Mut)), + ) => return false, + + // `{function_type}` -> `fn()` + (ty_app!(TypeCtor::FnDef(_)), ty_app!(TypeCtor::FnPtr { .. })) => { + match from_ty.callable_sig(self.db) { + None => return false, + Some(sig) => { + let num_args = sig.params_and_return.len() as u16 - 1; + from_ty = + Ty::apply(TypeCtor::FnPtr { num_args }, Substs(sig.params_and_return)); + } + } + } + + _ => {} + } + + if let Some(ret) = self.try_coerce_unsized(&from_ty, &to_ty) { + return ret; + } + + // Auto Deref if cannot coerce + match (&from_ty, to_ty) { + // FIXME: DerefMut + (ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2)) => { + self.unify_autoderef_behind_ref(&st1[0], &st2[0]) + } + + // Otherwise, normal unify + _ => self.unify(&from_ty, to_ty), + } + } + + /// Coerce a type using `from_ty: CoerceUnsized` + /// + /// See: https://doc.rust-lang.org/nightly/std/marker/trait.CoerceUnsized.html + fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty) -> Option { + let (ctor1, st1, ctor2, st2) = match (from_ty, to_ty) { + (ty_app!(ctor1, st1), ty_app!(ctor2, st2)) => (ctor1, st1, ctor2, st2), + _ => return None, + }; + + let coerce_generic_index = *self.coerce_unsized_map.get(&(*ctor1, *ctor2))?; + + // Check `Unsize` first + match self.check_unsize_and_coerce( + st1.0.get(coerce_generic_index)?, + st2.0.get(coerce_generic_index)?, + 0, + ) { + Some(true) => {} + ret => return ret, + } + + let ret = st1 + .iter() + .zip(st2.iter()) + .enumerate() + .filter(|&(idx, _)| idx != coerce_generic_index) + .all(|(_, (ty1, ty2))| self.unify(ty1, ty2)); + + Some(ret) + } + + /// Check if `from_ty: Unsize`, and coerce to `to_ty` if it holds. + /// + /// It should not be directly called. It is only used by `try_coerce_unsized`. + /// + /// See: https://doc.rust-lang.org/nightly/std/marker/trait.Unsize.html + fn check_unsize_and_coerce(&mut self, from_ty: &Ty, to_ty: &Ty, depth: usize) -> Option { + if depth > 1000 { + panic!("Infinite recursion in coercion"); + } + + match (&from_ty, &to_ty) { + // `[T; N]` -> `[T]` + (ty_app!(TypeCtor::Array, st1), ty_app!(TypeCtor::Slice, st2)) => { + Some(self.unify(&st1[0], &st2[0])) + } + + // `T` -> `dyn Trait` when `T: Trait` + (_, Ty::Dyn(_)) => { + // FIXME: Check predicates + Some(true) + } + + // `(..., T)` -> `(..., U)` when `T: Unsize` + ( + ty_app!(TypeCtor::Tuple { cardinality: len1 }, st1), + ty_app!(TypeCtor::Tuple { cardinality: len2 }, st2), + ) => { + if len1 != len2 || *len1 == 0 { + return None; + } + + match self.check_unsize_and_coerce( + st1.last().unwrap(), + st2.last().unwrap(), + depth + 1, + ) { + Some(true) => {} + ret => return ret, + } + + let ret = st1[..st1.len() - 1] + .iter() + .zip(&st2[..st2.len() - 1]) + .all(|(ty1, ty2)| self.unify(ty1, ty2)); + + Some(ret) + } + + // Foo<..., T, ...> is Unsize> if: + // - T: Unsize + // - Foo is a struct + // - Only the last field of Foo has a type involving T + // - T is not part of the type of any other fields + // - Bar: Unsize>, if the last field of Foo has type Bar + ( + ty_app!(TypeCtor::Adt(AdtId::StructId(struct1)), st1), + ty_app!(TypeCtor::Adt(AdtId::StructId(struct2)), st2), + ) if struct1 == struct2 => { + let field_tys = self.db.field_types((*struct1).into()); + let struct_data = self.db.struct_data(*struct1); + + let mut fields = struct_data.variant_data.fields().iter(); + let (last_field_id, _data) = fields.next_back()?; + + // Get the generic parameter involved in the last field. + let unsize_generic_index = { + let mut index = None; + let mut multiple_param = false; + field_tys[last_field_id].walk(&mut |ty| match ty { + &Ty::Param { idx, .. } => { + if index.is_none() { + index = Some(idx); + } else if Some(idx) != index { + multiple_param = true; + } + } + _ => {} + }); + + if multiple_param { + return None; + } + index? + }; + + // Check other fields do not involve it. + let mut multiple_used = false; + fields.for_each(|(field_id, _data)| { + field_tys[field_id].walk(&mut |ty| match ty { + &Ty::Param { idx, .. } if idx == unsize_generic_index => { + multiple_used = true + } + _ => {} + }) + }); + if multiple_used { + return None; + } + + let unsize_generic_index = unsize_generic_index as usize; + + // Check `Unsize` first + match self.check_unsize_and_coerce( + st1.get(unsize_generic_index)?, + st2.get(unsize_generic_index)?, + depth + 1, + ) { + Some(true) => {} + ret => return ret, + } + + // Then unify other parameters + let ret = st1 + .iter() + .zip(st2.iter()) + .enumerate() + .filter(|&(idx, _)| idx != unsize_generic_index) + .all(|(_, (ty1, ty2))| self.unify(ty1, ty2)); + + Some(ret) + } + + _ => None, + } + } + + /// Unify `from_ty` to `to_ty` with optional auto Deref + /// + /// Note that the parameters are already stripped the outer reference. + fn unify_autoderef_behind_ref(&mut self, from_ty: &Ty, to_ty: &Ty) -> bool { + let canonicalized = self.canonicalizer().canonicalize_ty(from_ty.clone()); + let to_ty = self.resolve_ty_shallow(&to_ty); + // FIXME: Auto DerefMut + for derefed_ty in autoderef::autoderef( + self.db, + self.resolver.krate(), + InEnvironment { + value: canonicalized.value.clone(), + environment: self.trait_env.clone(), + }, + ) { + let derefed_ty = canonicalized.decanonicalize_ty(derefed_ty.value); + match (&*self.resolve_ty_shallow(&derefed_ty), &*to_ty) { + // Stop when constructor matches. + (ty_app!(from_ctor, st1), ty_app!(to_ctor, st2)) if from_ctor == to_ctor => { + // It will not recurse to `coerce`. + return self.unify_substs(st1, st2, 0); + } + _ => {} + } + } + + false + } +} diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs new file mode 100644 index 000000000..2f9ca4bbb --- /dev/null +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -0,0 +1,686 @@ +//! Type inference for expressions. + +use std::iter::{repeat, repeat_with}; +use std::sync::Arc; + +use hir_def::{ + builtin_type::Signedness, + expr::{Array, BinaryOp, Expr, ExprId, Literal, Statement, UnaryOp}, + generics::GenericParams, + path::{GenericArg, GenericArgs}, + resolver::resolver_for_expr, + AdtId, ContainerId, Lookup, StructFieldId, +}; +use hir_expand::name::{self, Name}; + +use crate::{ + autoderef, db::HirDatabase, method_resolution, op, traits::InEnvironment, utils::variant_data, + CallableDef, InferTy, IntTy, Mutability, Obligation, ProjectionPredicate, ProjectionTy, Substs, + TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, +}; + +use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch}; + +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { + let ty = self.infer_expr_inner(tgt_expr, expected); + let could_unify = self.unify(&ty, &expected.ty); + if !could_unify { + self.result.type_mismatches.insert( + tgt_expr, + TypeMismatch { expected: expected.ty.clone(), actual: ty.clone() }, + ); + } + let ty = self.resolve_ty_as_possible(&mut vec![], ty); + ty + } + + /// Infer type of expression with possibly implicit coerce to the expected type. + /// Return the type after possible coercion. + fn infer_expr_coerce(&mut self, expr: ExprId, expected: &Expectation) -> Ty { + let ty = self.infer_expr_inner(expr, &expected); + let ty = if !self.coerce(&ty, &expected.ty) { + self.result + .type_mismatches + .insert(expr, TypeMismatch { expected: expected.ty.clone(), actual: ty.clone() }); + // Return actual type when type mismatch. + // This is needed for diagnostic when return type mismatch. + ty + } else if expected.ty == Ty::Unknown { + ty + } else { + expected.ty.clone() + }; + + self.resolve_ty_as_possible(&mut vec![], ty) + } + + fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty { + let body = Arc::clone(&self.body); // avoid borrow checker problem + let ty = match &body[tgt_expr] { + Expr::Missing => Ty::Unknown, + Expr::If { condition, then_branch, else_branch } => { + // if let is desugared to match, so this is always simple if + self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); + + let then_ty = self.infer_expr_inner(*then_branch, &expected); + let else_ty = match else_branch { + Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), + None => Ty::unit(), + }; + + self.coerce_merge_branch(&then_ty, &else_ty) + } + Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), + Expr::TryBlock { body } => { + let _inner = self.infer_expr(*body, expected); + // FIXME should be std::result::Result<{inner}, _> + Ty::Unknown + } + Expr::Loop { body } => { + self.infer_expr(*body, &Expectation::has_type(Ty::unit())); + // FIXME handle break with value + Ty::simple(TypeCtor::Never) + } + Expr::While { condition, body } => { + // while let is desugared to a match loop, so this is always simple while + self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool))); + self.infer_expr(*body, &Expectation::has_type(Ty::unit())); + Ty::unit() + } + Expr::For { iterable, body, pat } => { + let iterable_ty = self.infer_expr(*iterable, &Expectation::none()); + + let pat_ty = match self.resolve_into_iter_item() { + Some(into_iter_item_alias) => { + let pat_ty = self.new_type_var(); + let projection = ProjectionPredicate { + ty: pat_ty.clone(), + projection_ty: ProjectionTy { + associated_ty: into_iter_item_alias, + parameters: Substs::single(iterable_ty), + }, + }; + self.obligations.push(Obligation::Projection(projection)); + self.resolve_ty_as_possible(&mut vec![], pat_ty) + } + None => Ty::Unknown, + }; + + self.infer_pat(*pat, &pat_ty, BindingMode::default()); + self.infer_expr(*body, &Expectation::has_type(Ty::unit())); + Ty::unit() + } + Expr::Lambda { body, args, arg_types } => { + assert_eq!(args.len(), arg_types.len()); + + let mut sig_tys = Vec::new(); + + for (arg_pat, arg_type) in args.iter().zip(arg_types.iter()) { + let expected = if let Some(type_ref) = arg_type { + self.make_ty(type_ref) + } else { + Ty::Unknown + }; + let arg_ty = self.infer_pat(*arg_pat, &expected, BindingMode::default()); + sig_tys.push(arg_ty); + } + + // add return type + let ret_ty = self.new_type_var(); + sig_tys.push(ret_ty.clone()); + let sig_ty = Ty::apply( + TypeCtor::FnPtr { num_args: sig_tys.len() as u16 - 1 }, + Substs(sig_tys.into()), + ); + let closure_ty = Ty::apply_one( + TypeCtor::Closure { def: self.owner.into(), expr: tgt_expr }, + sig_ty, + ); + + // Eagerly try to relate the closure type with the expected + // type, otherwise we often won't have enough information to + // infer the body. + self.coerce(&closure_ty, &expected.ty); + + self.infer_expr(*body, &Expectation::has_type(ret_ty)); + closure_ty + } + Expr::Call { callee, args } => { + let callee_ty = self.infer_expr(*callee, &Expectation::none()); + let (param_tys, ret_ty) = match callee_ty.callable_sig(self.db) { + Some(sig) => (sig.params().to_vec(), sig.ret().clone()), + None => { + // Not callable + // FIXME: report an error + (Vec::new(), Ty::Unknown) + } + }; + self.register_obligations_for_call(&callee_ty); + self.check_call_arguments(args, ¶m_tys); + let ret_ty = self.normalize_associated_types_in(ret_ty); + ret_ty + } + Expr::MethodCall { receiver, args, method_name, generic_args } => self + .infer_method_call(tgt_expr, *receiver, &args, &method_name, generic_args.as_ref()), + Expr::Match { expr, arms } => { + let input_ty = self.infer_expr(*expr, &Expectation::none()); + + let mut result_ty = self.new_maybe_never_type_var(); + + for arm in arms { + for &pat in &arm.pats { + let _pat_ty = self.infer_pat(pat, &input_ty, BindingMode::default()); + } + if let Some(guard_expr) = arm.guard { + self.infer_expr( + guard_expr, + &Expectation::has_type(Ty::simple(TypeCtor::Bool)), + ); + } + + let arm_ty = self.infer_expr_inner(arm.expr, &expected); + result_ty = self.coerce_merge_branch(&result_ty, &arm_ty); + } + + result_ty + } + Expr::Path(p) => { + // FIXME this could be more efficient... + let resolver = resolver_for_expr(self.db, self.owner.into(), tgt_expr); + self.infer_path(&resolver, p, tgt_expr.into()).unwrap_or(Ty::Unknown) + } + Expr::Continue => Ty::simple(TypeCtor::Never), + Expr::Break { expr } => { + if let Some(expr) = expr { + // FIXME handle break with value + self.infer_expr(*expr, &Expectation::none()); + } + Ty::simple(TypeCtor::Never) + } + Expr::Return { expr } => { + if let Some(expr) = expr { + self.infer_expr(*expr, &Expectation::has_type(self.return_ty.clone())); + } + Ty::simple(TypeCtor::Never) + } + Expr::RecordLit { path, fields, spread } => { + let (ty, def_id) = self.resolve_variant(path.as_ref()); + if let Some(variant) = def_id { + self.write_variant_resolution(tgt_expr.into(), variant); + } + + self.unify(&ty, &expected.ty); + + let substs = ty.substs().unwrap_or_else(Substs::empty); + let field_types = + def_id.map(|it| self.db.field_types(it.into())).unwrap_or_default(); + let variant_data = def_id.map(|it| variant_data(self.db, it)); + for (field_idx, field) in fields.iter().enumerate() { + let field_def = + variant_data.as_ref().and_then(|it| match it.field(&field.name) { + Some(local_id) => { + Some(StructFieldId { parent: def_id.unwrap(), local_id }) + } + None => { + self.push_diagnostic(InferenceDiagnostic::NoSuchField { + expr: tgt_expr, + field: field_idx, + }); + None + } + }); + if let Some(field_def) = field_def { + self.result.record_field_resolutions.insert(field.expr, field_def); + } + let field_ty = field_def + .map_or(Ty::Unknown, |it| field_types[it.local_id].clone()) + .subst(&substs); + self.infer_expr_coerce(field.expr, &Expectation::has_type(field_ty)); + } + if let Some(expr) = spread { + self.infer_expr(*expr, &Expectation::has_type(ty.clone())); + } + ty + } + Expr::Field { expr, name } => { + let receiver_ty = self.infer_expr(*expr, &Expectation::none()); + let canonicalized = self.canonicalizer().canonicalize_ty(receiver_ty); + let ty = autoderef::autoderef( + self.db, + self.resolver.krate(), + InEnvironment { + value: canonicalized.value.clone(), + environment: self.trait_env.clone(), + }, + ) + .find_map(|derefed_ty| match canonicalized.decanonicalize_ty(derefed_ty.value) { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Tuple { .. } => name + .as_tuple_index() + .and_then(|idx| a_ty.parameters.0.get(idx).cloned()), + TypeCtor::Adt(AdtId::StructId(s)) => { + self.db.struct_data(s).variant_data.field(name).map(|local_id| { + let field = StructFieldId { parent: s.into(), local_id }.into(); + self.write_field_resolution(tgt_expr, field); + self.db.field_types(s.into())[field.local_id] + .clone() + .subst(&a_ty.parameters) + }) + } + // FIXME: + TypeCtor::Adt(AdtId::UnionId(_)) => None, + _ => None, + }, + _ => None, + }) + .unwrap_or(Ty::Unknown); + let ty = self.insert_type_vars(ty); + self.normalize_associated_types_in(ty) + } + Expr::Await { expr } => { + let inner_ty = self.infer_expr(*expr, &Expectation::none()); + let ty = match self.resolve_future_future_output() { + Some(future_future_output_alias) => { + let ty = self.new_type_var(); + let projection = ProjectionPredicate { + ty: ty.clone(), + projection_ty: ProjectionTy { + associated_ty: future_future_output_alias, + parameters: Substs::single(inner_ty), + }, + }; + self.obligations.push(Obligation::Projection(projection)); + self.resolve_ty_as_possible(&mut vec![], ty) + } + None => Ty::Unknown, + }; + ty + } + Expr::Try { expr } => { + let inner_ty = self.infer_expr(*expr, &Expectation::none()); + let ty = match self.resolve_ops_try_ok() { + Some(ops_try_ok_alias) => { + let ty = self.new_type_var(); + let projection = ProjectionPredicate { + ty: ty.clone(), + projection_ty: ProjectionTy { + associated_ty: ops_try_ok_alias, + parameters: Substs::single(inner_ty), + }, + }; + self.obligations.push(Obligation::Projection(projection)); + self.resolve_ty_as_possible(&mut vec![], ty) + } + None => Ty::Unknown, + }; + ty + } + Expr::Cast { expr, type_ref } => { + let _inner_ty = self.infer_expr(*expr, &Expectation::none()); + let cast_ty = self.make_ty(type_ref); + // FIXME check the cast... + cast_ty + } + Expr::Ref { expr, mutability } => { + let expectation = + if let Some((exp_inner, exp_mutability)) = &expected.ty.as_reference() { + if *exp_mutability == Mutability::Mut && *mutability == Mutability::Shared { + // FIXME: throw type error - expected mut reference but found shared ref, + // which cannot be coerced + } + Expectation::has_type(Ty::clone(exp_inner)) + } else { + Expectation::none() + }; + // FIXME reference coercions etc. + let inner_ty = self.infer_expr(*expr, &expectation); + Ty::apply_one(TypeCtor::Ref(*mutability), inner_ty) + } + Expr::Box { expr } => { + let inner_ty = self.infer_expr(*expr, &Expectation::none()); + if let Some(box_) = self.resolve_boxed_box() { + Ty::apply_one(TypeCtor::Adt(box_), inner_ty) + } else { + Ty::Unknown + } + } + Expr::UnaryOp { expr, op } => { + let inner_ty = self.infer_expr(*expr, &Expectation::none()); + match op { + UnaryOp::Deref => match self.resolver.krate() { + Some(krate) => { + let canonicalized = self.canonicalizer().canonicalize_ty(inner_ty); + match autoderef::deref( + self.db, + krate, + InEnvironment { + value: &canonicalized.value, + environment: self.trait_env.clone(), + }, + ) { + Some(derefed_ty) => { + canonicalized.decanonicalize_ty(derefed_ty.value) + } + None => Ty::Unknown, + } + } + None => Ty::Unknown, + }, + UnaryOp::Neg => { + match &inner_ty { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Int(Uncertain::Unknown) + | TypeCtor::Int(Uncertain::Known(IntTy { + signedness: Signedness::Signed, + .. + })) + | TypeCtor::Float(..) => inner_ty, + _ => Ty::Unknown, + }, + Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => { + inner_ty + } + // FIXME: resolve ops::Neg trait + _ => Ty::Unknown, + } + } + UnaryOp::Not => { + match &inner_ty { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Bool | TypeCtor::Int(_) => inner_ty, + _ => Ty::Unknown, + }, + Ty::Infer(InferTy::IntVar(..)) => inner_ty, + // FIXME: resolve ops::Not trait for inner_ty + _ => Ty::Unknown, + } + } + } + } + Expr::BinaryOp { lhs, rhs, op } => match op { + Some(op) => { + let lhs_expectation = match op { + BinaryOp::LogicOp(..) => Expectation::has_type(Ty::simple(TypeCtor::Bool)), + _ => Expectation::none(), + }; + let lhs_ty = self.infer_expr(*lhs, &lhs_expectation); + // FIXME: find implementation of trait corresponding to operation + // symbol and resolve associated `Output` type + let rhs_expectation = op::binary_op_rhs_expectation(*op, lhs_ty); + let rhs_ty = self.infer_expr(*rhs, &Expectation::has_type(rhs_expectation)); + + // FIXME: similar as above, return ty is often associated trait type + op::binary_op_return_ty(*op, rhs_ty) + } + _ => Ty::Unknown, + }, + Expr::Index { base, index } => { + let _base_ty = self.infer_expr(*base, &Expectation::none()); + let _index_ty = self.infer_expr(*index, &Expectation::none()); + // FIXME: use `std::ops::Index::Output` to figure out the real return type + Ty::Unknown + } + Expr::Tuple { exprs } => { + let mut tys = match &expected.ty { + ty_app!(TypeCtor::Tuple { .. }, st) => st + .iter() + .cloned() + .chain(repeat_with(|| self.new_type_var())) + .take(exprs.len()) + .collect::>(), + _ => (0..exprs.len()).map(|_| self.new_type_var()).collect(), + }; + + for (expr, ty) in exprs.iter().zip(tys.iter_mut()) { + self.infer_expr_coerce(*expr, &Expectation::has_type(ty.clone())); + } + + Ty::apply(TypeCtor::Tuple { cardinality: tys.len() as u16 }, Substs(tys.into())) + } + Expr::Array(array) => { + let elem_ty = match &expected.ty { + ty_app!(TypeCtor::Array, st) | ty_app!(TypeCtor::Slice, st) => { + st.as_single().clone() + } + _ => self.new_type_var(), + }; + + match array { + Array::ElementList(items) => { + for expr in items.iter() { + self.infer_expr_coerce(*expr, &Expectation::has_type(elem_ty.clone())); + } + } + Array::Repeat { initializer, repeat } => { + self.infer_expr_coerce( + *initializer, + &Expectation::has_type(elem_ty.clone()), + ); + self.infer_expr( + *repeat, + &Expectation::has_type(Ty::simple(TypeCtor::Int(Uncertain::Known( + IntTy::usize(), + )))), + ); + } + } + + Ty::apply_one(TypeCtor::Array, elem_ty) + } + Expr::Literal(lit) => match lit { + Literal::Bool(..) => Ty::simple(TypeCtor::Bool), + Literal::String(..) => { + Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str)) + } + Literal::ByteString(..) => { + let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); + let slice_type = Ty::apply_one(TypeCtor::Slice, byte_type); + Ty::apply_one(TypeCtor::Ref(Mutability::Shared), slice_type) + } + Literal::Char(..) => Ty::simple(TypeCtor::Char), + Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), + Literal::Float(_v, ty) => Ty::simple(TypeCtor::Float((*ty).into())), + }, + }; + // use a new type variable if we got Ty::Unknown here + let ty = self.insert_type_vars_shallow(ty); + let ty = self.resolve_ty_as_possible(&mut vec![], ty); + self.write_expr_ty(tgt_expr, ty.clone()); + ty + } + + fn infer_block( + &mut self, + statements: &[Statement], + tail: Option, + expected: &Expectation, + ) -> Ty { + let mut diverges = false; + for stmt in statements { + match stmt { + Statement::Let { pat, type_ref, initializer } => { + let decl_ty = + type_ref.as_ref().map(|tr| self.make_ty(tr)).unwrap_or(Ty::Unknown); + + // Always use the declared type when specified + let mut ty = decl_ty.clone(); + + if let Some(expr) = initializer { + let actual_ty = + self.infer_expr_coerce(*expr, &Expectation::has_type(decl_ty.clone())); + if decl_ty == Ty::Unknown { + ty = actual_ty; + } + } + + let ty = self.resolve_ty_as_possible(&mut vec![], ty); + self.infer_pat(*pat, &ty, BindingMode::default()); + } + Statement::Expr(expr) => { + if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) { + diverges = true; + } + } + } + } + + let ty = if let Some(expr) = tail { + self.infer_expr_coerce(expr, expected) + } else { + self.coerce(&Ty::unit(), &expected.ty); + Ty::unit() + }; + if diverges { + Ty::simple(TypeCtor::Never) + } else { + ty + } + } + + fn infer_method_call( + &mut self, + tgt_expr: ExprId, + receiver: ExprId, + args: &[ExprId], + method_name: &Name, + generic_args: Option<&GenericArgs>, + ) -> Ty { + let receiver_ty = self.infer_expr(receiver, &Expectation::none()); + let canonicalized_receiver = self.canonicalizer().canonicalize_ty(receiver_ty.clone()); + let resolved = method_resolution::lookup_method( + &canonicalized_receiver.value, + self.db, + method_name, + &self.resolver, + ); + let (derefed_receiver_ty, method_ty, def_generics) = match resolved { + Some((ty, func)) => { + let ty = canonicalized_receiver.decanonicalize_ty(ty); + self.write_method_resolution(tgt_expr, func); + (ty, self.db.value_ty(func.into()), Some(self.db.generic_params(func.into()))) + } + None => (receiver_ty, Ty::Unknown, None), + }; + let substs = self.substs_for_method_call(def_generics, generic_args, &derefed_receiver_ty); + let method_ty = method_ty.apply_substs(substs); + let method_ty = self.insert_type_vars(method_ty); + self.register_obligations_for_call(&method_ty); + let (expected_receiver_ty, param_tys, ret_ty) = match method_ty.callable_sig(self.db) { + Some(sig) => { + if !sig.params().is_empty() { + (sig.params()[0].clone(), sig.params()[1..].to_vec(), sig.ret().clone()) + } else { + (Ty::Unknown, Vec::new(), sig.ret().clone()) + } + } + None => (Ty::Unknown, Vec::new(), Ty::Unknown), + }; + // Apply autoref so the below unification works correctly + // FIXME: return correct autorefs from lookup_method + let actual_receiver_ty = match expected_receiver_ty.as_reference() { + Some((_, mutability)) => Ty::apply_one(TypeCtor::Ref(mutability), derefed_receiver_ty), + _ => derefed_receiver_ty, + }; + self.unify(&expected_receiver_ty, &actual_receiver_ty); + + self.check_call_arguments(args, ¶m_tys); + let ret_ty = self.normalize_associated_types_in(ret_ty); + ret_ty + } + + fn check_call_arguments(&mut self, args: &[ExprId], param_tys: &[Ty]) { + // Quoting https://github.com/rust-lang/rust/blob/6ef275e6c3cb1384ec78128eceeb4963ff788dca/src/librustc_typeck/check/mod.rs#L3325 -- + // We do this in a pretty awful way: first we type-check any arguments + // that are not closures, then we type-check the closures. This is so + // that we have more information about the types of arguments when we + // type-check the functions. This isn't really the right way to do this. + for &check_closures in &[false, true] { + let param_iter = param_tys.iter().cloned().chain(repeat(Ty::Unknown)); + for (&arg, param_ty) in args.iter().zip(param_iter) { + let is_closure = match &self.body[arg] { + Expr::Lambda { .. } => true, + _ => false, + }; + + if is_closure != check_closures { + continue; + } + + let param_ty = self.normalize_associated_types_in(param_ty); + self.infer_expr_coerce(arg, &Expectation::has_type(param_ty.clone())); + } + } + } + + fn substs_for_method_call( + &mut self, + def_generics: Option>, + generic_args: Option<&GenericArgs>, + receiver_ty: &Ty, + ) -> Substs { + let (parent_param_count, param_count) = + def_generics.as_ref().map_or((0, 0), |g| (g.count_parent_params(), g.params.len())); + let mut substs = Vec::with_capacity(parent_param_count + param_count); + // Parent arguments are unknown, except for the receiver type + if let Some(parent_generics) = def_generics.and_then(|p| p.parent_params.clone()) { + for param in &parent_generics.params { + if param.name == name::SELF_TYPE { + substs.push(receiver_ty.clone()); + } else { + substs.push(Ty::Unknown); + } + } + } + // handle provided type arguments + if let Some(generic_args) = generic_args { + // if args are provided, it should be all of them, but we can't rely on that + for arg in generic_args.args.iter().take(param_count) { + match arg { + GenericArg::Type(type_ref) => { + let ty = self.make_ty(type_ref); + substs.push(ty); + } + } + } + }; + let supplied_params = substs.len(); + for _ in supplied_params..parent_param_count + param_count { + substs.push(Ty::Unknown); + } + assert_eq!(substs.len(), parent_param_count + param_count); + Substs(substs.into()) + } + + fn register_obligations_for_call(&mut self, callable_ty: &Ty) { + if let Ty::Apply(a_ty) = callable_ty { + if let TypeCtor::FnDef(def) = a_ty.ctor { + let generic_predicates = self.db.generic_predicates(def.into()); + for predicate in generic_predicates.iter() { + let predicate = predicate.clone().subst(&a_ty.parameters); + if let Some(obligation) = Obligation::from_predicate(predicate) { + self.obligations.push(obligation); + } + } + // add obligation for trait implementation, if this is a trait method + match def { + CallableDef::FunctionId(f) => { + if let ContainerId::TraitId(trait_) = f.lookup(self.db).container { + // construct a TraitDef + let substs = a_ty.parameters.prefix( + self.db + .generic_params(trait_.into()) + .count_params_including_parent(), + ); + self.obligations.push(Obligation::Trait(TraitRef { + trait_: trait_.into(), + substs, + })); + } + } + CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => {} + } + } + } + } +} diff --git a/crates/ra_hir_ty/src/infer/pat.rs b/crates/ra_hir_ty/src/infer/pat.rs new file mode 100644 index 000000000..1ebb36239 --- /dev/null +++ b/crates/ra_hir_ty/src/infer/pat.rs @@ -0,0 +1,186 @@ +//! Type inference for patterns. + +use std::iter::repeat; +use std::sync::Arc; + +use hir_def::{ + expr::{BindingAnnotation, Pat, PatId, RecordFieldPat}, + path::Path, + type_ref::Mutability, +}; +use hir_expand::name::Name; +use test_utils::tested_by; + +use super::{BindingMode, InferenceContext}; +use crate::{db::HirDatabase, utils::variant_data, Substs, Ty, TypeCtor, TypeWalk}; + +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + fn infer_tuple_struct_pat( + &mut self, + path: Option<&Path>, + subpats: &[PatId], + expected: &Ty, + default_bm: BindingMode, + ) -> Ty { + let (ty, def) = self.resolve_variant(path); + let var_data = def.map(|it| variant_data(self.db, it)); + self.unify(&ty, expected); + + let substs = ty.substs().unwrap_or_else(Substs::empty); + + let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default(); + + for (i, &subpat) in subpats.iter().enumerate() { + let expected_ty = var_data + .as_ref() + .and_then(|d| d.field(&Name::new_tuple_field(i))) + .map_or(Ty::Unknown, |field| field_tys[field].clone()) + .subst(&substs); + let expected_ty = self.normalize_associated_types_in(expected_ty); + self.infer_pat(subpat, &expected_ty, default_bm); + } + + ty + } + + fn infer_record_pat( + &mut self, + path: Option<&Path>, + subpats: &[RecordFieldPat], + expected: &Ty, + default_bm: BindingMode, + id: PatId, + ) -> Ty { + let (ty, def) = self.resolve_variant(path); + let var_data = def.map(|it| variant_data(self.db, it)); + if let Some(variant) = def { + self.write_variant_resolution(id.into(), variant); + } + + self.unify(&ty, expected); + + let substs = ty.substs().unwrap_or_else(Substs::empty); + + let field_tys = def.map(|it| self.db.field_types(it.into())).unwrap_or_default(); + for subpat in subpats { + let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name)); + let expected_ty = + matching_field.map_or(Ty::Unknown, |field| field_tys[field].clone()).subst(&substs); + let expected_ty = self.normalize_associated_types_in(expected_ty); + self.infer_pat(subpat.pat, &expected_ty, default_bm); + } + + ty + } + + pub(super) fn infer_pat( + &mut self, + pat: PatId, + mut expected: &Ty, + mut default_bm: BindingMode, + ) -> Ty { + let body = Arc::clone(&self.body); // avoid borrow checker problem + + let is_non_ref_pat = match &body[pat] { + Pat::Tuple(..) + | Pat::TupleStruct { .. } + | Pat::Record { .. } + | Pat::Range { .. } + | Pat::Slice { .. } => true, + // FIXME: Path/Lit might actually evaluate to ref, but inference is unimplemented. + Pat::Path(..) | Pat::Lit(..) => true, + Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Missing => false, + }; + if is_non_ref_pat { + while let Some((inner, mutability)) = expected.as_reference() { + expected = inner; + default_bm = match default_bm { + BindingMode::Move => BindingMode::Ref(mutability), + BindingMode::Ref(Mutability::Shared) => BindingMode::Ref(Mutability::Shared), + BindingMode::Ref(Mutability::Mut) => BindingMode::Ref(mutability), + } + } + } else if let Pat::Ref { .. } = &body[pat] { + tested_by!(match_ergonomics_ref); + // When you encounter a `&pat` pattern, reset to Move. + // This is so that `w` is by value: `let (_, &w) = &(1, &2);` + default_bm = BindingMode::Move; + } + + // Lose mutability. + let default_bm = default_bm; + let expected = expected; + + let ty = match &body[pat] { + Pat::Tuple(ref args) => { + let expectations = match expected.as_tuple() { + Some(parameters) => &*parameters.0, + _ => &[], + }; + let expectations_iter = expectations.iter().chain(repeat(&Ty::Unknown)); + + let inner_tys = args + .iter() + .zip(expectations_iter) + .map(|(&pat, ty)| self.infer_pat(pat, ty, default_bm)) + .collect(); + + Ty::apply(TypeCtor::Tuple { cardinality: args.len() as u16 }, Substs(inner_tys)) + } + Pat::Ref { pat, mutability } => { + let expectation = match expected.as_reference() { + Some((inner_ty, exp_mut)) => { + if *mutability != exp_mut { + // FIXME: emit type error? + } + inner_ty + } + _ => &Ty::Unknown, + }; + let subty = self.infer_pat(*pat, expectation, default_bm); + Ty::apply_one(TypeCtor::Ref(*mutability), subty) + } + Pat::TupleStruct { path: p, args: subpats } => { + self.infer_tuple_struct_pat(p.as_ref(), subpats, expected, default_bm) + } + Pat::Record { path: p, args: fields } => { + self.infer_record_pat(p.as_ref(), fields, expected, default_bm, pat) + } + Pat::Path(path) => { + // FIXME use correct resolver for the surrounding expression + let resolver = self.resolver.clone(); + self.infer_path(&resolver, &path, pat.into()).unwrap_or(Ty::Unknown) + } + Pat::Bind { mode, name: _, subpat } => { + let mode = if mode == &BindingAnnotation::Unannotated { + default_bm + } else { + BindingMode::convert(*mode) + }; + let inner_ty = if let Some(subpat) = subpat { + self.infer_pat(*subpat, expected, default_bm) + } else { + expected.clone() + }; + let inner_ty = self.insert_type_vars_shallow(inner_ty); + + let bound_ty = match mode { + BindingMode::Ref(mutability) => { + Ty::apply_one(TypeCtor::Ref(mutability), inner_ty.clone()) + } + BindingMode::Move => inner_ty.clone(), + }; + let bound_ty = self.resolve_ty_as_possible(&mut vec![], bound_ty); + self.write_pat_ty(pat, bound_ty); + return inner_ty; + } + _ => Ty::Unknown, + }; + // use a new type variable if we got Ty::Unknown here + let ty = self.insert_type_vars_shallow(ty); + self.unify(&ty, expected); + let ty = self.resolve_ty_as_possible(&mut vec![], ty); + self.write_pat_ty(pat, ty.clone()); + ty + } +} diff --git a/crates/ra_hir_ty/src/infer/path.rs b/crates/ra_hir_ty/src/infer/path.rs new file mode 100644 index 000000000..e6676e1aa --- /dev/null +++ b/crates/ra_hir_ty/src/infer/path.rs @@ -0,0 +1,270 @@ +//! Path expression resolution. + +use hir_def::{ + path::{Path, PathKind, PathSegment}, + resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, + AssocItemId, ContainerId, Lookup, +}; +use hir_expand::name::Name; + +use crate::{db::HirDatabase, method_resolution, Substs, Ty, TypeWalk, ValueTyDefId}; + +use super::{ExprOrPatId, InferenceContext, TraitRef}; + +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + pub(super) fn infer_path( + &mut self, + resolver: &Resolver, + path: &Path, + id: ExprOrPatId, + ) -> Option { + let ty = self.resolve_value_path(resolver, path, id)?; + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + Some(ty) + } + + fn resolve_value_path( + &mut self, + resolver: &Resolver, + path: &Path, + id: ExprOrPatId, + ) -> Option { + let (value, self_subst) = if let PathKind::Type(type_ref) = &path.kind { + if path.segments.is_empty() { + // This can't actually happen syntax-wise + return None; + } + let ty = self.make_ty(type_ref); + let remaining_segments_for_ty = &path.segments[..path.segments.len() - 1]; + let ty = Ty::from_type_relative_path(self.db, resolver, ty, remaining_segments_for_ty); + self.resolve_ty_assoc_item( + ty, + &path.segments.last().expect("path had at least one segment").name, + id, + )? + } else { + let value_or_partial = resolver.resolve_path_in_value_ns(self.db, &path)?; + + match value_or_partial { + ResolveValueResult::ValueNs(it) => (it, None), + ResolveValueResult::Partial(def, remaining_index) => { + self.resolve_assoc_item(def, path, remaining_index, id)? + } + } + }; + + let typable: ValueTyDefId = match value { + ValueNs::LocalBinding(pat) => { + let ty = self.result.type_of_pat.get(pat)?.clone(); + let ty = self.resolve_ty_as_possible(&mut vec![], ty); + return Some(ty); + } + ValueNs::FunctionId(it) => it.into(), + ValueNs::ConstId(it) => it.into(), + ValueNs::StaticId(it) => it.into(), + ValueNs::StructId(it) => it.into(), + ValueNs::EnumVariantId(it) => it.into(), + }; + + let mut ty = self.db.value_ty(typable); + if let Some(self_subst) = self_subst { + ty = ty.subst(&self_subst); + } + let substs = Ty::substs_from_path(self.db, &self.resolver, path, typable); + let ty = ty.subst(&substs); + Some(ty) + } + + fn resolve_assoc_item( + &mut self, + def: TypeNs, + path: &Path, + remaining_index: usize, + id: ExprOrPatId, + ) -> Option<(ValueNs, Option)> { + assert!(remaining_index < path.segments.len()); + // there may be more intermediate segments between the resolved one and + // the end. Only the last segment needs to be resolved to a value; from + // the segments before that, we need to get either a type or a trait ref. + + let resolved_segment = &path.segments[remaining_index - 1]; + let remaining_segments = &path.segments[remaining_index..]; + let is_before_last = remaining_segments.len() == 1; + + match (def, is_before_last) { + (TypeNs::TraitId(trait_), true) => { + let segment = + remaining_segments.last().expect("there should be at least one segment here"); + let trait_ref = TraitRef::from_resolved_path( + self.db, + &self.resolver, + trait_.into(), + resolved_segment, + None, + ); + self.resolve_trait_assoc_item(trait_ref, segment, id) + } + (def, _) => { + // Either we already have a type (e.g. `Vec::new`), or we have a + // trait but it's not the last segment, so the next segment + // should resolve to an associated type of that trait (e.g. `::Item::default`) + let remaining_segments_for_ty = &remaining_segments[..remaining_segments.len() - 1]; + let ty = Ty::from_partly_resolved_hir_path( + self.db, + &self.resolver, + def, + resolved_segment, + remaining_segments_for_ty, + ); + if let Ty::Unknown = ty { + return None; + } + + let ty = self.insert_type_vars(ty); + let ty = self.normalize_associated_types_in(ty); + + let segment = + remaining_segments.last().expect("there should be at least one segment here"); + + self.resolve_ty_assoc_item(ty, &segment.name, id) + } + } + } + + fn resolve_trait_assoc_item( + &mut self, + trait_ref: TraitRef, + segment: &PathSegment, + id: ExprOrPatId, + ) -> Option<(ValueNs, Option)> { + let trait_ = trait_ref.trait_; + let item = self + .db + .trait_data(trait_) + .items + .iter() + .map(|(_name, id)| (*id).into()) + .find_map(|item| match item { + AssocItemId::FunctionId(func) => { + if segment.name == self.db.function_data(func).name { + Some(AssocItemId::FunctionId(func)) + } else { + None + } + } + + AssocItemId::ConstId(konst) => { + if self.db.const_data(konst).name.as_ref().map_or(false, |n| n == &segment.name) + { + Some(AssocItemId::ConstId(konst)) + } else { + None + } + } + AssocItemId::TypeAliasId(_) => None, + })?; + let def = match item { + AssocItemId::FunctionId(f) => ValueNs::FunctionId(f), + AssocItemId::ConstId(c) => ValueNs::ConstId(c), + AssocItemId::TypeAliasId(_) => unreachable!(), + }; + let substs = Substs::build_for_def(self.db, item) + .use_parent_substs(&trait_ref.substs) + .fill_with_params() + .build(); + + self.write_assoc_resolution(id, item); + Some((def, Some(substs))) + } + + fn resolve_ty_assoc_item( + &mut self, + ty: Ty, + name: &Name, + id: ExprOrPatId, + ) -> Option<(ValueNs, Option)> { + if let Ty::Unknown = ty { + return None; + } + + let canonical_ty = self.canonicalizer().canonicalize_ty(ty.clone()); + + method_resolution::iterate_method_candidates( + &canonical_ty.value, + self.db, + &self.resolver.clone(), + Some(name), + method_resolution::LookupMode::Path, + move |_ty, item| { + let (def, container) = match item { + AssocItemId::FunctionId(f) => { + (ValueNs::FunctionId(f), f.lookup(self.db).container) + } + AssocItemId::ConstId(c) => (ValueNs::ConstId(c), c.lookup(self.db).container), + AssocItemId::TypeAliasId(_) => unreachable!(), + }; + let substs = match container { + ContainerId::ImplId(_) => self.find_self_types(&def, ty.clone()), + ContainerId::TraitId(trait_) => { + // we're picking this method + let trait_substs = Substs::build_for_def(self.db, trait_) + .push(ty.clone()) + .fill(std::iter::repeat_with(|| self.new_type_var())) + .build(); + let substs = Substs::build_for_def(self.db, item) + .use_parent_substs(&trait_substs) + .fill_with_params() + .build(); + self.obligations.push(super::Obligation::Trait(TraitRef { + trait_, + substs: trait_substs, + })); + Some(substs) + } + ContainerId::ModuleId(_) => None, + }; + + self.write_assoc_resolution(id, item.into()); + Some((def, substs)) + }, + ) + } + + fn find_self_types(&self, def: &ValueNs, actual_def_ty: Ty) -> Option { + if let ValueNs::FunctionId(func) = *def { + // We only do the infer if parent has generic params + let gen = self.db.generic_params(func.into()); + if gen.count_parent_params() == 0 { + return None; + } + + let impl_id = match func.lookup(self.db).container { + ContainerId::ImplId(it) => it, + _ => return None, + }; + let resolver = impl_id.resolver(self.db); + let impl_data = self.db.impl_data(impl_id); + let impl_block = Ty::from_hir(self.db, &resolver, &impl_data.target_type); + let impl_block_substs = impl_block.substs()?; + let actual_substs = actual_def_ty.substs()?; + + let mut new_substs = vec![Ty::Unknown; gen.count_parent_params()]; + + // The following code *link up* the function actual parma type + // and impl_block type param index + impl_block_substs.iter().zip(actual_substs.iter()).for_each(|(param, pty)| { + if let Ty::Param { idx, .. } = param { + if let Some(s) = new_substs.get_mut(*idx as usize) { + *s = pty.clone(); + } + } + }); + + Some(Substs(new_substs.into())) + } else { + None + } + } +} diff --git a/crates/ra_hir_ty/src/infer/unify.rs b/crates/ra_hir_ty/src/infer/unify.rs new file mode 100644 index 000000000..f3a875678 --- /dev/null +++ b/crates/ra_hir_ty/src/infer/unify.rs @@ -0,0 +1,162 @@ +//! Unification and canonicalization logic. + +use super::{InferenceContext, Obligation}; +use crate::{ + db::HirDatabase, utils::make_mut_slice, Canonical, InEnvironment, InferTy, ProjectionPredicate, + ProjectionTy, Substs, TraitRef, Ty, TypeWalk, +}; + +impl<'a, D: HirDatabase> InferenceContext<'a, D> { + pub(super) fn canonicalizer<'b>(&'b mut self) -> Canonicalizer<'a, 'b, D> + where + 'a: 'b, + { + Canonicalizer { ctx: self, free_vars: Vec::new(), var_stack: Vec::new() } + } +} + +pub(super) struct Canonicalizer<'a, 'b, D: HirDatabase> +where + 'a: 'b, +{ + ctx: &'b mut InferenceContext<'a, D>, + free_vars: Vec, + /// A stack of type variables that is used to detect recursive types (which + /// are an error, but we need to protect against them to avoid stack + /// overflows). + var_stack: Vec, +} + +pub(super) struct Canonicalized { + pub value: Canonical, + free_vars: Vec, +} + +impl<'a, 'b, D: HirDatabase> Canonicalizer<'a, 'b, D> +where + 'a: 'b, +{ + fn add(&mut self, free_var: InferTy) -> usize { + self.free_vars.iter().position(|&v| v == free_var).unwrap_or_else(|| { + let next_index = self.free_vars.len(); + self.free_vars.push(free_var); + next_index + }) + } + + fn do_canonicalize_ty(&mut self, ty: Ty) -> Ty { + ty.fold(&mut |ty| match ty { + Ty::Infer(tv) => { + let inner = tv.to_inner(); + if self.var_stack.contains(&inner) { + // recursive type + return tv.fallback_value(); + } + if let Some(known_ty) = + self.ctx.var_unification_table.inlined_probe_value(inner).known() + { + self.var_stack.push(inner); + let result = self.do_canonicalize_ty(known_ty.clone()); + self.var_stack.pop(); + result + } else { + let root = self.ctx.var_unification_table.find(inner); + let free_var = match tv { + InferTy::TypeVar(_) => InferTy::TypeVar(root), + InferTy::IntVar(_) => InferTy::IntVar(root), + InferTy::FloatVar(_) => InferTy::FloatVar(root), + InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root), + }; + let position = self.add(free_var); + Ty::Bound(position as u32) + } + } + _ => ty, + }) + } + + fn do_canonicalize_trait_ref(&mut self, mut trait_ref: TraitRef) -> TraitRef { + for ty in make_mut_slice(&mut trait_ref.substs.0) { + *ty = self.do_canonicalize_ty(ty.clone()); + } + trait_ref + } + + fn into_canonicalized(self, result: T) -> Canonicalized { + Canonicalized { + value: Canonical { value: result, num_vars: self.free_vars.len() }, + free_vars: self.free_vars, + } + } + + fn do_canonicalize_projection_ty(&mut self, mut projection_ty: ProjectionTy) -> ProjectionTy { + for ty in make_mut_slice(&mut projection_ty.parameters.0) { + *ty = self.do_canonicalize_ty(ty.clone()); + } + projection_ty + } + + fn do_canonicalize_projection_predicate( + &mut self, + projection: ProjectionPredicate, + ) -> ProjectionPredicate { + let ty = self.do_canonicalize_ty(projection.ty); + let projection_ty = self.do_canonicalize_projection_ty(projection.projection_ty); + + ProjectionPredicate { ty, projection_ty } + } + + // FIXME: add some point, we need to introduce a `Fold` trait that abstracts + // over all the things that can be canonicalized (like Chalk and rustc have) + + pub(crate) fn canonicalize_ty(mut self, ty: Ty) -> Canonicalized { + let result = self.do_canonicalize_ty(ty); + self.into_canonicalized(result) + } + + pub(crate) fn canonicalize_obligation( + mut self, + obligation: InEnvironment, + ) -> Canonicalized> { + let result = match obligation.value { + Obligation::Trait(tr) => Obligation::Trait(self.do_canonicalize_trait_ref(tr)), + Obligation::Projection(pr) => { + Obligation::Projection(self.do_canonicalize_projection_predicate(pr)) + } + }; + self.into_canonicalized(InEnvironment { + value: result, + environment: obligation.environment, + }) + } +} + +impl Canonicalized { + pub fn decanonicalize_ty(&self, mut ty: Ty) -> Ty { + ty.walk_mut_binders( + &mut |ty, binders| match ty { + &mut Ty::Bound(idx) => { + if idx as usize >= binders && (idx as usize - binders) < self.free_vars.len() { + *ty = Ty::Infer(self.free_vars[idx as usize - binders]); + } + } + _ => {} + }, + 0, + ); + ty + } + + pub fn apply_solution( + &self, + ctx: &mut InferenceContext<'_, impl HirDatabase>, + solution: Canonical>, + ) { + // the solution may contain new variables, which we need to convert to new inference vars + let new_vars = Substs((0..solution.num_vars).map(|_| ctx.new_type_var()).collect()); + for (i, ty) in solution.value.into_iter().enumerate() { + let var = self.free_vars[i]; + ctx.unify(&Ty::Infer(var), &ty.subst_bound_vars(&new_vars)); + } + } +} diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index 28859ba63..f25846326 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -1,4 +1,1134 @@ -//! Work in Progress: everything related to types, type inference and trait -//! solving. +//! The type system. We currently use this to infer types for completion, hover +//! information and various assists. +macro_rules! impl_froms { + ($e:ident: $($v:ident $(($($sv:ident),*))?),*) => { + $( + impl From<$v> for $e { + fn from(it: $v) -> $e { + $e::$v(it) + } + } + $($( + impl From<$sv> for $e { + fn from(it: $sv) -> $e { + $e::$v($v::$sv(it)) + } + } + )*)? + )* + } +} + +mod autoderef; pub mod primitive; +pub mod traits; +pub mod method_resolution; +mod op; +mod lower; +mod infer; +pub mod display; +pub(crate) mod utils; +pub mod db; +pub mod diagnostics; +pub mod expr; + +#[cfg(test)] +mod tests; +#[cfg(test)] +mod test_db; +mod marks; + +use std::ops::Deref; +use std::sync::Arc; +use std::{fmt, iter, mem}; + +use hir_def::{ + expr::ExprId, generics::GenericParams, type_ref::Mutability, AdtId, ContainerId, DefWithBodyId, + GenericDefId, HasModule, Lookup, TraitId, TypeAliasId, +}; +use hir_expand::name::Name; +use ra_db::{impl_intern_key, salsa, CrateId}; + +use crate::{ + db::HirDatabase, + primitive::{FloatTy, IntTy, Uncertain}, + utils::make_mut_slice, +}; +use display::{HirDisplay, HirFormatter}; + +pub use autoderef::autoderef; +pub use infer::{infer_query, InferTy, InferenceResult}; +pub use lower::CallableDef; +pub use lower::{callable_item_sig, TyDefId, ValueTyDefId}; +pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; + +/// A type constructor or type name: this might be something like the primitive +/// type `bool`, a struct like `Vec`, or things like function pointers or +/// tuples. +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum TypeCtor { + /// The primitive boolean type. Written as `bool`. + Bool, + + /// The primitive character type; holds a Unicode scalar value + /// (a non-surrogate code point). Written as `char`. + Char, + + /// A primitive integer type. For example, `i32`. + Int(Uncertain), + + /// A primitive floating-point type. For example, `f64`. + Float(Uncertain), + + /// Structures, enumerations and unions. + Adt(AdtId), + + /// The pointee of a string slice. Written as `str`. + Str, + + /// The pointee of an array slice. Written as `[T]`. + Slice, + + /// An array with the given length. Written as `[T; n]`. + Array, + + /// A raw pointer. Written as `*mut T` or `*const T` + RawPtr(Mutability), + + /// A reference; a pointer with an associated lifetime. Written as + /// `&'a mut T` or `&'a T`. + Ref(Mutability), + + /// The anonymous type of a function declaration/definition. Each + /// function has a unique type, which is output (for a function + /// named `foo` returning an `i32`) as `fn() -> i32 {foo}`. + /// + /// This includes tuple struct / enum variant constructors as well. + /// + /// For example the type of `bar` here: + /// + /// ``` + /// fn foo() -> i32 { 1 } + /// let bar = foo; // bar: fn() -> i32 {foo} + /// ``` + FnDef(CallableDef), + + /// A pointer to a function. Written as `fn() -> i32`. + /// + /// For example the type of `bar` here: + /// + /// ``` + /// fn foo() -> i32 { 1 } + /// let bar: fn() -> i32 = foo; + /// ``` + FnPtr { num_args: u16 }, + + /// The never type `!`. + Never, + + /// A tuple type. For example, `(i32, bool)`. + Tuple { cardinality: u16 }, + + /// Represents an associated item like `Iterator::Item`. This is used + /// when we have tried to normalize a projection like `T::Item` but + /// couldn't find a better representation. In that case, we generate + /// an **application type** like `(Iterator::Item)`. + AssociatedType(TypeAliasId), + + /// The type of a specific closure. + /// + /// The closure signature is stored in a `FnPtr` type in the first type + /// parameter. + Closure { def: DefWithBodyId, expr: ExprId }, +} + +/// This exists just for Chalk, because Chalk just has a single `StructId` where +/// we have different kinds of ADTs, primitive types and special type +/// constructors like tuples and function pointers. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct TypeCtorId(salsa::InternId); +impl_intern_key!(TypeCtorId); + +impl TypeCtor { + pub fn num_ty_params(self, db: &impl HirDatabase) -> usize { + match self { + TypeCtor::Bool + | TypeCtor::Char + | TypeCtor::Int(_) + | TypeCtor::Float(_) + | TypeCtor::Str + | TypeCtor::Never => 0, + TypeCtor::Slice + | TypeCtor::Array + | TypeCtor::RawPtr(_) + | TypeCtor::Ref(_) + | TypeCtor::Closure { .. } // 1 param representing the signature of the closure + => 1, + TypeCtor::Adt(adt) => { + let generic_params = db.generic_params(AdtId::from(adt).into()); + generic_params.count_params_including_parent() + } + TypeCtor::FnDef(callable) => { + let generic_params = db.generic_params(callable.into()); + generic_params.count_params_including_parent() + } + TypeCtor::AssociatedType(type_alias) => { + let generic_params = db.generic_params(type_alias.into()); + generic_params.count_params_including_parent() + } + TypeCtor::FnPtr { num_args } => num_args as usize + 1, + TypeCtor::Tuple { cardinality } => cardinality as usize, + } + } + + pub fn krate(self, db: &impl HirDatabase) -> Option { + match self { + TypeCtor::Bool + | TypeCtor::Char + | TypeCtor::Int(_) + | TypeCtor::Float(_) + | TypeCtor::Str + | TypeCtor::Never + | TypeCtor::Slice + | TypeCtor::Array + | TypeCtor::RawPtr(_) + | TypeCtor::Ref(_) + | TypeCtor::FnPtr { .. } + | TypeCtor::Tuple { .. } => None, + // Closure's krate is irrelevant for coherence I would think? + TypeCtor::Closure { .. } => None, + TypeCtor::Adt(adt) => Some(adt.module(db).krate), + TypeCtor::FnDef(callable) => Some(callable.krate(db)), + TypeCtor::AssociatedType(type_alias) => Some(type_alias.lookup(db).module(db).krate), + } + } + + pub fn as_generic_def(self) -> Option { + match self { + TypeCtor::Bool + | TypeCtor::Char + | TypeCtor::Int(_) + | TypeCtor::Float(_) + | TypeCtor::Str + | TypeCtor::Never + | TypeCtor::Slice + | TypeCtor::Array + | TypeCtor::RawPtr(_) + | TypeCtor::Ref(_) + | TypeCtor::FnPtr { .. } + | TypeCtor::Tuple { .. } + | TypeCtor::Closure { .. } => None, + TypeCtor::Adt(adt) => Some(adt.into()), + TypeCtor::FnDef(callable) => Some(callable.into()), + TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()), + } + } +} + +/// A nominal type with (maybe 0) type parameters. This might be a primitive +/// type like `bool`, a struct, tuple, function pointer, reference or +/// several other things. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct ApplicationTy { + pub ctor: TypeCtor, + pub parameters: Substs, +} + +/// A "projection" type corresponds to an (unnormalized) +/// projection like `>::Foo`. Note that the +/// trait and all its parameters are fully known. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct ProjectionTy { + pub associated_ty: TypeAliasId, + pub parameters: Substs, +} + +impl ProjectionTy { + pub fn trait_ref(&self, db: &impl HirDatabase) -> TraitRef { + TraitRef { trait_: self.trait_(db).into(), substs: self.parameters.clone() } + } + + fn trait_(&self, db: &impl HirDatabase) -> TraitId { + match self.associated_ty.lookup(db).container { + ContainerId::TraitId(it) => it, + _ => panic!("projection ty without parent trait"), + } + } +} + +impl TypeWalk for ProjectionTy { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + self.parameters.walk(f); + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + self.parameters.walk_mut_binders(f, binders); + } +} + +/// A type. +/// +/// See also the `TyKind` enum in rustc (librustc/ty/sty.rs), which represents +/// the same thing (but in a different way). +/// +/// This should be cheap to clone. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub enum Ty { + /// A nominal type with (maybe 0) type parameters. This might be a primitive + /// type like `bool`, a struct, tuple, function pointer, reference or + /// several other things. + Apply(ApplicationTy), + + /// A "projection" type corresponds to an (unnormalized) + /// projection like `>::Foo`. Note that the + /// trait and all its parameters are fully known. + Projection(ProjectionTy), + + /// A type parameter; for example, `T` in `fn f(x: T) {} + Param { + /// The index of the parameter (starting with parameters from the + /// surrounding impl, then the current function). + idx: u32, + /// The name of the parameter, for displaying. + // FIXME get rid of this + name: Name, + }, + + /// A bound type variable. Used during trait resolution to represent Chalk + /// variables, and in `Dyn` and `Opaque` bounds to represent the `Self` type. + Bound(u32), + + /// A type variable used during type checking. Not to be confused with a + /// type parameter. + Infer(InferTy), + + /// A trait object (`dyn Trait` or bare `Trait` in pre-2018 Rust). + /// + /// The predicates are quantified over the `Self` type, i.e. `Ty::Bound(0)` + /// represents the `Self` type inside the bounds. This is currently + /// implicit; Chalk has the `Binders` struct to make it explicit, but it + /// didn't seem worth the overhead yet. + Dyn(Arc<[GenericPredicate]>), + + /// An opaque type (`impl Trait`). + /// + /// The predicates are quantified over the `Self` type; see `Ty::Dyn` for + /// more. + Opaque(Arc<[GenericPredicate]>), + + /// A placeholder for a type which could not be computed; this is propagated + /// to avoid useless error messages. Doubles as a placeholder where type + /// variables are inserted before type checking, since we want to try to + /// infer a better type here anyway -- for the IDE use case, we want to try + /// to infer as much as possible even in the presence of type errors. + Unknown, +} + +/// A list of substitutions for generic parameters. +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct Substs(Arc<[Ty]>); + +impl TypeWalk for Substs { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + for t in self.0.iter() { + t.walk(f); + } + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + for t in make_mut_slice(&mut self.0) { + t.walk_mut_binders(f, binders); + } + } +} + +impl Substs { + pub fn empty() -> Substs { + Substs(Arc::new([])) + } + + pub fn single(ty: Ty) -> Substs { + Substs(Arc::new([ty])) + } + + pub fn prefix(&self, n: usize) -> Substs { + Substs(self.0[..std::cmp::min(self.0.len(), n)].into()) + } + + pub fn as_single(&self) -> &Ty { + if self.0.len() != 1 { + panic!("expected substs of len 1, got {:?}", self); + } + &self.0[0] + } + + /// Return Substs that replace each parameter by itself (i.e. `Ty::Param`). + pub fn identity(generic_params: &GenericParams) -> Substs { + Substs( + generic_params + .params_including_parent() + .into_iter() + .map(|p| Ty::Param { idx: p.idx, name: p.name.clone() }) + .collect(), + ) + } + + /// Return Substs that replace each parameter by a bound variable. + pub fn bound_vars(generic_params: &GenericParams) -> Substs { + Substs( + generic_params + .params_including_parent() + .into_iter() + .map(|p| Ty::Bound(p.idx)) + .collect(), + ) + } + + pub fn build_for_def(db: &impl HirDatabase, def: impl Into) -> SubstsBuilder { + let def = def.into(); + let params = db.generic_params(def); + let param_count = params.count_params_including_parent(); + Substs::builder(param_count) + } + + pub fn build_for_generics(generic_params: &GenericParams) -> SubstsBuilder { + Substs::builder(generic_params.count_params_including_parent()) + } + + pub fn build_for_type_ctor(db: &impl HirDatabase, type_ctor: TypeCtor) -> SubstsBuilder { + Substs::builder(type_ctor.num_ty_params(db)) + } + + fn builder(param_count: usize) -> SubstsBuilder { + SubstsBuilder { vec: Vec::with_capacity(param_count), param_count } + } +} + +#[derive(Debug, Clone)] +pub struct SubstsBuilder { + vec: Vec, + param_count: usize, +} + +impl SubstsBuilder { + pub fn build(self) -> Substs { + assert_eq!(self.vec.len(), self.param_count); + Substs(self.vec.into()) + } + + pub fn push(mut self, ty: Ty) -> Self { + self.vec.push(ty); + self + } + + fn remaining(&self) -> usize { + self.param_count - self.vec.len() + } + + pub fn fill_with_bound_vars(self, starting_from: u32) -> Self { + self.fill((starting_from..).map(Ty::Bound)) + } + + pub fn fill_with_params(self) -> Self { + let start = self.vec.len() as u32; + self.fill((start..).map(|idx| Ty::Param { idx, name: Name::missing() })) + } + + pub fn fill_with_unknown(self) -> Self { + self.fill(iter::repeat(Ty::Unknown)) + } + + pub fn fill(mut self, filler: impl Iterator) -> Self { + self.vec.extend(filler.take(self.remaining())); + assert_eq!(self.remaining(), 0); + self + } + + pub fn use_parent_substs(mut self, parent_substs: &Substs) -> Self { + assert!(self.vec.is_empty()); + assert!(parent_substs.len() <= self.param_count); + self.vec.extend(parent_substs.iter().cloned()); + self + } +} + +impl Deref for Substs { + type Target = [Ty]; + + fn deref(&self) -> &[Ty] { + &self.0 + } +} + +/// A trait with type parameters. This includes the `Self`, so this represents a concrete type implementing the trait. +/// Name to be bikeshedded: TraitBound? TraitImplements? +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct TraitRef { + /// FIXME name? + pub trait_: TraitId, + pub substs: Substs, +} + +impl TraitRef { + pub fn self_ty(&self) -> &Ty { + &self.substs[0] + } +} + +impl TypeWalk for TraitRef { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + self.substs.walk(f); + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + self.substs.walk_mut_binders(f, binders); + } +} + +/// Like `generics::WherePredicate`, but with resolved types: A condition on the +/// parameters of a generic item. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum GenericPredicate { + /// The given trait needs to be implemented for its type parameters. + Implemented(TraitRef), + /// An associated type bindings like in `Iterator`. + Projection(ProjectionPredicate), + /// We couldn't resolve the trait reference. (If some type parameters can't + /// be resolved, they will just be Unknown). + Error, +} + +impl GenericPredicate { + pub fn is_error(&self) -> bool { + match self { + GenericPredicate::Error => true, + _ => false, + } + } + + pub fn is_implemented(&self) -> bool { + match self { + GenericPredicate::Implemented(_) => true, + _ => false, + } + } + + pub fn trait_ref(&self, db: &impl HirDatabase) -> Option { + match self { + GenericPredicate::Implemented(tr) => Some(tr.clone()), + GenericPredicate::Projection(proj) => Some(proj.projection_ty.trait_ref(db)), + GenericPredicate::Error => None, + } + } +} + +impl TypeWalk for GenericPredicate { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + match self { + GenericPredicate::Implemented(trait_ref) => trait_ref.walk(f), + GenericPredicate::Projection(projection_pred) => projection_pred.walk(f), + GenericPredicate::Error => {} + } + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + match self { + GenericPredicate::Implemented(trait_ref) => trait_ref.walk_mut_binders(f, binders), + GenericPredicate::Projection(projection_pred) => { + projection_pred.walk_mut_binders(f, binders) + } + GenericPredicate::Error => {} + } + } +} + +/// Basically a claim (currently not validated / checked) that the contained +/// type / trait ref contains no inference variables; any inference variables it +/// contained have been replaced by bound variables, and `num_vars` tells us how +/// many there are. This is used to erase irrelevant differences between types +/// before using them in queries. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct Canonical { + pub value: T, + pub num_vars: usize, +} + +/// A function signature as seen by type inference: Several parameter types and +/// one return type. +#[derive(Clone, PartialEq, Eq, Debug)] +pub struct FnSig { + params_and_return: Arc<[Ty]>, +} + +impl FnSig { + pub fn from_params_and_return(mut params: Vec, ret: Ty) -> FnSig { + params.push(ret); + FnSig { params_and_return: params.into() } + } + + pub fn from_fn_ptr_substs(substs: &Substs) -> FnSig { + FnSig { params_and_return: Arc::clone(&substs.0) } + } + + pub fn params(&self) -> &[Ty] { + &self.params_and_return[0..self.params_and_return.len() - 1] + } + + pub fn ret(&self) -> &Ty { + &self.params_and_return[self.params_and_return.len() - 1] + } +} + +impl TypeWalk for FnSig { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + for t in self.params_and_return.iter() { + t.walk(f); + } + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + for t in make_mut_slice(&mut self.params_and_return) { + t.walk_mut_binders(f, binders); + } + } +} + +impl Ty { + pub fn simple(ctor: TypeCtor) -> Ty { + Ty::Apply(ApplicationTy { ctor, parameters: Substs::empty() }) + } + pub fn apply_one(ctor: TypeCtor, param: Ty) -> Ty { + Ty::Apply(ApplicationTy { ctor, parameters: Substs::single(param) }) + } + pub fn apply(ctor: TypeCtor, parameters: Substs) -> Ty { + Ty::Apply(ApplicationTy { ctor, parameters }) + } + pub fn unit() -> Self { + Ty::apply(TypeCtor::Tuple { cardinality: 0 }, Substs::empty()) + } + + pub fn as_reference(&self) -> Option<(&Ty, Mutability)> { + match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::Ref(mutability), parameters }) => { + Some((parameters.as_single(), *mutability)) + } + _ => None, + } + } + + pub fn as_adt(&self) -> Option<(AdtId, &Substs)> { + match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::Adt(adt_def), parameters }) => { + Some((*adt_def, parameters)) + } + _ => None, + } + } + + pub fn as_tuple(&self) -> Option<&Substs> { + match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::Tuple { .. }, parameters }) => { + Some(parameters) + } + _ => None, + } + } + + pub fn as_callable(&self) -> Option<(CallableDef, &Substs)> { + match self { + Ty::Apply(ApplicationTy { ctor: TypeCtor::FnDef(callable_def), parameters }) => { + Some((*callable_def, parameters)) + } + _ => None, + } + } + + fn builtin_deref(&self) -> Option { + match self { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Ref(..) => Some(Ty::clone(a_ty.parameters.as_single())), + TypeCtor::RawPtr(..) => Some(Ty::clone(a_ty.parameters.as_single())), + _ => None, + }, + _ => None, + } + } + + fn callable_sig(&self, db: &impl HirDatabase) -> Option { + match self { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::FnPtr { .. } => Some(FnSig::from_fn_ptr_substs(&a_ty.parameters)), + TypeCtor::FnDef(def) => { + let sig = db.callable_item_signature(def); + Some(sig.subst(&a_ty.parameters)) + } + TypeCtor::Closure { .. } => { + let sig_param = &a_ty.parameters[0]; + sig_param.callable_sig(db) + } + _ => None, + }, + _ => None, + } + } + + /// If this is a type with type parameters (an ADT or function), replaces + /// the `Substs` for these type parameters with the given ones. (So e.g. if + /// `self` is `Option<_>` and the substs contain `u32`, we'll have + /// `Option` afterwards.) + pub fn apply_substs(self, substs: Substs) -> Ty { + match self { + Ty::Apply(ApplicationTy { ctor, parameters: previous_substs }) => { + assert_eq!(previous_substs.len(), substs.len()); + Ty::Apply(ApplicationTy { ctor, parameters: substs }) + } + _ => self, + } + } + + /// Returns the type parameters of this type if it has some (i.e. is an ADT + /// or function); so if `self` is `Option`, this returns the `u32`. + pub fn substs(&self) -> Option { + match self { + Ty::Apply(ApplicationTy { parameters, .. }) => Some(parameters.clone()), + _ => None, + } + } + + /// If this is an `impl Trait` or `dyn Trait`, returns that trait. + pub fn inherent_trait(&self) -> Option { + match self { + Ty::Dyn(predicates) | Ty::Opaque(predicates) => { + predicates.iter().find_map(|pred| match pred { + GenericPredicate::Implemented(tr) => Some(tr.trait_), + _ => None, + }) + } + _ => None, + } + } +} + +/// This allows walking structures that contain types to do something with those +/// types, similar to Chalk's `Fold` trait. +pub trait TypeWalk { + fn walk(&self, f: &mut impl FnMut(&Ty)); + fn walk_mut(&mut self, f: &mut impl FnMut(&mut Ty)) { + self.walk_mut_binders(&mut |ty, _binders| f(ty), 0); + } + /// Walk the type, counting entered binders. + /// + /// `Ty::Bound` variables use DeBruijn indexing, which means that 0 refers + /// to the innermost binder, 1 to the next, etc.. So when we want to + /// substitute a certain bound variable, we can't just walk the whole type + /// and blindly replace each instance of a certain index; when we 'enter' + /// things that introduce new bound variables, we have to keep track of + /// that. Currently, the only thing that introduces bound variables on our + /// side are `Ty::Dyn` and `Ty::Opaque`, which each introduce a bound + /// variable for the self type. + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize); + + fn fold(mut self, f: &mut impl FnMut(Ty) -> Ty) -> Self + where + Self: Sized, + { + self.walk_mut(&mut |ty_mut| { + let ty = mem::replace(ty_mut, Ty::Unknown); + *ty_mut = f(ty); + }); + self + } + + /// Replaces type parameters in this type using the given `Substs`. (So e.g. + /// if `self` is `&[T]`, where type parameter T has index 0, and the + /// `Substs` contain `u32` at index 0, we'll have `&[u32]` afterwards.) + fn subst(self, substs: &Substs) -> Self + where + Self: Sized, + { + self.fold(&mut |ty| match ty { + Ty::Param { idx, name } => { + substs.get(idx as usize).cloned().unwrap_or(Ty::Param { idx, name }) + } + ty => ty, + }) + } + + /// Substitutes `Ty::Bound` vars (as opposed to type parameters). + fn subst_bound_vars(mut self, substs: &Substs) -> Self + where + Self: Sized, + { + self.walk_mut_binders( + &mut |ty, binders| match ty { + &mut Ty::Bound(idx) => { + if idx as usize >= binders && (idx as usize - binders) < substs.len() { + *ty = substs.0[idx as usize - binders].clone(); + } + } + _ => {} + }, + 0, + ); + self + } + + /// Shifts up `Ty::Bound` vars by `n`. + fn shift_bound_vars(self, n: i32) -> Self + where + Self: Sized, + { + self.fold(&mut |ty| match ty { + Ty::Bound(idx) => { + assert!(idx as i32 >= -n); + Ty::Bound((idx as i32 + n) as u32) + } + ty => ty, + }) + } +} + +impl TypeWalk for Ty { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + match self { + Ty::Apply(a_ty) => { + for t in a_ty.parameters.iter() { + t.walk(f); + } + } + Ty::Projection(p_ty) => { + for t in p_ty.parameters.iter() { + t.walk(f); + } + } + Ty::Dyn(predicates) | Ty::Opaque(predicates) => { + for p in predicates.iter() { + p.walk(f); + } + } + Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} + } + f(self); + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + match self { + Ty::Apply(a_ty) => { + a_ty.parameters.walk_mut_binders(f, binders); + } + Ty::Projection(p_ty) => { + p_ty.parameters.walk_mut_binders(f, binders); + } + Ty::Dyn(predicates) | Ty::Opaque(predicates) => { + for p in make_mut_slice(predicates) { + p.walk_mut_binders(f, binders + 1); + } + } + Ty::Param { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} + } + f(self, binders); + } +} + +impl HirDisplay for &Ty { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + HirDisplay::hir_fmt(*self, f) + } +} + +impl HirDisplay for ApplicationTy { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + if f.should_truncate() { + return write!(f, "…"); + } + + match self.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 => { + let t = self.parameters.as_single(); + write!(f, "[{}]", t.display(f.db))?; + } + TypeCtor::Array => { + let t = self.parameters.as_single(); + write!(f, "[{};_]", t.display(f.db))?; + } + TypeCtor::RawPtr(m) => { + let t = self.parameters.as_single(); + write!(f, "*{}{}", m.as_keyword_for_ptr(), t.display(f.db))?; + } + TypeCtor::Ref(m) => { + let t = self.parameters.as_single(); + write!(f, "&{}{}", m.as_keyword_for_ref(), t.display(f.db))?; + } + TypeCtor::Never => write!(f, "!")?, + TypeCtor::Tuple { .. } => { + let ts = &self.parameters; + if ts.len() == 1 { + write!(f, "({},)", ts[0].display(f.db))?; + } else { + write!(f, "(")?; + f.write_joined(&*ts.0, ", ")?; + write!(f, ")")?; + } + } + TypeCtor::FnPtr { .. } => { + let sig = FnSig::from_fn_ptr_substs(&self.parameters); + write!(f, "fn(")?; + f.write_joined(sig.params(), ", ")?; + write!(f, ") -> {}", sig.ret().display(f.db))?; + } + TypeCtor::FnDef(def) => { + let sig = f.db.callable_item_signature(def); + let name = match def { + CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), + CallableDef::StructId(s) => { + f.db.struct_data(s).name.clone().unwrap_or_else(Name::missing) + } + CallableDef::EnumVariantId(e) => { + let enum_data = f.db.enum_data(e.parent); + enum_data.variants[e.local_id].name.clone().unwrap_or_else(Name::missing) + } + }; + match def { + CallableDef::FunctionId(_) => write!(f, "fn {}", name)?, + CallableDef::StructId(_) | CallableDef::EnumVariantId(_) => { + write!(f, "{}", name)? + } + } + if self.parameters.len() > 0 { + write!(f, "<")?; + f.write_joined(&*self.parameters.0, ", ")?; + write!(f, ">")?; + } + write!(f, "(")?; + f.write_joined(sig.params(), ", ")?; + write!(f, ") -> {}", sig.ret().display(f.db))?; + } + TypeCtor::Adt(def_id) => { + let name = match def_id { + AdtId::StructId(it) => f.db.struct_data(it).name.clone(), + AdtId::UnionId(it) => f.db.union_data(it).name.clone(), + AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), + } + .unwrap_or_else(Name::missing); + write!(f, "{}", name)?; + if self.parameters.len() > 0 { + write!(f, "<")?; + f.write_joined(&*self.parameters.0, ", ")?; + write!(f, ">")?; + } + } + TypeCtor::AssociatedType(type_alias) => { + let trait_ = match type_alias.lookup(f.db).container { + ContainerId::TraitId(it) => it, + _ => panic!("not an associated type"), + }; + let trait_name = f.db.trait_data(trait_).name.clone().unwrap_or_else(Name::missing); + let name = f.db.type_alias_data(type_alias).name.clone(); + write!(f, "{}::{}", trait_name, name)?; + if self.parameters.len() > 0 { + write!(f, "<")?; + f.write_joined(&*self.parameters.0, ", ")?; + write!(f, ">")?; + } + } + TypeCtor::Closure { .. } => { + let sig = self.parameters[0] + .callable_sig(f.db) + .expect("first closure parameter should contain signature"); + write!(f, "|")?; + f.write_joined(sig.params(), ", ")?; + write!(f, "| -> {}", sig.ret().display(f.db))?; + } + } + Ok(()) + } +} + +impl HirDisplay for ProjectionTy { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + if f.should_truncate() { + return write!(f, "…"); + } + + let trait_name = + f.db.trait_data(self.trait_(f.db)).name.clone().unwrap_or_else(Name::missing); + write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; + if self.parameters.len() > 1 { + write!(f, "<")?; + f.write_joined(&self.parameters[1..], ", ")?; + write!(f, ">")?; + } + write!(f, ">::{}", f.db.type_alias_data(self.associated_ty).name)?; + Ok(()) + } +} + +impl HirDisplay for Ty { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + if f.should_truncate() { + return write!(f, "…"); + } + + match self { + Ty::Apply(a_ty) => a_ty.hir_fmt(f)?, + Ty::Projection(p_ty) => p_ty.hir_fmt(f)?, + Ty::Param { name, .. } => write!(f, "{}", name)?, + Ty::Bound(idx) => write!(f, "?{}", idx)?, + Ty::Dyn(predicates) | Ty::Opaque(predicates) => { + match self { + Ty::Dyn(_) => write!(f, "dyn ")?, + Ty::Opaque(_) => write!(f, "impl ")?, + _ => unreachable!(), + }; + // Note: This code is written to produce nice results (i.e. + // corresponding to surface Rust) for types that can occur in + // actual Rust. It will have weird results if the predicates + // aren't as expected (i.e. self types = $0, projection + // predicates for a certain trait come after the Implemented + // predicate for that trait). + let mut first = true; + let mut angle_open = false; + for p in predicates.iter() { + match p { + GenericPredicate::Implemented(trait_ref) => { + if angle_open { + write!(f, ">")?; + } + if !first { + write!(f, " + ")?; + } + // We assume that the self type is $0 (i.e. the + // existential) here, which is the only thing that's + // possible in actual Rust, and hence don't print it + write!( + f, + "{}", + f.db.trait_data(trait_ref.trait_) + .name + .clone() + .unwrap_or_else(Name::missing) + )?; + if trait_ref.substs.len() > 1 { + write!(f, "<")?; + f.write_joined(&trait_ref.substs[1..], ", ")?; + // there might be assoc type bindings, so we leave the angle brackets open + angle_open = true; + } + } + GenericPredicate::Projection(projection_pred) => { + // in types in actual Rust, these will always come + // after the corresponding Implemented predicate + if angle_open { + write!(f, ", ")?; + } else { + write!(f, "<")?; + angle_open = true; + } + let name = + f.db.type_alias_data(projection_pred.projection_ty.associated_ty) + .name + .clone(); + write!(f, "{} = ", name)?; + projection_pred.ty.hir_fmt(f)?; + } + GenericPredicate::Error => { + if angle_open { + // impl Trait + write!(f, ", ")?; + } else if !first { + // impl Trait + {error} + write!(f, " + ")?; + } + p.hir_fmt(f)?; + } + } + first = false; + } + if angle_open { + write!(f, ">")?; + } + } + Ty::Unknown => write!(f, "{{unknown}}")?, + Ty::Infer(..) => write!(f, "_")?, + } + Ok(()) + } +} + +impl TraitRef { + fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result { + if f.should_truncate() { + return write!(f, "…"); + } + + self.substs[0].hir_fmt(f)?; + if use_as { + write!(f, " as ")?; + } else { + write!(f, ": ")?; + } + write!(f, "{}", f.db.trait_data(self.trait_).name.clone().unwrap_or_else(Name::missing))?; + if self.substs.len() > 1 { + write!(f, "<")?; + f.write_joined(&self.substs[1..], ", ")?; + write!(f, ">")?; + } + Ok(()) + } +} + +impl HirDisplay for TraitRef { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + self.hir_fmt_ext(f, false) + } +} + +impl HirDisplay for &GenericPredicate { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + HirDisplay::hir_fmt(*self, f) + } +} + +impl HirDisplay for GenericPredicate { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + if f.should_truncate() { + return write!(f, "…"); + } + + match self { + GenericPredicate::Implemented(trait_ref) => trait_ref.hir_fmt(f)?, + GenericPredicate::Projection(projection_pred) => { + write!(f, "<")?; + projection_pred.projection_ty.trait_ref(f.db).hir_fmt_ext(f, true)?; + write!( + f, + ">::{} = {}", + f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name, + projection_pred.ty.display(f.db) + )?; + } + GenericPredicate::Error => write!(f, "{{error}}")?, + } + Ok(()) + } +} + +impl HirDisplay for Obligation { + fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result { + match self { + Obligation::Trait(tr) => write!(f, "Implements({})", tr.display(f.db)), + Obligation::Projection(proj) => write!( + f, + "Normalize({} => {})", + proj.projection_ty.display(f.db), + proj.ty.display(f.db) + ), + } + } +} diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs new file mode 100644 index 000000000..53d955a12 --- /dev/null +++ b/crates/ra_hir_ty/src/lower.rs @@ -0,0 +1,753 @@ +//! Methods for lowering the HIR to types. There are two main cases here: +//! +//! - Lowering a type reference like `&usize` or `Option` to a +//! type: The entry point for this is `Ty::from_hir`. +//! - Building the type for an item: This happens through the `type_for_def` query. +//! +//! This usually involves resolving names, collecting generic arguments etc. +use std::iter; +use std::sync::Arc; + +use hir_def::{ + builtin_type::BuiltinType, + generics::WherePredicate, + path::{GenericArg, Path, PathKind, PathSegment}, + resolver::{HasResolver, Resolver, TypeNs}, + type_ref::{TypeBound, TypeRef}, + AdtId, AstItemDef, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, + LocalStructFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, UnionId, VariantId, +}; +use ra_arena::map::ArenaMap; +use ra_db::CrateId; + +use super::{ + FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, + Ty, TypeCtor, TypeWalk, +}; +use crate::{ + db::HirDatabase, + primitive::{FloatTy, IntTy}, + utils::make_mut_slice, + utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, +}; + +impl Ty { + pub fn from_hir(db: &impl HirDatabase, resolver: &Resolver, type_ref: &TypeRef) -> Self { + match type_ref { + TypeRef::Never => Ty::simple(TypeCtor::Never), + TypeRef::Tuple(inner) => { + let inner_tys: Arc<[Ty]> = + inner.iter().map(|tr| Ty::from_hir(db, resolver, tr)).collect(); + Ty::apply( + TypeCtor::Tuple { cardinality: inner_tys.len() as u16 }, + Substs(inner_tys), + ) + } + TypeRef::Path(path) => Ty::from_hir_path(db, resolver, path), + TypeRef::RawPtr(inner, mutability) => { + let inner_ty = Ty::from_hir(db, resolver, inner); + Ty::apply_one(TypeCtor::RawPtr(*mutability), inner_ty) + } + TypeRef::Array(inner) => { + let inner_ty = Ty::from_hir(db, resolver, inner); + Ty::apply_one(TypeCtor::Array, inner_ty) + } + TypeRef::Slice(inner) => { + let inner_ty = Ty::from_hir(db, resolver, inner); + Ty::apply_one(TypeCtor::Slice, inner_ty) + } + TypeRef::Reference(inner, mutability) => { + let inner_ty = Ty::from_hir(db, resolver, inner); + Ty::apply_one(TypeCtor::Ref(*mutability), inner_ty) + } + TypeRef::Placeholder => Ty::Unknown, + TypeRef::Fn(params) => { + let sig = Substs(params.iter().map(|tr| Ty::from_hir(db, resolver, tr)).collect()); + Ty::apply(TypeCtor::FnPtr { num_args: sig.len() as u16 - 1 }, sig) + } + TypeRef::DynTrait(bounds) => { + let self_ty = Ty::Bound(0); + let predicates = bounds + .iter() + .flat_map(|b| { + GenericPredicate::from_type_bound(db, resolver, b, self_ty.clone()) + }) + .collect(); + Ty::Dyn(predicates) + } + TypeRef::ImplTrait(bounds) => { + let self_ty = Ty::Bound(0); + let predicates = bounds + .iter() + .flat_map(|b| { + GenericPredicate::from_type_bound(db, resolver, b, self_ty.clone()) + }) + .collect(); + Ty::Opaque(predicates) + } + TypeRef::Error => Ty::Unknown, + } + } + + /// This is only for `generic_predicates_for_param`, where we can't just + /// lower the self types of the predicates since that could lead to cycles. + /// So we just check here if the `type_ref` resolves to a generic param, and which. + fn from_hir_only_param( + db: &impl HirDatabase, + resolver: &Resolver, + type_ref: &TypeRef, + ) -> Option { + let path = match type_ref { + TypeRef::Path(path) => path, + _ => return None, + }; + if let PathKind::Type(_) = &path.kind { + return None; + } + if path.segments.len() > 1 { + return None; + } + let resolution = match resolver.resolve_path_in_type_ns(db, path) { + Some((it, None)) => it, + _ => return None, + }; + if let TypeNs::GenericParam(idx) = resolution { + Some(idx) + } else { + None + } + } + + pub(crate) fn from_type_relative_path( + db: &impl HirDatabase, + resolver: &Resolver, + ty: Ty, + remaining_segments: &[PathSegment], + ) -> Ty { + if remaining_segments.len() == 1 { + // resolve unselected assoc types + let segment = &remaining_segments[0]; + Ty::select_associated_type(db, resolver, ty, segment) + } else if remaining_segments.len() > 1 { + // FIXME report error (ambiguous associated type) + Ty::Unknown + } else { + ty + } + } + + pub(crate) fn from_partly_resolved_hir_path( + db: &impl HirDatabase, + resolver: &Resolver, + resolution: TypeNs, + resolved_segment: &PathSegment, + remaining_segments: &[PathSegment], + ) -> Ty { + let ty = match resolution { + TypeNs::TraitId(trait_) => { + let trait_ref = + TraitRef::from_resolved_path(db, resolver, trait_, resolved_segment, None); + return if remaining_segments.len() == 1 { + let segment = &remaining_segments[0]; + let associated_ty = associated_type_by_name_including_super_traits( + db, + trait_ref.trait_, + &segment.name, + ); + match associated_ty { + Some(associated_ty) => { + // FIXME handle type parameters on the segment + Ty::Projection(ProjectionTy { + associated_ty, + parameters: trait_ref.substs, + }) + } + None => { + // FIXME: report error (associated type not found) + Ty::Unknown + } + } + } else if remaining_segments.len() > 1 { + // FIXME report error (ambiguous associated type) + Ty::Unknown + } else { + Ty::Dyn(Arc::new([GenericPredicate::Implemented(trait_ref)])) + }; + } + TypeNs::GenericParam(idx) => { + // FIXME: maybe return name in resolution? + let name = resolved_segment.name.clone(); + Ty::Param { idx, name } + } + TypeNs::SelfType(impl_id) => { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + Ty::from_hir(db, &resolver, &impl_data.target_type) + } + TypeNs::AdtSelfType(adt) => db.ty(adt.into()), + + TypeNs::AdtId(it) => Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()), + TypeNs::BuiltinType(it) => { + Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()) + } + TypeNs::TypeAliasId(it) => { + Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()) + } + // FIXME: report error + TypeNs::EnumVariantId(_) => return Ty::Unknown, + }; + + Ty::from_type_relative_path(db, resolver, ty, remaining_segments) + } + + pub(crate) fn from_hir_path(db: &impl HirDatabase, resolver: &Resolver, path: &Path) -> Ty { + // Resolve the path (in type namespace) + if let PathKind::Type(type_ref) = &path.kind { + let ty = Ty::from_hir(db, resolver, &type_ref); + let remaining_segments = &path.segments[..]; + return Ty::from_type_relative_path(db, resolver, ty, remaining_segments); + } + let (resolution, remaining_index) = match resolver.resolve_path_in_type_ns(db, path) { + Some(it) => it, + None => return Ty::Unknown, + }; + let (resolved_segment, remaining_segments) = match remaining_index { + None => ( + path.segments.last().expect("resolved path has at least one element"), + &[] as &[PathSegment], + ), + Some(i) => (&path.segments[i - 1], &path.segments[i..]), + }; + Ty::from_partly_resolved_hir_path( + db, + resolver, + resolution, + resolved_segment, + remaining_segments, + ) + } + + fn select_associated_type( + db: &impl HirDatabase, + resolver: &Resolver, + self_ty: Ty, + segment: &PathSegment, + ) -> Ty { + let param_idx = match self_ty { + Ty::Param { idx, .. } => idx, + _ => return Ty::Unknown, // Error: Ambiguous associated type + }; + let def = match resolver.generic_def() { + Some(def) => def, + None => return Ty::Unknown, // this can't actually happen + }; + let predicates = db.generic_predicates_for_param(def.into(), param_idx); + let traits_from_env = predicates.iter().filter_map(|pred| match pred { + GenericPredicate::Implemented(tr) if tr.self_ty() == &self_ty => Some(tr.trait_), + _ => None, + }); + let traits = traits_from_env.flat_map(|t| all_super_traits(db, t)); + for t in traits { + if let Some(associated_ty) = db.trait_data(t).associated_type_by_name(&segment.name) { + let substs = + Substs::build_for_def(db, t).push(self_ty.clone()).fill_with_unknown().build(); + // FIXME handle type parameters on the segment + return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); + } + } + Ty::Unknown + } + + fn from_hir_path_inner( + db: &impl HirDatabase, + resolver: &Resolver, + segment: &PathSegment, + typable: TyDefId, + ) -> Ty { + let generic_def = match typable { + TyDefId::BuiltinType(_) => None, + TyDefId::AdtId(it) => Some(it.into()), + TyDefId::TypeAliasId(it) => Some(it.into()), + }; + let substs = substs_from_path_segment(db, resolver, segment, generic_def, false); + db.ty(typable).subst(&substs) + } + + /// Collect generic arguments from a path into a `Substs`. See also + /// `create_substs_for_ast_path` and `def_to_ty` in rustc. + pub(super) fn substs_from_path( + db: &impl HirDatabase, + resolver: &Resolver, + path: &Path, + // Note that we don't call `db.value_type(resolved)` here, + // `ValueTyDefId` is just a convenient way to pass generics and + // special-case enum variants + resolved: ValueTyDefId, + ) -> Substs { + let last = path.segments.last().expect("path should have at least one segment"); + let (segment, generic_def) = match resolved { + ValueTyDefId::FunctionId(it) => (last, Some(it.into())), + ValueTyDefId::StructId(it) => (last, Some(it.into())), + ValueTyDefId::ConstId(it) => (last, Some(it.into())), + ValueTyDefId::StaticId(_) => (last, None), + ValueTyDefId::EnumVariantId(var) => { + // the generic args for an enum variant may be either specified + // on the segment referring to the enum, or on the segment + // referring to the variant. So `Option::::None` and + // `Option::None::` are both allowed (though the former is + // preferred). See also `def_ids_for_path_segments` in rustc. + let len = path.segments.len(); + let segment = if len >= 2 && path.segments[len - 2].args_and_bindings.is_some() { + // Option::::None + &path.segments[len - 2] + } else { + // Option::None:: + last + }; + (segment, Some(var.parent.into())) + } + }; + substs_from_path_segment(db, resolver, segment, generic_def, false) + } +} + +pub(super) fn substs_from_path_segment( + db: &impl HirDatabase, + resolver: &Resolver, + segment: &PathSegment, + def_generic: Option, + add_self_param: bool, +) -> Substs { + let mut substs = Vec::new(); + let def_generics = def_generic.map(|def| db.generic_params(def.into())); + + let (parent_param_count, param_count) = + def_generics.map_or((0, 0), |g| (g.count_parent_params(), g.params.len())); + substs.extend(iter::repeat(Ty::Unknown).take(parent_param_count)); + if add_self_param { + // FIXME this add_self_param argument is kind of a hack: Traits have the + // Self type as an implicit first type parameter, but it can't be + // actually provided in the type arguments + // (well, actually sometimes it can, in the form of type-relative paths: `::default()`) + substs.push(Ty::Unknown); + } + if let Some(generic_args) = &segment.args_and_bindings { + // if args are provided, it should be all of them, but we can't rely on that + let self_param_correction = if add_self_param { 1 } else { 0 }; + let param_count = param_count - self_param_correction; + for arg in generic_args.args.iter().take(param_count) { + match arg { + GenericArg::Type(type_ref) => { + let ty = Ty::from_hir(db, resolver, type_ref); + substs.push(ty); + } + } + } + } + // add placeholders for args that were not provided + let supplied_params = substs.len(); + for _ in supplied_params..parent_param_count + param_count { + substs.push(Ty::Unknown); + } + assert_eq!(substs.len(), parent_param_count + param_count); + + // handle defaults + if let Some(def_generic) = def_generic { + let default_substs = db.generic_defaults(def_generic.into()); + assert_eq!(substs.len(), default_substs.len()); + + for (i, default_ty) in default_substs.iter().enumerate() { + if substs[i] == Ty::Unknown { + substs[i] = default_ty.clone(); + } + } + } + + Substs(substs.into()) +} + +impl TraitRef { + pub(crate) fn from_path( + db: &impl HirDatabase, + resolver: &Resolver, + path: &Path, + explicit_self_ty: Option, + ) -> Option { + let resolved = match resolver.resolve_path_in_type_ns_fully(db, &path)? { + TypeNs::TraitId(tr) => tr, + _ => return None, + }; + let segment = path.segments.last().expect("path should have at least one segment"); + Some(TraitRef::from_resolved_path(db, resolver, resolved.into(), segment, explicit_self_ty)) + } + + pub(super) fn from_resolved_path( + db: &impl HirDatabase, + resolver: &Resolver, + resolved: TraitId, + segment: &PathSegment, + explicit_self_ty: Option, + ) -> Self { + let mut substs = TraitRef::substs_from_path(db, resolver, segment, resolved); + if let Some(self_ty) = explicit_self_ty { + make_mut_slice(&mut substs.0)[0] = self_ty; + } + TraitRef { trait_: resolved, substs } + } + + pub(crate) fn from_hir( + db: &impl HirDatabase, + resolver: &Resolver, + type_ref: &TypeRef, + explicit_self_ty: Option, + ) -> Option { + let path = match type_ref { + TypeRef::Path(path) => path, + _ => return None, + }; + TraitRef::from_path(db, resolver, path, explicit_self_ty) + } + + fn substs_from_path( + db: &impl HirDatabase, + resolver: &Resolver, + segment: &PathSegment, + resolved: TraitId, + ) -> Substs { + let has_self_param = + segment.args_and_bindings.as_ref().map(|a| a.has_self_type).unwrap_or(false); + substs_from_path_segment(db, resolver, segment, Some(resolved.into()), !has_self_param) + } + + pub fn for_trait(db: &impl HirDatabase, trait_: TraitId) -> TraitRef { + let substs = Substs::identity(&db.generic_params(trait_.into())); + TraitRef { trait_, substs } + } + + pub(crate) fn from_type_bound( + db: &impl HirDatabase, + resolver: &Resolver, + bound: &TypeBound, + self_ty: Ty, + ) -> Option { + match bound { + TypeBound::Path(path) => TraitRef::from_path(db, resolver, path, Some(self_ty)), + TypeBound::Error => None, + } + } +} + +impl GenericPredicate { + pub(crate) fn from_where_predicate<'a>( + db: &'a impl HirDatabase, + resolver: &'a Resolver, + where_predicate: &'a WherePredicate, + ) -> impl Iterator + 'a { + let self_ty = Ty::from_hir(db, resolver, &where_predicate.type_ref); + GenericPredicate::from_type_bound(db, resolver, &where_predicate.bound, self_ty) + } + + pub(crate) fn from_type_bound<'a>( + db: &'a impl HirDatabase, + resolver: &'a Resolver, + bound: &'a TypeBound, + self_ty: Ty, + ) -> impl Iterator + 'a { + let trait_ref = TraitRef::from_type_bound(db, &resolver, bound, self_ty); + iter::once(trait_ref.clone().map_or(GenericPredicate::Error, GenericPredicate::Implemented)) + .chain( + trait_ref.into_iter().flat_map(move |tr| { + assoc_type_bindings_from_type_bound(db, resolver, bound, tr) + }), + ) + } +} + +fn assoc_type_bindings_from_type_bound<'a>( + db: &'a impl HirDatabase, + resolver: &'a Resolver, + bound: &'a TypeBound, + trait_ref: TraitRef, +) -> impl Iterator + 'a { + let last_segment = match bound { + TypeBound::Path(path) => path.segments.last(), + TypeBound::Error => None, + }; + last_segment + .into_iter() + .flat_map(|segment| segment.args_and_bindings.iter()) + .flat_map(|args_and_bindings| args_and_bindings.bindings.iter()) + .map(move |(name, type_ref)| { + let associated_ty = + associated_type_by_name_including_super_traits(db, trait_ref.trait_, &name); + let associated_ty = match associated_ty { + None => return GenericPredicate::Error, + Some(t) => t, + }; + let projection_ty = + ProjectionTy { associated_ty, parameters: trait_ref.substs.clone() }; + let ty = Ty::from_hir(db, resolver, type_ref); + let projection_predicate = ProjectionPredicate { projection_ty, ty }; + GenericPredicate::Projection(projection_predicate) + }) +} + +/// Build the signature of a callable item (function, struct or enum variant). +pub fn callable_item_sig(db: &impl HirDatabase, def: CallableDef) -> FnSig { + match def { + CallableDef::FunctionId(f) => fn_sig_for_fn(db, f), + CallableDef::StructId(s) => fn_sig_for_struct_constructor(db, s), + CallableDef::EnumVariantId(e) => fn_sig_for_enum_variant_constructor(db, e), + } +} + +/// Build the type of all specific fields of a struct or enum variant. +pub(crate) fn field_types_query( + db: &impl HirDatabase, + variant_id: VariantId, +) -> Arc> { + let var_data = variant_data(db, variant_id); + let resolver = match variant_id { + VariantId::StructId(it) => it.resolver(db), + VariantId::UnionId(it) => it.resolver(db), + VariantId::EnumVariantId(it) => it.parent.resolver(db), + }; + let mut res = ArenaMap::default(); + for (field_id, field_data) in var_data.fields().iter() { + res.insert(field_id, Ty::from_hir(db, &resolver, &field_data.type_ref)) + } + Arc::new(res) +} + +/// This query exists only to be used when resolving short-hand associated types +/// like `T::Item`. +/// +/// See the analogous query in rustc and its comment: +/// https://github.com/rust-lang/rust/blob/9150f844e2624eb013ec78ca08c1d416e6644026/src/librustc_typeck/astconv.rs#L46 +/// This is a query mostly to handle cycles somewhat gracefully; e.g. the +/// following bounds are disallowed: `T: Foo, U: Foo`, but +/// these are fine: `T: Foo, U: Foo<()>`. +pub(crate) fn generic_predicates_for_param_query( + db: &impl HirDatabase, + def: GenericDefId, + param_idx: u32, +) -> Arc<[GenericPredicate]> { + let resolver = def.resolver(db); + resolver + .where_predicates_in_scope() + // we have to filter out all other predicates *first*, before attempting to lower them + .filter(|pred| Ty::from_hir_only_param(db, &resolver, &pred.type_ref) == Some(param_idx)) + .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) + .collect() +} + +impl TraitEnvironment { + pub fn lower(db: &impl HirDatabase, resolver: &Resolver) -> Arc { + let predicates = resolver + .where_predicates_in_scope() + .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) + .collect::>(); + + Arc::new(TraitEnvironment { predicates }) + } +} + +/// Resolve the where clause(s) of an item with generics. +pub(crate) fn generic_predicates_query( + db: &impl HirDatabase, + def: GenericDefId, +) -> Arc<[GenericPredicate]> { + let resolver = def.resolver(db); + resolver + .where_predicates_in_scope() + .flat_map(|pred| GenericPredicate::from_where_predicate(db, &resolver, pred)) + .collect() +} + +/// Resolve the default type params from generics +pub(crate) fn generic_defaults_query(db: &impl HirDatabase, def: GenericDefId) -> Substs { + let resolver = def.resolver(db); + let generic_params = db.generic_params(def.into()); + + let defaults = generic_params + .params_including_parent() + .into_iter() + .map(|p| p.default.as_ref().map_or(Ty::Unknown, |t| Ty::from_hir(db, &resolver, t))) + .collect(); + + Substs(defaults) +} + +fn fn_sig_for_fn(db: &impl HirDatabase, def: FunctionId) -> FnSig { + let data = db.function_data(def); + let resolver = def.resolver(db); + let params = data.params.iter().map(|tr| Ty::from_hir(db, &resolver, tr)).collect::>(); + let ret = Ty::from_hir(db, &resolver, &data.ret_type); + FnSig::from_params_and_return(params, ret) +} + +/// Build the declared type of a function. This should not need to look at the +/// function body. +fn type_for_fn(db: &impl HirDatabase, def: FunctionId) -> Ty { + let generics = db.generic_params(def.into()); + let substs = Substs::identity(&generics); + Ty::apply(TypeCtor::FnDef(def.into()), substs) +} + +/// Build the declared type of a const. +fn type_for_const(db: &impl HirDatabase, def: ConstId) -> Ty { + let data = db.const_data(def); + let resolver = def.resolver(db); + + Ty::from_hir(db, &resolver, &data.type_ref) +} + +/// Build the declared type of a static. +fn type_for_static(db: &impl HirDatabase, def: StaticId) -> Ty { + let data = db.static_data(def); + let resolver = def.resolver(db); + + Ty::from_hir(db, &resolver, &data.type_ref) +} + +/// Build the declared type of a static. +fn type_for_builtin(def: BuiltinType) -> Ty { + Ty::simple(match def { + BuiltinType::Char => TypeCtor::Char, + BuiltinType::Bool => TypeCtor::Bool, + BuiltinType::Str => TypeCtor::Str, + BuiltinType::Int(t) => TypeCtor::Int(IntTy::from(t).into()), + BuiltinType::Float(t) => TypeCtor::Float(FloatTy::from(t).into()), + }) +} + +fn fn_sig_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> FnSig { + let struct_data = db.struct_data(def.into()); + let fields = struct_data.variant_data.fields(); + let resolver = def.resolver(db); + let params = fields + .iter() + .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) + .collect::>(); + let ret = type_for_adt(db, def.into()); + FnSig::from_params_and_return(params, ret) +} + +/// Build the type of a tuple struct constructor. +fn type_for_struct_constructor(db: &impl HirDatabase, def: StructId) -> Ty { + let struct_data = db.struct_data(def.into()); + if struct_data.variant_data.is_unit() { + return type_for_adt(db, def.into()); // Unit struct + } + let generics = db.generic_params(def.into()); + let substs = Substs::identity(&generics); + Ty::apply(TypeCtor::FnDef(def.into()), substs) +} + +fn fn_sig_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> FnSig { + let enum_data = db.enum_data(def.parent); + let var_data = &enum_data.variants[def.local_id]; + let fields = var_data.variant_data.fields(); + let resolver = def.parent.resolver(db); + let params = fields + .iter() + .map(|(_, field)| Ty::from_hir(db, &resolver, &field.type_ref)) + .collect::>(); + let generics = db.generic_params(def.parent.into()); + let substs = Substs::identity(&generics); + let ret = type_for_adt(db, def.parent.into()).subst(&substs); + FnSig::from_params_and_return(params, ret) +} + +/// Build the type of a tuple enum variant constructor. +fn type_for_enum_variant_constructor(db: &impl HirDatabase, def: EnumVariantId) -> Ty { + let enum_data = db.enum_data(def.parent); + let var_data = &enum_data.variants[def.local_id].variant_data; + if var_data.is_unit() { + return type_for_adt(db, def.parent.into()); // Unit variant + } + let generics = db.generic_params(def.parent.into()); + let substs = Substs::identity(&generics); + Ty::apply(TypeCtor::FnDef(EnumVariantId::from(def).into()), substs) +} + +fn type_for_adt(db: &impl HirDatabase, adt: AdtId) -> Ty { + let generics = db.generic_params(adt.into()); + Ty::apply(TypeCtor::Adt(adt), Substs::identity(&generics)) +} + +fn type_for_type_alias(db: &impl HirDatabase, t: TypeAliasId) -> Ty { + let generics = db.generic_params(t.into()); + let resolver = t.resolver(db); + let type_ref = &db.type_alias_data(t).type_ref; + let substs = Substs::identity(&generics); + let inner = Ty::from_hir(db, &resolver, type_ref.as_ref().unwrap_or(&TypeRef::Error)); + inner.subst(&substs) +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum CallableDef { + FunctionId(FunctionId), + StructId(StructId), + EnumVariantId(EnumVariantId), +} +impl_froms!(CallableDef: FunctionId, StructId, EnumVariantId); + +impl CallableDef { + pub fn krate(self, db: &impl HirDatabase) -> CrateId { + match self { + CallableDef::FunctionId(f) => f.lookup(db).module(db).krate, + CallableDef::StructId(s) => s.module(db).krate, + CallableDef::EnumVariantId(e) => e.parent.module(db).krate, + } + } +} + +impl From for GenericDefId { + fn from(def: CallableDef) -> GenericDefId { + match def { + CallableDef::FunctionId(f) => f.into(), + CallableDef::StructId(s) => s.into(), + CallableDef::EnumVariantId(e) => e.into(), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum TyDefId { + BuiltinType(BuiltinType), + AdtId(AdtId), + TypeAliasId(TypeAliasId), +} +impl_froms!(TyDefId: BuiltinType, AdtId(StructId, EnumId, UnionId), TypeAliasId); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ValueTyDefId { + FunctionId(FunctionId), + StructId(StructId), + EnumVariantId(EnumVariantId), + ConstId(ConstId), + StaticId(StaticId), +} +impl_froms!(ValueTyDefId: FunctionId, StructId, EnumVariantId, ConstId, StaticId); + +/// Build the declared type of an item. This depends on the namespace; e.g. for +/// `struct Foo(usize)`, we have two types: The type of the struct itself, and +/// the constructor function `(usize) -> Foo` which lives in the values +/// namespace. +pub(crate) fn ty_query(db: &impl HirDatabase, def: TyDefId) -> Ty { + match def { + TyDefId::BuiltinType(it) => type_for_builtin(it), + TyDefId::AdtId(it) => type_for_adt(db, it), + TyDefId::TypeAliasId(it) => type_for_type_alias(db, it), + } +} +pub(crate) fn value_ty_query(db: &impl HirDatabase, def: ValueTyDefId) -> Ty { + match def { + ValueTyDefId::FunctionId(it) => type_for_fn(db, it), + ValueTyDefId::StructId(it) => type_for_struct_constructor(db, it), + ValueTyDefId::EnumVariantId(it) => type_for_enum_variant_constructor(db, it), + ValueTyDefId::ConstId(it) => type_for_const(db, it), + ValueTyDefId::StaticId(it) => type_for_static(db, it), + } +} diff --git a/crates/ra_hir_ty/src/marks.rs b/crates/ra_hir_ty/src/marks.rs new file mode 100644 index 000000000..0f754eb9c --- /dev/null +++ b/crates/ra_hir_ty/src/marks.rs @@ -0,0 +1,9 @@ +//! See test_utils/src/marks.rs + +test_utils::marks!( + type_var_cycles_resolve_completely + type_var_cycles_resolve_as_possible + type_var_resolves_to_int_var + match_ergonomics_ref + coerce_merge_fail_fallback +); diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs new file mode 100644 index 000000000..53c541eb8 --- /dev/null +++ b/crates/ra_hir_ty/src/method_resolution.rs @@ -0,0 +1,363 @@ +//! This module is concerned with finding methods that a given type provides. +//! For details about how this works in rustc, see the method lookup page in the +//! [rustc guide](https://rust-lang.github.io/rustc-guide/method-lookup.html) +//! and the corresponding code mostly in librustc_typeck/check/method/probe.rs. +use std::sync::Arc; + +use arrayvec::ArrayVec; +use hir_def::{ + lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, type_ref::Mutability, + AssocItemId, AstItemDef, FunctionId, HasModule, ImplId, TraitId, +}; +use hir_expand::name::Name; +use ra_db::CrateId; +use ra_prof::profile; +use rustc_hash::FxHashMap; + +use crate::{ + db::HirDatabase, + primitive::{FloatBitness, Uncertain}, + utils::all_super_traits, + Ty, TypeCtor, +}; + +use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; + +/// This is used as a key for indexing impls. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum TyFingerprint { + Apply(TypeCtor), +} + +impl TyFingerprint { + /// Creates a TyFingerprint for looking up an impl. Only certain types can + /// have impls: if we have some `struct S`, we can have an `impl S`, but not + /// `impl &S`. Hence, this will return `None` for reference types and such. + fn for_impl(ty: &Ty) -> Option { + match ty { + Ty::Apply(a_ty) => Some(TyFingerprint::Apply(a_ty.ctor)), + _ => None, + } + } +} + +#[derive(Debug, PartialEq, Eq)] +pub struct CrateImplBlocks { + impls: FxHashMap>, + impls_by_trait: FxHashMap>, +} + +impl CrateImplBlocks { + pub(crate) fn impls_in_crate_query( + db: &impl HirDatabase, + krate: CrateId, + ) -> Arc { + let _p = profile("impls_in_crate_query"); + let mut res = + CrateImplBlocks { impls: FxHashMap::default(), impls_by_trait: FxHashMap::default() }; + + let crate_def_map = db.crate_def_map(krate); + for (_module_id, module_data) in crate_def_map.modules.iter() { + for &impl_id in module_data.impls.iter() { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + + match &impl_data.target_trait { + Some(trait_ref) => { + if let Some(tr) = + TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty)) + { + res.impls_by_trait.entry(tr.trait_).or_default().push(impl_id); + } + } + None => { + if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { + res.impls.entry(target_ty_fp).or_default().push(impl_id); + } + } + } + } + } + + Arc::new(res) + } + pub fn lookup_impl_blocks(&self, ty: &Ty) -> impl Iterator + '_ { + let fingerprint = TyFingerprint::for_impl(ty); + fingerprint.and_then(|f| self.impls.get(&f)).into_iter().flatten().copied() + } + + pub fn lookup_impl_blocks_for_trait(&self, tr: TraitId) -> impl Iterator + '_ { + self.impls_by_trait.get(&tr).into_iter().flatten().copied() + } + + pub fn all_impls<'a>(&'a self) -> impl Iterator + 'a { + self.impls.values().chain(self.impls_by_trait.values()).flatten().copied() + } +} + +impl Ty { + pub fn def_crates( + &self, + db: &impl HirDatabase, + cur_crate: CrateId, + ) -> Option> { + // Types like slice can have inherent impls in several crates, (core and alloc). + // The corresponding impls are marked with lang items, so we can use them to find the required crates. + macro_rules! lang_item_crate { + ($($name:expr),+ $(,)?) => {{ + let mut v = ArrayVec::<[LangItemTarget; 2]>::new(); + $( + v.extend(db.lang_item(cur_crate, $name.into())); + )+ + v + }}; + } + + let lang_item_targets = match self { + Ty::Apply(a_ty) => match a_ty.ctor { + TypeCtor::Adt(def_id) => { + return Some(std::iter::once(def_id.module(db).krate).collect()) + } + TypeCtor::Bool => lang_item_crate!("bool"), + TypeCtor::Char => lang_item_crate!("char"), + TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { + // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) + FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), + FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), + }, + TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), + TypeCtor::Str => lang_item_crate!("str_alloc", "str"), + TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), + TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), + TypeCtor::RawPtr(Mutability::Mut) => lang_item_crate!("mut_ptr"), + _ => return None, + }, + _ => return None, + }; + let res = lang_item_targets + .into_iter() + .filter_map(|it| match it { + LangItemTarget::ImplBlockId(it) => Some(it), + _ => None, + }) + .map(|it| it.module(db).krate) + .collect(); + Some(res) + } +} +/// Look up the method with the given name, returning the actual autoderefed +/// receiver type (but without autoref applied yet). +pub(crate) fn lookup_method( + ty: &Canonical, + db: &impl HirDatabase, + name: &Name, + resolver: &Resolver, +) -> Option<(Ty, FunctionId)> { + iterate_method_candidates(ty, db, resolver, Some(name), LookupMode::MethodCall, |ty, f| match f + { + AssocItemId::FunctionId(f) => Some((ty.clone(), f)), + _ => None, + }) +} + +/// Whether we're looking up a dotted method call (like `v.len()`) or a path +/// (like `Vec::new`). +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub enum LookupMode { + /// Looking up a method call like `v.len()`: We only consider candidates + /// that have a `self` parameter, and do autoderef. + MethodCall, + /// Looking up a path like `Vec::new` or `Vec::default`: We consider all + /// candidates including associated constants, but don't do autoderef. + Path, +} + +// This would be nicer if it just returned an iterator, but that runs into +// lifetime problems, because we need to borrow temp `CrateImplBlocks`. +// FIXME add a context type here? +pub fn iterate_method_candidates( + ty: &Canonical, + db: &impl HirDatabase, + resolver: &Resolver, + name: Option<&Name>, + mode: LookupMode, + mut callback: impl FnMut(&Ty, AssocItemId) -> Option, +) -> Option { + let krate = resolver.krate()?; + match mode { + LookupMode::MethodCall => { + // For method calls, rust first does any number of autoderef, and then one + // autoref (i.e. when the method takes &self or &mut self). We just ignore + // the autoref currently -- when we find a method matching the given name, + // we assume it fits. + + // Also note that when we've got a receiver like &S, even if the method we + // find in the end takes &self, we still do the autoderef step (just as + // rustc does an autoderef and then autoref again). + let environment = TraitEnvironment::lower(db, resolver); + let ty = InEnvironment { value: ty.clone(), environment }; + for derefed_ty in autoderef::autoderef(db, resolver.krate(), ty) { + if let Some(result) = + iterate_inherent_methods(&derefed_ty, db, name, mode, krate, &mut callback) + { + return Some(result); + } + if let Some(result) = iterate_trait_method_candidates( + &derefed_ty, + db, + resolver, + name, + mode, + &mut callback, + ) { + return Some(result); + } + } + } + LookupMode::Path => { + // No autoderef for path lookups + if let Some(result) = + iterate_inherent_methods(&ty, db, name, mode, krate.into(), &mut callback) + { + return Some(result); + } + if let Some(result) = + iterate_trait_method_candidates(&ty, db, resolver, name, mode, &mut callback) + { + return Some(result); + } + } + } + None +} + +fn iterate_trait_method_candidates( + ty: &Canonical, + db: &impl HirDatabase, + resolver: &Resolver, + name: Option<&Name>, + mode: LookupMode, + mut callback: impl FnMut(&Ty, AssocItemId) -> Option, +) -> Option { + let krate = resolver.krate()?; + // FIXME: maybe put the trait_env behind a query (need to figure out good input parameters for that) + let env = TraitEnvironment::lower(db, resolver); + // if ty is `impl Trait` or `dyn Trait`, the trait doesn't need to be in scope + let inherent_trait = ty.value.inherent_trait().into_iter(); + // if we have `T: Trait` in the param env, the trait doesn't need to be in scope + let traits_from_env = env + .trait_predicates_for_self_ty(&ty.value) + .map(|tr| tr.trait_) + .flat_map(|t| all_super_traits(db, t)); + let traits = + inherent_trait.chain(traits_from_env).chain(resolver.traits_in_scope(db).into_iter()); + 'traits: for t in traits { + let data = db.trait_data(t); + + // we'll be lazy about checking whether the type implements the + // trait, but if we find out it doesn't, we'll skip the rest of the + // iteration + let mut known_implemented = false; + for (_name, item) in data.items.iter() { + if !is_valid_candidate(db, name, mode, (*item).into()) { + continue; + } + if !known_implemented { + let goal = generic_implements_goal(db, env.clone(), t, ty.clone()); + if db.trait_solve(krate.into(), goal).is_none() { + continue 'traits; + } + } + known_implemented = true; + if let Some(result) = callback(&ty.value, (*item).into()) { + return Some(result); + } + } + } + None +} + +fn iterate_inherent_methods( + ty: &Canonical, + db: &impl HirDatabase, + name: Option<&Name>, + mode: LookupMode, + krate: CrateId, + mut callback: impl FnMut(&Ty, AssocItemId) -> Option, +) -> Option { + for krate in ty.value.def_crates(db, krate)? { + let impls = db.impls_in_crate(krate); + + for impl_block in impls.lookup_impl_blocks(&ty.value) { + for &item in db.impl_data(impl_block).items.iter() { + if !is_valid_candidate(db, name, mode, item) { + continue; + } + if let Some(result) = callback(&ty.value, item.into()) { + return Some(result); + } + } + } + } + None +} + +fn is_valid_candidate( + db: &impl HirDatabase, + name: Option<&Name>, + mode: LookupMode, + item: AssocItemId, +) -> bool { + match item { + AssocItemId::FunctionId(m) => { + let data = db.function_data(m); + name.map_or(true, |name| &data.name == name) + && (data.has_self_param || mode == LookupMode::Path) + } + AssocItemId::ConstId(c) => { + let data = db.const_data(c); + name.map_or(true, |name| data.name.as_ref() == Some(name)) && (mode == LookupMode::Path) + } + _ => false, + } +} + +pub fn implements_trait( + ty: &Canonical, + db: &impl HirDatabase, + resolver: &Resolver, + krate: CrateId, + trait_: TraitId, +) -> bool { + if ty.value.inherent_trait() == Some(trait_) { + // FIXME this is a bit of a hack, since Chalk should say the same thing + // anyway, but currently Chalk doesn't implement `dyn/impl Trait` yet + return true; + } + let env = TraitEnvironment::lower(db, resolver); + let goal = generic_implements_goal(db, env, trait_, ty.clone()); + let solution = db.trait_solve(krate.into(), goal); + + solution.is_some() +} + +/// This creates Substs for a trait with the given Self type and type variables +/// for all other parameters, to query Chalk with it. +fn generic_implements_goal( + db: &impl HirDatabase, + env: Arc, + trait_: TraitId, + self_ty: Canonical, +) -> Canonical> { + let num_vars = self_ty.num_vars; + let substs = super::Substs::build_for_def(db, trait_) + .push(self_ty.value) + .fill_with_bound_vars(num_vars as u32) + .build(); + let num_vars = substs.len() - 1 + self_ty.num_vars; + let trait_ref = TraitRef { trait_, substs }; + let obligation = super::Obligation::Trait(trait_ref); + Canonical { num_vars, value: InEnvironment::new(env, obligation) } +} diff --git a/crates/ra_hir_ty/src/op.rs b/crates/ra_hir_ty/src/op.rs new file mode 100644 index 000000000..09c47a76d --- /dev/null +++ b/crates/ra_hir_ty/src/op.rs @@ -0,0 +1,50 @@ +//! FIXME: write short doc here +use hir_def::expr::{BinaryOp, CmpOp}; + +use super::{InferTy, Ty, TypeCtor}; +use crate::ApplicationTy; + +pub(super) fn binary_op_return_ty(op: BinaryOp, rhs_ty: Ty) -> Ty { + match op { + BinaryOp::LogicOp(_) | BinaryOp::CmpOp(_) => Ty::simple(TypeCtor::Bool), + BinaryOp::Assignment { .. } => Ty::unit(), + BinaryOp::ArithOp(_) => match rhs_ty { + Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { + TypeCtor::Int(..) | TypeCtor::Float(..) => rhs_ty, + _ => Ty::Unknown, + }, + Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => rhs_ty, + _ => Ty::Unknown, + }, + } +} + +pub(super) fn binary_op_rhs_expectation(op: BinaryOp, lhs_ty: Ty) -> Ty { + match op { + BinaryOp::LogicOp(..) => Ty::simple(TypeCtor::Bool), + BinaryOp::Assignment { op: None } | BinaryOp::CmpOp(CmpOp::Eq { negated: _ }) => { + match lhs_ty { + Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { + TypeCtor::Int(..) + | TypeCtor::Float(..) + | TypeCtor::Str + | TypeCtor::Char + | TypeCtor::Bool => lhs_ty, + _ => Ty::Unknown, + }, + Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty, + _ => Ty::Unknown, + } + } + BinaryOp::CmpOp(CmpOp::Ord { .. }) + | BinaryOp::Assignment { op: Some(_) } + | BinaryOp::ArithOp(_) => match lhs_ty { + Ty::Apply(ApplicationTy { ctor, .. }) => match ctor { + TypeCtor::Int(..) | TypeCtor::Float(..) => lhs_ty, + _ => Ty::Unknown, + }, + Ty::Infer(InferTy::IntVar(..)) | Ty::Infer(InferTy::FloatVar(..)) => lhs_ty, + _ => Ty::Unknown, + }, + } +} diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs new file mode 100644 index 000000000..0e51f4130 --- /dev/null +++ b/crates/ra_hir_ty/src/test_db.rs @@ -0,0 +1,144 @@ +//! Database used for testing `hir`. + +use std::{panic, sync::Arc}; + +use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId, ModuleId}; +use hir_expand::diagnostics::DiagnosticSink; +use parking_lot::Mutex; +use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, SourceDatabase}; + +use crate::{db::HirDatabase, expr::ExprValidator}; + +#[salsa::database( + ra_db::SourceDatabaseExtStorage, + ra_db::SourceDatabaseStorage, + hir_expand::db::AstDatabaseStorage, + hir_def::db::InternDatabaseStorage, + hir_def::db::DefDatabaseStorage, + crate::db::HirDatabaseStorage +)] +#[derive(Debug, Default)] +pub struct TestDB { + events: Mutex>>>, + runtime: salsa::Runtime, +} + +impl salsa::Database for TestDB { + fn salsa_runtime(&self) -> &salsa::Runtime { + &self.runtime + } + + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } + + fn salsa_event(&self, event: impl Fn() -> salsa::Event) { + let mut events = self.events.lock(); + if let Some(events) = &mut *events { + events.push(event()); + } + } +} + +impl salsa::ParallelDatabase for TestDB { + fn snapshot(&self) -> salsa::Snapshot { + salsa::Snapshot::new(TestDB { + events: Default::default(), + runtime: self.runtime.snapshot(self), + }) + } +} + +impl panic::RefUnwindSafe for TestDB {} + +impl FileLoader for TestDB { + fn file_text(&self, file_id: FileId) -> Arc { + FileLoaderDelegate(self).file_text(file_id) + } + fn resolve_relative_path( + &self, + anchor: FileId, + relative_path: &RelativePath, + ) -> Option { + FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + } + fn relevant_crates(&self, file_id: FileId) -> Arc> { + FileLoaderDelegate(self).relevant_crates(file_id) + } +} + +impl TestDB { + pub fn module_for_file(&self, file_id: FileId) -> ModuleId { + for &krate in self.relevant_crates(file_id).iter() { + let crate_def_map = self.crate_def_map(krate); + for (module_id, data) in crate_def_map.modules.iter() { + if data.definition == Some(file_id) { + return ModuleId { krate, module_id }; + } + } + } + panic!("Can't find module for file") + } + + // FIXME: don't duplicate this + pub fn diagnostics(&self) -> String { + let mut buf = String::new(); + let crate_graph = self.crate_graph(); + for krate in crate_graph.iter().next() { + let crate_def_map = self.crate_def_map(krate); + + let mut fns = Vec::new(); + for (module_id, _) in crate_def_map.modules.iter() { + for decl in crate_def_map[module_id].scope.declarations() { + match decl { + ModuleDefId::FunctionId(f) => fns.push(f), + _ => (), + } + } + + for &impl_id in crate_def_map[module_id].impls.iter() { + let impl_data = self.impl_data(impl_id); + for item in impl_data.items.iter() { + if let AssocItemId::FunctionId(f) = item { + fns.push(*f) + } + } + } + } + + for f in fns { + let infer = self.infer(f.into()); + let mut sink = DiagnosticSink::new(|d| { + buf += &format!("{:?}: {}\n", d.syntax_node(self).text(), d.message()); + }); + infer.add_diagnostics(self, f, &mut sink); + let mut validator = ExprValidator::new(f, infer, &mut sink); + validator.validate_body(self); + } + } + buf + } +} + +impl TestDB { + pub fn log(&self, f: impl FnOnce()) -> Vec> { + *self.events.lock() = Some(Vec::new()); + f(); + self.events.lock().take().unwrap() + } + + pub fn log_executed(&self, f: impl FnOnce()) -> Vec { + let events = self.log(f); + events + .into_iter() + .filter_map(|e| match e.kind { + // This pretty horrible, but `Debug` is the only way to inspect + // QueryDescriptor at the moment. + salsa::EventKind::WillExecute { database_key } => { + Some(format!("{:?}", database_key)) + } + _ => None, + }) + .collect() + } +} diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs new file mode 100644 index 000000000..c1744663a --- /dev/null +++ b/crates/ra_hir_ty/src/tests.rs @@ -0,0 +1,4958 @@ +mod never_type; +mod coercion; + +use std::fmt::Write; +use std::sync::Arc; + +use hir_def::{ + body::BodySourceMap, db::DefDatabase, nameres::CrateDefMap, AssocItemId, DefWithBodyId, + LocalModuleId, Lookup, ModuleDefId, +}; +use hir_expand::Source; +use insta::assert_snapshot; +use ra_db::{fixture::WithFixture, salsa::Database, FilePosition, SourceDatabase}; +use ra_syntax::{ + algo, + ast::{self, AstNode}, +}; +use test_utils::covers; + +use crate::{db::HirDatabase, display::HirDisplay, test_db::TestDB, InferenceResult}; + +// These tests compare the inference results for all expressions in a file +// against snapshots of the expected results using insta. Use cargo-insta to +// update the snapshots. + +#[test] +fn cfg_impl_block() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:foo cfg:test +use foo::S as T; +struct S; + +#[cfg(test)] +impl S { + fn foo1(&self) -> i32 { 0 } +} + +#[cfg(not(test))] +impl S { + fn foo2(&self) -> i32 { 0 } +} + +fn test() { + let t = (S.foo1(), S.foo2(), T.foo3(), T.foo4()); + t<|>; +} + +//- /foo.rs crate:foo +struct S; + +#[cfg(not(test))] +impl S { + fn foo3(&self) -> i32 { 0 } +} + +#[cfg(test)] +impl S { + fn foo4(&self) -> i32 { 0 } +} +"#, + ); + assert_eq!("(i32, {unknown}, i32, {unknown})", type_at_pos(&db, pos)); +} + +#[test] +fn infer_await() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std + +struct IntFuture; + +impl Future for IntFuture { + type Output = u64; +} + +fn test() { + let r = IntFuture; + let v = r.await; + v<|>; +} + +//- /std.rs crate:std +#[prelude_import] use future::*; +mod future { + trait Future { + type Output; + } +} + +"#, + ); + assert_eq!("u64", type_at_pos(&db, pos)); +} + +#[test] +fn infer_box() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std + +fn test() { + let x = box 1; + let t = (x, box x, box &1, box [1]); + t<|>; +} + +//- /std.rs crate:std +#[prelude_import] use prelude::*; +mod prelude {} + +mod boxed { + pub struct Box { + inner: *mut T, + } +} + +"#, + ); + assert_eq!("(Box, Box>, Box<&i32>, Box<[i32;_]>)", type_at_pos(&db, pos)); +} + +#[test] +fn infer_adt_self() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs +enum Nat { Succ(Self), Demo(Nat), Zero } + +fn test() { + let foo: Nat = Nat::Zero; + if let Nat::Succ(x) = foo { + x<|> + } +} + +"#, + ); + assert_eq!("Nat", type_at_pos(&db, pos)); +} + +#[test] +fn infer_try() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std + +fn test() { + let r: Result = Result::Ok(1); + let v = r?; + v<|>; +} + +//- /std.rs crate:std + +#[prelude_import] use ops::*; +mod ops { + trait Try { + type Ok; + type Error; + } +} + +#[prelude_import] use result::*; +mod result { + enum Result { + Ok(O), + Err(E) + } + + impl crate::ops::Try for Result { + type Ok = O; + type Error = E; + } +} + +"#, + ); + assert_eq!("i32", type_at_pos(&db, pos)); +} + +#[test] +fn infer_for_loop() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std + +use std::collections::Vec; + +fn test() { + let v = Vec::new(); + v.push("foo"); + for x in v { + x<|>; + } +} + +//- /std.rs crate:std + +#[prelude_import] use iter::*; +mod iter { + trait IntoIterator { + type Item; + } +} + +mod collections { + struct Vec {} + impl Vec { + fn new() -> Self { Vec {} } + fn push(&mut self, t: T) { } + } + + impl crate::iter::IntoIterator for Vec { + type Item=T; + } +} +"#, + ); + assert_eq!("&str", type_at_pos(&db, pos)); +} + +#[test] +fn infer_while_let() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs +enum Option { Some(T), None } + +fn test() { + let foo: Option = None; + while let Option::Some(x) = foo { + <|>x + } +} + +"#, + ); + assert_eq!("f32", type_at_pos(&db, pos)); +} + +#[test] +fn infer_basics() { + assert_snapshot!( + infer(r#" +fn test(a: u32, b: isize, c: !, d: &str) { + a; + b; + c; + d; + 1usize; + 1isize; + "test"; + 1.0f32; +}"#), + @r###" + [9; 10) 'a': u32 + [17; 18) 'b': isize + [27; 28) 'c': ! + [33; 34) 'd': &str + [42; 121) '{ ...f32; }': ! + [48; 49) 'a': u32 + [55; 56) 'b': isize + [62; 63) 'c': ! + [69; 70) 'd': &str + [76; 82) '1usize': usize + [88; 94) '1isize': isize + [100; 106) '"test"': &str + [112; 118) '1.0f32': f32 + "### + ); +} + +#[test] +fn infer_let() { + assert_snapshot!( + infer(r#" +fn test() { + let a = 1isize; + let b: usize = 1; + let c = b; + let d: u32; + let e; + let f: i32 = e; +} +"#), + @r###" + [11; 118) '{ ...= e; }': () + [21; 22) 'a': isize + [25; 31) '1isize': isize + [41; 42) 'b': usize + [52; 53) '1': usize + [63; 64) 'c': usize + [67; 68) 'b': usize + [78; 79) 'd': u32 + [94; 95) 'e': i32 + [105; 106) 'f': i32 + [114; 115) 'e': i32 + "### + ); +} + +#[test] +fn infer_paths() { + assert_snapshot!( + infer(r#" +fn a() -> u32 { 1 } + +mod b { + fn c() -> u32 { 1 } +} + +fn test() { + a(); + b::c(); +} +"#), + @r###" + [15; 20) '{ 1 }': u32 + [17; 18) '1': u32 + [48; 53) '{ 1 }': u32 + [50; 51) '1': u32 + [67; 91) '{ ...c(); }': () + [73; 74) 'a': fn a() -> u32 + [73; 76) 'a()': u32 + [82; 86) 'b::c': fn c() -> u32 + [82; 88) 'b::c()': u32 + "### + ); +} + +#[test] +fn infer_path_type() { + assert_snapshot!( + infer(r#" +struct S; + +impl S { + fn foo() -> i32 { 1 } +} + +fn test() { + S::foo(); + ::foo(); +} +"#), + @r###" + [41; 46) '{ 1 }': i32 + [43; 44) '1': i32 + [60; 93) '{ ...o(); }': () + [66; 72) 'S::foo': fn foo() -> i32 + [66; 74) 'S::foo()': i32 + [80; 88) '::foo': fn foo() -> i32 + [80; 90) '::foo()': i32 + "### + ); +} + +#[test] +fn infer_slice_method() { + assert_snapshot!( + infer(r#" +#[lang = "slice"] +impl [T] { + fn foo(&self) -> T { + loop {} + } +} + +#[lang = "slice_alloc"] +impl [T] {} + +fn test() { + <[_]>::foo(b"foo"); +} +"#), + @r###" + [45; 49) 'self': &[T] + [56; 79) '{ ... }': T + [66; 73) 'loop {}': ! + [71; 73) '{}': () + [133; 160) '{ ...o"); }': () + [139; 149) '<[_]>::foo': fn foo(&[T]) -> T + [139; 157) '<[_]>:..."foo")': u8 + [150; 156) 'b"foo"': &[u8] + "### + ); +} + +#[test] +fn infer_struct() { + assert_snapshot!( + infer(r#" +struct A { + b: B, + c: C, +} +struct B; +struct C(usize); + +fn test() { + let c = C(1); + B; + let a: A = A { b: B, c: C(1) }; + a.b; + a.c; +} +"#), + @r###" + [72; 154) '{ ...a.c; }': () + [82; 83) 'c': C + [86; 87) 'C': C(usize) -> C + [86; 90) 'C(1)': C + [88; 89) '1': usize + [96; 97) 'B': B + [107; 108) 'a': A + [114; 133) 'A { b:...C(1) }': A + [121; 122) 'B': B + [127; 128) 'C': C(usize) -> C + [127; 131) 'C(1)': C + [129; 130) '1': usize + [139; 140) 'a': A + [139; 142) 'a.b': B + [148; 149) 'a': A + [148; 151) 'a.c': C + "### + ); +} + +#[test] +fn infer_enum() { + assert_snapshot!( + infer(r#" +enum E { + V1 { field: u32 }, + V2 +} +fn test() { + E::V1 { field: 1 }; + E::V2; +}"#), + @r###" + [48; 82) '{ E:...:V2; }': () + [52; 70) 'E::V1 ...d: 1 }': E + [67; 68) '1': u32 + [74; 79) 'E::V2': E + "### + ); +} + +#[test] +fn infer_refs() { + assert_snapshot!( + infer(r#" +fn test(a: &u32, b: &mut u32, c: *const u32, d: *mut u32) { + a; + *a; + &a; + &mut a; + b; + *b; + &b; + c; + *c; + d; + *d; +} +"#), + @r###" + [9; 10) 'a': &u32 + [18; 19) 'b': &mut u32 + [31; 32) 'c': *const u32 + [46; 47) 'd': *mut u32 + [59; 150) '{ ... *d; }': () + [65; 66) 'a': &u32 + [72; 74) '*a': u32 + [73; 74) 'a': &u32 + [80; 82) '&a': &&u32 + [81; 82) 'a': &u32 + [88; 94) '&mut a': &mut &u32 + [93; 94) 'a': &u32 + [100; 101) 'b': &mut u32 + [107; 109) '*b': u32 + [108; 109) 'b': &mut u32 + [115; 117) '&b': &&mut u32 + [116; 117) 'b': &mut u32 + [123; 124) 'c': *const u32 + [130; 132) '*c': u32 + [131; 132) 'c': *const u32 + [138; 139) 'd': *mut u32 + [145; 147) '*d': u32 + [146; 147) 'd': *mut u32 + "### + ); +} + +#[test] +fn infer_literals() { + assert_snapshot!( + infer(r##" +fn test() { + 5i32; + 5f32; + 5f64; + "hello"; + b"bytes"; + 'c'; + b'b'; + 3.14; + 5000; + false; + true; + r#" + //! doc + // non-doc + mod foo {} + "#; + br#"yolo"#; +} +"##), + @r###" + [11; 221) '{ ...o"#; }': () + [17; 21) '5i32': i32 + [27; 31) '5f32': f32 + [37; 41) '5f64': f64 + [47; 54) '"hello"': &str + [60; 68) 'b"bytes"': &[u8] + [74; 77) ''c'': char + [83; 87) 'b'b'': u8 + [93; 97) '3.14': f64 + [103; 107) '5000': i32 + [113; 118) 'false': bool + [124; 128) 'true': bool + [134; 202) 'r#" ... "#': &str + [208; 218) 'br#"yolo"#': &[u8] + "### + ); +} + +#[test] +fn infer_unary_op() { + assert_snapshot!( + infer(r#" +enum SomeType {} + +fn test(x: SomeType) { + let b = false; + let c = !b; + let a = 100; + let d: i128 = -a; + let e = -100; + let f = !!!true; + let g = !42; + let h = !10u32; + let j = !a; + -3.14; + !3; + -x; + !x; + -"hello"; + !"hello"; +} +"#), + @r###" + [27; 28) 'x': SomeType + [40; 272) '{ ...lo"; }': () + [50; 51) 'b': bool + [54; 59) 'false': bool + [69; 70) 'c': bool + [73; 75) '!b': bool + [74; 75) 'b': bool + [85; 86) 'a': i128 + [89; 92) '100': i128 + [102; 103) 'd': i128 + [112; 114) '-a': i128 + [113; 114) 'a': i128 + [124; 125) 'e': i32 + [128; 132) '-100': i32 + [129; 132) '100': i32 + [142; 143) 'f': bool + [146; 153) '!!!true': bool + [147; 153) '!!true': bool + [148; 153) '!true': bool + [149; 153) 'true': bool + [163; 164) 'g': i32 + [167; 170) '!42': i32 + [168; 170) '42': i32 + [180; 181) 'h': u32 + [184; 190) '!10u32': u32 + [185; 190) '10u32': u32 + [200; 201) 'j': i128 + [204; 206) '!a': i128 + [205; 206) 'a': i128 + [212; 217) '-3.14': f64 + [213; 217) '3.14': f64 + [223; 225) '!3': i32 + [224; 225) '3': i32 + [231; 233) '-x': {unknown} + [232; 233) 'x': SomeType + [239; 241) '!x': {unknown} + [240; 241) 'x': SomeType + [247; 255) '-"hello"': {unknown} + [248; 255) '"hello"': &str + [261; 269) '!"hello"': {unknown} + [262; 269) '"hello"': &str + "### + ); +} + +#[test] +fn infer_backwards() { + assert_snapshot!( + infer(r#" +fn takes_u32(x: u32) {} + +struct S { i32_field: i32 } + +fn test() -> &mut &f64 { + let a = unknown_function(); + takes_u32(a); + let b = unknown_function(); + S { i32_field: b }; + let c = unknown_function(); + &mut &c +} +"#), + @r###" + [14; 15) 'x': u32 + [22; 24) '{}': () + [78; 231) '{ ...t &c }': &mut &f64 + [88; 89) 'a': u32 + [92; 108) 'unknow...nction': {unknown} + [92; 110) 'unknow...tion()': u32 + [116; 125) 'takes_u32': fn takes_u32(u32) -> () + [116; 128) 'takes_u32(a)': () + [126; 127) 'a': u32 + [138; 139) 'b': i32 + [142; 158) 'unknow...nction': {unknown} + [142; 160) 'unknow...tion()': i32 + [166; 184) 'S { i3...d: b }': S + [181; 182) 'b': i32 + [194; 195) 'c': f64 + [198; 214) 'unknow...nction': {unknown} + [198; 216) 'unknow...tion()': f64 + [222; 229) '&mut &c': &mut &f64 + [227; 229) '&c': &f64 + [228; 229) 'c': f64 + "### + ); +} + +#[test] +fn infer_self() { + assert_snapshot!( + infer(r#" +struct S; + +impl S { + fn test(&self) { + self; + } + fn test2(self: &Self) { + self; + } + fn test3() -> Self { + S {} + } + fn test4() -> Self { + Self {} + } +} +"#), + @r###" + [34; 38) 'self': &S + [40; 61) '{ ... }': () + [50; 54) 'self': &S + [75; 79) 'self': &S + [88; 109) '{ ... }': () + [98; 102) 'self': &S + [133; 153) '{ ... }': S + [143; 147) 'S {}': S + [177; 200) '{ ... }': S + [187; 194) 'Self {}': S + "### + ); +} + +#[test] +fn infer_binary_op() { + assert_snapshot!( + infer(r#" +fn f(x: bool) -> i32 { + 0i32 +} + +fn test() -> bool { + let x = a && b; + let y = true || false; + let z = x == y; + let t = x != y; + let minus_forty: isize = -40isize; + let h = minus_forty <= CONST_2; + let c = f(z || y) + 5; + let d = b; + let g = minus_forty ^= i; + let ten: usize = 10; + let ten_is_eleven = ten == some_num; + + ten < 3 +} +"#), + @r###" + [6; 7) 'x': bool + [22; 34) '{ 0i32 }': i32 + [28; 32) '0i32': i32 + [54; 370) '{ ... < 3 }': bool + [64; 65) 'x': bool + [68; 69) 'a': bool + [68; 74) 'a && b': bool + [73; 74) 'b': bool + [84; 85) 'y': bool + [88; 92) 'true': bool + [88; 101) 'true || false': bool + [96; 101) 'false': bool + [111; 112) 'z': bool + [115; 116) 'x': bool + [115; 121) 'x == y': bool + [120; 121) 'y': bool + [131; 132) 't': bool + [135; 136) 'x': bool + [135; 141) 'x != y': bool + [140; 141) 'y': bool + [151; 162) 'minus_forty': isize + [172; 180) '-40isize': isize + [173; 180) '40isize': isize + [190; 191) 'h': bool + [194; 205) 'minus_forty': isize + [194; 216) 'minus_...ONST_2': bool + [209; 216) 'CONST_2': isize + [226; 227) 'c': i32 + [230; 231) 'f': fn f(bool) -> i32 + [230; 239) 'f(z || y)': i32 + [230; 243) 'f(z || y) + 5': i32 + [232; 233) 'z': bool + [232; 238) 'z || y': bool + [237; 238) 'y': bool + [242; 243) '5': i32 + [253; 254) 'd': {unknown} + [257; 258) 'b': {unknown} + [268; 269) 'g': () + [272; 283) 'minus_forty': isize + [272; 288) 'minus_...y ^= i': () + [287; 288) 'i': isize + [298; 301) 'ten': usize + [311; 313) '10': usize + [323; 336) 'ten_is_eleven': bool + [339; 342) 'ten': usize + [339; 354) 'ten == some_num': bool + [346; 354) 'some_num': usize + [361; 364) 'ten': usize + [361; 368) 'ten < 3': bool + [367; 368) '3': usize + "### + ); +} + +#[test] +fn infer_field_autoderef() { + assert_snapshot!( + infer(r#" +struct A { + b: B, +} +struct B; + +fn test1(a: A) { + let a1 = a; + a1.b; + let a2 = &a; + a2.b; + let a3 = &mut a; + a3.b; + let a4 = &&&&&&&a; + a4.b; + let a5 = &mut &&mut &&mut a; + a5.b; +} + +fn test2(a1: *const A, a2: *mut A) { + a1.b; + a2.b; +} +"#), + @r###" + [44; 45) 'a': A + [50; 213) '{ ...5.b; }': () + [60; 62) 'a1': A + [65; 66) 'a': A + [72; 74) 'a1': A + [72; 76) 'a1.b': B + [86; 88) 'a2': &A + [91; 93) '&a': &A + [92; 93) 'a': A + [99; 101) 'a2': &A + [99; 103) 'a2.b': B + [113; 115) 'a3': &mut A + [118; 124) '&mut a': &mut A + [123; 124) 'a': A + [130; 132) 'a3': &mut A + [130; 134) 'a3.b': B + [144; 146) 'a4': &&&&&&&A + [149; 157) '&&&&&&&a': &&&&&&&A + [150; 157) '&&&&&&a': &&&&&&A + [151; 157) '&&&&&a': &&&&&A + [152; 157) '&&&&a': &&&&A + [153; 157) '&&&a': &&&A + [154; 157) '&&a': &&A + [155; 157) '&a': &A + [156; 157) 'a': A + [163; 165) 'a4': &&&&&&&A + [163; 167) 'a4.b': B + [177; 179) 'a5': &mut &&mut &&mut A + [182; 200) '&mut &...&mut a': &mut &&mut &&mut A + [187; 200) '&&mut &&mut a': &&mut &&mut A + [188; 200) '&mut &&mut a': &mut &&mut A + [193; 200) '&&mut a': &&mut A + [194; 200) '&mut a': &mut A + [199; 200) 'a': A + [206; 208) 'a5': &mut &&mut &&mut A + [206; 210) 'a5.b': B + [224; 226) 'a1': *const A + [238; 240) 'a2': *mut A + [250; 273) '{ ...2.b; }': () + [256; 258) 'a1': *const A + [256; 260) 'a1.b': B + [266; 268) 'a2': *mut A + [266; 270) 'a2.b': B + "### + ); +} + +#[test] +fn infer_argument_autoderef() { + assert_snapshot!( + infer(r#" +#[lang = "deref"] +pub trait Deref { + type Target; + fn deref(&self) -> &Self::Target; +} + +struct A(T); + +impl A { + fn foo(&self) -> &T { + &self.0 + } +} + +struct B(T); + +impl Deref for B { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn test() { + let t = A::foo(&&B(B(A(42)))); +} +"#), + @r###" + [68; 72) 'self': &Self + [139; 143) 'self': &A + [151; 174) '{ ... }': &T + [161; 168) '&self.0': &T + [162; 166) 'self': &A + [162; 168) 'self.0': T + [255; 259) 'self': &B + [278; 301) '{ ... }': &T + [288; 295) '&self.0': &T + [289; 293) 'self': &B + [289; 295) 'self.0': T + [315; 353) '{ ...))); }': () + [325; 326) 't': &i32 + [329; 335) 'A::foo': fn foo(&A) -> &T + [329; 350) 'A::foo...42))))': &i32 + [336; 349) '&&B(B(A(42)))': &&B>> + [337; 349) '&B(B(A(42)))': &B>> + [338; 339) 'B': B>>(T) -> B + [338; 349) 'B(B(A(42)))': B>> + [340; 341) 'B': B>(T) -> B + [340; 348) 'B(A(42))': B> + [342; 343) 'A': A(T) -> A + [342; 347) 'A(42)': A + [344; 346) '42': i32 + "### + ); +} + +#[test] +fn infer_method_argument_autoderef() { + assert_snapshot!( + infer(r#" +#[lang = "deref"] +pub trait Deref { + type Target; + fn deref(&self) -> &Self::Target; +} + +struct A(*mut T); + +impl A { + fn foo(&self, x: &A) -> &T { + &*x.0 + } +} + +struct B(T); + +impl Deref for B { + type Target = T; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +fn test(a: A) { + let t = A(0 as *mut _).foo(&&B(B(a))); +} +"#), + @r###" + [68; 72) 'self': &Self + [144; 148) 'self': &A + [150; 151) 'x': &A + [166; 187) '{ ... }': &T + [176; 181) '&*x.0': &T + [177; 181) '*x.0': T + [178; 179) 'x': &A + [178; 181) 'x.0': *mut T + [268; 272) 'self': &B + [291; 314) '{ ... }': &T + [301; 308) '&self.0': &T + [302; 306) 'self': &B + [302; 308) 'self.0': T + [326; 327) 'a': A + [337; 383) '{ ...))); }': () + [347; 348) 't': &i32 + [351; 352) 'A': A(*mut T) -> A + [351; 365) 'A(0 as *mut _)': A + [351; 380) 'A(0 as...B(a)))': &i32 + [353; 354) '0': i32 + [353; 364) '0 as *mut _': *mut i32 + [370; 379) '&&B(B(a))': &&B>> + [371; 379) '&B(B(a))': &B>> + [372; 373) 'B': B>>(T) -> B + [372; 379) 'B(B(a))': B>> + [374; 375) 'B': B>(T) -> B + [374; 378) 'B(a)': B> + [376; 377) 'a': A + "### + ); +} + +#[test] +fn bug_484() { + assert_snapshot!( + infer(r#" +fn test() { + let x = if true {}; +} +"#), + @r###" + [11; 37) '{ l... {}; }': () + [20; 21) 'x': () + [24; 34) 'if true {}': () + [27; 31) 'true': bool + [32; 34) '{}': () + "### + ); +} + +#[test] +fn infer_in_elseif() { + assert_snapshot!( + infer(r#" +struct Foo { field: i32 } +fn main(foo: Foo) { + if true { + + } else if false { + foo.field + } +} +"#), + @r###" + [35; 38) 'foo': Foo + [45; 109) '{ ... } }': () + [51; 107) 'if tru... }': () + [54; 58) 'true': bool + [59; 67) '{ }': () + [73; 107) 'if fal... }': () + [76; 81) 'false': bool + [82; 107) '{ ... }': i32 + [92; 95) 'foo': Foo + [92; 101) 'foo.field': i32 + "### + ) +} + +#[test] +fn infer_if_match_with_return() { + assert_snapshot!( + infer(r#" +fn foo() { + let _x1 = if true { + 1 + } else { + return; + }; + let _x2 = if true { + 2 + } else { + return + }; + let _x3 = match true { + true => 3, + _ => { + return; + } + }; + let _x4 = match true { + true => 4, + _ => return + }; +}"#), + @r###" + [10; 323) '{ ... }; }': () + [20; 23) '_x1': i32 + [26; 80) 'if tru... }': i32 + [29; 33) 'true': bool + [34; 51) '{ ... }': i32 + [44; 45) '1': i32 + [57; 80) '{ ... }': ! + [67; 73) 'return': ! + [90; 93) '_x2': i32 + [96; 149) 'if tru... }': i32 + [99; 103) 'true': bool + [104; 121) '{ ... }': i32 + [114; 115) '2': i32 + [127; 149) '{ ... }': ! + [137; 143) 'return': ! + [159; 162) '_x3': i32 + [165; 247) 'match ... }': i32 + [171; 175) 'true': bool + [186; 190) 'true': bool + [194; 195) '3': i32 + [205; 206) '_': bool + [210; 241) '{ ... }': ! + [224; 230) 'return': ! + [257; 260) '_x4': i32 + [263; 320) 'match ... }': i32 + [269; 273) 'true': bool + [284; 288) 'true': bool + [292; 293) '4': i32 + [303; 304) '_': bool + [308; 314) 'return': ! + "### + ) +} + +#[test] +fn infer_inherent_method() { + assert_snapshot!( + infer(r#" +struct A; + +impl A { + fn foo(self, x: u32) -> i32 {} +} + +mod b { + impl super::A { + fn bar(&self, x: u64) -> i64 {} + } +} + +fn test(a: A) { + a.foo(1); + (&a).bar(1); + a.bar(1); +} +"#), + @r###" + [32; 36) 'self': A + [38; 39) 'x': u32 + [53; 55) '{}': () + [103; 107) 'self': &A + [109; 110) 'x': u64 + [124; 126) '{}': () + [144; 145) 'a': A + [150; 198) '{ ...(1); }': () + [156; 157) 'a': A + [156; 164) 'a.foo(1)': i32 + [162; 163) '1': u32 + [170; 181) '(&a).bar(1)': i64 + [171; 173) '&a': &A + [172; 173) 'a': A + [179; 180) '1': u64 + [187; 188) 'a': A + [187; 195) 'a.bar(1)': i64 + [193; 194) '1': u64 + "### + ); +} + +#[test] +fn infer_inherent_method_str() { + assert_snapshot!( + infer(r#" +#[lang = "str"] +impl str { + fn foo(&self) -> i32 {} +} + +fn test() { + "foo".foo(); +} +"#), + @r###" + [40; 44) 'self': &str + [53; 55) '{}': () + [69; 89) '{ ...o(); }': () + [75; 80) '"foo"': &str + [75; 86) '"foo".foo()': i32 + "### + ); +} + +#[test] +fn infer_tuple() { + assert_snapshot!( + infer(r#" +fn test(x: &str, y: isize) { + let a: (u32, &str) = (1, "a"); + let b = (a, x); + let c = (y, x); + let d = (c, x); + let e = (1, "e"); + let f = (e, "d"); +} +"#), + @r###" + [9; 10) 'x': &str + [18; 19) 'y': isize + [28; 170) '{ ...d"); }': () + [38; 39) 'a': (u32, &str) + [55; 63) '(1, "a")': (u32, &str) + [56; 57) '1': u32 + [59; 62) '"a"': &str + [73; 74) 'b': ((u32, &str), &str) + [77; 83) '(a, x)': ((u32, &str), &str) + [78; 79) 'a': (u32, &str) + [81; 82) 'x': &str + [93; 94) 'c': (isize, &str) + [97; 103) '(y, x)': (isize, &str) + [98; 99) 'y': isize + [101; 102) 'x': &str + [113; 114) 'd': ((isize, &str), &str) + [117; 123) '(c, x)': ((isize, &str), &str) + [118; 119) 'c': (isize, &str) + [121; 122) 'x': &str + [133; 134) 'e': (i32, &str) + [137; 145) '(1, "e")': (i32, &str) + [138; 139) '1': i32 + [141; 144) '"e"': &str + [155; 156) 'f': ((i32, &str), &str) + [159; 167) '(e, "d")': ((i32, &str), &str) + [160; 161) 'e': (i32, &str) + [163; 166) '"d"': &str + "### + ); +} + +#[test] +fn infer_array() { + assert_snapshot!( + infer(r#" +fn test(x: &str, y: isize) { + let a = [x]; + let b = [a, a]; + let c = [b, b]; + + let d = [y, 1, 2, 3]; + let d = [1, y, 2, 3]; + let e = [y]; + let f = [d, d]; + let g = [e, e]; + + let h = [1, 2]; + let i = ["a", "b"]; + + let b = [a, ["b"]]; + let x: [u8; 0] = []; +} +"#), + @r###" + [9; 10) 'x': &str + [18; 19) 'y': isize + [28; 293) '{ ... []; }': () + [38; 39) 'a': [&str;_] + [42; 45) '[x]': [&str;_] + [43; 44) 'x': &str + [55; 56) 'b': [[&str;_];_] + [59; 65) '[a, a]': [[&str;_];_] + [60; 61) 'a': [&str;_] + [63; 64) 'a': [&str;_] + [75; 76) 'c': [[[&str;_];_];_] + [79; 85) '[b, b]': [[[&str;_];_];_] + [80; 81) 'b': [[&str;_];_] + [83; 84) 'b': [[&str;_];_] + [96; 97) 'd': [isize;_] + [100; 112) '[y, 1, 2, 3]': [isize;_] + [101; 102) 'y': isize + [104; 105) '1': isize + [107; 108) '2': isize + [110; 111) '3': isize + [122; 123) 'd': [isize;_] + [126; 138) '[1, y, 2, 3]': [isize;_] + [127; 128) '1': isize + [130; 131) 'y': isize + [133; 134) '2': isize + [136; 137) '3': isize + [148; 149) 'e': [isize;_] + [152; 155) '[y]': [isize;_] + [153; 154) 'y': isize + [165; 166) 'f': [[isize;_];_] + [169; 175) '[d, d]': [[isize;_];_] + [170; 171) 'd': [isize;_] + [173; 174) 'd': [isize;_] + [185; 186) 'g': [[isize;_];_] + [189; 195) '[e, e]': [[isize;_];_] + [190; 191) 'e': [isize;_] + [193; 194) 'e': [isize;_] + [206; 207) 'h': [i32;_] + [210; 216) '[1, 2]': [i32;_] + [211; 212) '1': i32 + [214; 215) '2': i32 + [226; 227) 'i': [&str;_] + [230; 240) '["a", "b"]': [&str;_] + [231; 234) '"a"': &str + [236; 239) '"b"': &str + [251; 252) 'b': [[&str;_];_] + [255; 265) '[a, ["b"]]': [[&str;_];_] + [256; 257) 'a': [&str;_] + [259; 264) '["b"]': [&str;_] + [260; 263) '"b"': &str + [275; 276) 'x': [u8;_] + [288; 290) '[]': [u8;_] + "### + ); +} + +#[test] +fn infer_pattern() { + assert_snapshot!( + infer(r#" +fn test(x: &i32) { + let y = x; + let &z = x; + let a = z; + let (c, d) = (1, "hello"); + + for (e, f) in some_iter { + let g = e; + } + + if let [val] = opt { + let h = val; + } + + let lambda = |a: u64, b, c: i32| { a + b; c }; + + let ref ref_to_x = x; + let mut mut_x = x; + let ref mut mut_ref_to_x = x; + let k = mut_ref_to_x; +} +"#), + @r###" + [9; 10) 'x': &i32 + [18; 369) '{ ...o_x; }': () + [28; 29) 'y': &i32 + [32; 33) 'x': &i32 + [43; 45) '&z': &i32 + [44; 45) 'z': i32 + [48; 49) 'x': &i32 + [59; 60) 'a': i32 + [63; 64) 'z': i32 + [74; 80) '(c, d)': (i32, &str) + [75; 76) 'c': i32 + [78; 79) 'd': &str + [83; 95) '(1, "hello")': (i32, &str) + [84; 85) '1': i32 + [87; 94) '"hello"': &str + [102; 152) 'for (e... }': () + [106; 112) '(e, f)': ({unknown}, {unknown}) + [107; 108) 'e': {unknown} + [110; 111) 'f': {unknown} + [116; 125) 'some_iter': {unknown} + [126; 152) '{ ... }': () + [140; 141) 'g': {unknown} + [144; 145) 'e': {unknown} + [158; 205) 'if let... }': () + [165; 170) '[val]': {unknown} + [173; 176) 'opt': {unknown} + [177; 205) '{ ... }': () + [191; 192) 'h': {unknown} + [195; 198) 'val': {unknown} + [215; 221) 'lambda': |u64, u64, i32| -> i32 + [224; 256) '|a: u6...b; c }': |u64, u64, i32| -> i32 + [225; 226) 'a': u64 + [233; 234) 'b': u64 + [236; 237) 'c': i32 + [244; 256) '{ a + b; c }': i32 + [246; 247) 'a': u64 + [246; 251) 'a + b': u64 + [250; 251) 'b': u64 + [253; 254) 'c': i32 + [267; 279) 'ref ref_to_x': &&i32 + [282; 283) 'x': &i32 + [293; 302) 'mut mut_x': &i32 + [305; 306) 'x': &i32 + [316; 336) 'ref mu...f_to_x': &mut &i32 + [339; 340) 'x': &i32 + [350; 351) 'k': &mut &i32 + [354; 366) 'mut_ref_to_x': &mut &i32 + "### + ); +} + +#[test] +fn infer_pattern_match_ergonomics() { + assert_snapshot!( + infer(r#" +struct A(T); + +fn test() { + let A(n) = &A(1); + let A(n) = &mut A(1); +} +"#), + @r###" + [28; 79) '{ ...(1); }': () + [38; 42) 'A(n)': A + [40; 41) 'n': &i32 + [45; 50) '&A(1)': &A + [46; 47) 'A': A(T) -> A + [46; 50) 'A(1)': A + [48; 49) '1': i32 + [60; 64) 'A(n)': A + [62; 63) 'n': &mut i32 + [67; 76) '&mut A(1)': &mut A + [72; 73) 'A': A(T) -> A + [72; 76) 'A(1)': A + [74; 75) '1': i32 + "### + ); +} + +#[test] +fn infer_pattern_match_ergonomics_ref() { + covers!(match_ergonomics_ref); + assert_snapshot!( + infer(r#" +fn test() { + let v = &(1, &2); + let (_, &w) = v; +} +"#), + @r###" + [11; 57) '{ ...= v; }': () + [21; 22) 'v': &(i32, &i32) + [25; 33) '&(1, &2)': &(i32, &i32) + [26; 33) '(1, &2)': (i32, &i32) + [27; 28) '1': i32 + [30; 32) '&2': &i32 + [31; 32) '2': i32 + [43; 50) '(_, &w)': (i32, &i32) + [44; 45) '_': i32 + [47; 49) '&w': &i32 + [48; 49) 'w': i32 + [53; 54) 'v': &(i32, &i32) + "### + ); +} + +#[test] +fn infer_adt_pattern() { + assert_snapshot!( + infer(r#" +enum E { + A { x: usize }, + B +} + +struct S(u32, E); + +fn test() { + let e = E::A { x: 3 }; + + let S(y, z) = foo; + let E::A { x: new_var } = e; + + match e { + E::A { x } => x, + E::B if foo => 1, + E::B => 10, + }; + + let ref d @ E::A { .. } = e; + d; +} +"#), + @r###" + [68; 289) '{ ... d; }': () + [78; 79) 'e': E + [82; 95) 'E::A { x: 3 }': E + [92; 93) '3': usize + [106; 113) 'S(y, z)': S + [108; 109) 'y': u32 + [111; 112) 'z': E + [116; 119) 'foo': S + [129; 148) 'E::A {..._var }': E + [139; 146) 'new_var': usize + [151; 152) 'e': E + [159; 245) 'match ... }': usize + [165; 166) 'e': E + [177; 187) 'E::A { x }': E + [184; 185) 'x': usize + [191; 192) 'x': usize + [202; 206) 'E::B': E + [210; 213) 'foo': bool + [217; 218) '1': usize + [228; 232) 'E::B': E + [236; 238) '10': usize + [256; 275) 'ref d ...{ .. }': &E + [264; 275) 'E::A { .. }': E + [278; 279) 'e': E + [285; 286) 'd': &E + "### + ); +} + +#[test] +fn infer_struct_generics() { + assert_snapshot!( + infer(r#" +struct A { + x: T, +} + +fn test(a1: A, i: i32) { + a1.x; + let a2 = A { x: i }; + a2.x; + let a3 = A:: { x: 1 }; + a3.x; +} +"#), + @r###" + [36; 38) 'a1': A + [48; 49) 'i': i32 + [56; 147) '{ ...3.x; }': () + [62; 64) 'a1': A + [62; 66) 'a1.x': u32 + [76; 78) 'a2': A + [81; 91) 'A { x: i }': A + [88; 89) 'i': i32 + [97; 99) 'a2': A + [97; 101) 'a2.x': i32 + [111; 113) 'a3': A + [116; 134) 'A:: + [131; 132) '1': i128 + [140; 142) 'a3': A + [140; 144) 'a3.x': i128 + "### + ); +} + +#[test] +fn infer_tuple_struct_generics() { + assert_snapshot!( + infer(r#" +struct A(T); +enum Option { Some(T), None } +use Option::*; + +fn test() { + A(42); + A(42u128); + Some("x"); + Option::Some("x"); + None; + let x: Option = None; +} +"#), + @r###" + [76; 184) '{ ...one; }': () + [82; 83) 'A': A(T) -> A + [82; 87) 'A(42)': A + [84; 86) '42': i32 + [93; 94) 'A': A(T) -> A + [93; 102) 'A(42u128)': A + [95; 101) '42u128': u128 + [108; 112) 'Some': Some<&str>(T) -> Option + [108; 117) 'Some("x")': Option<&str> + [113; 116) '"x"': &str + [123; 135) 'Option::Some': Some<&str>(T) -> Option + [123; 140) 'Option...e("x")': Option<&str> + [136; 139) '"x"': &str + [146; 150) 'None': Option<{unknown}> + [160; 161) 'x': Option + [177; 181) 'None': Option + "### + ); +} + +#[test] +fn infer_generics_in_patterns() { + assert_snapshot!( + infer(r#" +struct A { + x: T, +} + +enum Option { + Some(T), + None, +} + +fn test(a1: A, o: Option) { + let A { x: x2 } = a1; + let A:: { x: x3 } = A { x: 1 }; + match o { + Option::Some(t) => t, + _ => 1, + }; +} +"#), + @r###" + [79; 81) 'a1': A + [91; 92) 'o': Option + [107; 244) '{ ... }; }': () + [117; 128) 'A { x: x2 }': A + [124; 126) 'x2': u32 + [131; 133) 'a1': A + [143; 161) 'A:: + [157; 159) 'x3': i64 + [164; 174) 'A { x: 1 }': A + [171; 172) '1': i64 + [180; 241) 'match ... }': u64 + [186; 187) 'o': Option + [198; 213) 'Option::Some(t)': Option + [211; 212) 't': u64 + [217; 218) 't': u64 + [228; 229) '_': Option + [233; 234) '1': u64 + "### + ); +} + +#[test] +fn infer_function_generics() { + assert_snapshot!( + infer(r#" +fn id(t: T) -> T { t } + +fn test() { + id(1u32); + id::(1); + let x: u64 = id(1); +} +"#), + @r###" + [10; 11) 't': T + [21; 26) '{ t }': T + [23; 24) 't': T + [38; 98) '{ ...(1); }': () + [44; 46) 'id': fn id(T) -> T + [44; 52) 'id(1u32)': u32 + [47; 51) '1u32': u32 + [58; 68) 'id::': fn id(T) -> T + [58; 71) 'id::(1)': i128 + [69; 70) '1': i128 + [81; 82) 'x': u64 + [90; 92) 'id': fn id(T) -> T + [90; 95) 'id(1)': u64 + [93; 94) '1': u64 + "### + ); +} + +#[test] +fn infer_impl_generics() { + assert_snapshot!( + infer(r#" +struct A { + x: T1, + y: T2, +} +impl A { + fn x(self) -> X { + self.x + } + fn y(self) -> Y { + self.y + } + fn z(self, t: T) -> (X, Y, T) { + (self.x, self.y, t) + } +} + +fn test() -> i128 { + let a = A { x: 1u64, y: 1i64 }; + a.x(); + a.y(); + a.z(1i128); + a.z::(1); +} +"#), + @r###" + [74; 78) 'self': A + [85; 107) '{ ... }': X + [95; 99) 'self': A + [95; 101) 'self.x': X + [117; 121) 'self': A + [128; 150) '{ ... }': Y + [138; 142) 'self': A + [138; 144) 'self.y': Y + [163; 167) 'self': A + [169; 170) 't': T + [188; 223) '{ ... }': (X, Y, T) + [198; 217) '(self.....y, t)': (X, Y, T) + [199; 203) 'self': A + [199; 205) 'self.x': X + [207; 211) 'self': A + [207; 213) 'self.y': Y + [215; 216) 't': T + [245; 342) '{ ...(1); }': () + [255; 256) 'a': A + [259; 281) 'A { x:...1i64 }': A + [266; 270) '1u64': u64 + [275; 279) '1i64': i64 + [287; 288) 'a': A + [287; 292) 'a.x()': u64 + [298; 299) 'a': A + [298; 303) 'a.y()': i64 + [309; 310) 'a': A + [309; 319) 'a.z(1i128)': (u64, i64, i128) + [313; 318) '1i128': i128 + [325; 326) 'a': A + [325; 339) 'a.z::(1)': (u64, i64, u128) + [337; 338) '1': u128 + "### + ); +} + +#[test] +fn infer_impl_generics_with_autoderef() { + assert_snapshot!( + infer(r#" +enum Option { + Some(T), + None, +} +impl Option { + fn as_ref(&self) -> Option<&T> {} +} +fn test(o: Option) { + (&o).as_ref(); + o.as_ref(); +} +"#), + @r###" + [78; 82) 'self': &Option + [98; 100) '{}': () + [111; 112) 'o': Option + [127; 165) '{ ...f(); }': () + [133; 146) '(&o).as_ref()': Option<&u32> + [134; 136) '&o': &Option + [135; 136) 'o': Option + [152; 153) 'o': Option + [152; 162) 'o.as_ref()': Option<&u32> + "### + ); +} + +#[test] +fn infer_generic_chain() { + assert_snapshot!( + infer(r#" +struct A { + x: T, +} +impl A { + fn x(self) -> T2 { + self.x + } +} +fn id(t: T) -> T { t } + +fn test() -> i128 { + let x = 1; + let y = id(x); + let a = A { x: id(y) }; + let z = id(a.x); + let b = A { x: z }; + b.x() +} +"#), + @r###" + [53; 57) 'self': A + [65; 87) '{ ... }': T2 + [75; 79) 'self': A + [75; 81) 'self.x': T2 + [99; 100) 't': T + [110; 115) '{ t }': T + [112; 113) 't': T + [135; 261) '{ ....x() }': i128 + [146; 147) 'x': i128 + [150; 151) '1': i128 + [162; 163) 'y': i128 + [166; 168) 'id': fn id(T) -> T + [166; 171) 'id(x)': i128 + [169; 170) 'x': i128 + [182; 183) 'a': A + [186; 200) 'A { x: id(y) }': A + [193; 195) 'id': fn id(T) -> T + [193; 198) 'id(y)': i128 + [196; 197) 'y': i128 + [211; 212) 'z': i128 + [215; 217) 'id': fn id(T) -> T + [215; 222) 'id(a.x)': i128 + [218; 219) 'a': A + [218; 221) 'a.x': i128 + [233; 234) 'b': A + [237; 247) 'A { x: z }': A + [244; 245) 'z': i128 + [254; 255) 'b': A + [254; 259) 'b.x()': i128 + "### + ); +} + +#[test] +fn infer_associated_const() { + assert_snapshot!( + infer(r#" +struct Struct; + +impl Struct { + const FOO: u32 = 1; +} + +enum Enum {} + +impl Enum { + const BAR: u32 = 2; +} + +trait Trait { + const ID: u32; +} + +struct TraitTest; + +impl Trait for TraitTest { + const ID: u32 = 5; +} + +fn test() { + let x = Struct::FOO; + let y = Enum::BAR; + let z = TraitTest::ID; +} +"#), + @r###" + [52; 53) '1': u32 + [105; 106) '2': u32 + [213; 214) '5': u32 + [229; 307) '{ ...:ID; }': () + [239; 240) 'x': u32 + [243; 254) 'Struct::FOO': u32 + [264; 265) 'y': u32 + [268; 277) 'Enum::BAR': u32 + [287; 288) 'z': u32 + [291; 304) 'TraitTest::ID': u32 + "### + ); +} + +#[test] +fn infer_associated_method_struct() { + assert_snapshot!( + infer(r#" +struct A { x: u32 } + +impl A { + fn new() -> A { + A { x: 0 } + } +} +fn test() { + let a = A::new(); + a.x; +} +"#), + @r###" + [49; 75) '{ ... }': A + [59; 69) 'A { x: 0 }': A + [66; 67) '0': u32 + [88; 122) '{ ...a.x; }': () + [98; 99) 'a': A + [102; 108) 'A::new': fn new() -> A + [102; 110) 'A::new()': A + [116; 117) 'a': A + [116; 119) 'a.x': u32 + "### + ); +} + +#[test] +fn infer_associated_method_enum() { + assert_snapshot!( + infer(r#" +enum A { B, C } + +impl A { + pub fn b() -> A { + A::B + } + pub fn c() -> A { + A::C + } +} +fn test() { + let a = A::b(); + a; + let c = A::c(); + c; +} +"#), + @r###" + [47; 67) '{ ... }': A + [57; 61) 'A::B': A + [88; 108) '{ ... }': A + [98; 102) 'A::C': A + [121; 178) '{ ... c; }': () + [131; 132) 'a': A + [135; 139) 'A::b': fn b() -> A + [135; 141) 'A::b()': A + [147; 148) 'a': A + [158; 159) 'c': A + [162; 166) 'A::c': fn c() -> A + [162; 168) 'A::c()': A + [174; 175) 'c': A + "### + ); +} + +#[test] +fn infer_associated_method_with_modules() { + assert_snapshot!( + infer(r#" +mod a { + struct A; + impl A { pub fn thing() -> A { A {} }} +} + +mod b { + struct B; + impl B { pub fn thing() -> u32 { 99 }} + + mod c { + struct C; + impl C { pub fn thing() -> C { C {} }} + } +} +use b::c; + +fn test() { + let x = a::A::thing(); + let y = b::B::thing(); + let z = c::C::thing(); +} +"#), + @r###" + [56; 64) '{ A {} }': A + [58; 62) 'A {}': A + [126; 132) '{ 99 }': u32 + [128; 130) '99': u32 + [202; 210) '{ C {} }': C + [204; 208) 'C {}': C + [241; 325) '{ ...g(); }': () + [251; 252) 'x': A + [255; 266) 'a::A::thing': fn thing() -> A + [255; 268) 'a::A::thing()': A + [278; 279) 'y': u32 + [282; 293) 'b::B::thing': fn thing() -> u32 + [282; 295) 'b::B::thing()': u32 + [305; 306) 'z': C + [309; 320) 'c::C::thing': fn thing() -> C + [309; 322) 'c::C::thing()': C + "### + ); +} + +#[test] +fn infer_associated_method_generics() { + assert_snapshot!( + infer(r#" +struct Gen { + val: T +} + +impl Gen { + pub fn make(val: T) -> Gen { + Gen { val } + } +} + +fn test() { + let a = Gen::make(0u32); +} +"#), + @r###" + [64; 67) 'val': T + [82; 109) '{ ... }': Gen + [92; 103) 'Gen { val }': Gen + [98; 101) 'val': T + [123; 155) '{ ...32); }': () + [133; 134) 'a': Gen + [137; 146) 'Gen::make': fn make(T) -> Gen + [137; 152) 'Gen::make(0u32)': Gen + [147; 151) '0u32': u32 + "### + ); +} + +#[test] +fn infer_associated_method_generics_with_default_param() { + assert_snapshot!( + infer(r#" +struct Gen { + val: T +} + +impl Gen { + pub fn make() -> Gen { + loop { } + } +} + +fn test() { + let a = Gen::make(); +} +"#), + @r###" + [80; 104) '{ ... }': Gen + [90; 98) 'loop { }': ! + [95; 98) '{ }': () + [118; 146) '{ ...e(); }': () + [128; 129) 'a': Gen + [132; 141) 'Gen::make': fn make() -> Gen + [132; 143) 'Gen::make()': Gen + "### + ); +} + +#[test] +fn infer_associated_method_generics_with_default_tuple_param() { + let t = type_at( + r#" +//- /main.rs +struct Gen { + val: T +} + +impl Gen { + pub fn make() -> Gen { + loop { } + } +} + +fn test() { + let a = Gen::make(); + a.val<|>; +} +"#, + ); + assert_eq!(t, "()"); +} + +#[test] +fn infer_associated_method_generics_without_args() { + assert_snapshot!( + infer(r#" +struct Gen { + val: T +} + +impl Gen { + pub fn make() -> Gen { + loop { } + } +} + +fn test() { + let a = Gen::::make(); +} +"#), + @r###" + [76; 100) '{ ... }': Gen + [86; 94) 'loop { }': ! + [91; 94) '{ }': () + [114; 149) '{ ...e(); }': () + [124; 125) 'a': Gen + [128; 144) 'Gen::<...::make': fn make() -> Gen + [128; 146) 'Gen::<...make()': Gen + "### + ); +} + +#[test] +fn infer_associated_method_generics_2_type_params_without_args() { + assert_snapshot!( + infer(r#" +struct Gen { + val: T, + val2: U, +} + +impl Gen { + pub fn make() -> Gen { + loop { } + } +} + +fn test() { + let a = Gen::::make(); +} +"#), + @r###" + [102; 126) '{ ... }': Gen + [112; 120) 'loop { }': ! + [117; 120) '{ }': () + [140; 180) '{ ...e(); }': () + [150; 151) 'a': Gen + [154; 175) 'Gen::<...::make': fn make() -> Gen + [154; 177) 'Gen::<...make()': Gen + "### + ); +} + +#[test] +fn infer_type_alias() { + assert_snapshot!( + infer(r#" +struct A { x: X, y: Y } +type Foo = A; +type Bar = A; +type Baz = A; +fn test(x: Foo, y: Bar<&str>, z: Baz) { + x.x; + x.y; + y.x; + y.y; + z.x; + z.y; +} +"#), + @r###" + [116; 117) 'x': A + [124; 125) 'y': A<&str, u128> + [138; 139) 'z': A + [154; 211) '{ ...z.y; }': () + [160; 161) 'x': A + [160; 163) 'x.x': u32 + [169; 170) 'x': A + [169; 172) 'x.y': i128 + [178; 179) 'y': A<&str, u128> + [178; 181) 'y.x': &str + [187; 188) 'y': A<&str, u128> + [187; 190) 'y.y': u128 + [196; 197) 'z': A + [196; 199) 'z.x': u8 + [205; 206) 'z': A + [205; 208) 'z.y': i8 + "### + ) +} + +#[test] +#[should_panic] // we currently can't handle this +fn recursive_type_alias() { + assert_snapshot!( + infer(r#" +struct A {} +type Foo = Foo; +type Bar = A; +fn test(x: Foo) {} +"#), + @"" + ) +} + +#[test] +fn no_panic_on_field_of_enum() { + assert_snapshot!( + infer(r#" +enum X {} + +fn test(x: X) { + x.some_field; +} +"#), + @r###" + [20; 21) 'x': X + [26; 47) '{ ...eld; }': () + [32; 33) 'x': X + [32; 44) 'x.some_field': {unknown} + "### + ); +} + +#[test] +fn bug_585() { + assert_snapshot!( + infer(r#" +fn test() { + X {}; + match x { + A::B {} => (), + A::Y() => (), + } +} +"#), + @r###" + [11; 89) '{ ... } }': () + [17; 21) 'X {}': {unknown} + [27; 87) 'match ... }': () + [33; 34) 'x': {unknown} + [45; 52) 'A::B {}': {unknown} + [56; 58) '()': () + [68; 74) 'A::Y()': {unknown} + [78; 80) '()': () + "### + ); +} + +#[test] +fn bug_651() { + assert_snapshot!( + infer(r#" +fn quux() { + let y = 92; + 1 + y; +} +"#), + @r###" + [11; 41) '{ ...+ y; }': () + [21; 22) 'y': i32 + [25; 27) '92': i32 + [33; 34) '1': i32 + [33; 38) '1 + y': i32 + [37; 38) 'y': i32 + "### + ); +} + +#[test] +fn recursive_vars() { + covers!(type_var_cycles_resolve_completely); + covers!(type_var_cycles_resolve_as_possible); + assert_snapshot!( + infer(r#" +fn test() { + let y = unknown; + [y, &y]; +} +"#), + @r###" + [11; 48) '{ ...&y]; }': () + [21; 22) 'y': &{unknown} + [25; 32) 'unknown': &{unknown} + [38; 45) '[y, &y]': [&&{unknown};_] + [39; 40) 'y': &{unknown} + [42; 44) '&y': &&{unknown} + [43; 44) 'y': &{unknown} + "### + ); +} + +#[test] +fn recursive_vars_2() { + covers!(type_var_cycles_resolve_completely); + covers!(type_var_cycles_resolve_as_possible); + assert_snapshot!( + infer(r#" +fn test() { + let x = unknown; + let y = unknown; + [(x, y), (&y, &x)]; +} +"#), + @r###" + [11; 80) '{ ...x)]; }': () + [21; 22) 'x': &&{unknown} + [25; 32) 'unknown': &&{unknown} + [42; 43) 'y': &&{unknown} + [46; 53) 'unknown': &&{unknown} + [59; 77) '[(x, y..., &x)]': [(&&&{unknown}, &&&{unknown});_] + [60; 66) '(x, y)': (&&&{unknown}, &&&{unknown}) + [61; 62) 'x': &&{unknown} + [64; 65) 'y': &&{unknown} + [68; 76) '(&y, &x)': (&&&{unknown}, &&&{unknown}) + [69; 71) '&y': &&&{unknown} + [70; 71) 'y': &&{unknown} + [73; 75) '&x': &&&{unknown} + [74; 75) 'x': &&{unknown} + "### + ); +} + +#[test] +fn infer_type_param() { + assert_snapshot!( + infer(r#" +fn id(x: T) -> T { + x +} + +fn clone(x: &T) -> T { + *x +} + +fn test() { + let y = 10u32; + id(y); + let x: bool = clone(z); + id::(1); +} +"#), + @r###" + [10; 11) 'x': T + [21; 30) '{ x }': T + [27; 28) 'x': T + [44; 45) 'x': &T + [56; 66) '{ *x }': T + [62; 64) '*x': T + [63; 64) 'x': &T + [78; 158) '{ ...(1); }': () + [88; 89) 'y': u32 + [92; 97) '10u32': u32 + [103; 105) 'id': fn id(T) -> T + [103; 108) 'id(y)': u32 + [106; 107) 'y': u32 + [118; 119) 'x': bool + [128; 133) 'clone': fn clone(&T) -> T + [128; 136) 'clone(z)': bool + [134; 135) 'z': &bool + [142; 152) 'id::': fn id(T) -> T + [142; 155) 'id::(1)': i128 + [153; 154) '1': i128 + "### + ); +} + +#[test] +fn infer_std_crash_1() { + // caused stack overflow, taken from std + assert_snapshot!( + infer(r#" +enum Maybe { + Real(T), + Fake, +} + +fn write() { + match something_unknown { + Maybe::Real(ref mut something) => (), + } +} +"#), + @r###" + [54; 139) '{ ... } }': () + [60; 137) 'match ... }': () + [66; 83) 'someth...nknown': Maybe<{unknown}> + [94; 124) 'Maybe:...thing)': Maybe<{unknown}> + [106; 123) 'ref mu...ething': &mut {unknown} + [128; 130) '()': () + "### + ); +} + +#[test] +fn infer_std_crash_2() { + covers!(type_var_resolves_to_int_var); + // caused "equating two type variables, ...", taken from std + assert_snapshot!( + infer(r#" +fn test_line_buffer() { + &[0, b'\n', 1, b'\n']; +} +"#), + @r###" + [23; 53) '{ ...n']; }': () + [29; 50) '&[0, b...b'\n']': &[u8;_] + [30; 50) '[0, b'...b'\n']': [u8;_] + [31; 32) '0': u8 + [34; 39) 'b'\n'': u8 + [41; 42) '1': u8 + [44; 49) 'b'\n'': u8 + "### + ); +} + +#[test] +fn infer_std_crash_3() { + // taken from rustc + assert_snapshot!( + infer(r#" +pub fn compute() { + match nope!() { + SizeSkeleton::Pointer { non_zero: true, tail } => {} + } +} +"#), + @r###" + [18; 108) '{ ... } }': () + [24; 106) 'match ... }': () + [30; 37) 'nope!()': {unknown} + [48; 94) 'SizeSk...tail }': {unknown} + [82; 86) 'true': {unknown} + [88; 92) 'tail': {unknown} + [98; 100) '{}': () + "### + ); +} + +#[test] +fn infer_std_crash_4() { + // taken from rustc + assert_snapshot!( + infer(r#" +pub fn primitive_type() { + match *self { + BorrowedRef { type_: Primitive(p), ..} => {}, + } +} +"#), + @r###" + [25; 106) '{ ... } }': () + [31; 104) 'match ... }': () + [37; 42) '*self': {unknown} + [38; 42) 'self': {unknown} + [53; 91) 'Borrow...), ..}': {unknown} + [74; 86) 'Primitive(p)': {unknown} + [84; 85) 'p': {unknown} + [95; 97) '{}': () + "### + ); +} + +#[test] +fn infer_std_crash_5() { + // taken from rustc + assert_snapshot!( + infer(r#" +fn extra_compiler_flags() { + for content in doesnt_matter { + let name = if doesnt_matter { + first + } else { + &content + }; + + let content = if ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE.contains(&name) { + name + } else { + content + }; + } +} +"#), + @r###" + [27; 323) '{ ... } }': () + [33; 321) 'for co... }': () + [37; 44) 'content': &{unknown} + [48; 61) 'doesnt_matter': {unknown} + [62; 321) '{ ... }': () + [76; 80) 'name': &&{unknown} + [83; 167) 'if doe... }': &&{unknown} + [86; 99) 'doesnt_matter': bool + [100; 129) '{ ... }': &&{unknown} + [114; 119) 'first': &&{unknown} + [135; 167) '{ ... }': &&{unknown} + [149; 157) '&content': &&{unknown} + [150; 157) 'content': &{unknown} + [182; 189) 'content': &{unknown} + [192; 314) 'if ICE... }': &{unknown} + [195; 232) 'ICE_RE..._VALUE': {unknown} + [195; 248) 'ICE_RE...&name)': bool + [242; 247) '&name': &&&{unknown} + [243; 247) 'name': &&{unknown} + [249; 277) '{ ... }': &&{unknown} + [263; 267) 'name': &&{unknown} + [283; 314) '{ ... }': &{unknown} + [297; 304) 'content': &{unknown} + "### + ); +} + +#[test] +fn infer_nested_generics_crash() { + // another crash found typechecking rustc + assert_snapshot!( + infer(r#" +struct Canonical { + value: V, +} +struct QueryResponse { + value: V, +} +fn test(query_response: Canonical>) { + &query_response.value; +} +"#), + @r###" + [92; 106) 'query_response': Canonical> + [137; 167) '{ ...lue; }': () + [143; 164) '&query....value': &QueryResponse + [144; 158) 'query_response': Canonical> + [144; 164) 'query_....value': QueryResponse + "### + ); +} + +#[test] +fn bug_1030() { + assert_snapshot!(infer(r#" +struct HashSet; +struct FxHasher; +type FxHashSet = HashSet; + +impl HashSet { + fn default() -> HashSet {} +} + +pub fn main_loop() { + FxHashSet::default(); +} +"#), + @r###" + [144; 146) '{}': () + [169; 198) '{ ...t(); }': () + [175; 193) 'FxHash...efault': fn default<{unknown}, FxHasher>() -> HashSet + [175; 195) 'FxHash...ault()': HashSet<{unknown}, FxHasher> + "### + ); +} + +#[test] +fn cross_crate_associated_method_call() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:other_crate +fn test() { + let x = other_crate::foo::S::thing(); + x<|>; +} + +//- /lib.rs crate:other_crate +mod foo { + struct S; + impl S { + fn thing() -> i128 {} + } +} +"#, + ); + assert_eq!("i128", type_at_pos(&db, pos)); +} + +#[test] +fn infer_const() { + assert_snapshot!( + infer(r#" +struct Foo; +impl Foo { const ASSOC_CONST: u32 = 0; } +const GLOBAL_CONST: u32 = 101; +fn test() { + const LOCAL_CONST: u32 = 99; + let x = LOCAL_CONST; + let z = GLOBAL_CONST; + let id = Foo::ASSOC_CONST; +} +"#), + @r###" + [49; 50) '0': u32 + [80; 83) '101': u32 + [95; 213) '{ ...NST; }': () + [138; 139) 'x': {unknown} + [142; 153) 'LOCAL_CONST': {unknown} + [163; 164) 'z': u32 + [167; 179) 'GLOBAL_CONST': u32 + [189; 191) 'id': u32 + [194; 210) 'Foo::A..._CONST': u32 + "### + ); +} + +#[test] +fn infer_static() { + assert_snapshot!( + infer(r#" +static GLOBAL_STATIC: u32 = 101; +static mut GLOBAL_STATIC_MUT: u32 = 101; +fn test() { + static LOCAL_STATIC: u32 = 99; + static mut LOCAL_STATIC_MUT: u32 = 99; + let x = LOCAL_STATIC; + let y = LOCAL_STATIC_MUT; + let z = GLOBAL_STATIC; + let w = GLOBAL_STATIC_MUT; +} +"#), + @r###" + [29; 32) '101': u32 + [70; 73) '101': u32 + [85; 280) '{ ...MUT; }': () + [173; 174) 'x': {unknown} + [177; 189) 'LOCAL_STATIC': {unknown} + [199; 200) 'y': {unknown} + [203; 219) 'LOCAL_...IC_MUT': {unknown} + [229; 230) 'z': u32 + [233; 246) 'GLOBAL_STATIC': u32 + [256; 257) 'w': u32 + [260; 277) 'GLOBAL...IC_MUT': u32 + "### + ); +} + +#[test] +fn infer_trait_method_simple() { + // the trait implementation is intentionally incomplete -- it shouldn't matter + assert_snapshot!( + infer(r#" +trait Trait1 { + fn method(&self) -> u32; +} +struct S1; +impl Trait1 for S1 {} +trait Trait2 { + fn method(&self) -> i128; +} +struct S2; +impl Trait2 for S2 {} +fn test() { + S1.method(); // -> u32 + S2.method(); // -> i128 +} +"#), + @r###" + [31; 35) 'self': &Self + [110; 114) 'self': &Self + [170; 228) '{ ...i128 }': () + [176; 178) 'S1': S1 + [176; 187) 'S1.method()': u32 + [203; 205) 'S2': S2 + [203; 214) 'S2.method()': i128 + "### + ); +} + +#[test] +fn infer_trait_method_scoped() { + // the trait implementation is intentionally incomplete -- it shouldn't matter + assert_snapshot!( + infer(r#" +struct S; +mod foo { + pub trait Trait1 { + fn method(&self) -> u32; + } + impl Trait1 for super::S {} +} +mod bar { + pub trait Trait2 { + fn method(&self) -> i128; + } + impl Trait2 for super::S {} +} + +mod foo_test { + use super::S; + use super::foo::Trait1; + fn test() { + S.method(); // -> u32 + } +} + +mod bar_test { + use super::S; + use super::bar::Trait2; + fn test() { + S.method(); // -> i128 + } +} +"#), + @r###" + [63; 67) 'self': &Self + [169; 173) 'self': &Self + [300; 337) '{ ... }': () + [310; 311) 'S': S + [310; 320) 'S.method()': u32 + [416; 454) '{ ... }': () + [426; 427) 'S': S + [426; 436) 'S.method()': i128 + "### + ); +} + +#[test] +fn infer_trait_method_generic_1() { + // the trait implementation is intentionally incomplete -- it shouldn't matter + assert_snapshot!( + infer(r#" +trait Trait { + fn method(&self) -> T; +} +struct S; +impl Trait for S {} +fn test() { + S.method(); +} +"#), + @r###" + [33; 37) 'self': &Self + [92; 111) '{ ...d(); }': () + [98; 99) 'S': S + [98; 108) 'S.method()': u32 + "### + ); +} + +#[test] +fn infer_trait_method_generic_more_params() { + // the trait implementation is intentionally incomplete -- it shouldn't matter + assert_snapshot!( + infer(r#" +trait Trait { + fn method1(&self) -> (T1, T2, T3); + fn method2(&self) -> (T3, T2, T1); +} +struct S1; +impl Trait for S1 {} +struct S2; +impl Trait for S2 {} +fn test() { + S1.method1(); // u8, u16, u32 + S1.method2(); // u32, u16, u8 + S2.method1(); // i8, i16, {unknown} + S2.method2(); // {unknown}, i16, i8 +} +"#), + @r###" + [43; 47) 'self': &Self + [82; 86) 'self': &Self + [210; 361) '{ ..., i8 }': () + [216; 218) 'S1': S1 + [216; 228) 'S1.method1()': (u8, u16, u32) + [250; 252) 'S1': S1 + [250; 262) 'S1.method2()': (u32, u16, u8) + [284; 286) 'S2': S2 + [284; 296) 'S2.method1()': (i8, i16, {unknown}) + [324; 326) 'S2': S2 + [324; 336) 'S2.method2()': ({unknown}, i16, i8) + "### + ); +} + +#[test] +fn infer_trait_method_generic_2() { + // the trait implementation is intentionally incomplete -- it shouldn't matter + assert_snapshot!( + infer(r#" +trait Trait { + fn method(&self) -> T; +} +struct S(T); +impl Trait for S {} +fn test() { + S(1u32).method(); +} +"#), + @r###" + [33; 37) 'self': &Self + [102; 127) '{ ...d(); }': () + [108; 109) 'S': S(T) -> S + [108; 115) 'S(1u32)': S + [108; 124) 'S(1u32...thod()': u32 + [110; 114) '1u32': u32 + "### + ); +} + +#[test] +fn infer_trait_assoc_method() { + assert_snapshot!( + infer(r#" +trait Default { + fn default() -> Self; +} +struct S; +impl Default for S {} +fn test() { + let s1: S = Default::default(); + let s2 = S::default(); + let s3 = ::default(); +} +"#), + @r###" + [87; 193) '{ ...t(); }': () + [97; 99) 's1': S + [105; 121) 'Defaul...efault': fn default() -> Self + [105; 123) 'Defaul...ault()': S + [133; 135) 's2': S + [138; 148) 'S::default': fn default() -> Self + [138; 150) 'S::default()': S + [160; 162) 's3': S + [165; 188) '() -> Self + [165; 190) ' { + fn make() -> T; +} +struct S; +impl Trait for S {} +struct G; +impl Trait for G {} +fn test() { + let a = S::make(); + let b = G::::make(); + let c: f64 = G::make(); +} +"#), + @r###" + [127; 211) '{ ...e(); }': () + [137; 138) 'a': u32 + [141; 148) 'S::make': fn make() -> T + [141; 150) 'S::make()': u32 + [160; 161) 'b': u64 + [164; 178) 'G::::make': fn make, u64>() -> T + [164; 180) 'G::, f64>() -> T + [199; 208) 'G::make()': f64 + "### + ); +} + +#[test] +fn infer_trait_assoc_method_generics_2() { + assert_snapshot!( + infer(r#" +trait Trait { + fn make() -> (T, U); +} +struct S; +impl Trait for S {} +struct G; +impl Trait for G {} +fn test() { + let a = S::make::(); + let b: (_, i64) = S::make(); + let c = G::::make::(); + let d: (u32, _) = G::make::(); + let e: (u32, i64) = G::make(); +} +"#), + @r###" + [135; 313) '{ ...e(); }': () + [145; 146) 'a': (u32, i64) + [149; 163) 'S::make::': fn make() -> (T, U) + [149; 165) 'S::mak...i64>()': (u32, i64) + [175; 176) 'b': (u32, i64) + [189; 196) 'S::make': fn make() -> (T, U) + [189; 198) 'S::make()': (u32, i64) + [208; 209) 'c': (u32, i64) + [212; 233) 'G::': fn make, u32, i64>() -> (T, U) + [212; 235) 'G::()': (u32, i64) + [245; 246) 'd': (u32, i64) + [259; 273) 'G::make::': fn make, u32, i64>() -> (T, U) + [259; 275) 'G::mak...i64>()': (u32, i64) + [285; 286) 'e': (u32, i64) + [301; 308) 'G::make': fn make, u32, i64>() -> (T, U) + [301; 310) 'G::make()': (u32, i64) + "### + ); +} + +#[test] +fn infer_trait_assoc_method_generics_3() { + assert_snapshot!( + infer(r#" +trait Trait { + fn make() -> (Self, T); +} +struct S; +impl Trait for S {} +fn test() { + let a = S::make(); +} +"#), + @r###" + [101; 127) '{ ...e(); }': () + [111; 112) 'a': (S, i64) + [115; 122) 'S::make': fn make, i64>() -> (Self, T) + [115; 124) 'S::make()': (S, i64) + "### + ); +} + +#[test] +fn infer_trait_assoc_method_generics_4() { + assert_snapshot!( + infer(r#" +trait Trait { + fn make() -> (Self, T); +} +struct S; +impl Trait for S {} +impl Trait for S {} +fn test() { + let a: (S, _) = S::make(); + let b: (_, i32) = S::make(); +} +"#), + @r###" + [131; 203) '{ ...e(); }': () + [141; 142) 'a': (S, i64) + [158; 165) 'S::make': fn make, i64>() -> (Self, T) + [158; 167) 'S::make()': (S, i64) + [177; 178) 'b': (S, i32) + [191; 198) 'S::make': fn make, i32>() -> (Self, T) + [191; 200) 'S::make()': (S, i32) + "### + ); +} + +#[test] +fn infer_trait_assoc_method_generics_5() { + assert_snapshot!( + infer(r#" +trait Trait { + fn make() -> (Self, T, U); +} +struct S; +impl Trait for S {} +fn test() { + let a = >::make::(); + let b: (S, _, _) = Trait::::make::(); +} +"#), + @r###" + [107; 211) '{ ...>(); }': () + [117; 118) 'a': (S, i64, u8) + [121; 150) '': fn make, i64, u8>() -> (Self, T, U) + [121; 152) '()': (S, i64, u8) + [162; 163) 'b': (S, i64, u8) + [182; 206) 'Trait:...::': fn make, i64, u8>() -> (Self, T, U) + [182; 208) 'Trait:...()': (S, i64, u8) + "### + ); +} + +#[test] +fn infer_from_bound_1() { + assert_snapshot!( + infer(r#" +trait Trait {} +struct S(T); +impl Trait for S {} +fn foo>(t: T) {} +fn test() { + let s = S(unknown); + foo(s); +} +"#), + @r###" + [86; 87) 't': T + [92; 94) '{}': () + [105; 144) '{ ...(s); }': () + [115; 116) 's': S + [119; 120) 'S': S(T) -> S + [119; 129) 'S(unknown)': S + [121; 128) 'unknown': u32 + [135; 138) 'foo': fn foo>(T) -> () + [135; 141) 'foo(s)': () + [139; 140) 's': S + "### + ); +} + +#[test] +fn infer_from_bound_2() { + assert_snapshot!( + infer(r#" +trait Trait {} +struct S(T); +impl Trait for S {} +fn foo>(t: T) -> U {} +fn test() { + let s = S(unknown); + let x: u32 = foo(s); +} +"#), + @r###" + [87; 88) 't': T + [98; 100) '{}': () + [111; 163) '{ ...(s); }': () + [121; 122) 's': S + [125; 126) 'S': S(T) -> S + [125; 135) 'S(unknown)': S + [127; 134) 'unknown': u32 + [145; 146) 'x': u32 + [154; 157) 'foo': fn foo>(T) -> U + [154; 160) 'foo(s)': u32 + [158; 159) 's': S + "### + ); +} + +#[test] +fn infer_call_trait_method_on_generic_param_1() { + assert_snapshot!( + infer(r#" +trait Trait { + fn method(&self) -> u32; +} +fn test(t: T) { + t.method(); +} +"#), + @r###" + [30; 34) 'self': &Self + [64; 65) 't': T + [70; 89) '{ ...d(); }': () + [76; 77) 't': T + [76; 86) 't.method()': u32 + "### + ); +} + +#[test] +fn infer_call_trait_method_on_generic_param_2() { + assert_snapshot!( + infer(r#" +trait Trait { + fn method(&self) -> T; +} +fn test>(t: T) { + t.method(); +} +"#), + @r###" + [33; 37) 'self': &Self + [71; 72) 't': T + [77; 96) '{ ...d(); }': () + [83; 84) 't': T + [83; 93) 't.method()': [missing name] + "### + ); +} + +#[test] +fn infer_with_multiple_trait_impls() { + assert_snapshot!( + infer(r#" +trait Into { + fn into(self) -> T; +} +struct S; +impl Into for S {} +impl Into for S {} +fn test() { + let x: u32 = S.into(); + let y: u64 = S.into(); + let z = Into::::into(S); +} +"#), + @r###" + [29; 33) 'self': Self + [111; 202) '{ ...(S); }': () + [121; 122) 'x': u32 + [130; 131) 'S': S + [130; 138) 'S.into()': u32 + [148; 149) 'y': u64 + [157; 158) 'S': S + [157; 165) 'S.into()': u64 + [175; 176) 'z': u64 + [179; 196) 'Into::...::into': fn into(Self) -> T + [179; 199) 'Into::...nto(S)': u64 + [197; 198) 'S': S + "### + ); +} + +#[test] +fn infer_project_associated_type() { + // y, z, a don't yet work because of https://github.com/rust-lang/chalk/issues/234 + assert_snapshot!( + infer(r#" +trait Iterable { + type Item; +} +struct S; +impl Iterable for S { type Item = u32; } +fn test() { + let x: ::Item = 1; + let y: ::Item = no_matter; + let z: T::Item = no_matter; + let a: ::Item = no_matter; +} +"#), + @r###" + [108; 261) '{ ...ter; }': () + [118; 119) 'x': u32 + [145; 146) '1': u32 + [156; 157) 'y': {unknown} + [183; 192) 'no_matter': {unknown} + [202; 203) 'z': {unknown} + [215; 224) 'no_matter': {unknown} + [234; 235) 'a': {unknown} + [249; 258) 'no_matter': {unknown} + "### + ); +} + +#[test] +fn infer_return_associated_type() { + assert_snapshot!( + infer(r#" +trait Iterable { + type Item; +} +struct S; +impl Iterable for S { type Item = u32; } +fn foo1(t: T) -> T::Item {} +fn foo2(t: T) -> ::Item {} +fn foo3(t: T) -> ::Item {} +fn test() { + let x = foo1(S); + let y = foo2(S); + let z = foo3(S); +} +"#), + @r###" + [106; 107) 't': T + [123; 125) '{}': () + [147; 148) 't': T + [178; 180) '{}': () + [202; 203) 't': T + [221; 223) '{}': () + [234; 300) '{ ...(S); }': () + [244; 245) 'x': u32 + [248; 252) 'foo1': fn foo1(T) -> ::Item + [248; 255) 'foo1(S)': u32 + [253; 254) 'S': S + [265; 266) 'y': u32 + [269; 273) 'foo2': fn foo2(T) -> ::Item + [269; 276) 'foo2(S)': u32 + [274; 275) 'S': S + [286; 287) 'z': u32 + [290; 294) 'foo3': fn foo3(T) -> ::Item + [290; 297) 'foo3(S)': u32 + [295; 296) 'S': S + "### + ); +} + +#[test] +fn infer_associated_type_bound() { + assert_snapshot!( + infer(r#" +trait Iterable { + type Item; +} +fn test>() { + let y: T::Item = unknown; +} +"#), + @r###" + [67; 100) '{ ...own; }': () + [77; 78) 'y': {unknown} + [90; 97) 'unknown': {unknown} + "### + ); +} + +#[test] +fn infer_const_body() { + assert_snapshot!( + infer(r#" +const A: u32 = 1 + 1; +static B: u64 = { let x = 1; x }; +"#), + @r###" + [16; 17) '1': u32 + [16; 21) '1 + 1': u32 + [20; 21) '1': u32 + [39; 55) '{ let ...1; x }': u64 + [45; 46) 'x': u64 + [49; 50) '1': u64 + [52; 53) 'x': u64 + "### + ); +} + +#[test] +fn tuple_struct_fields() { + assert_snapshot!( + infer(r#" +struct S(i32, u64); +fn test() -> u64 { + let a = S(4, 6); + let b = a.0; + a.1 +} +"#), + @r###" + [38; 87) '{ ... a.1 }': u64 + [48; 49) 'a': S + [52; 53) 'S': S(i32, u64) -> S + [52; 59) 'S(4, 6)': S + [54; 55) '4': i32 + [57; 58) '6': u64 + [69; 70) 'b': i32 + [73; 74) 'a': S + [73; 76) 'a.0': i32 + [82; 83) 'a': S + [82; 85) 'a.1': u64 + "### + ); +} + +#[test] +fn tuple_struct_with_fn() { + assert_snapshot!( + infer(r#" +struct S(fn(u32) -> u64); +fn test() -> u64 { + let a = S(|i| 2*i); + let b = a.0(4); + a.0(2) +} +"#), + @r###" + [44; 102) '{ ...0(2) }': u64 + [54; 55) 'a': S + [58; 59) 'S': S(fn(u32) -> u64) -> S + [58; 68) 'S(|i| 2*i)': S + [60; 67) '|i| 2*i': |i32| -> i32 + [61; 62) 'i': i32 + [64; 65) '2': i32 + [64; 67) '2*i': i32 + [66; 67) 'i': i32 + [78; 79) 'b': u64 + [82; 83) 'a': S + [82; 85) 'a.0': fn(u32) -> u64 + [82; 88) 'a.0(4)': u64 + [86; 87) '4': u32 + [94; 95) 'a': S + [94; 97) 'a.0': fn(u32) -> u64 + [94; 100) 'a.0(2)': u64 + [98; 99) '2': u32 + "### + ); +} + +#[test] +fn indexing_arrays() { + assert_snapshot!( + infer("fn main() { &mut [9][2]; }"), + @r###" + [10; 26) '{ &mut...[2]; }': () + [12; 23) '&mut [9][2]': &mut {unknown} + [17; 20) '[9]': [i32;_] + [17; 23) '[9][2]': {unknown} + [18; 19) '9': i32 + [21; 22) '2': i32 + "### + ) +} + +#[test] +fn infer_macros_expanded() { + assert_snapshot!( + infer(r#" +struct Foo(Vec); + +macro_rules! foo { + ($($item:expr),*) => { + { + Foo(vec![$($item,)*]) + } + }; +} + +fn main() { + let x = foo!(1,2); +} +"#), + @r###" + ![0; 17) '{Foo(v...,2,])}': Foo + ![1; 4) 'Foo': Foo({unknown}) -> Foo + ![1; 16) 'Foo(vec![1,2,])': Foo + ![5; 15) 'vec![1,2,]': {unknown} + [156; 182) '{ ...,2); }': () + [166; 167) 'x': Foo + "### + ); +} + +#[test] +fn infer_legacy_textual_scoped_macros_expanded() { + assert_snapshot!( + infer(r#" +struct Foo(Vec); + +#[macro_use] +mod m { + macro_rules! foo { + ($($item:expr),*) => { + { + Foo(vec![$($item,)*]) + } + }; + } +} + +fn main() { + let x = foo!(1,2); + let y = crate::foo!(1,2); +} +"#), + @r###" + ![0; 17) '{Foo(v...,2,])}': Foo + ![1; 4) 'Foo': Foo({unknown}) -> Foo + ![1; 16) 'Foo(vec![1,2,])': Foo + ![5; 15) 'vec![1,2,]': {unknown} + [195; 251) '{ ...,2); }': () + [205; 206) 'x': Foo + [228; 229) 'y': {unknown} + [232; 248) 'crate:...!(1,2)': {unknown} + "### + ); +} + +#[test] +fn infer_path_qualified_macros_expanded() { + assert_snapshot!( + infer(r#" +#[macro_export] +macro_rules! foo { + () => { 42i32 } +} + +mod m { + pub use super::foo as bar; +} + +fn main() { + let x = crate::foo!(); + let y = m::bar!(); +} +"#), + @r###" + ![0; 5) '42i32': i32 + ![0; 5) '42i32': i32 + [111; 164) '{ ...!(); }': () + [121; 122) 'x': i32 + [148; 149) 'y': i32 + "### + ); +} + +#[test] +fn infer_type_value_macro_having_same_name() { + assert_snapshot!( + infer(r#" +#[macro_export] +macro_rules! foo { + () => { + mod foo { + pub use super::foo; + } + }; + ($x:tt) => { + $x + }; +} + +foo!(); + +fn foo() { + let foo = foo::foo!(42i32); +} +"#), + @r###" + ![0; 5) '42i32': i32 + [171; 206) '{ ...32); }': () + [181; 184) 'foo': i32 + "### + ); +} + +#[test] +fn processes_impls_generated_by_macros() { + let t = type_at( + r#" +//- /main.rs +macro_rules! m { + ($ident:ident) => (impl Trait for $ident {}) +} +trait Trait { fn foo(self) -> u128 {} } +struct S; +m!(S); +fn test() { S.foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[test] +fn infer_macro_with_dollar_crate_is_correct_in_expr() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:foo +fn test() { + let x = (foo::foo!(1), foo::foo!(2)); + x<|>; +} + +//- /lib.rs crate:foo +#[macro_export] +macro_rules! foo { + (1) => { $crate::bar!() }; + (2) => { 1 + $crate::baz() }; +} + +#[macro_export] +macro_rules! bar { + () => { 42 } +} + +pub fn baz() -> usize { 31usize } +"#, + ); + assert_eq!("(i32, usize)", type_at_pos(&db, pos)); +} + +#[ignore] +#[test] +fn method_resolution_trait_before_autoref() { + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl S { fn foo(&self) -> i8 { 0 } } +impl Trait for S { fn foo(self) -> u128 { 0 } } +fn test() { S.foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[ignore] +#[test] +fn method_resolution_by_value_before_autoref() { + let t = type_at( + r#" +//- /main.rs +trait Clone { fn clone(&self) -> Self; } +struct S; +impl Clone for S {} +impl Clone for &S {} +fn test() { (S.clone(), (&S).clone(), (&&S).clone())<|>; } +"#, + ); + assert_eq!(t, "(S, S, &S)"); +} + +#[test] +fn method_resolution_trait_before_autoderef() { + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl S { fn foo(self) -> i8 { 0 } } +impl Trait for &S { fn foo(self) -> u128 { 0 } } +fn test() { (&S).foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[test] +fn method_resolution_impl_before_trait() { + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl S { fn foo(self) -> i8 { 0 } } +impl Trait for S { fn foo(self) -> u128 { 0 } } +fn test() { S.foo()<|>; } +"#, + ); + assert_eq!(t, "i8"); +} + +#[test] +fn method_resolution_trait_autoderef() { + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl Trait for S { fn foo(self) -> u128 { 0 } } +fn test() { (&S).foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[test] +fn method_resolution_trait_from_prelude() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:other_crate +struct S; +impl Clone for S {} + +fn test() { + S.clone()<|>; +} + +//- /lib.rs crate:other_crate +#[prelude_import] use foo::*; + +mod foo { + trait Clone { + fn clone(&self) -> Self; + } +} +"#, + ); + assert_eq!("S", type_at_pos(&db, pos)); +} + +#[test] +fn method_resolution_where_clause_for_unknown_trait() { + // The blanket impl shouldn't apply because we can't even resolve UnknownTrait + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl Trait for T where T: UnknownTrait {} +fn test() { (&S).foo()<|>; } +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn method_resolution_where_clause_not_met() { + // The blanket impl shouldn't apply because we can't prove S: Clone + let t = type_at( + r#" +//- /main.rs +trait Clone {} +trait Trait { fn foo(self) -> u128; } +struct S; +impl Trait for T where T: Clone {} +fn test() { (&S).foo()<|>; } +"#, + ); + // This is also to make sure that we don't resolve to the foo method just + // because that's the only method named foo we can find, which would make + // the below tests not work + assert_eq!(t, "{unknown}"); +} + +#[test] +fn method_resolution_where_clause_inline_not_met() { + // The blanket impl shouldn't apply because we can't prove S: Clone + let t = type_at( + r#" +//- /main.rs +trait Clone {} +trait Trait { fn foo(self) -> u128; } +struct S; +impl Trait for T {} +fn test() { (&S).foo()<|>; } +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn method_resolution_where_clause_1() { + let t = type_at( + r#" +//- /main.rs +trait Clone {} +trait Trait { fn foo(self) -> u128; } +struct S; +impl Clone for S {} +impl Trait for T where T: Clone {} +fn test() { S.foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[test] +fn method_resolution_where_clause_2() { + let t = type_at( + r#" +//- /main.rs +trait Into { fn into(self) -> T; } +trait From { fn from(other: T) -> Self; } +struct S1; +struct S2; +impl From for S1 {} +impl Into for T where U: From {} +fn test() { S2.into()<|>; } +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn method_resolution_where_clause_inline() { + let t = type_at( + r#" +//- /main.rs +trait Into { fn into(self) -> T; } +trait From { fn from(other: T) -> Self; } +struct S1; +struct S2; +impl From for S1 {} +impl> Into for T {} +fn test() { S2.into()<|>; } +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn method_resolution_encountering_fn_type() { + type_at( + r#" +//- /main.rs +fn foo() {} +trait FnOnce { fn call(self); } +fn test() { foo.call()<|>; } +"#, + ); +} + +#[test] +fn method_resolution_slow() { + // this can get quite slow if we set the solver size limit too high + let t = type_at( + r#" +//- /main.rs +trait SendX {} + +struct S1; impl SendX for S1 {} +struct S2; impl SendX for S2 {} +struct U1; + +trait Trait { fn method(self); } + +struct X1 {} +impl SendX for X1 where A: SendX, B: SendX {} + +struct S {} + +trait FnX {} + +impl Trait for S where C: FnX, B: SendX {} + +fn test() { (S {}).method()<|>; } +"#, + ); + assert_eq!(t, "()"); +} + +#[test] +fn shadowing_primitive() { + let t = type_at( + r#" +//- /main.rs +struct i32; +struct Foo; + +impl i32 { fn foo(&self) -> Foo { Foo } } + +fn main() { + let x: i32 = i32; + x.foo()<|>; +}"#, + ); + assert_eq!(t, "Foo"); +} + +#[test] +fn deref_trait() { + let t = type_at( + r#" +//- /main.rs +#[lang = "deref"] +trait Deref { + type Target; + fn deref(&self) -> &Self::Target; +} + +struct Arc; +impl Deref for Arc { + type Target = T; +} + +struct S; +impl S { + fn foo(&self) -> u128 {} +} + +fn test(s: Arc) { + (*s, s.foo())<|>; +} +"#, + ); + assert_eq!(t, "(S, u128)"); +} + +#[test] +fn deref_trait_with_inference_var() { + let t = type_at( + r#" +//- /main.rs +#[lang = "deref"] +trait Deref { + type Target; + fn deref(&self) -> &Self::Target; +} + +struct Arc; +fn new_arc() -> Arc {} +impl Deref for Arc { + type Target = T; +} + +struct S; +fn foo(a: Arc) {} + +fn test() { + let a = new_arc(); + let b = (*a)<|>; + foo(a); +} +"#, + ); + assert_eq!(t, "S"); +} + +#[test] +fn deref_trait_infinite_recursion() { + let t = type_at( + r#" +//- /main.rs +#[lang = "deref"] +trait Deref { + type Target; + fn deref(&self) -> &Self::Target; +} + +struct S; + +impl Deref for S { + type Target = S; +} + +fn test(s: S) { + s.foo()<|>; +} +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn deref_trait_with_question_mark_size() { + let t = type_at( + r#" +//- /main.rs +#[lang = "deref"] +trait Deref { + type Target; + fn deref(&self) -> &Self::Target; +} + +struct Arc; +impl Deref for Arc { + type Target = T; +} + +struct S; +impl S { + fn foo(&self) -> u128 {} +} + +fn test(s: Arc) { + (*s, s.foo())<|>; +} +"#, + ); + assert_eq!(t, "(S, u128)"); +} + +#[test] +fn obligation_from_function_clause() { + let t = type_at( + r#" +//- /main.rs +struct S; + +trait Trait {} +impl Trait for S {} + +fn foo, U>(t: T) -> U {} + +fn test(s: S) { + foo(s)<|>; +} +"#, + ); + assert_eq!(t, "u32"); +} + +#[test] +fn obligation_from_method_clause() { + let t = type_at( + r#" +//- /main.rs +struct S; + +trait Trait {} +impl Trait for S {} + +struct O; +impl O { + fn foo, U>(&self, t: T) -> U {} +} + +fn test() { + O.foo(S)<|>; +} +"#, + ); + assert_eq!(t, "isize"); +} + +#[test] +fn obligation_from_self_method_clause() { + let t = type_at( + r#" +//- /main.rs +struct S; + +trait Trait {} +impl Trait for S {} + +impl S { + fn foo(&self) -> U where Self: Trait {} +} + +fn test() { + S.foo()<|>; +} +"#, + ); + assert_eq!(t, "i64"); +} + +#[test] +fn obligation_from_impl_clause() { + let t = type_at( + r#" +//- /main.rs +struct S; + +trait Trait {} +impl Trait<&str> for S {} + +struct O; +impl> O { + fn foo(&self) -> U {} +} + +fn test(o: O) { + o.foo()<|>; +} +"#, + ); + assert_eq!(t, "&str"); +} + +#[test] +fn generic_param_env_1() { + let t = type_at( + r#" +//- /main.rs +trait Clone {} +trait Trait { fn foo(self) -> u128; } +struct S; +impl Clone for S {} +impl Trait for T where T: Clone {} +fn test(t: T) { t.foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[test] +fn generic_param_env_1_not_met() { + let t = type_at( + r#" +//- /main.rs +trait Clone {} +trait Trait { fn foo(self) -> u128; } +struct S; +impl Clone for S {} +impl Trait for T where T: Clone {} +fn test(t: T) { t.foo()<|>; } +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn generic_param_env_2() { + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl Trait for S {} +fn test(t: T) { t.foo()<|>; } +"#, + ); + assert_eq!(t, "u128"); +} + +#[test] +fn generic_param_env_2_not_met() { + let t = type_at( + r#" +//- /main.rs +trait Trait { fn foo(self) -> u128; } +struct S; +impl Trait for S {} +fn test(t: T) { t.foo()<|>; } +"#, + ); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn generic_param_env_deref() { + let t = type_at( + r#" +//- /main.rs +#[lang = "deref"] +trait Deref { + type Target; +} +trait Trait {} +impl Deref for T where T: Trait { + type Target = i128; +} +fn test(t: T) { (*t)<|>; } +"#, + ); + assert_eq!(t, "i128"); +} + +#[test] +fn associated_type_placeholder() { + let t = type_at( + r#" +//- /main.rs +pub trait ApplyL { + type Out; +} + +pub struct RefMutL; + +impl ApplyL for RefMutL { + type Out = ::Out; +} + +fn test() { + let y: as ApplyL>::Out = no_matter; + y<|>; +} +"#, + ); + // inside the generic function, the associated type gets normalized to a placeholder `ApplL::Out` [https://rust-lang.github.io/rustc-guide/traits/associated-types.html#placeholder-associated-types]. + // FIXME: fix type parameter names going missing when going through Chalk + assert_eq!(t, "ApplyL::Out<[missing name]>"); +} + +#[test] +fn associated_type_placeholder_2() { + let t = type_at( + r#" +//- /main.rs +pub trait ApplyL { + type Out; +} +fn foo(t: T) -> ::Out; + +fn test(t: T) { + let y = foo(t); + y<|>; +} +"#, + ); + // FIXME here Chalk doesn't normalize the type to a placeholder. I think we + // need to add a rule like Normalize(::Out -> ApplyL::Out) + // to the trait env ourselves here; probably Chalk can't do this by itself. + // assert_eq!(t, "ApplyL::Out<[missing name]>"); + assert_eq!(t, "{unknown}"); +} + +#[test] +fn impl_trait() { + assert_snapshot!( + infer(r#" +trait Trait { + fn foo(&self) -> T; + fn foo2(&self) -> i64; +} +fn bar() -> impl Trait {} + +fn test(x: impl Trait, y: &impl Trait) { + x; + y; + let z = bar(); + x.foo(); + y.foo(); + z.foo(); + x.foo2(); + y.foo2(); + z.foo2(); +} +"#), + @r###" + [30; 34) 'self': &Self + [55; 59) 'self': &Self + [99; 101) '{}': () + [111; 112) 'x': impl Trait + [131; 132) 'y': &impl Trait + [152; 269) '{ ...2(); }': () + [158; 159) 'x': impl Trait + [165; 166) 'y': &impl Trait + [176; 177) 'z': impl Trait + [180; 183) 'bar': fn bar() -> impl Trait + [180; 185) 'bar()': impl Trait + [191; 192) 'x': impl Trait + [191; 198) 'x.foo()': u64 + [204; 205) 'y': &impl Trait + [204; 211) 'y.foo()': u64 + [217; 218) 'z': impl Trait + [217; 224) 'z.foo()': u64 + [230; 231) 'x': impl Trait + [230; 238) 'x.foo2()': i64 + [244; 245) 'y': &impl Trait + [244; 252) 'y.foo2()': i64 + [258; 259) 'z': impl Trait + [258; 266) 'z.foo2()': i64 + "### + ); +} + +#[test] +fn dyn_trait() { + assert_snapshot!( + infer(r#" +trait Trait { + fn foo(&self) -> T; + fn foo2(&self) -> i64; +} +fn bar() -> dyn Trait {} + +fn test(x: dyn Trait, y: &dyn Trait) { + x; + y; + let z = bar(); + x.foo(); + y.foo(); + z.foo(); + x.foo2(); + y.foo2(); + z.foo2(); +} +"#), + @r###" + [30; 34) 'self': &Self + [55; 59) 'self': &Self + [98; 100) '{}': () + [110; 111) 'x': dyn Trait + [129; 130) 'y': &dyn Trait + [149; 266) '{ ...2(); }': () + [155; 156) 'x': dyn Trait + [162; 163) 'y': &dyn Trait + [173; 174) 'z': dyn Trait + [177; 180) 'bar': fn bar() -> dyn Trait + [177; 182) 'bar()': dyn Trait + [188; 189) 'x': dyn Trait + [188; 195) 'x.foo()': u64 + [201; 202) 'y': &dyn Trait + [201; 208) 'y.foo()': u64 + [214; 215) 'z': dyn Trait + [214; 221) 'z.foo()': u64 + [227; 228) 'x': dyn Trait + [227; 235) 'x.foo2()': i64 + [241; 242) 'y': &dyn Trait + [241; 249) 'y.foo2()': i64 + [255; 256) 'z': dyn Trait + [255; 263) 'z.foo2()': i64 + "### + ); +} + +#[test] +fn dyn_trait_bare() { + assert_snapshot!( + infer(r#" +trait Trait { + fn foo(&self) -> u64; +} +fn bar() -> Trait {} + +fn test(x: Trait, y: &Trait) -> u64 { + x; + y; + let z = bar(); + x.foo(); + y.foo(); + z.foo(); +} +"#), + @r###" + [27; 31) 'self': &Self + [61; 63) '{}': () + [73; 74) 'x': dyn Trait + [83; 84) 'y': &dyn Trait + [101; 176) '{ ...o(); }': () + [107; 108) 'x': dyn Trait + [114; 115) 'y': &dyn Trait + [125; 126) 'z': dyn Trait + [129; 132) 'bar': fn bar() -> dyn Trait + [129; 134) 'bar()': dyn Trait + [140; 141) 'x': dyn Trait + [140; 147) 'x.foo()': u64 + [153; 154) 'y': &dyn Trait + [153; 160) 'y.foo()': u64 + [166; 167) 'z': dyn Trait + [166; 173) 'z.foo()': u64 + "### + ); +} + +#[test] +fn weird_bounds() { + assert_snapshot!( + infer(r#" +trait Trait {} +fn test() { + let a: impl Trait + 'lifetime = foo; + let b: impl 'lifetime = foo; + let b: impl (Trait) = foo; + let b: impl ('lifetime) = foo; + let d: impl ?Sized = foo; + let e: impl Trait + ?Sized = foo; +} +"#), + @r###" + [26; 237) '{ ...foo; }': () + [36; 37) 'a': impl Trait + {error} + [64; 67) 'foo': impl Trait + {error} + [77; 78) 'b': impl {error} + [97; 100) 'foo': impl {error} + [110; 111) 'b': impl Trait + [128; 131) 'foo': impl Trait + [141; 142) 'b': impl {error} + [163; 166) 'foo': impl {error} + [176; 177) 'd': impl {error} + [193; 196) 'foo': impl {error} + [206; 207) 'e': impl Trait + {error} + [231; 234) 'foo': impl Trait + {error} + "### + ); +} + +#[test] +fn assoc_type_bindings() { + assert_snapshot!( + infer(r#" +trait Trait { + type Type; +} + +fn get(t: T) -> ::Type {} +fn get2>(t: T) -> U {} +fn set>(t: T) -> T {t} + +struct S; +impl Trait for S { type Type = T; } + +fn test>(x: T, y: impl Trait) { + get(x); + get2(x); + get(y); + get2(y); + get(set(S)); + get2(set(S)); + get2(S::); +} +"#), + @r###" + [50; 51) 't': T + [78; 80) '{}': () + [112; 113) 't': T + [123; 125) '{}': () + [155; 156) 't': T + [166; 169) '{t}': T + [167; 168) 't': T + [257; 258) 'x': T + [263; 264) 'y': impl Trait + [290; 398) '{ ...r>); }': () + [296; 299) 'get': fn get(T) -> ::Type + [296; 302) 'get(x)': {unknown} + [300; 301) 'x': T + [308; 312) 'get2': fn get2<{unknown}, T>(T) -> U + [308; 315) 'get2(x)': {unknown} + [313; 314) 'x': T + [321; 324) 'get': fn get>(T) -> ::Type + [321; 327) 'get(y)': {unknown} + [325; 326) 'y': impl Trait + [333; 337) 'get2': fn get2<{unknown}, impl Trait>(T) -> U + [333; 340) 'get2(y)': {unknown} + [338; 339) 'y': impl Trait + [346; 349) 'get': fn get>(T) -> ::Type + [346; 357) 'get(set(S))': u64 + [350; 353) 'set': fn set>(T) -> T + [350; 356) 'set(S)': S + [354; 355) 'S': S + [363; 367) 'get2': fn get2>(T) -> U + [363; 375) 'get2(set(S))': u64 + [368; 371) 'set': fn set>(T) -> T + [368; 374) 'set(S)': S + [372; 373) 'S': S + [381; 385) 'get2': fn get2>(T) -> U + [381; 395) 'get2(S::)': str + [386; 394) 'S::': S + "### + ); +} + +#[test] +fn impl_trait_assoc_binding_projection_bug() { + let (db, pos) = TestDB::with_position( + r#" +//- /main.rs crate:main deps:std +pub trait Language { + type Kind; +} +pub enum RustLanguage {} +impl Language for RustLanguage { + type Kind = SyntaxKind; +} +struct SyntaxNode {} +fn foo() -> impl Iterator> {} + +trait Clone { + fn clone(&self) -> Self; +} + +fn api_walkthrough() { + for node in foo() { + node.clone()<|>; + } +} + +//- /std.rs crate:std +#[prelude_import] use iter::*; +mod iter { + trait IntoIterator { + type Item; + } + trait Iterator { + type Item; + } + impl IntoIterator for T { + type Item = ::Item; + } +} +"#, + ); + assert_eq!("{unknown}", type_at_pos(&db, pos)); +} + +#[test] +fn projection_eq_within_chalk() { + // std::env::set_var("CHALK_DEBUG", "1"); + assert_snapshot!( + infer(r#" +trait Trait1 { + type Type; +} +trait Trait2 { + fn foo(self) -> T; +} +impl Trait2 for U where U: Trait1 {} + +fn test>(x: T) { + x.foo(); +} +"#), + @r###" + [62; 66) 'self': Self + [164; 165) 'x': T + [170; 186) '{ ...o(); }': () + [176; 177) 'x': T + [176; 183) 'x.foo()': {unknown} + "### + ); +} + +#[test] +fn where_clause_trait_in_scope_for_method_resolution() { + let t = type_at( + r#" +//- /main.rs +mod foo { + trait Trait { + fn foo(&self) -> u32 {} + } +} + +fn test(x: T) { + x.foo()<|>; +} +"#, + ); + assert_eq!(t, "u32"); +} + +#[test] +fn super_trait_method_resolution() { + assert_snapshot!( + infer(r#" +mod foo { + trait SuperTrait { + fn foo(&self) -> u32 {} + } +} +trait Trait1: foo::SuperTrait {} +trait Trait2 where Self: foo::SuperTrait {} + +fn test(x: T, y: U) { + x.foo(); + y.foo(); +} +"#), + @r###" + [50; 54) 'self': &Self + [63; 65) '{}': () + [182; 183) 'x': T + [188; 189) 'y': U + [194; 223) '{ ...o(); }': () + [200; 201) 'x': T + [200; 207) 'x.foo()': u32 + [213; 214) 'y': U + [213; 220) 'y.foo()': u32 + "### + ); +} + +#[test] +fn super_trait_cycle() { + // This just needs to not crash + assert_snapshot!( + infer(r#" +trait A: B {} +trait B: A {} + +fn test(x: T) { + x.foo(); +} +"#), + @r###" + [44; 45) 'x': T + [50; 66) '{ ...o(); }': () + [56; 57) 'x': T + [56; 63) 'x.foo()': {unknown} + "### + ); +} + +#[test] +fn super_trait_assoc_type_bounds() { + assert_snapshot!( + infer(r#" +trait SuperTrait { type Type; } +trait Trait where Self: SuperTrait {} + +fn get2>(t: T) -> U {} +fn set>(t: T) -> T {t} + +struct S; +impl SuperTrait for S { type Type = T; } +impl Trait for S {} + +fn test() { + get2(set(S)); +} +"#), + @r###" + [103; 104) 't': T + [114; 116) '{}': () + [146; 147) 't': T + [157; 160) '{t}': T + [158; 159) 't': T + [259; 280) '{ ...S)); }': () + [265; 269) 'get2': fn get2>(T) -> U + [265; 277) 'get2(set(S))': u64 + [270; 273) 'set': fn set>(T) -> T + [270; 276) 'set(S)': S + [274; 275) 'S': S + "### + ); +} + +#[test] +fn fn_trait() { + assert_snapshot!( + infer(r#" +trait FnOnce { + type Output; + + fn call_once(self, args: Args) -> >::Output; +} + +fn test u128>(f: F) { + f.call_once((1, 2)); +} +"#), + @r###" + [57; 61) 'self': Self + [63; 67) 'args': Args + [150; 151) 'f': F + [156; 184) '{ ...2)); }': () + [162; 163) 'f': F + [162; 181) 'f.call...1, 2))': {unknown} + [174; 180) '(1, 2)': (u32, u64) + [175; 176) '1': u32 + [178; 179) '2': u64 + "### + ); +} + +#[test] +fn closure_1() { + assert_snapshot!( + infer(r#" +#[lang = "fn_once"] +trait FnOnce { + type Output; +} + +enum Option { Some(T), None } +impl Option { + fn map U>(self, f: F) -> Option {} +} + +fn test() { + let x = Option::Some(1u32); + x.map(|v| v + 1); + x.map(|_v| 1u64); + let y: Option = x.map(|_v| 1); +} +"#), + @r###" + [148; 152) 'self': Option + [154; 155) 'f': F + [173; 175) '{}': () + [189; 308) '{ ... 1); }': () + [199; 200) 'x': Option + [203; 215) 'Option::Some': Some(T) -> Option + [203; 221) 'Option...(1u32)': Option + [216; 220) '1u32': u32 + [227; 228) 'x': Option + [227; 243) 'x.map(...v + 1)': Option + [233; 242) '|v| v + 1': |u32| -> u32 + [234; 235) 'v': u32 + [237; 238) 'v': u32 + [237; 242) 'v + 1': u32 + [241; 242) '1': u32 + [249; 250) 'x': Option + [249; 265) 'x.map(... 1u64)': Option + [255; 264) '|_v| 1u64': |u32| -> u64 + [256; 258) '_v': u32 + [260; 264) '1u64': u64 + [275; 276) 'y': Option + [292; 293) 'x': Option + [292; 305) 'x.map(|_v| 1)': Option + [298; 304) '|_v| 1': |u32| -> i64 + [299; 301) '_v': u32 + [303; 304) '1': i64 + "### + ); +} + +#[test] +fn closure_2() { + assert_snapshot!( + infer(r#" +trait FnOnce { + type Output; +} + +fn test u64>(f: F) { + f(1); + let g = |v| v + 1; + g(1u64); + let h = |v| 1u128 + v; +} +"#), + @r###" + [73; 74) 'f': F + [79; 155) '{ ...+ v; }': () + [85; 86) 'f': F + [85; 89) 'f(1)': {unknown} + [87; 88) '1': i32 + [99; 100) 'g': |u64| -> i32 + [103; 112) '|v| v + 1': |u64| -> i32 + [104; 105) 'v': u64 + [107; 108) 'v': u64 + [107; 112) 'v + 1': i32 + [111; 112) '1': i32 + [118; 119) 'g': |u64| -> i32 + [118; 125) 'g(1u64)': i32 + [120; 124) '1u64': u64 + [135; 136) 'h': |u128| -> u128 + [139; 152) '|v| 1u128 + v': |u128| -> u128 + [140; 141) 'v': u128 + [143; 148) '1u128': u128 + [143; 152) '1u128 + v': u128 + [151; 152) 'v': u128 + "### + ); +} + +#[test] +fn closure_as_argument_inference_order() { + assert_snapshot!( + infer(r#" +#[lang = "fn_once"] +trait FnOnce { + type Output; +} + +fn foo1 U>(x: T, f: F) -> U {} +fn foo2 U>(f: F, x: T) -> U {} + +struct S; +impl S { + fn method(self) -> u64; + + fn foo1 U>(self, x: T, f: F) -> U {} + fn foo2 U>(self, f: F, x: T) -> U {} +} + +fn test() { + let x1 = foo1(S, |s| s.method()); + let x2 = foo2(|s| s.method(), S); + let x3 = S.foo1(S, |s| s.method()); + let x4 = S.foo2(|s| s.method(), S); +} +"#), + @r###" + [95; 96) 'x': T + [101; 102) 'f': F + [112; 114) '{}': () + [148; 149) 'f': F + [154; 155) 'x': T + [165; 167) '{}': () + [202; 206) 'self': S + [254; 258) 'self': S + [260; 261) 'x': T + [266; 267) 'f': F + [277; 279) '{}': () + [317; 321) 'self': S + [323; 324) 'f': F + [329; 330) 'x': T + [340; 342) '{}': () + [356; 515) '{ ... S); }': () + [366; 368) 'x1': u64 + [371; 375) 'foo1': fn foo1 u64>(T, F) -> U + [371; 394) 'foo1(S...hod())': u64 + [376; 377) 'S': S + [379; 393) '|s| s.method()': |S| -> u64 + [380; 381) 's': S + [383; 384) 's': S + [383; 393) 's.method()': u64 + [404; 406) 'x2': u64 + [409; 413) 'foo2': fn foo2 u64>(F, T) -> U + [409; 432) 'foo2(|...(), S)': u64 + [414; 428) '|s| s.method()': |S| -> u64 + [415; 416) 's': S + [418; 419) 's': S + [418; 428) 's.method()': u64 + [430; 431) 'S': S + [442; 444) 'x3': u64 + [447; 448) 'S': S + [447; 472) 'S.foo1...hod())': u64 + [454; 455) 'S': S + [457; 471) '|s| s.method()': |S| -> u64 + [458; 459) 's': S + [461; 462) 's': S + [461; 471) 's.method()': u64 + [482; 484) 'x4': u64 + [487; 488) 'S': S + [487; 512) 'S.foo2...(), S)': u64 + [494; 508) '|s| s.method()': |S| -> u64 + [495; 496) 's': S + [498; 499) 's': S + [498; 508) 's.method()': u64 + [510; 511) 'S': S + "### + ); +} + +#[test] +fn unselected_projection_in_trait_env_1() { + let t = type_at( + r#" +//- /main.rs +trait Trait { + type Item; +} + +trait Trait2 { + fn foo(&self) -> u32; +} + +fn test() where T::Item: Trait2 { + let x: T::Item = no_matter; + x.foo()<|>; +} +"#, + ); + assert_eq!(t, "u32"); +} + +#[test] +fn unselected_projection_in_trait_env_2() { + let t = type_at( + r#" +//- /main.rs +trait Trait { + type Item; +} + +trait Trait2 { + fn foo(&self) -> u32; +} + +fn test() where T::Item: Trait2, T: Trait, U: Trait<()> { + let x: T::Item = no_matter; + x.foo()<|>; +} +"#, + ); + assert_eq!(t, "u32"); +} + +#[test] +// FIXME this is currently a Salsa panic; it would be nicer if it just returned +// in Unknown, and we should be able to do that once Salsa allows us to handle +// the cycle. But at least it doesn't overflow for now. +#[should_panic] +fn unselected_projection_in_trait_env_cycle_1() { + let t = type_at( + r#" +//- /main.rs +trait Trait { + type Item; +} + +trait Trait2 {} + +fn test() where T: Trait2 { + let x: T::Item = no_matter<|>; +} +"#, + ); + // this is a legitimate cycle + assert_eq!(t, "{unknown}"); +} + +#[test] +// FIXME this is currently a Salsa panic; it would be nicer if it just returned +// in Unknown, and we should be able to do that once Salsa allows us to handle +// the cycle. But at least it doesn't overflow for now. +#[should_panic] +fn unselected_projection_in_trait_env_cycle_2() { + let t = type_at( + r#" +//- /main.rs +trait Trait { + type Item; +} + +fn test() where T: Trait, U: Trait { + let x: T::Item = no_matter<|>; +} +"#, + ); + // this is a legitimate cycle + assert_eq!(t, "{unknown}"); +} + +fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { + let file = db.parse(pos.file_id).ok().unwrap(); + let expr = algo::find_node_at_offset::(file.syntax(), pos.offset).unwrap(); + + let module = db.module_for_file(pos.file_id); + let crate_def_map = db.crate_def_map(module.krate); + for decl in crate_def_map[module.module_id].scope.declarations() { + if let ModuleDefId::FunctionId(func) = decl { + let (_body, source_map) = db.body_with_source_map(func.into()); + if let Some(expr_id) = source_map.node_expr(Source::new(pos.file_id.into(), &expr)) { + let infer = db.infer(func.into()); + let ty = &infer[expr_id]; + return ty.display(db).to_string(); + } + } + } + panic!("Can't find expression") +} + +fn type_at(content: &str) -> String { + let (db, file_pos) = TestDB::with_position(content); + type_at_pos(&db, file_pos) +} + +fn infer(content: &str) -> String { + let (db, file_id) = TestDB::with_single_file(content); + + let mut acc = String::new(); + + let mut infer_def = |inference_result: Arc, + body_source_map: Arc| { + let mut types = Vec::new(); + + for (pat, ty) in inference_result.type_of_pat.iter() { + let syntax_ptr = match body_source_map.pat_syntax(pat) { + Some(sp) => { + sp.map(|ast| ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr())) + } + None => continue, + }; + types.push((syntax_ptr, ty)); + } + + for (expr, ty) in inference_result.type_of_expr.iter() { + let syntax_ptr = match body_source_map.expr_syntax(expr) { + Some(sp) => { + sp.map(|ast| ast.either(|it| it.syntax_node_ptr(), |it| it.syntax_node_ptr())) + } + None => continue, + }; + types.push((syntax_ptr, ty)); + } + + // sort ranges for consistency + types.sort_by_key(|(src_ptr, _)| { + (src_ptr.value.range().start(), src_ptr.value.range().end()) + }); + for (src_ptr, ty) in &types { + let node = src_ptr.value.to_node(&src_ptr.file_syntax(&db)); + + let (range, text) = if let Some(self_param) = ast::SelfParam::cast(node.clone()) { + (self_param.self_kw_token().text_range(), "self".to_string()) + } else { + (src_ptr.value.range(), node.text().to_string().replace("\n", " ")) + }; + let macro_prefix = if src_ptr.file_id != file_id.into() { "!" } else { "" }; + write!( + acc, + "{}{} '{}': {}\n", + macro_prefix, + range, + ellipsize(text, 15), + ty.display(&db) + ) + .unwrap(); + } + }; + + let module = db.module_for_file(file_id); + let crate_def_map = db.crate_def_map(module.krate); + + let mut defs: Vec = Vec::new(); + visit_module(&db, &crate_def_map, module.module_id, &mut |it| defs.push(it)); + defs.sort_by_key(|def| match def { + DefWithBodyId::FunctionId(it) => { + it.lookup(&db).ast_id.to_node(&db).syntax().text_range().start() + } + DefWithBodyId::ConstId(it) => { + it.lookup(&db).ast_id.to_node(&db).syntax().text_range().start() + } + DefWithBodyId::StaticId(it) => { + it.lookup(&db).ast_id.to_node(&db).syntax().text_range().start() + } + }); + for def in defs { + let (_body, source_map) = db.body_with_source_map(def); + let infer = db.infer(def); + infer_def(infer, source_map); + } + + acc.truncate(acc.trim_end().len()); + acc +} + +fn visit_module( + db: &TestDB, + crate_def_map: &CrateDefMap, + module_id: LocalModuleId, + cb: &mut dyn FnMut(DefWithBodyId), +) { + for decl in crate_def_map[module_id].scope.declarations() { + match decl { + ModuleDefId::FunctionId(it) => cb(it.into()), + ModuleDefId::ConstId(it) => cb(it.into()), + ModuleDefId::StaticId(it) => cb(it.into()), + ModuleDefId::TraitId(it) => { + let trait_data = db.trait_data(it); + for &(_, item) in trait_data.items.iter() { + match item { + AssocItemId::FunctionId(it) => cb(it.into()), + AssocItemId::ConstId(it) => cb(it.into()), + AssocItemId::TypeAliasId(_) => (), + } + } + } + ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.module_id, cb), + _ => (), + } + } + for &impl_id in crate_def_map[module_id].impls.iter() { + let impl_data = db.impl_data(impl_id); + for &item in impl_data.items.iter() { + match item { + AssocItemId::FunctionId(it) => cb(it.into()), + AssocItemId::ConstId(it) => cb(it.into()), + AssocItemId::TypeAliasId(_) => (), + } + } + } +} + +fn ellipsize(mut text: String, max_len: usize) -> String { + if text.len() <= max_len { + return text; + } + let ellipsis = "..."; + let e_len = ellipsis.len(); + let mut prefix_len = (max_len - e_len) / 2; + while !text.is_char_boundary(prefix_len) { + prefix_len += 1; + } + let mut suffix_len = max_len - e_len - prefix_len; + while !text.is_char_boundary(text.len() - suffix_len) { + suffix_len += 1; + } + text.replace_range(prefix_len..text.len() - suffix_len, ellipsis); + text +} + +#[test] +fn typing_whitespace_inside_a_function_should_not_invalidate_types() { + let (mut db, pos) = TestDB::with_position( + " + //- /lib.rs + fn foo() -> i32 { + <|>1 + 1 + } + ", + ); + { + let events = db.log_executed(|| { + let module = db.module_for_file(pos.file_id); + let crate_def_map = db.crate_def_map(module.krate); + visit_module(&db, &crate_def_map, module.module_id, &mut |def| { + db.infer(def); + }); + }); + assert!(format!("{:?}", events).contains("infer")) + } + + let new_text = " + fn foo() -> i32 { + 1 + + + 1 + } + " + .to_string(); + + db.query_mut(ra_db::FileTextQuery).set(pos.file_id, Arc::new(new_text)); + + { + let events = db.log_executed(|| { + let module = db.module_for_file(pos.file_id); + let crate_def_map = db.crate_def_map(module.krate); + visit_module(&db, &crate_def_map, module.module_id, &mut |def| { + db.infer(def); + }); + }); + assert!(!format!("{:?}", events).contains("infer"), "{:#?}", events) + } +} + +#[test] +fn no_such_field_diagnostics() { + let diagnostics = TestDB::with_files( + r" + //- /lib.rs + struct S { foo: i32, bar: () } + impl S { + fn new() -> S { + S { + foo: 92, + baz: 62, + } + } + } + ", + ) + .diagnostics(); + + assert_snapshot!(diagnostics, @r###" + "baz: 62": no such field + "{\n foo: 92,\n baz: 62,\n }": Missing structure fields: + - bar + "### + ); +} + +#[test] +fn infer_builtin_macros_line() { + assert_snapshot!( + infer(r#" +#[rustc_builtin_macro] +macro_rules! line {() => {}} + +fn main() { + let x = line!(); +} +"#), + @r###" + ![0; 1) '6': i32 + [64; 88) '{ ...!(); }': () + [74; 75) 'x': i32 + "### + ); +} + +#[test] +fn infer_builtin_macros_file() { + assert_snapshot!( + infer(r#" +#[rustc_builtin_macro] +macro_rules! file {() => {}} + +fn main() { + let x = file!(); +} +"#), + @r###" + ![0; 2) '""': &str + [64; 88) '{ ...!(); }': () + [74; 75) 'x': &str + "### + ); +} + +#[test] +fn infer_builtin_macros_column() { + assert_snapshot!( + infer(r#" +#[rustc_builtin_macro] +macro_rules! column {() => {}} + +fn main() { + let x = column!(); +} +"#), + @r###" + ![0; 2) '13': i32 + [66; 92) '{ ...!(); }': () + [76; 77) 'x': i32 + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/coercion.rs b/crates/ra_hir_ty/src/tests/coercion.rs new file mode 100644 index 000000000..1530fcc63 --- /dev/null +++ b/crates/ra_hir_ty/src/tests/coercion.rs @@ -0,0 +1,369 @@ +use insta::assert_snapshot; +use test_utils::covers; + +// Infer with some common definitions and impls. +fn infer(source: &str) -> String { + let defs = r#" + #[lang = "sized"] + pub trait Sized {} + #[lang = "unsize"] + pub trait Unsize {} + #[lang = "coerce_unsized"] + pub trait CoerceUnsized {} + + impl<'a, 'b: 'a, T: ?Sized + Unsize, U: ?Sized> CoerceUnsized<&'a U> for &'b T {} + impl, U: ?Sized> CoerceUnsized<*mut U> for *mut T {} + "#; + + // Append to the end to keep positions unchanged. + super::infer(&format!("{}{}", source, defs)) +} + +#[test] +fn infer_block_expr_type_mismatch() { + assert_snapshot!( + infer(r#" +fn test() { + let a: i32 = { 1i64 }; +} +"#), + @r###" + [11; 41) '{ ...4 }; }': () + [21; 22) 'a': i32 + [30; 38) '{ 1i64 }': i64 + [32; 36) '1i64': i64 + "###); +} + +#[test] +fn coerce_places() { + assert_snapshot!( + infer(r#" +struct S { a: T } + +fn f(_: &[T]) -> T { loop {} } +fn g(_: S<&[T]>) -> T { loop {} } + +fn gen() -> *mut [T; 2] { loop {} } +fn test1() -> *mut [U] { + gen() +} + +fn test2() { + let arr: &[u8; 1] = &[1]; + + let a: &[_] = arr; + let b = f(arr); + let c: &[_] = { arr }; + let d = g(S { a: arr }); + let e: [&[_]; 1] = [arr]; + let f: [&[_]; 2] = [arr; 2]; + let g: (&[_], &[_]) = (arr, arr); +} +"#), + @r###" + [31; 32) '_': &[T] + [45; 56) '{ loop {} }': T + [47; 54) 'loop {}': ! + [52; 54) '{}': () + [65; 66) '_': S<&[T]> + [82; 93) '{ loop {} }': T + [84; 91) 'loop {}': ! + [89; 91) '{}': () + [122; 133) '{ loop {} }': *mut [T;_] + [124; 131) 'loop {}': ! + [129; 131) '{}': () + [160; 173) '{ gen() }': *mut [U] + [166; 169) 'gen': fn gen() -> *mut [T;_] + [166; 171) 'gen()': *mut [U;_] + [186; 420) '{ ...rr); }': () + [196; 199) 'arr': &[u8;_] + [212; 216) '&[1]': &[u8;_] + [213; 216) '[1]': [u8;_] + [214; 215) '1': u8 + [227; 228) 'a': &[u8] + [237; 240) 'arr': &[u8;_] + [250; 251) 'b': u8 + [254; 255) 'f': fn f(&[T]) -> T + [254; 260) 'f(arr)': u8 + [256; 259) 'arr': &[u8;_] + [270; 271) 'c': &[u8] + [280; 287) '{ arr }': &[u8] + [282; 285) 'arr': &[u8;_] + [297; 298) 'd': u8 + [301; 302) 'g': fn g(S<&[T]>) -> T + [301; 316) 'g(S { a: arr })': u8 + [303; 315) 'S { a: arr }': S<&[u8]> + [310; 313) 'arr': &[u8;_] + [326; 327) 'e': [&[u8];_] + [341; 346) '[arr]': [&[u8];_] + [342; 345) 'arr': &[u8;_] + [356; 357) 'f': [&[u8];_] + [371; 379) '[arr; 2]': [&[u8];_] + [372; 375) 'arr': &[u8;_] + [377; 378) '2': usize + [389; 390) 'g': (&[u8], &[u8]) + [407; 417) '(arr, arr)': (&[u8], &[u8]) + [408; 411) 'arr': &[u8;_] + [413; 416) 'arr': &[u8;_] + "### + ); +} + +#[test] +fn infer_let_stmt_coerce() { + assert_snapshot!( + infer(r#" +fn test() { + let x: &[i32] = &[1]; +} +"#), + @r###" + [11; 40) '{ ...[1]; }': () + [21; 22) 'x': &[i32] + [33; 37) '&[1]': &[i32;_] + [34; 37) '[1]': [i32;_] + [35; 36) '1': i32 + "###); +} + +#[test] +fn infer_custom_coerce_unsized() { + assert_snapshot!( + infer(r#" +struct A(*const T); +struct B(*const T); +struct C { inner: *const T } + +impl, U: ?Sized> CoerceUnsized> for B {} +impl, U: ?Sized> CoerceUnsized> for C {} + +fn foo1(x: A<[T]>) -> A<[T]> { x } +fn foo2(x: B<[T]>) -> B<[T]> { x } +fn foo3(x: C<[T]>) -> C<[T]> { x } + +fn test(a: A<[u8; 2]>, b: B<[u8; 2]>, c: C<[u8; 2]>) { + let d = foo1(a); + let e = foo2(b); + let f = foo3(c); +} +"#), + @r###" + [258; 259) 'x': A<[T]> + [279; 284) '{ x }': A<[T]> + [281; 282) 'x': A<[T]> + [296; 297) 'x': B<[T]> + [317; 322) '{ x }': B<[T]> + [319; 320) 'x': B<[T]> + [334; 335) 'x': C<[T]> + [355; 360) '{ x }': C<[T]> + [357; 358) 'x': C<[T]> + [370; 371) 'a': A<[u8;_]> + [385; 386) 'b': B<[u8;_]> + [400; 401) 'c': C<[u8;_]> + [415; 481) '{ ...(c); }': () + [425; 426) 'd': A<[{unknown}]> + [429; 433) 'foo1': fn foo1<{unknown}>(A<[T]>) -> A<[T]> + [429; 436) 'foo1(a)': A<[{unknown}]> + [434; 435) 'a': A<[u8;_]> + [446; 447) 'e': B<[u8]> + [450; 454) 'foo2': fn foo2(B<[T]>) -> B<[T]> + [450; 457) 'foo2(b)': B<[u8]> + [455; 456) 'b': B<[u8;_]> + [467; 468) 'f': C<[u8]> + [471; 475) 'foo3': fn foo3(C<[T]>) -> C<[T]> + [471; 478) 'foo3(c)': C<[u8]> + [476; 477) 'c': C<[u8;_]> + "### + ); +} + +#[test] +fn infer_if_coerce() { + assert_snapshot!( + infer(r#" +fn foo(x: &[T]) -> &[T] { loop {} } +fn test() { + let x = if true { + foo(&[1]) + } else { + &[1] + }; +} +"#), + @r###" + [11; 12) 'x': &[T] + [28; 39) '{ loop {} }': &[T] + [30; 37) 'loop {}': ! + [35; 37) '{}': () + [50; 126) '{ ... }; }': () + [60; 61) 'x': &[i32] + [64; 123) 'if tru... }': &[i32] + [67; 71) 'true': bool + [72; 97) '{ ... }': &[i32] + [82; 85) 'foo': fn foo(&[T]) -> &[T] + [82; 91) 'foo(&[1])': &[i32] + [86; 90) '&[1]': &[i32;_] + [87; 90) '[1]': [i32;_] + [88; 89) '1': i32 + [103; 123) '{ ... }': &[i32;_] + [113; 117) '&[1]': &[i32;_] + [114; 117) '[1]': [i32;_] + [115; 116) '1': i32 + "### + ); +} + +#[test] +fn infer_if_else_coerce() { + assert_snapshot!( + infer(r#" +fn foo(x: &[T]) -> &[T] { loop {} } +fn test() { + let x = if true { + &[1] + } else { + foo(&[1]) + }; +} +"#), + @r###" + [11; 12) 'x': &[T] + [28; 39) '{ loop {} }': &[T] + [30; 37) 'loop {}': ! + [35; 37) '{}': () + [50; 126) '{ ... }; }': () + [60; 61) 'x': &[i32] + [64; 123) 'if tru... }': &[i32] + [67; 71) 'true': bool + [72; 92) '{ ... }': &[i32;_] + [82; 86) '&[1]': &[i32;_] + [83; 86) '[1]': [i32;_] + [84; 85) '1': i32 + [98; 123) '{ ... }': &[i32] + [108; 111) 'foo': fn foo(&[T]) -> &[T] + [108; 117) 'foo(&[1])': &[i32] + [112; 116) '&[1]': &[i32;_] + [113; 116) '[1]': [i32;_] + [114; 115) '1': i32 + "### + ); +} + +#[test] +fn infer_match_first_coerce() { + assert_snapshot!( + infer(r#" +fn foo(x: &[T]) -> &[T] { loop {} } +fn test(i: i32) { + let x = match i { + 2 => foo(&[2]), + 1 => &[1], + _ => &[3], + }; +} +"#), + @r###" + [11; 12) 'x': &[T] + [28; 39) '{ loop {} }': &[T] + [30; 37) 'loop {}': ! + [35; 37) '{}': () + [48; 49) 'i': i32 + [56; 150) '{ ... }; }': () + [66; 67) 'x': &[i32] + [70; 147) 'match ... }': &[i32] + [76; 77) 'i': i32 + [88; 89) '2': i32 + [93; 96) 'foo': fn foo(&[T]) -> &[T] + [93; 102) 'foo(&[2])': &[i32] + [97; 101) '&[2]': &[i32;_] + [98; 101) '[2]': [i32;_] + [99; 100) '2': i32 + [112; 113) '1': i32 + [117; 121) '&[1]': &[i32;_] + [118; 121) '[1]': [i32;_] + [119; 120) '1': i32 + [131; 132) '_': i32 + [136; 140) '&[3]': &[i32;_] + [137; 140) '[3]': [i32;_] + [138; 139) '3': i32 + "### + ); +} + +#[test] +fn infer_match_second_coerce() { + assert_snapshot!( + infer(r#" +fn foo(x: &[T]) -> &[T] { loop {} } +fn test(i: i32) { + let x = match i { + 1 => &[1], + 2 => foo(&[2]), + _ => &[3], + }; +} +"#), + @r###" + [11; 12) 'x': &[T] + [28; 39) '{ loop {} }': &[T] + [30; 37) 'loop {}': ! + [35; 37) '{}': () + [48; 49) 'i': i32 + [56; 150) '{ ... }; }': () + [66; 67) 'x': &[i32] + [70; 147) 'match ... }': &[i32] + [76; 77) 'i': i32 + [88; 89) '1': i32 + [93; 97) '&[1]': &[i32;_] + [94; 97) '[1]': [i32;_] + [95; 96) '1': i32 + [107; 108) '2': i32 + [112; 115) 'foo': fn foo(&[T]) -> &[T] + [112; 121) 'foo(&[2])': &[i32] + [116; 120) '&[2]': &[i32;_] + [117; 120) '[2]': [i32;_] + [118; 119) '2': i32 + [131; 132) '_': i32 + [136; 140) '&[3]': &[i32;_] + [137; 140) '[3]': [i32;_] + [138; 139) '3': i32 + "### + ); +} + +#[test] +fn coerce_merge_one_by_one1() { + covers!(coerce_merge_fail_fallback); + + assert_snapshot!( + infer(r#" +fn test() { + let t = &mut 1; + let x = match 1 { + 1 => t as *mut i32, + 2 => t as &i32, + _ => t as *const i32, + }; +} +"#), + @r###" + [11; 145) '{ ... }; }': () + [21; 22) 't': &mut i32 + [25; 31) '&mut 1': &mut i32 + [30; 31) '1': i32 + [41; 42) 'x': *const i32 + [45; 142) 'match ... }': *const i32 + [51; 52) '1': i32 + [63; 64) '1': i32 + [68; 69) 't': &mut i32 + [68; 81) 't as *mut i32': *mut i32 + [91; 92) '2': i32 + [96; 97) 't': &mut i32 + [96; 105) 't as &i32': &i32 + [115; 116) '_': i32 + [120; 121) 't': &mut i32 + [120; 135) 't as *const i32': *const i32 + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/never_type.rs b/crates/ra_hir_ty/src/tests/never_type.rs new file mode 100644 index 000000000..c202f545a --- /dev/null +++ b/crates/ra_hir_ty/src/tests/never_type.rs @@ -0,0 +1,246 @@ +use super::type_at; + +#[test] +fn infer_never1() { + let t = type_at( + r#" +//- /main.rs +fn test() { + let t = return; + t<|>; +} +"#, + ); + assert_eq!(t, "!"); +} + +#[test] +fn infer_never2() { + let t = type_at( + r#" +//- /main.rs +fn gen() -> T { loop {} } + +fn test() { + let a = gen(); + if false { a } else { loop {} }; + a<|>; +} +"#, + ); + assert_eq!(t, "!"); +} + +#[test] +fn infer_never3() { + let t = type_at( + r#" +//- /main.rs +fn gen() -> T { loop {} } + +fn test() { + let a = gen(); + if false { loop {} } else { a }; + a<|>; +} +"#, + ); + assert_eq!(t, "!"); +} + +#[test] +fn never_type_in_generic_args() { + let t = type_at( + r#" +//- /main.rs +enum Option { None, Some(T) } + +fn test() { + let a = if true { Option::None } else { Option::Some(return) }; + a<|>; +} +"#, + ); + assert_eq!(t, "Option"); +} + +#[test] +fn never_type_can_be_reinferred1() { + let t = type_at( + r#" +//- /main.rs +fn gen() -> T { loop {} } + +fn test() { + let a = gen(); + if false { loop {} } else { a }; + a<|>; + if false { a }; +} +"#, + ); + assert_eq!(t, "()"); +} + +#[test] +fn never_type_can_be_reinferred2() { + let t = type_at( + r#" +//- /main.rs +enum Option { None, Some(T) } + +fn test() { + let a = if true { Option::None } else { Option::Some(return) }; + a<|>; + match 42 { + 42 => a, + _ => Option::Some(42), + }; +} +"#, + ); + assert_eq!(t, "Option"); +} +#[test] +fn never_type_can_be_reinferred3() { + let t = type_at( + r#" +//- /main.rs +enum Option { None, Some(T) } + +fn test() { + let a = if true { Option::None } else { Option::Some(return) }; + a<|>; + match 42 { + 42 => a, + _ => Option::Some("str"), + }; +} +"#, + ); + assert_eq!(t, "Option<&str>"); +} + +#[test] +fn match_no_arm() { + let t = type_at( + r#" +//- /main.rs +enum Void {} + +fn test(a: Void) { + let t = match a {}; + t<|>; +} +"#, + ); + assert_eq!(t, "!"); +} + +#[test] +fn if_never() { + let t = type_at( + r#" +//- /main.rs +fn test() { + let i = if true { + loop {} + } else { + 3.0 + }; + i<|>; +} +"#, + ); + assert_eq!(t, "f64"); +} + +#[test] +fn if_else_never() { + let t = type_at( + r#" +//- /main.rs +fn test(input: bool) { + let i = if input { + 2.0 + } else { + return + }; + i<|>; +} +"#, + ); + assert_eq!(t, "f64"); +} + +#[test] +fn match_first_arm_never() { + let t = type_at( + r#" +//- /main.rs +fn test(a: i32) { + let i = match a { + 1 => return, + 2 => 2.0, + 3 => loop {}, + _ => 3.0, + }; + i<|>; +} +"#, + ); + assert_eq!(t, "f64"); +} + +#[test] +fn match_second_arm_never() { + let t = type_at( + r#" +//- /main.rs +fn test(a: i32) { + let i = match a { + 1 => 3.0, + 2 => loop {}, + 3 => 3.0, + _ => return, + }; + i<|>; +} +"#, + ); + assert_eq!(t, "f64"); +} + +#[test] +fn match_all_arms_never() { + let t = type_at( + r#" +//- /main.rs +fn test(a: i32) { + let i = match a { + 2 => return, + _ => loop {}, + }; + i<|>; +} +"#, + ); + assert_eq!(t, "!"); +} + +#[test] +fn match_no_never_arms() { + let t = type_at( + r#" +//- /main.rs +fn test(a: i32) { + let i = match a { + 2 => 2.0, + _ => 3.0, + }; + i<|>; +} +"#, + ); + assert_eq!(t, "f64"); +} diff --git a/crates/ra_hir_ty/src/traits.rs b/crates/ra_hir_ty/src/traits.rs new file mode 100644 index 000000000..76189a60b --- /dev/null +++ b/crates/ra_hir_ty/src/traits.rs @@ -0,0 +1,328 @@ +//! Trait solving using Chalk. +use std::sync::{Arc, Mutex}; + +use chalk_ir::{cast::Cast, family::ChalkIr}; +use hir_def::{expr::ExprId, DefWithBodyId, ImplId, TraitId, TypeAliasId}; +use log::debug; +use ra_db::{impl_intern_key, salsa, CrateId}; +use ra_prof::profile; +use rustc_hash::FxHashSet; + +use crate::db::HirDatabase; + +use super::{Canonical, GenericPredicate, HirDisplay, ProjectionTy, TraitRef, Ty, TypeWalk}; + +use self::chalk::{from_chalk, ToChalk}; + +pub(crate) mod chalk; + +#[derive(Debug, Clone)] +pub struct TraitSolver { + krate: CrateId, + inner: Arc>>, +} + +/// We need eq for salsa +impl PartialEq for TraitSolver { + fn eq(&self, other: &TraitSolver) -> bool { + Arc::ptr_eq(&self.inner, &other.inner) + } +} + +impl Eq for TraitSolver {} + +impl TraitSolver { + fn solve( + &self, + db: &impl HirDatabase, + goal: &chalk_ir::UCanonical>>, + ) -> Option> { + let context = ChalkContext { db, krate: self.krate }; + debug!("solve goal: {:?}", goal); + let mut solver = match self.inner.lock() { + Ok(it) => it, + // Our cancellation works via unwinding, but, as chalk is not + // panic-safe, we need to make sure to propagate the cancellation. + // Ideally, we should also make chalk panic-safe. + Err(_) => ra_db::Canceled::throw(), + }; + let solution = solver.solve(&context, goal); + debug!("solve({:?}) => {:?}", goal, solution); + solution + } +} + +/// This controls the maximum size of types Chalk considers. If we set this too +/// high, we can run into slow edge cases; if we set it too low, Chalk won't +/// find some solutions. +const CHALK_SOLVER_MAX_SIZE: usize = 4; + +#[derive(Debug, Copy, Clone)] +struct ChalkContext<'a, DB> { + db: &'a DB, + krate: CrateId, +} + +pub(crate) fn trait_solver_query( + db: &(impl HirDatabase + salsa::Database), + krate: CrateId, +) -> TraitSolver { + db.salsa_runtime().report_untracked_read(); + // krate parameter is just so we cache a unique solver per crate + let solver_choice = chalk_solve::SolverChoice::SLG { max_size: CHALK_SOLVER_MAX_SIZE }; + debug!("Creating new solver for crate {:?}", krate); + TraitSolver { krate, inner: Arc::new(Mutex::new(solver_choice.into_solver())) } +} + +/// Collects impls for the given trait in the whole dependency tree of `krate`. +pub(crate) fn impls_for_trait_query( + db: &impl HirDatabase, + krate: CrateId, + trait_: TraitId, +) -> Arc<[ImplId]> { + let mut impls = FxHashSet::default(); + // We call the query recursively here. On the one hand, this means we can + // reuse results from queries for different crates; on the other hand, this + // will only ever get called for a few crates near the root of the tree (the + // ones the user is editing), so this may actually be a waste of memory. I'm + // doing it like this mainly for simplicity for now. + for dep in db.crate_graph().dependencies(krate) { + impls.extend(db.impls_for_trait(dep.crate_id, trait_).iter()); + } + let crate_impl_blocks = db.impls_in_crate(krate); + impls.extend(crate_impl_blocks.lookup_impl_blocks_for_trait(trait_)); + impls.into_iter().collect() +} + +/// A set of clauses that we assume to be true. E.g. if we are inside this function: +/// ```rust +/// fn foo(t: T) {} +/// ``` +/// we assume that `T: Default`. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct TraitEnvironment { + pub predicates: Vec, +} + +impl TraitEnvironment { + /// Returns trait refs with the given self type which are supposed to hold + /// in this trait env. E.g. if we are in `foo()`, this will + /// find that `T: SomeTrait` if we call it for `T`. + pub(crate) fn trait_predicates_for_self_ty<'a>( + &'a self, + ty: &'a Ty, + ) -> impl Iterator + 'a { + self.predicates.iter().filter_map(move |pred| match pred { + GenericPredicate::Implemented(tr) if tr.self_ty() == ty => Some(tr), + _ => None, + }) + } +} + +/// Something (usually a goal), along with an environment. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct InEnvironment { + pub environment: Arc, + pub value: T, +} + +impl InEnvironment { + pub fn new(environment: Arc, value: T) -> InEnvironment { + InEnvironment { environment, value } + } +} + +/// Something that needs to be proven (by Chalk) during type checking, e.g. that +/// a certain type implements a certain trait. Proving the Obligation might +/// result in additional information about inference variables. +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum Obligation { + /// Prove that a certain type implements a trait (the type is the `Self` type + /// parameter to the `TraitRef`). + Trait(TraitRef), + Projection(ProjectionPredicate), +} + +impl Obligation { + pub fn from_predicate(predicate: GenericPredicate) -> Option { + match predicate { + GenericPredicate::Implemented(trait_ref) => Some(Obligation::Trait(trait_ref)), + GenericPredicate::Projection(projection_pred) => { + Some(Obligation::Projection(projection_pred)) + } + GenericPredicate::Error => None, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub struct ProjectionPredicate { + pub projection_ty: ProjectionTy, + pub ty: Ty, +} + +impl TypeWalk for ProjectionPredicate { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + self.projection_ty.walk(f); + self.ty.walk(f); + } + + fn walk_mut_binders(&mut self, f: &mut impl FnMut(&mut Ty, usize), binders: usize) { + self.projection_ty.walk_mut_binders(f, binders); + self.ty.walk_mut_binders(f, binders); + } +} + +/// Solve a trait goal using Chalk. +pub(crate) fn trait_solve_query( + db: &impl HirDatabase, + krate: CrateId, + goal: Canonical>, +) -> Option { + let _p = profile("trait_solve_query"); + debug!("trait_solve_query({})", goal.value.value.display(db)); + + if let Obligation::Projection(pred) = &goal.value.value { + if let Ty::Bound(_) = &pred.projection_ty.parameters[0] { + // Hack: don't ask Chalk to normalize with an unknown self type, it'll say that's impossible + return Some(Solution::Ambig(Guidance::Unknown)); + } + } + + let canonical = goal.to_chalk(db).cast(); + + // We currently don't deal with universes (I think / hope they're not yet + // relevant for our use cases?) + let u_canonical = chalk_ir::UCanonical { canonical, universes: 1 }; + let solution = db.trait_solver(krate).solve(db, &u_canonical); + solution.map(|solution| solution_from_chalk(db, solution)) +} + +fn solution_from_chalk( + db: &impl HirDatabase, + solution: chalk_solve::Solution, +) -> Solution { + let convert_subst = |subst: chalk_ir::Canonical>| { + let value = subst + .value + .parameters + .into_iter() + .map(|p| { + let ty = match p { + chalk_ir::Parameter(chalk_ir::ParameterKind::Ty(ty)) => from_chalk(db, ty), + chalk_ir::Parameter(chalk_ir::ParameterKind::Lifetime(_)) => unimplemented!(), + }; + ty + }) + .collect(); + let result = Canonical { value, num_vars: subst.binders.len() }; + SolutionVariables(result) + }; + match solution { + chalk_solve::Solution::Unique(constr_subst) => { + let subst = chalk_ir::Canonical { + value: constr_subst.value.subst, + binders: constr_subst.binders, + }; + Solution::Unique(convert_subst(subst)) + } + chalk_solve::Solution::Ambig(chalk_solve::Guidance::Definite(subst)) => { + Solution::Ambig(Guidance::Definite(convert_subst(subst))) + } + chalk_solve::Solution::Ambig(chalk_solve::Guidance::Suggested(subst)) => { + Solution::Ambig(Guidance::Suggested(convert_subst(subst))) + } + chalk_solve::Solution::Ambig(chalk_solve::Guidance::Unknown) => { + Solution::Ambig(Guidance::Unknown) + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SolutionVariables(pub Canonical>); + +#[derive(Clone, Debug, PartialEq, Eq)] +/// A (possible) solution for a proposed goal. +pub enum Solution { + /// The goal indeed holds, and there is a unique value for all existential + /// variables. + Unique(SolutionVariables), + + /// The goal may be provable in multiple ways, but regardless we may have some guidance + /// for type inference. In this case, we don't return any lifetime + /// constraints, since we have not "committed" to any particular solution + /// yet. + Ambig(Guidance), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +/// When a goal holds ambiguously (e.g., because there are multiple possible +/// solutions), we issue a set of *guidance* back to type inference. +pub enum Guidance { + /// The existential variables *must* have the given values if the goal is + /// ever to hold, but that alone isn't enough to guarantee the goal will + /// actually hold. + Definite(SolutionVariables), + + /// There are multiple plausible values for the existentials, but the ones + /// here are suggested as the preferred choice heuristically. These should + /// be used for inference fallback only. + Suggested(SolutionVariables), + + /// There's no useful information to feed back to type inference + Unknown, +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum FnTrait { + FnOnce, + FnMut, + Fn, +} + +impl FnTrait { + fn lang_item_name(self) -> &'static str { + match self { + FnTrait::FnOnce => "fn_once", + FnTrait::FnMut => "fn_mut", + FnTrait::Fn => "fn", + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct ClosureFnTraitImplData { + def: DefWithBodyId, + expr: ExprId, + fn_trait: FnTrait, +} + +/// An impl. Usually this comes from an impl block, but some built-in types get +/// synthetic impls. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum Impl { + /// A normal impl from an impl block. + ImplBlock(ImplId), + /// Closure types implement the Fn traits synthetically. + ClosureFnTraitImpl(ClosureFnTraitImplData), +} +/// This exists just for Chalk, because our ImplIds are only unique per module. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct GlobalImplId(salsa::InternId); +impl_intern_key!(GlobalImplId); + +/// An associated type value. Usually this comes from a `type` declaration +/// inside an impl block, but for built-in impls we have to synthesize it. +/// (We only need this because Chalk wants a unique ID for each of these.) +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub enum AssocTyValue { + /// A normal assoc type value from an impl block. + TypeAlias(TypeAliasId), + /// The output type of the Fn trait implementation. + ClosureFnTraitImplOutput(ClosureFnTraitImplData), +} +/// This exists just for Chalk, because it needs a unique ID for each associated +/// type value in an impl (even synthetic ones). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AssocTyValueId(salsa::InternId); +impl_intern_key!(AssocTyValueId); diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs new file mode 100644 index 000000000..810e8c21a --- /dev/null +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -0,0 +1,906 @@ +//! Conversion code from/to Chalk. +use std::sync::Arc; + +use log::debug; + +use chalk_ir::{ + cast::Cast, family::ChalkIr, Identifier, Parameter, PlaceholderIndex, TypeId, TypeKindId, + TypeName, UniverseIndex, +}; +use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum, TraitDatum}; +use ra_db::CrateId; + +use hir_def::{ + expr::Expr, lang_item::LangItemTarget, resolver::HasResolver, AssocItemId, AstItemDef, + ContainerId, GenericDefId, ImplId, Lookup, TraitId, TypeAliasId, +}; +use hir_expand::name; + +use ra_db::salsa::{InternId, InternKey}; + +use super::{AssocTyValue, Canonical, ChalkContext, Impl, Obligation}; +use crate::{ + db::HirDatabase, + display::HirDisplay, + {ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, +}; + +/// This represents a trait whose name we could not resolve. +const UNKNOWN_TRAIT: chalk_ir::TraitId = + chalk_ir::TraitId(chalk_ir::RawId { index: u32::max_value() }); + +pub(super) trait ToChalk { + type Chalk; + fn to_chalk(self, db: &impl HirDatabase) -> Self::Chalk; + fn from_chalk(db: &impl HirDatabase, chalk: Self::Chalk) -> Self; +} + +pub(super) fn from_chalk(db: &impl HirDatabase, chalk: ChalkT) -> T +where + T: ToChalk, +{ + T::from_chalk(db, chalk) +} + +impl ToChalk for Ty { + type Chalk = chalk_ir::Ty; + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Ty { + match self { + Ty::Apply(apply_ty) => { + let name = match apply_ty.ctor { + TypeCtor::AssociatedType(type_alias) => { + let type_id = type_alias.to_chalk(db); + TypeName::AssociatedType(type_id) + } + _ => { + // other TypeCtors get interned and turned into a chalk StructId + let struct_id = apply_ty.ctor.to_chalk(db); + TypeName::TypeKindId(struct_id.into()) + } + }; + let parameters = apply_ty.parameters.to_chalk(db); + chalk_ir::ApplicationTy { name, parameters }.cast().intern() + } + Ty::Projection(proj_ty) => { + let associated_ty_id = proj_ty.associated_ty.to_chalk(db); + let parameters = proj_ty.parameters.to_chalk(db); + chalk_ir::ProjectionTy { associated_ty_id, parameters }.cast().intern() + } + Ty::Param { idx, .. } => { + PlaceholderIndex { ui: UniverseIndex::ROOT, idx: idx as usize }.to_ty::() + } + Ty::Bound(idx) => chalk_ir::TyData::BoundVar(idx as usize).intern(), + Ty::Infer(_infer_ty) => panic!("uncanonicalized infer ty"), + Ty::Dyn(predicates) => { + let where_clauses = predicates.iter().cloned().map(|p| p.to_chalk(db)).collect(); + chalk_ir::TyData::Dyn(make_binders(where_clauses, 1)).intern() + } + Ty::Opaque(predicates) => { + let where_clauses = predicates.iter().cloned().map(|p| p.to_chalk(db)).collect(); + chalk_ir::TyData::Opaque(make_binders(where_clauses, 1)).intern() + } + Ty::Unknown => { + let parameters = Vec::new(); + let name = TypeName::Error; + chalk_ir::ApplicationTy { name, parameters }.cast().intern() + } + } + } + fn from_chalk(db: &impl HirDatabase, chalk: chalk_ir::Ty) -> Self { + match chalk.data().clone() { + chalk_ir::TyData::Apply(apply_ty) => { + // FIXME this is kind of hacky due to the fact that + // TypeName::Placeholder is a Ty::Param on our side + match apply_ty.name { + TypeName::TypeKindId(TypeKindId::StructId(struct_id)) => { + let ctor = from_chalk(db, struct_id); + let parameters = from_chalk(db, apply_ty.parameters); + Ty::Apply(ApplicationTy { ctor, parameters }) + } + TypeName::AssociatedType(type_id) => { + let ctor = TypeCtor::AssociatedType(from_chalk(db, type_id)); + let parameters = from_chalk(db, apply_ty.parameters); + Ty::Apply(ApplicationTy { ctor, parameters }) + } + TypeName::Error => Ty::Unknown, + // FIXME handle TypeKindId::Trait/Type here + TypeName::TypeKindId(_) => unimplemented!(), + TypeName::Placeholder(idx) => { + assert_eq!(idx.ui, UniverseIndex::ROOT); + Ty::Param { idx: idx.idx as u32, name: crate::Name::missing() } + } + } + } + chalk_ir::TyData::Projection(proj) => { + let associated_ty = from_chalk(db, proj.associated_ty_id); + let parameters = from_chalk(db, proj.parameters); + Ty::Projection(ProjectionTy { associated_ty, parameters }) + } + chalk_ir::TyData::ForAll(_) => unimplemented!(), + chalk_ir::TyData::BoundVar(idx) => Ty::Bound(idx as u32), + chalk_ir::TyData::InferenceVar(_iv) => Ty::Unknown, + chalk_ir::TyData::Dyn(where_clauses) => { + assert_eq!(where_clauses.binders.len(), 1); + let predicates = + where_clauses.value.into_iter().map(|c| from_chalk(db, c)).collect(); + Ty::Dyn(predicates) + } + chalk_ir::TyData::Opaque(where_clauses) => { + assert_eq!(where_clauses.binders.len(), 1); + let predicates = + where_clauses.value.into_iter().map(|c| from_chalk(db, c)).collect(); + Ty::Opaque(predicates) + } + } + } +} + +impl ToChalk for Substs { + type Chalk = Vec>; + + fn to_chalk(self, db: &impl HirDatabase) -> Vec> { + self.iter().map(|ty| ty.clone().to_chalk(db).cast()).collect() + } + + fn from_chalk(db: &impl HirDatabase, parameters: Vec>) -> Substs { + let tys = parameters + .into_iter() + .map(|p| match p { + chalk_ir::Parameter(chalk_ir::ParameterKind::Ty(ty)) => from_chalk(db, ty), + chalk_ir::Parameter(chalk_ir::ParameterKind::Lifetime(_)) => unimplemented!(), + }) + .collect(); + Substs(tys) + } +} + +impl ToChalk for TraitRef { + type Chalk = chalk_ir::TraitRef; + + fn to_chalk(self: TraitRef, db: &impl HirDatabase) -> chalk_ir::TraitRef { + let trait_id = self.trait_.to_chalk(db); + let parameters = self.substs.to_chalk(db); + chalk_ir::TraitRef { trait_id, parameters } + } + + fn from_chalk(db: &impl HirDatabase, trait_ref: chalk_ir::TraitRef) -> Self { + let trait_ = from_chalk(db, trait_ref.trait_id); + let substs = from_chalk(db, trait_ref.parameters); + TraitRef { trait_, substs } + } +} + +impl ToChalk for TraitId { + type Chalk = chalk_ir::TraitId; + + fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TraitId { + chalk_ir::TraitId(id_to_chalk(self)) + } + + fn from_chalk(_db: &impl HirDatabase, trait_id: chalk_ir::TraitId) -> TraitId { + id_from_chalk(trait_id.0) + } +} + +impl ToChalk for TypeCtor { + type Chalk = chalk_ir::StructId; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::StructId { + db.intern_type_ctor(self).into() + } + + fn from_chalk(db: &impl HirDatabase, struct_id: chalk_ir::StructId) -> TypeCtor { + db.lookup_intern_type_ctor(struct_id.into()) + } +} + +impl ToChalk for Impl { + type Chalk = chalk_ir::ImplId; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::ImplId { + db.intern_chalk_impl(self).into() + } + + fn from_chalk(db: &impl HirDatabase, impl_id: chalk_ir::ImplId) -> Impl { + db.lookup_intern_chalk_impl(impl_id.into()) + } +} + +impl ToChalk for TypeAliasId { + type Chalk = chalk_ir::TypeId; + + fn to_chalk(self, _db: &impl HirDatabase) -> chalk_ir::TypeId { + chalk_ir::TypeId(id_to_chalk(self)) + } + + fn from_chalk(_db: &impl HirDatabase, type_alias_id: chalk_ir::TypeId) -> TypeAliasId { + id_from_chalk(type_alias_id.0) + } +} + +impl ToChalk for AssocTyValue { + type Chalk = chalk_rust_ir::AssociatedTyValueId; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_rust_ir::AssociatedTyValueId { + db.intern_assoc_ty_value(self).into() + } + + fn from_chalk( + db: &impl HirDatabase, + assoc_ty_value_id: chalk_rust_ir::AssociatedTyValueId, + ) -> AssocTyValue { + db.lookup_intern_assoc_ty_value(assoc_ty_value_id.into()) + } +} + +impl ToChalk for GenericPredicate { + type Chalk = chalk_ir::QuantifiedWhereClause; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::QuantifiedWhereClause { + match self { + GenericPredicate::Implemented(trait_ref) => { + make_binders(chalk_ir::WhereClause::Implemented(trait_ref.to_chalk(db)), 0) + } + GenericPredicate::Projection(projection_pred) => make_binders( + chalk_ir::WhereClause::ProjectionEq(chalk_ir::ProjectionEq { + projection: projection_pred.projection_ty.to_chalk(db), + ty: projection_pred.ty.to_chalk(db), + }), + 0, + ), + GenericPredicate::Error => { + let impossible_trait_ref = chalk_ir::TraitRef { + trait_id: UNKNOWN_TRAIT, + parameters: vec![Ty::Unknown.to_chalk(db).cast()], + }; + make_binders(chalk_ir::WhereClause::Implemented(impossible_trait_ref), 0) + } + } + } + + fn from_chalk( + db: &impl HirDatabase, + where_clause: chalk_ir::QuantifiedWhereClause, + ) -> GenericPredicate { + match where_clause.value { + chalk_ir::WhereClause::Implemented(tr) => { + if tr.trait_id == UNKNOWN_TRAIT { + // FIXME we need an Error enum on the Chalk side to avoid this + return GenericPredicate::Error; + } + GenericPredicate::Implemented(from_chalk(db, tr)) + } + chalk_ir::WhereClause::ProjectionEq(projection_eq) => { + let projection_ty = from_chalk(db, projection_eq.projection); + let ty = from_chalk(db, projection_eq.ty); + GenericPredicate::Projection(super::ProjectionPredicate { projection_ty, ty }) + } + } + } +} + +impl ToChalk for ProjectionTy { + type Chalk = chalk_ir::ProjectionTy; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::ProjectionTy { + chalk_ir::ProjectionTy { + associated_ty_id: self.associated_ty.to_chalk(db), + parameters: self.parameters.to_chalk(db), + } + } + + fn from_chalk( + db: &impl HirDatabase, + projection_ty: chalk_ir::ProjectionTy, + ) -> ProjectionTy { + ProjectionTy { + associated_ty: from_chalk(db, projection_ty.associated_ty_id), + parameters: from_chalk(db, projection_ty.parameters), + } + } +} + +impl ToChalk for super::ProjectionPredicate { + type Chalk = chalk_ir::Normalize; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Normalize { + chalk_ir::Normalize { + projection: self.projection_ty.to_chalk(db), + ty: self.ty.to_chalk(db), + } + } + + fn from_chalk(_db: &impl HirDatabase, _normalize: chalk_ir::Normalize) -> Self { + unimplemented!() + } +} + +impl ToChalk for Obligation { + type Chalk = chalk_ir::DomainGoal; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::DomainGoal { + match self { + Obligation::Trait(tr) => tr.to_chalk(db).cast(), + Obligation::Projection(pr) => pr.to_chalk(db).cast(), + } + } + + fn from_chalk(_db: &impl HirDatabase, _goal: chalk_ir::DomainGoal) -> Self { + unimplemented!() + } +} + +impl ToChalk for Canonical +where + T: ToChalk, +{ + type Chalk = chalk_ir::Canonical; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Canonical { + let parameter = chalk_ir::ParameterKind::Ty(chalk_ir::UniverseIndex::ROOT); + let value = self.value.to_chalk(db); + let canonical = chalk_ir::Canonical { value, binders: vec![parameter; self.num_vars] }; + canonical + } + + fn from_chalk(db: &impl HirDatabase, canonical: chalk_ir::Canonical) -> Canonical { + Canonical { num_vars: canonical.binders.len(), value: from_chalk(db, canonical.value) } + } +} + +impl ToChalk for Arc { + type Chalk = chalk_ir::Environment; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::Environment { + let mut clauses = Vec::new(); + for pred in &self.predicates { + if pred.is_error() { + // for env, we just ignore errors + continue; + } + let program_clause: chalk_ir::ProgramClause = pred.clone().to_chalk(db).cast(); + clauses.push(program_clause.into_from_env_clause()); + } + chalk_ir::Environment::new().add_clauses(clauses) + } + + fn from_chalk( + _db: &impl HirDatabase, + _env: chalk_ir::Environment, + ) -> Arc { + unimplemented!() + } +} + +impl ToChalk for super::InEnvironment +where + T::Chalk: chalk_ir::family::HasTypeFamily, +{ + type Chalk = chalk_ir::InEnvironment; + + fn to_chalk(self, db: &impl HirDatabase) -> chalk_ir::InEnvironment { + chalk_ir::InEnvironment { + environment: self.environment.to_chalk(db), + goal: self.value.to_chalk(db), + } + } + + fn from_chalk( + db: &impl HirDatabase, + in_env: chalk_ir::InEnvironment, + ) -> super::InEnvironment { + super::InEnvironment { + environment: from_chalk(db, in_env.environment), + value: from_chalk(db, in_env.goal), + } + } +} + +fn make_binders(value: T, num_vars: usize) -> chalk_ir::Binders { + chalk_ir::Binders { + value, + binders: std::iter::repeat(chalk_ir::ParameterKind::Ty(())).take(num_vars).collect(), + } +} + +fn convert_where_clauses( + db: &impl HirDatabase, + def: GenericDefId, + substs: &Substs, +) -> Vec> { + let generic_predicates = db.generic_predicates(def); + let mut result = Vec::with_capacity(generic_predicates.len()); + for pred in generic_predicates.iter() { + if pred.is_error() { + // HACK: Return just the single predicate (which is always false + // anyway), otherwise Chalk can easily get into slow situations + return vec![pred.clone().subst(substs).to_chalk(db)]; + } + result.push(pred.clone().subst(substs).to_chalk(db)); + } + result +} + +impl<'a, DB> chalk_solve::RustIrDatabase for ChalkContext<'a, DB> +where + DB: HirDatabase, +{ + fn associated_ty_data(&self, id: TypeId) -> Arc> { + self.db.associated_ty_data(id) + } + fn trait_datum(&self, trait_id: chalk_ir::TraitId) -> Arc> { + self.db.trait_datum(self.krate, trait_id) + } + fn struct_datum(&self, struct_id: chalk_ir::StructId) -> Arc> { + self.db.struct_datum(self.krate, struct_id) + } + fn impl_datum(&self, impl_id: chalk_ir::ImplId) -> Arc> { + self.db.impl_datum(self.krate, impl_id) + } + fn impls_for_trait( + &self, + trait_id: chalk_ir::TraitId, + parameters: &[Parameter], + ) -> Vec { + debug!("impls_for_trait {:?}", trait_id); + if trait_id == UNKNOWN_TRAIT { + return Vec::new(); + } + let trait_: TraitId = from_chalk(self.db, trait_id); + let mut result: Vec<_> = self + .db + .impls_for_trait(self.krate, trait_.into()) + .iter() + .copied() + .map(|it| Impl::ImplBlock(it.into())) + .map(|impl_| impl_.to_chalk(self.db)) + .collect(); + + let ty: Ty = from_chalk(self.db, parameters[0].assert_ty_ref().clone()); + if let Ty::Apply(ApplicationTy { ctor: TypeCtor::Closure { def, expr }, .. }) = ty { + for &fn_trait in + [super::FnTrait::FnOnce, super::FnTrait::FnMut, super::FnTrait::Fn].iter() + { + if let Some(actual_trait) = get_fn_trait(self.db, self.krate, fn_trait) { + if trait_ == actual_trait { + let impl_ = super::ClosureFnTraitImplData { def, expr, fn_trait }; + result.push(Impl::ClosureFnTraitImpl(impl_).to_chalk(self.db)); + } + } + } + } + + debug!("impls_for_trait returned {} impls", result.len()); + result + } + fn impl_provided_for( + &self, + auto_trait_id: chalk_ir::TraitId, + struct_id: chalk_ir::StructId, + ) -> bool { + debug!("impl_provided_for {:?}, {:?}", auto_trait_id, struct_id); + false // FIXME + } + fn type_name(&self, _id: TypeKindId) -> Identifier { + unimplemented!() + } + fn associated_ty_value( + &self, + id: chalk_rust_ir::AssociatedTyValueId, + ) -> Arc> { + self.db.associated_ty_value(self.krate.into(), id) + } + fn custom_clauses(&self) -> Vec> { + vec![] + } + fn local_impls_to_coherence_check( + &self, + _trait_id: chalk_ir::TraitId, + ) -> Vec { + // We don't do coherence checking (yet) + unimplemented!() + } +} + +pub(crate) fn associated_ty_data_query( + db: &impl HirDatabase, + id: TypeId, +) -> Arc> { + debug!("associated_ty_data {:?}", id); + let type_alias: TypeAliasId = from_chalk(db, id); + let trait_ = match type_alias.lookup(db).container { + ContainerId::TraitId(t) => t, + _ => panic!("associated type not in trait"), + }; + let generic_params = db.generic_params(type_alias.into()); + let bound_data = chalk_rust_ir::AssociatedTyDatumBound { + // FIXME add bounds and where clauses + bounds: vec![], + where_clauses: vec![], + }; + let datum = AssociatedTyDatum { + trait_id: trait_.to_chalk(db), + id, + name: lalrpop_intern::intern(&db.type_alias_data(type_alias).name.to_string()), + binders: make_binders(bound_data, generic_params.count_params_including_parent()), + }; + Arc::new(datum) +} + +pub(crate) fn trait_datum_query( + db: &impl HirDatabase, + krate: CrateId, + trait_id: chalk_ir::TraitId, +) -> Arc> { + debug!("trait_datum {:?}", trait_id); + if trait_id == UNKNOWN_TRAIT { + let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses: Vec::new() }; + + let flags = chalk_rust_ir::TraitFlags { + auto: false, + marker: false, + upstream: true, + fundamental: false, + non_enumerable: true, + coinductive: false, + }; + return Arc::new(TraitDatum { + id: trait_id, + binders: make_binders(trait_datum_bound, 1), + flags, + associated_ty_ids: vec![], + }); + } + let trait_: TraitId = from_chalk(db, trait_id); + let trait_data = db.trait_data(trait_); + debug!("trait {:?} = {:?}", trait_id, trait_data.name); + let generic_params = db.generic_params(trait_.into()); + let bound_vars = Substs::bound_vars(&generic_params); + let flags = chalk_rust_ir::TraitFlags { + auto: trait_data.auto, + upstream: trait_.module(db).krate != krate, + non_enumerable: true, + coinductive: false, // only relevant for Chalk testing + // FIXME set these flags correctly + marker: false, + fundamental: false, + }; + let where_clauses = convert_where_clauses(db, trait_.into(), &bound_vars); + let associated_ty_ids = + trait_data.associated_types().map(|type_alias| type_alias.to_chalk(db)).collect(); + let trait_datum_bound = chalk_rust_ir::TraitDatumBound { where_clauses }; + let trait_datum = TraitDatum { + id: trait_id, + binders: make_binders(trait_datum_bound, bound_vars.len()), + flags, + associated_ty_ids, + }; + Arc::new(trait_datum) +} + +pub(crate) fn struct_datum_query( + db: &impl HirDatabase, + krate: CrateId, + struct_id: chalk_ir::StructId, +) -> Arc> { + debug!("struct_datum {:?}", struct_id); + let type_ctor: TypeCtor = from_chalk(db, struct_id); + debug!("struct {:?} = {:?}", struct_id, type_ctor); + let num_params = type_ctor.num_ty_params(db); + let upstream = type_ctor.krate(db) != Some(krate); + let where_clauses = type_ctor + .as_generic_def() + .map(|generic_def| { + let generic_params = db.generic_params(generic_def.into()); + let bound_vars = Substs::bound_vars(&generic_params); + convert_where_clauses(db, generic_def, &bound_vars) + }) + .unwrap_or_else(Vec::new); + let flags = chalk_rust_ir::StructFlags { + upstream, + // FIXME set fundamental flag correctly + fundamental: false, + }; + let struct_datum_bound = chalk_rust_ir::StructDatumBound { + fields: Vec::new(), // FIXME add fields (only relevant for auto traits) + where_clauses, + }; + let struct_datum = + StructDatum { id: struct_id, binders: make_binders(struct_datum_bound, num_params), flags }; + Arc::new(struct_datum) +} + +pub(crate) fn impl_datum_query( + db: &impl HirDatabase, + krate: CrateId, + impl_id: chalk_ir::ImplId, +) -> Arc> { + let _p = ra_prof::profile("impl_datum"); + debug!("impl_datum {:?}", impl_id); + let impl_: Impl = from_chalk(db, impl_id); + match impl_ { + Impl::ImplBlock(impl_block) => impl_block_datum(db, krate, impl_id, impl_block), + Impl::ClosureFnTraitImpl(data) => closure_fn_trait_impl_datum(db, krate, data), + } + .unwrap_or_else(invalid_impl_datum) +} + +fn impl_block_datum( + db: &impl HirDatabase, + krate: CrateId, + chalk_id: chalk_ir::ImplId, + impl_id: ImplId, +) -> Option>> { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + + // `CoerseUnsized` has one generic parameter for the target type. + let trait_ref = + TraitRef::from_hir(db, &resolver, impl_data.target_trait.as_ref()?, Some(target_ty))?; + + let generic_params = db.generic_params(impl_id.into()); + let bound_vars = Substs::bound_vars(&generic_params); + let trait_ref = trait_ref.subst(&bound_vars); + let trait_ = trait_ref.trait_; + let impl_type = if impl_id.module(db).krate == krate { + chalk_rust_ir::ImplType::Local + } else { + chalk_rust_ir::ImplType::External + }; + let where_clauses = convert_where_clauses(db, impl_id.into(), &bound_vars); + let negative = impl_data.is_negative; + debug!( + "impl {:?}: {}{} where {:?}", + chalk_id, + if negative { "!" } else { "" }, + trait_ref.display(db), + where_clauses + ); + let trait_ref = trait_ref.to_chalk(db); + + let polarity = if negative { + chalk_rust_ir::Polarity::Negative + } else { + chalk_rust_ir::Polarity::Positive + }; + + let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses }; + let trait_data = db.trait_data(trait_); + let associated_ty_value_ids = impl_data + .items + .iter() + .filter_map(|item| match item { + AssocItemId::TypeAliasId(type_alias) => Some(*type_alias), + _ => None, + }) + .filter(|&type_alias| { + // don't include associated types that don't exist in the trait + let name = &db.type_alias_data(type_alias).name; + trait_data.associated_type_by_name(name).is_some() + }) + .map(|type_alias| AssocTyValue::TypeAlias(type_alias).to_chalk(db)) + .collect(); + debug!("impl_datum: {:?}", impl_datum_bound); + let impl_datum = ImplDatum { + binders: make_binders(impl_datum_bound, bound_vars.len()), + impl_type, + polarity, + associated_ty_value_ids, + }; + Some(Arc::new(impl_datum)) +} + +fn invalid_impl_datum() -> Arc> { + let trait_ref = chalk_ir::TraitRef { + trait_id: UNKNOWN_TRAIT, + parameters: vec![chalk_ir::TyData::BoundVar(0).cast().intern().cast()], + }; + let impl_datum_bound = chalk_rust_ir::ImplDatumBound { trait_ref, where_clauses: Vec::new() }; + let impl_datum = ImplDatum { + binders: make_binders(impl_datum_bound, 1), + impl_type: chalk_rust_ir::ImplType::External, + polarity: chalk_rust_ir::Polarity::Positive, + associated_ty_value_ids: Vec::new(), + }; + Arc::new(impl_datum) +} + +fn closure_fn_trait_impl_datum( + db: &impl HirDatabase, + krate: CrateId, + data: super::ClosureFnTraitImplData, +) -> Option>> { + // for some closure |X, Y| -> Z: + // impl Fn<(T, U)> for closure V> { Output = V } + + let trait_ = get_fn_trait(db, krate, data.fn_trait)?; // get corresponding fn trait + + // validate FnOnce trait, since we need it in the assoc ty value definition + // and don't want to return a valid value only to find out later that FnOnce + // is broken + let fn_once_trait = get_fn_trait(db, krate, super::FnTrait::FnOnce)?; + let _output = db.trait_data(fn_once_trait).associated_type_by_name(&name::OUTPUT_TYPE)?; + + let num_args: u16 = match &db.body(data.def.into())[data.expr] { + Expr::Lambda { args, .. } => args.len() as u16, + _ => { + log::warn!("closure for closure type {:?} not found", data); + 0 + } + }; + + let arg_ty = Ty::apply( + TypeCtor::Tuple { cardinality: num_args }, + Substs::builder(num_args as usize).fill_with_bound_vars(0).build(), + ); + let sig_ty = Ty::apply( + TypeCtor::FnPtr { num_args }, + Substs::builder(num_args as usize + 1).fill_with_bound_vars(0).build(), + ); + + let self_ty = Ty::apply_one(TypeCtor::Closure { def: data.def, expr: data.expr }, sig_ty); + + let trait_ref = TraitRef { + trait_: trait_.into(), + substs: Substs::build_for_def(db, trait_).push(self_ty).push(arg_ty).build(), + }; + + let output_ty_id = AssocTyValue::ClosureFnTraitImplOutput(data.clone()).to_chalk(db); + + let impl_type = chalk_rust_ir::ImplType::External; + + let impl_datum_bound = chalk_rust_ir::ImplDatumBound { + trait_ref: trait_ref.to_chalk(db), + where_clauses: Vec::new(), + }; + let impl_datum = ImplDatum { + binders: make_binders(impl_datum_bound, num_args as usize + 1), + impl_type, + polarity: chalk_rust_ir::Polarity::Positive, + associated_ty_value_ids: vec![output_ty_id], + }; + Some(Arc::new(impl_datum)) +} + +pub(crate) fn associated_ty_value_query( + db: &impl HirDatabase, + krate: CrateId, + id: chalk_rust_ir::AssociatedTyValueId, +) -> Arc> { + let data: AssocTyValue = from_chalk(db, id); + match data { + AssocTyValue::TypeAlias(type_alias) => { + type_alias_associated_ty_value(db, krate, type_alias) + } + AssocTyValue::ClosureFnTraitImplOutput(data) => { + closure_fn_trait_output_assoc_ty_value(db, krate, data) + } + } +} + +fn type_alias_associated_ty_value( + db: &impl HirDatabase, + _krate: CrateId, + type_alias: TypeAliasId, +) -> Arc> { + let type_alias_data = db.type_alias_data(type_alias); + let impl_id = match type_alias.lookup(db).container { + ContainerId::ImplId(it) => it, + _ => panic!("assoc ty value should be in impl"), + }; + + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + let target_trait = impl_data + .target_trait + .as_ref() + .and_then(|trait_ref| TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty))) + .expect("assoc ty value should not exist"); // we don't return any assoc ty values if the impl'd trait can't be resolved + + let assoc_ty = db + .trait_data(target_trait.trait_) + .associated_type_by_name(&type_alias_data.name) + .expect("assoc ty value should not exist"); // validated when building the impl data as well + let generic_params = db.generic_params(impl_id.into()); + let bound_vars = Substs::bound_vars(&generic_params); + let ty = db.ty(type_alias.into()).subst(&bound_vars); + let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: ty.to_chalk(db) }; + let value = chalk_rust_ir::AssociatedTyValue { + impl_id: Impl::ImplBlock(impl_id.into()).to_chalk(db), + associated_ty_id: assoc_ty.to_chalk(db), + value: make_binders(value_bound, bound_vars.len()), + }; + Arc::new(value) +} + +fn closure_fn_trait_output_assoc_ty_value( + db: &impl HirDatabase, + krate: CrateId, + data: super::ClosureFnTraitImplData, +) -> Arc> { + let impl_id = Impl::ClosureFnTraitImpl(data.clone()).to_chalk(db); + + let num_args: u16 = match &db.body(data.def.into())[data.expr] { + Expr::Lambda { args, .. } => args.len() as u16, + _ => { + log::warn!("closure for closure type {:?} not found", data); + 0 + } + }; + + let output_ty = Ty::Bound(num_args.into()); + + let fn_once_trait = + get_fn_trait(db, krate, super::FnTrait::FnOnce).expect("assoc ty value should not exist"); + + let output_ty_id = db + .trait_data(fn_once_trait) + .associated_type_by_name(&name::OUTPUT_TYPE) + .expect("assoc ty value should not exist"); + + let value_bound = chalk_rust_ir::AssociatedTyValueBound { ty: output_ty.to_chalk(db) }; + + let value = chalk_rust_ir::AssociatedTyValue { + associated_ty_id: output_ty_id.to_chalk(db), + impl_id, + value: make_binders(value_bound, num_args as usize + 1), + }; + Arc::new(value) +} + +fn get_fn_trait( + db: &impl HirDatabase, + krate: CrateId, + fn_trait: super::FnTrait, +) -> Option { + let target = db.lang_item(krate, fn_trait.lang_item_name().into())?; + match target { + LangItemTarget::TraitId(t) => Some(t), + _ => None, + } +} + +fn id_from_chalk(chalk_id: chalk_ir::RawId) -> T { + T::from_intern_id(InternId::from(chalk_id.index)) +} +fn id_to_chalk(salsa_id: T) -> chalk_ir::RawId { + chalk_ir::RawId { index: salsa_id.as_intern_id().as_u32() } +} + +impl From for crate::TypeCtorId { + fn from(struct_id: chalk_ir::StructId) -> Self { + id_from_chalk(struct_id.0) + } +} + +impl From for chalk_ir::StructId { + fn from(type_ctor_id: crate::TypeCtorId) -> Self { + chalk_ir::StructId(id_to_chalk(type_ctor_id)) + } +} + +impl From for crate::traits::GlobalImplId { + fn from(impl_id: chalk_ir::ImplId) -> Self { + id_from_chalk(impl_id.0) + } +} + +impl From for chalk_ir::ImplId { + fn from(impl_id: crate::traits::GlobalImplId) -> Self { + chalk_ir::ImplId(id_to_chalk(impl_id)) + } +} + +impl From for crate::traits::AssocTyValueId { + fn from(id: chalk_rust_ir::AssociatedTyValueId) -> Self { + id_from_chalk(id.0) + } +} + +impl From for chalk_rust_ir::AssociatedTyValueId { + fn from(assoc_ty_value_id: crate::traits::AssocTyValueId) -> Self { + chalk_rust_ir::AssociatedTyValueId(id_to_chalk(assoc_ty_value_id)) + } +} diff --git a/crates/ra_hir_ty/src/utils.rs b/crates/ra_hir_ty/src/utils.rs new file mode 100644 index 000000000..e4ba890ef --- /dev/null +++ b/crates/ra_hir_ty/src/utils.rs @@ -0,0 +1,84 @@ +//! Helper functions for working with def, which don't need to be a separate +//! query, but can't be computed directly from `*Data` (ie, which need a `db`). +use std::sync::Arc; + +use hir_def::{ + adt::VariantData, + db::DefDatabase, + resolver::{HasResolver, TypeNs}, + type_ref::TypeRef, + TraitId, TypeAliasId, VariantId, +}; +use hir_expand::name::{self, Name}; + +// FIXME: this is wrong, b/c it can't express `trait T: PartialEq<()>`. +// We should return a `TraitREf` here. +fn direct_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { + let resolver = trait_.resolver(db); + // returning the iterator directly doesn't easily work because of + // lifetime problems, but since there usually shouldn't be more than a + // few direct traits this should be fine (we could even use some kind of + // SmallVec if performance is a concern) + db.generic_params(trait_.into()) + .where_predicates + .iter() + .filter_map(|pred| match &pred.type_ref { + TypeRef::Path(p) if p.as_ident() == Some(&name::SELF_TYPE) => pred.bound.as_path(), + _ => None, + }) + .filter_map(|path| match resolver.resolve_path_in_type_ns_fully(db, path) { + Some(TypeNs::TraitId(t)) => Some(t), + _ => None, + }) + .collect() +} + +/// Returns an iterator over the whole super trait hierarchy (including the +/// trait itself). +pub(super) fn all_super_traits(db: &impl DefDatabase, trait_: TraitId) -> Vec { + // we need to take care a bit here to avoid infinite loops in case of cycles + // (i.e. if we have `trait A: B; trait B: A;`) + let mut result = vec![trait_]; + let mut i = 0; + while i < result.len() { + let t = result[i]; + // yeah this is quadratic, but trait hierarchies should be flat + // enough that this doesn't matter + for tt in direct_super_traits(db, t) { + if !result.contains(&tt) { + result.push(tt); + } + } + i += 1; + } + result +} + +pub(super) fn associated_type_by_name_including_super_traits( + db: &impl DefDatabase, + trait_: TraitId, + name: &Name, +) -> Option { + all_super_traits(db, trait_) + .into_iter() + .find_map(|t| db.trait_data(t).associated_type_by_name(name)) +} + +pub(super) fn variant_data(db: &impl DefDatabase, var: VariantId) -> Arc { + match var { + VariantId::StructId(it) => db.struct_data(it).variant_data.clone(), + VariantId::UnionId(it) => db.union_data(it).variant_data.clone(), + VariantId::EnumVariantId(it) => { + db.enum_data(it.parent).variants[it.local_id].variant_data.clone() + } + } +} + +/// Helper for mutating `Arc<[T]>` (i.e. `Arc::make_mut` for Arc slices). +/// The underlying values are cloned if there are other strong references. +pub(crate) fn make_mut_slice(a: &mut Arc<[T]>) -> &mut [T] { + if Arc::get_mut(a).is_none() { + *a = a.iter().cloned().collect(); + } + Arc::get_mut(a).unwrap() +} diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs index b3ebd9145..aa480e399 100644 --- a/crates/ra_ide_api/src/impls.rs +++ b/crates/ra_ide_api/src/impls.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use hir::{ApplicationTy, FromSource, ImplBlock, Ty, TypeCtor}; +use hir::{FromSource, ImplBlock}; use ra_db::SourceDatabase; use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; @@ -61,7 +61,7 @@ fn impls_for_def( Some( impls .into_iter() - .filter(|impl_block| is_equal_for_find_impls(&ty, &impl_block.target_ty(db))) + .filter(|impl_block| ty.is_equal_for_find_impls(&impl_block.target_ty(db))) .map(|imp| imp.to_nav(db)) .collect(), ) @@ -82,19 +82,6 @@ fn impls_for_trait( Some(impls.into_iter().map(|imp| imp.to_nav(db)).collect()) } -fn is_equal_for_find_impls(original_ty: &Ty, impl_ty: &Ty) -> bool { - match (original_ty, impl_ty) { - (Ty::Apply(a_original_ty), Ty::Apply(ApplicationTy { ctor, parameters })) => match ctor { - TypeCtor::Ref(..) => match parameters.as_single() { - Ty::Apply(a_ty) => a_original_ty.ctor == a_ty.ctor, - _ => false, - }, - _ => a_original_ty.ctor == *ctor, - }, - _ => false, - } -} - #[cfg(test)] mod tests { use crate::mock_analysis::analysis_and_position; -- cgit v1.2.3 From 47ec2ceb12df756b3482ddd2b1947e4b38f23706 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 21:23:31 +0300 Subject: prune deps --- crates/ra_hir/Cargo.toml | 20 -------------------- crates/ra_hir_ty/Cargo.toml | 11 +++++------ crates/ra_hir_ty/src/test_db.rs | 12 +++++++----- 3 files changed, 12 insertions(+), 31 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/Cargo.toml b/crates/ra_hir/Cargo.toml index caba85a4f..e79361e7c 100644 --- a/crates/ra_hir/Cargo.toml +++ b/crates/ra_hir/Cargo.toml @@ -8,31 +8,11 @@ authors = ["rust-analyzer developers"] doctest = false [dependencies] -arrayvec = "0.5.1" log = "0.4.5" rustc-hash = "1.0" -parking_lot = "0.10.0" -ena = "0.13" -once_cell = "1.0.1" ra_syntax = { path = "../ra_syntax" } -ra_arena = { path = "../ra_arena" } -ra_cfg = { path = "../ra_cfg" } ra_db = { path = "../ra_db" } -mbe = { path = "../ra_mbe", package = "ra_mbe" } -tt = { path = "../ra_tt", package = "ra_tt" } hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } hir_ty = { path = "../ra_hir_ty", package = "ra_hir_ty" } -test_utils = { path = "../test_utils" } -ra_prof = { path = "../ra_prof" } - -# https://github.com/rust-lang/chalk/pull/294 -chalk-solve = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } -chalk-rust-ir = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } -chalk-ir = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } - -lalrpop-intern = "0.15.1" - -[dev-dependencies] -insta = "0.12.0" diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 199afff49..429242870 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -9,18 +9,17 @@ doctest = false [dependencies] arrayvec = "0.5.1" +ena = "0.13" log = "0.4.5" rustc-hash = "1.0" -parking_lot = "0.10.0" -ena = "0.13" -ra_syntax = { path = "../ra_syntax" } +hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } +hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } ra_arena = { path = "../ra_arena" } ra_db = { path = "../ra_db" } -hir_expand = { path = "../ra_hir_expand", package = "ra_hir_expand" } -hir_def = { path = "../ra_hir_def", package = "ra_hir_def" } -test_utils = { path = "../test_utils" } ra_prof = { path = "../ra_prof" } +ra_syntax = { path = "../ra_syntax" } +test_utils = { path = "../test_utils" } # https://github.com/rust-lang/chalk/pull/294 chalk-solve = { git = "https://github.com/jackh726/chalk.git", rev = "095cd38a4f16337913bba487f2055b9ca0179f30" } diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs index 0e51f4130..874357008 100644 --- a/crates/ra_hir_ty/src/test_db.rs +++ b/crates/ra_hir_ty/src/test_db.rs @@ -1,10 +1,12 @@ //! Database used for testing `hir`. -use std::{panic, sync::Arc}; +use std::{ + panic, + sync::{Arc, Mutex}, +}; use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId, ModuleId}; use hir_expand::diagnostics::DiagnosticSink; -use parking_lot::Mutex; use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, SourceDatabase}; use crate::{db::HirDatabase, expr::ExprValidator}; @@ -33,7 +35,7 @@ impl salsa::Database for TestDB { } fn salsa_event(&self, event: impl Fn() -> salsa::Event) { - let mut events = self.events.lock(); + let mut events = self.events.lock().unwrap(); if let Some(events) = &mut *events { events.push(event()); } @@ -122,9 +124,9 @@ impl TestDB { impl TestDB { pub fn log(&self, f: impl FnOnce()) -> Vec> { - *self.events.lock() = Some(Vec::new()); + *self.events.lock().unwrap() = Some(Vec::new()); f(); - self.events.lock().take().unwrap() + self.events.lock().unwrap().take().unwrap() } pub fn log_executed(&self, f: impl FnOnce()) -> Vec { -- cgit v1.2.3 From d9a36a736bfb91578a36505e7237212959bb55fe Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 21:31:51 +0300 Subject: Rename module_id -> local_id --- crates/ra_hir/src/code_model.rs | 20 ++++++++++---------- crates/ra_hir/src/code_model/src.rs | 4 ++-- crates/ra_hir/src/from_source.rs | 8 ++++---- crates/ra_hir_def/src/attr.rs | 2 +- crates/ra_hir_def/src/body.rs | 2 +- crates/ra_hir_def/src/docs.rs | 2 +- crates/ra_hir_def/src/lang_item.rs | 4 ++-- crates/ra_hir_def/src/lib.rs | 2 +- crates/ra_hir_def/src/nameres/collector.rs | 14 +++++++------- crates/ra_hir_def/src/nameres/path_resolution.rs | 16 ++++++++-------- crates/ra_hir_def/src/resolver.rs | 6 +++--- crates/ra_hir_ty/src/test_db.rs | 4 ++-- crates/ra_hir_ty/src/tests.rs | 10 +++++----- 13 files changed, 47 insertions(+), 47 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 87c78d98e..5a3e24e9e 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -174,15 +174,15 @@ pub use hir_def::attr::Attrs; impl Module { pub(crate) fn new(krate: Crate, crate_module_id: LocalModuleId) -> Module { - Module { id: ModuleId { krate: krate.crate_id, module_id: crate_module_id } } + Module { id: ModuleId { krate: krate.crate_id, local_id: crate_module_id } } } /// Name of this module. pub fn name(self, db: &impl DefDatabase) -> Option { let def_map = db.crate_def_map(self.id.krate); - let parent = def_map[self.id.module_id].parent?; + let parent = def_map[self.id.local_id].parent?; def_map[parent].children.iter().find_map(|(name, module_id)| { - if *module_id == self.id.module_id { + if *module_id == self.id.local_id { Some(name.clone()) } else { None @@ -206,14 +206,14 @@ impl Module { /// Finds a child module with the specified name. pub fn child(self, db: &impl DefDatabase, name: &Name) -> Option { let def_map = db.crate_def_map(self.id.krate); - let child_id = def_map[self.id.module_id].children.get(name)?; + let child_id = def_map[self.id.local_id].children.get(name)?; Some(self.with_module_id(*child_id)) } /// Iterates over all child modules. pub fn children(self, db: &impl DefDatabase) -> impl Iterator { let def_map = db.crate_def_map(self.id.krate); - let children = def_map[self.id.module_id] + let children = def_map[self.id.local_id] .children .iter() .map(|(_, module_id)| self.with_module_id(*module_id)) @@ -224,7 +224,7 @@ impl Module { /// Finds a parent module. pub fn parent(self, db: &impl DefDatabase) -> Option { let def_map = db.crate_def_map(self.id.krate); - let parent_id = def_map[self.id.module_id].parent?; + let parent_id = def_map[self.id.local_id].parent?; Some(self.with_module_id(parent_id)) } @@ -240,7 +240,7 @@ impl Module { /// Returns a `ModuleScope`: a set of items, visible in this module. pub fn scope(self, db: &impl HirDatabase) -> Vec<(Name, ScopeDef, Option)> { - db.crate_def_map(self.id.krate)[self.id.module_id] + db.crate_def_map(self.id.krate)[self.id.local_id] .scope .entries() .map(|(name, res)| { @@ -250,7 +250,7 @@ impl Module { } pub fn diagnostics(self, db: &impl HirDatabase, sink: &mut DiagnosticSink) { - db.crate_def_map(self.id.krate).add_diagnostics(db, self.id.module_id, sink); + db.crate_def_map(self.id.krate).add_diagnostics(db, self.id.local_id, sink); for decl in self.declarations(db) { match decl { crate::ModuleDef::Function(f) => f.diagnostics(db, sink), @@ -275,12 +275,12 @@ impl Module { pub fn declarations(self, db: &impl DefDatabase) -> Vec { let def_map = db.crate_def_map(self.id.krate); - def_map[self.id.module_id].scope.declarations().map(ModuleDef::from).collect() + def_map[self.id.local_id].scope.declarations().map(ModuleDef::from).collect() } pub fn impl_blocks(self, db: &impl DefDatabase) -> Vec { let def_map = db.crate_def_map(self.id.krate); - def_map[self.id.module_id].impls.iter().copied().map(ImplBlock::from).collect() + def_map[self.id.local_id].impls.iter().copied().map(ImplBlock::from).collect() } fn with_module_id(self, module_id: LocalModuleId) -> Module { diff --git a/crates/ra_hir/src/code_model/src.rs b/crates/ra_hir/src/code_model/src.rs index 076d86f2b..bf3ee0834 100644 --- a/crates/ra_hir/src/code_model/src.rs +++ b/crates/ra_hir/src/code_model/src.rs @@ -22,7 +22,7 @@ impl Module { /// Returns a node which defines this module. That is, a file or a `mod foo {}` with items. pub fn definition_source(self, db: &impl DefDatabase) -> Source { let def_map = db.crate_def_map(self.id.krate); - let src = def_map[self.id.module_id].definition_source(db); + let src = def_map[self.id.local_id].definition_source(db); src.map(|it| match it { Either::A(it) => ModuleSource::SourceFile(it), Either::B(it) => ModuleSource::Module(it), @@ -33,7 +33,7 @@ impl Module { /// `None` for the crate root. pub fn declaration_source(self, db: &impl DefDatabase) -> Option> { let def_map = db.crate_def_map(self.id.krate); - def_map[self.id.module_id].declaration_source(db) + def_map[self.id.local_id].declaration_source(db) } } diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs index 95db7161b..7756ca80e 100644 --- a/crates/ra_hir/src/from_source.rs +++ b/crates/ra_hir/src/from_source.rs @@ -262,13 +262,13 @@ impl Module { let original_file = src.file_id.original_file(db); - let (krate, module_id) = + let (krate, local_id) = db.relevant_crates(original_file).iter().find_map(|&crate_id| { let crate_def_map = db.crate_def_map(crate_id); - let local_module_id = crate_def_map.modules_for_file(original_file).next()?; - Some((crate_id, local_module_id)) + let local_id = crate_def_map.modules_for_file(original_file).next()?; + Some((crate_id, local_id)) })?; - Some(Module { id: ModuleId { krate, module_id } }) + Some(Module { id: ModuleId { krate, local_id } }) } } diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 298608e27..fffb22201 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -35,7 +35,7 @@ impl Attrs { match def { AttrDefId::ModuleId(module) => { let def_map = db.crate_def_map(module.krate); - let src = match def_map[module.module_id].declaration_source(db) { + let src = match def_map[module.local_id].declaration_source(db) { Some(it) => it, None => return Attrs::default(), }; diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs index 78a532bdd..a57a0176d 100644 --- a/crates/ra_hir_def/src/body.rs +++ b/crates/ra_hir_def/src/body.rs @@ -82,7 +82,7 @@ impl Expander { } fn resolve_path_as_macro(&self, db: &impl DefDatabase, path: &Path) -> Option { - self.crate_def_map.resolve_path(db, self.module.module_id, path).0.take_macros() + self.crate_def_map.resolve_path(db, self.module.local_id, path).0.take_macros() } } diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index 4749b642f..34ed9b7a5 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -36,7 +36,7 @@ impl Documentation { match def { AttrDefId::ModuleId(module) => { let def_map = db.crate_def_map(module.krate); - let src = def_map[module.module_id].declaration_source(db)?; + let src = def_map[module.local_id].declaration_source(db)?; docs_from_ast(&src.value) } AttrDefId::StructFieldId(it) => { diff --git a/crates/ra_hir_def/src/lang_item.rs b/crates/ra_hir_def/src/lang_item.rs index f15c23db9..f4fdbdcfc 100644 --- a/crates/ra_hir_def/src/lang_item.rs +++ b/crates/ra_hir_def/src/lang_item.rs @@ -41,7 +41,7 @@ impl LangItems { crate_def_map .modules .iter() - .filter_map(|(module_id, _)| db.module_lang_items(ModuleId { krate, module_id })) + .filter_map(|(local_id, _)| db.module_lang_items(ModuleId { krate, local_id })) .for_each(|it| lang_items.items.extend(it.items.iter().map(|(k, v)| (k.clone(), *v)))); Arc::new(lang_items) @@ -80,7 +80,7 @@ impl LangItems { fn collect_lang_items(&mut self, db: &impl DefDatabase, module: ModuleId) { // Look for impl targets let def_map = db.crate_def_map(module.krate); - let module_data = &def_map[module.module_id]; + let module_data = &def_map[module.local_id]; for &impl_block in module_data.impls.iter() { self.collect_lang_item(db, impl_block, LangItemTarget::ImplBlockId) } diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index ddf464c60..bc5530896 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -50,7 +50,7 @@ impl_arena_id!(LocalImportId); #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct ModuleId { pub krate: CrateId, - pub module_id: LocalModuleId, + pub local_id: LocalModuleId, } /// An ID of a module, **local** to a specific crate diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index ea3abfdae..6cd14026b 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -37,7 +37,7 @@ pub(super) fn collect_defs(db: &impl DefDatabase, mut def_map: CrateDefMap) -> C log::debug!("crate dep {:?} -> {:?}", dep.name, dep.crate_id); def_map.extern_prelude.insert( dep.as_name(), - ModuleId { krate: dep.crate_id, module_id: dep_def_map.root }.into(), + ModuleId { krate: dep.crate_id, local_id: dep_def_map.root }.into(), ); // look for the prelude @@ -323,7 +323,7 @@ where tested_by!(glob_across_crates); // glob import from other crate => we can just import everything once let item_map = self.db.crate_def_map(m.krate); - let scope = &item_map[m.module_id].scope; + let scope = &item_map[m.local_id].scope; // Module scoped macros is included let items = scope @@ -337,7 +337,7 @@ where // glob import from same crate => we do an initial // import, and then need to propagate any further // additions - let scope = &self.def_map[m.module_id].scope; + let scope = &self.def_map[m.local_id].scope; // Module scoped macros is included let items = scope @@ -349,7 +349,7 @@ where self.update(module_id, Some(import_id), &items); // record the glob import in case we add further items self.glob_imports - .entry(m.module_id) + .entry(m.local_id) .or_default() .push((module_id, import_id)); } @@ -590,7 +590,7 @@ where raw::RawItemKind::Impl(imp) => { let module = ModuleId { krate: self.def_collector.def_map.krate, - module_id: self.module_id, + local_id: self.module_id, }; let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id); let imp_id = ImplId::from_ast_id(ctx, self.raw_items[imp].ast_id); @@ -673,7 +673,7 @@ where modules[self.module_id].children.insert(name.clone(), res); let resolution = Resolution { def: PerNs::types( - ModuleId { krate: self.def_collector.def_map.krate, module_id: res }.into(), + ModuleId { krate: self.def_collector.def_map.krate, local_id: res }.into(), ), import: None, }; @@ -683,7 +683,7 @@ where fn define_def(&mut self, def: &raw::DefData) { let module = - ModuleId { krate: self.def_collector.def_map.krate, module_id: self.module_id }; + ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id); let name = def.name.clone(); diff --git a/crates/ra_hir_def/src/nameres/path_resolution.rs b/crates/ra_hir_def/src/nameres/path_resolution.rs index 9455f22bb..b72c55bd1 100644 --- a/crates/ra_hir_def/src/nameres/path_resolution.rs +++ b/crates/ra_hir_def/src/nameres/path_resolution.rs @@ -74,19 +74,19 @@ impl CrateDefMap { PathKind::DollarCrate(krate) => { if krate == self.krate { tested_by!(macro_dollar_crate_self); - PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into()) + PerNs::types(ModuleId { krate: self.krate, local_id: self.root }.into()) } else { let def_map = db.crate_def_map(krate); - let module = ModuleId { krate, module_id: def_map.root }; + let module = ModuleId { krate, local_id: def_map.root }; tested_by!(macro_dollar_crate_other); PerNs::types(module.into()) } } PathKind::Crate => { - PerNs::types(ModuleId { krate: self.krate, module_id: self.root }.into()) + PerNs::types(ModuleId { krate: self.krate, local_id: self.root }.into()) } PathKind::Self_ => { - PerNs::types(ModuleId { krate: self.krate, module_id: original_module }.into()) + PerNs::types(ModuleId { krate: self.krate, local_id: original_module }.into()) } // plain import or absolute path in 2015: crate-relative with // fallback to extern prelude (with the simplification in @@ -113,7 +113,7 @@ impl CrateDefMap { } PathKind::Super => { if let Some(p) = self.modules[original_module].parent { - PerNs::types(ModuleId { krate: self.krate, module_id: p }.into()) + PerNs::types(ModuleId { krate: self.krate, local_id: p }.into()) } else { log::debug!("super path in root module"); return ResolvePathResult::empty(ReachedFixedPoint::Yes); @@ -160,7 +160,7 @@ impl CrateDefMap { Path { segments: path.segments[i..].to_vec(), kind: PathKind::Self_ }; log::debug!("resolving {:?} in other crate", path); let defp_map = db.crate_def_map(module.krate); - let (def, s) = defp_map.resolve_path(db, module.module_id, &path); + let (def, s) = defp_map.resolve_path(db, module.local_id, &path); return ResolvePathResult::with( def, ReachedFixedPoint::Yes, @@ -169,7 +169,7 @@ impl CrateDefMap { } // Since it is a qualified path here, it should not contains legacy macros - match self[module.module_id].scope.get(&segment.name) { + match self[module.local_id].scope.get(&segment.name) { Some(res) => res.def, _ => { log::debug!("path segment {:?} not found", segment.name); @@ -254,7 +254,7 @@ impl CrateDefMap { keep = db.crate_def_map(prelude.krate); &keep }; - def_map[prelude.module_id].scope.get(name).map_or_else(PerNs::none, |res| res.def) + def_map[prelude.local_id].scope.get(name).map_or_else(PerNs::none, |res| res.def) } else { PerNs::none() } diff --git a/crates/ra_hir_def/src/resolver.rs b/crates/ra_hir_def/src/resolver.rs index 5155365cc..0847f6dcf 100644 --- a/crates/ra_hir_def/src/resolver.rs +++ b/crates/ra_hir_def/src/resolver.rs @@ -325,7 +325,7 @@ impl Resolver { if let Scope::ModuleScope(m) = scope { if let Some(prelude) = m.crate_def_map.prelude { let prelude_def_map = db.crate_def_map(prelude.krate); - traits.extend(prelude_def_map[prelude.module_id].scope.traits()); + traits.extend(prelude_def_map[prelude.local_id].scope.traits()); } traits.extend(m.crate_def_map[m.module_id].scope.traits()); } @@ -402,7 +402,7 @@ impl Scope { }); if let Some(prelude) = m.crate_def_map.prelude { let prelude_def_map = db.crate_def_map(prelude.krate); - prelude_def_map[prelude.module_id].scope.entries().for_each(|(name, res)| { + prelude_def_map[prelude.local_id].scope.entries().for_each(|(name, res)| { f(name.clone(), ScopeDef::PerNs(res.def)); }); } @@ -492,7 +492,7 @@ pub trait HasResolver: Copy { impl HasResolver for ModuleId { fn resolver(self, db: &impl DefDatabase) -> Resolver { let def_map = db.crate_def_map(self.krate); - Resolver::default().push_module_scope(def_map, self.module_id) + Resolver::default().push_module_scope(def_map, self.local_id) } } diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs index 874357008..1dc9793f9 100644 --- a/crates/ra_hir_ty/src/test_db.rs +++ b/crates/ra_hir_ty/src/test_db.rs @@ -73,9 +73,9 @@ impl TestDB { pub fn module_for_file(&self, file_id: FileId) -> ModuleId { for &krate in self.relevant_crates(file_id).iter() { let crate_def_map = self.crate_def_map(krate); - for (module_id, data) in crate_def_map.modules.iter() { + for (local_id, data) in crate_def_map.modules.iter() { if data.definition == Some(file_id) { - return ModuleId { krate, module_id }; + return ModuleId { krate, local_id }; } } } diff --git a/crates/ra_hir_ty/src/tests.rs b/crates/ra_hir_ty/src/tests.rs index c1744663a..c8461b447 100644 --- a/crates/ra_hir_ty/src/tests.rs +++ b/crates/ra_hir_ty/src/tests.rs @@ -4677,7 +4677,7 @@ fn type_at_pos(db: &TestDB, pos: FilePosition) -> String { let module = db.module_for_file(pos.file_id); let crate_def_map = db.crate_def_map(module.krate); - for decl in crate_def_map[module.module_id].scope.declarations() { + for decl in crate_def_map[module.local_id].scope.declarations() { if let ModuleDefId::FunctionId(func) = decl { let (_body, source_map) = db.body_with_source_map(func.into()); if let Some(expr_id) = source_map.node_expr(Source::new(pos.file_id.into(), &expr)) { @@ -4753,7 +4753,7 @@ fn infer(content: &str) -> String { let crate_def_map = db.crate_def_map(module.krate); let mut defs: Vec = Vec::new(); - visit_module(&db, &crate_def_map, module.module_id, &mut |it| defs.push(it)); + visit_module(&db, &crate_def_map, module.local_id, &mut |it| defs.push(it)); defs.sort_by_key(|def| match def { DefWithBodyId::FunctionId(it) => { it.lookup(&db).ast_id.to_node(&db).syntax().text_range().start() @@ -4796,7 +4796,7 @@ fn visit_module( } } } - ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.module_id, cb), + ModuleDefId::ModuleId(it) => visit_module(db, crate_def_map, it.local_id, cb), _ => (), } } @@ -4844,7 +4844,7 @@ fn typing_whitespace_inside_a_function_should_not_invalidate_types() { let events = db.log_executed(|| { let module = db.module_for_file(pos.file_id); let crate_def_map = db.crate_def_map(module.krate); - visit_module(&db, &crate_def_map, module.module_id, &mut |def| { + visit_module(&db, &crate_def_map, module.local_id, &mut |def| { db.infer(def); }); }); @@ -4866,7 +4866,7 @@ fn typing_whitespace_inside_a_function_should_not_invalidate_types() { let events = db.log_executed(|| { let module = db.module_for_file(pos.file_id); let crate_def_map = db.crate_def_map(module.krate); - visit_module(&db, &crate_def_map, module.module_id, &mut |def| { + visit_module(&db, &crate_def_map, module.local_id, &mut |def| { db.infer(def); }); }); -- cgit v1.2.3 From 757e593b253b4df7e6fc8bf15a4d4f34c9d484c5 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 21:32:33 +0300 Subject: rename ra_ide_api -> ra_ide --- crates/ra_batch/Cargo.toml | 2 +- crates/ra_batch/src/lib.rs | 2 +- crates/ra_cli/Cargo.toml | 2 +- crates/ra_cli/src/analysis_bench.rs | 2 +- crates/ra_cli/src/main.rs | 2 +- crates/ra_db/src/lib.rs | 2 +- crates/ra_ide/Cargo.toml | 46 ++ crates/ra_ide/src/assists.rs | 28 + crates/ra_ide/src/call_info.rs | 592 ++++++++++++++ crates/ra_ide/src/change.rs | 354 +++++++++ crates/ra_ide/src/completion.rs | 77 ++ crates/ra_ide/src/completion/complete_dot.rs | 456 +++++++++++ crates/ra_ide/src/completion/complete_fn_param.rs | 136 ++++ crates/ra_ide/src/completion/complete_keyword.rs | 781 ++++++++++++++++++ .../completion/complete_macro_in_item_position.rs | 143 ++++ crates/ra_ide/src/completion/complete_path.rs | 785 ++++++++++++++++++ crates/ra_ide/src/completion/complete_pattern.rs | 89 +++ crates/ra_ide/src/completion/complete_postfix.rs | 282 +++++++ .../src/completion/complete_record_literal.rs | 159 ++++ .../src/completion/complete_record_pattern.rs | 93 +++ crates/ra_ide/src/completion/complete_scope.rs | 876 +++++++++++++++++++++ crates/ra_ide/src/completion/complete_snippet.rs | 120 +++ crates/ra_ide/src/completion/completion_context.rs | 274 +++++++ crates/ra_ide/src/completion/completion_item.rs | 322 ++++++++ crates/ra_ide/src/completion/presentation.rs | 676 ++++++++++++++++ crates/ra_ide/src/db.rs | 144 ++++ crates/ra_ide/src/diagnostics.rs | 652 +++++++++++++++ crates/ra_ide/src/display.rs | 84 ++ crates/ra_ide/src/display/function_signature.rs | 215 +++++ crates/ra_ide/src/display/navigation_target.rs | 411 ++++++++++ crates/ra_ide/src/display/short_label.rs | 97 +++ crates/ra_ide/src/display/structure.rs | 401 ++++++++++ crates/ra_ide/src/expand.rs | 63 ++ crates/ra_ide/src/expand_macro.rs | 295 +++++++ crates/ra_ide/src/extend_selection.rs | 452 +++++++++++ crates/ra_ide/src/feature_flags.rs | 70 ++ crates/ra_ide/src/folding_ranges.rs | 378 +++++++++ crates/ra_ide/src/goto_definition.rs | 696 ++++++++++++++++ crates/ra_ide/src/goto_type_definition.rs | 105 +++ crates/ra_ide/src/hover.rs | 730 +++++++++++++++++ crates/ra_ide/src/impls.rs | 206 +++++ crates/ra_ide/src/inlay_hints.rs | 543 +++++++++++++ crates/ra_ide/src/join_lines.rs | 611 ++++++++++++++ crates/ra_ide/src/lib.rs | 489 ++++++++++++ crates/ra_ide/src/line_index.rs | 283 +++++++ crates/ra_ide/src/line_index_utils.rs | 331 ++++++++ crates/ra_ide/src/marks.rs | 13 + crates/ra_ide/src/matching_brace.rs | 43 + crates/ra_ide/src/mock_analysis.rs | 149 ++++ crates/ra_ide/src/parent_module.rs | 104 +++ crates/ra_ide/src/references.rs | 389 +++++++++ crates/ra_ide/src/references/classify.rs | 186 +++++ crates/ra_ide/src/references/name_definition.rs | 83 ++ crates/ra_ide/src/references/rename.rs | 328 ++++++++ crates/ra_ide/src/references/search_scope.rs | 145 ++++ crates/ra_ide/src/runnables.rs | 242 ++++++ crates/ra_ide/src/snapshots/highlighting.html | 48 ++ .../ra_ide/src/snapshots/rainbow_highlighting.html | 33 + crates/ra_ide/src/source_change.rs | 119 +++ crates/ra_ide/src/status.rs | 136 ++++ crates/ra_ide/src/symbol_index.rs | 405 ++++++++++ crates/ra_ide/src/syntax_highlighting.rs | 342 ++++++++ crates/ra_ide/src/syntax_tree.rs | 359 +++++++++ crates/ra_ide/src/test_utils.rs | 21 + crates/ra_ide/src/typing.rs | 490 ++++++++++++ crates/ra_ide/src/wasm_shims.rs | 19 + crates/ra_ide_api/Cargo.toml | 46 -- crates/ra_ide_api/src/assists.rs | 28 - crates/ra_ide_api/src/call_info.rs | 592 -------------- crates/ra_ide_api/src/change.rs | 354 --------- crates/ra_ide_api/src/completion.rs | 77 -- crates/ra_ide_api/src/completion/complete_dot.rs | 456 ----------- .../ra_ide_api/src/completion/complete_fn_param.rs | 136 ---- .../ra_ide_api/src/completion/complete_keyword.rs | 781 ------------------ .../completion/complete_macro_in_item_position.rs | 143 ---- crates/ra_ide_api/src/completion/complete_path.rs | 785 ------------------ .../ra_ide_api/src/completion/complete_pattern.rs | 89 --- .../ra_ide_api/src/completion/complete_postfix.rs | 282 ------- .../src/completion/complete_record_literal.rs | 159 ---- .../src/completion/complete_record_pattern.rs | 93 --- crates/ra_ide_api/src/completion/complete_scope.rs | 876 --------------------- .../ra_ide_api/src/completion/complete_snippet.rs | 120 --- .../src/completion/completion_context.rs | 274 ------- .../ra_ide_api/src/completion/completion_item.rs | 322 -------- crates/ra_ide_api/src/completion/presentation.rs | 676 ---------------- crates/ra_ide_api/src/db.rs | 144 ---- crates/ra_ide_api/src/diagnostics.rs | 652 --------------- crates/ra_ide_api/src/display.rs | 84 -- .../ra_ide_api/src/display/function_signature.rs | 215 ----- crates/ra_ide_api/src/display/navigation_target.rs | 411 ---------- crates/ra_ide_api/src/display/short_label.rs | 97 --- crates/ra_ide_api/src/display/structure.rs | 401 ---------- crates/ra_ide_api/src/expand.rs | 63 -- crates/ra_ide_api/src/expand_macro.rs | 295 ------- crates/ra_ide_api/src/extend_selection.rs | 452 ----------- crates/ra_ide_api/src/feature_flags.rs | 70 -- crates/ra_ide_api/src/folding_ranges.rs | 378 --------- crates/ra_ide_api/src/goto_definition.rs | 696 ---------------- crates/ra_ide_api/src/goto_type_definition.rs | 105 --- crates/ra_ide_api/src/hover.rs | 730 ----------------- crates/ra_ide_api/src/impls.rs | 206 ----- crates/ra_ide_api/src/inlay_hints.rs | 543 ------------- crates/ra_ide_api/src/join_lines.rs | 611 -------------- crates/ra_ide_api/src/lib.rs | 489 ------------ crates/ra_ide_api/src/line_index.rs | 283 ------- crates/ra_ide_api/src/line_index_utils.rs | 331 -------- crates/ra_ide_api/src/marks.rs | 13 - crates/ra_ide_api/src/matching_brace.rs | 43 - crates/ra_ide_api/src/mock_analysis.rs | 149 ---- crates/ra_ide_api/src/parent_module.rs | 104 --- crates/ra_ide_api/src/references.rs | 389 --------- crates/ra_ide_api/src/references/classify.rs | 186 ----- .../ra_ide_api/src/references/name_definition.rs | 83 -- crates/ra_ide_api/src/references/rename.rs | 328 -------- crates/ra_ide_api/src/references/search_scope.rs | 145 ---- crates/ra_ide_api/src/runnables.rs | 242 ------ crates/ra_ide_api/src/snapshots/highlighting.html | 48 -- .../src/snapshots/rainbow_highlighting.html | 33 - crates/ra_ide_api/src/source_change.rs | 119 --- crates/ra_ide_api/src/status.rs | 136 ---- crates/ra_ide_api/src/symbol_index.rs | 405 ---------- crates/ra_ide_api/src/syntax_highlighting.rs | 342 -------- crates/ra_ide_api/src/syntax_tree.rs | 359 --------- crates/ra_ide_api/src/test_utils.rs | 21 - crates/ra_ide_api/src/typing.rs | 490 ------------ crates/ra_ide_api/src/wasm_shims.rs | 19 - crates/ra_lsp_server/Cargo.toml | 2 +- crates/ra_lsp_server/src/cargo_target_spec.rs | 2 +- crates/ra_lsp_server/src/conv.rs | 6 +- crates/ra_lsp_server/src/lib.rs | 2 +- crates/ra_lsp_server/src/main_loop.rs | 2 +- crates/ra_lsp_server/src/main_loop/handlers.rs | 6 +- .../ra_lsp_server/src/main_loop/subscriptions.rs | 2 +- crates/ra_lsp_server/src/world.rs | 2 +- 134 files changed, 17217 insertions(+), 17217 deletions(-) create mode 100644 crates/ra_ide/Cargo.toml create mode 100644 crates/ra_ide/src/assists.rs create mode 100644 crates/ra_ide/src/call_info.rs create mode 100644 crates/ra_ide/src/change.rs create mode 100644 crates/ra_ide/src/completion.rs create mode 100644 crates/ra_ide/src/completion/complete_dot.rs create mode 100644 crates/ra_ide/src/completion/complete_fn_param.rs create mode 100644 crates/ra_ide/src/completion/complete_keyword.rs create mode 100644 crates/ra_ide/src/completion/complete_macro_in_item_position.rs create mode 100644 crates/ra_ide/src/completion/complete_path.rs create mode 100644 crates/ra_ide/src/completion/complete_pattern.rs create mode 100644 crates/ra_ide/src/completion/complete_postfix.rs create mode 100644 crates/ra_ide/src/completion/complete_record_literal.rs create mode 100644 crates/ra_ide/src/completion/complete_record_pattern.rs create mode 100644 crates/ra_ide/src/completion/complete_scope.rs create mode 100644 crates/ra_ide/src/completion/complete_snippet.rs create mode 100644 crates/ra_ide/src/completion/completion_context.rs create mode 100644 crates/ra_ide/src/completion/completion_item.rs create mode 100644 crates/ra_ide/src/completion/presentation.rs create mode 100644 crates/ra_ide/src/db.rs create mode 100644 crates/ra_ide/src/diagnostics.rs create mode 100644 crates/ra_ide/src/display.rs create mode 100644 crates/ra_ide/src/display/function_signature.rs create mode 100644 crates/ra_ide/src/display/navigation_target.rs create mode 100644 crates/ra_ide/src/display/short_label.rs create mode 100644 crates/ra_ide/src/display/structure.rs create mode 100644 crates/ra_ide/src/expand.rs create mode 100644 crates/ra_ide/src/expand_macro.rs create mode 100644 crates/ra_ide/src/extend_selection.rs create mode 100644 crates/ra_ide/src/feature_flags.rs create mode 100644 crates/ra_ide/src/folding_ranges.rs create mode 100644 crates/ra_ide/src/goto_definition.rs create mode 100644 crates/ra_ide/src/goto_type_definition.rs create mode 100644 crates/ra_ide/src/hover.rs create mode 100644 crates/ra_ide/src/impls.rs create mode 100644 crates/ra_ide/src/inlay_hints.rs create mode 100644 crates/ra_ide/src/join_lines.rs create mode 100644 crates/ra_ide/src/lib.rs create mode 100644 crates/ra_ide/src/line_index.rs create mode 100644 crates/ra_ide/src/line_index_utils.rs create mode 100644 crates/ra_ide/src/marks.rs create mode 100644 crates/ra_ide/src/matching_brace.rs create mode 100644 crates/ra_ide/src/mock_analysis.rs create mode 100644 crates/ra_ide/src/parent_module.rs create mode 100644 crates/ra_ide/src/references.rs create mode 100644 crates/ra_ide/src/references/classify.rs create mode 100644 crates/ra_ide/src/references/name_definition.rs create mode 100644 crates/ra_ide/src/references/rename.rs create mode 100644 crates/ra_ide/src/references/search_scope.rs create mode 100644 crates/ra_ide/src/runnables.rs create mode 100644 crates/ra_ide/src/snapshots/highlighting.html create mode 100644 crates/ra_ide/src/snapshots/rainbow_highlighting.html create mode 100644 crates/ra_ide/src/source_change.rs create mode 100644 crates/ra_ide/src/status.rs create mode 100644 crates/ra_ide/src/symbol_index.rs create mode 100644 crates/ra_ide/src/syntax_highlighting.rs create mode 100644 crates/ra_ide/src/syntax_tree.rs create mode 100644 crates/ra_ide/src/test_utils.rs create mode 100644 crates/ra_ide/src/typing.rs create mode 100644 crates/ra_ide/src/wasm_shims.rs delete mode 100644 crates/ra_ide_api/Cargo.toml delete mode 100644 crates/ra_ide_api/src/assists.rs delete mode 100644 crates/ra_ide_api/src/call_info.rs delete mode 100644 crates/ra_ide_api/src/change.rs delete mode 100644 crates/ra_ide_api/src/completion.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_dot.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_fn_param.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_keyword.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_path.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_pattern.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_postfix.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_record_literal.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_record_pattern.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_scope.rs delete mode 100644 crates/ra_ide_api/src/completion/complete_snippet.rs delete mode 100644 crates/ra_ide_api/src/completion/completion_context.rs delete mode 100644 crates/ra_ide_api/src/completion/completion_item.rs delete mode 100644 crates/ra_ide_api/src/completion/presentation.rs delete mode 100644 crates/ra_ide_api/src/db.rs delete mode 100644 crates/ra_ide_api/src/diagnostics.rs delete mode 100644 crates/ra_ide_api/src/display.rs delete mode 100644 crates/ra_ide_api/src/display/function_signature.rs delete mode 100644 crates/ra_ide_api/src/display/navigation_target.rs delete mode 100644 crates/ra_ide_api/src/display/short_label.rs delete mode 100644 crates/ra_ide_api/src/display/structure.rs delete mode 100644 crates/ra_ide_api/src/expand.rs delete mode 100644 crates/ra_ide_api/src/expand_macro.rs delete mode 100644 crates/ra_ide_api/src/extend_selection.rs delete mode 100644 crates/ra_ide_api/src/feature_flags.rs delete mode 100644 crates/ra_ide_api/src/folding_ranges.rs delete mode 100644 crates/ra_ide_api/src/goto_definition.rs delete mode 100644 crates/ra_ide_api/src/goto_type_definition.rs delete mode 100644 crates/ra_ide_api/src/hover.rs delete mode 100644 crates/ra_ide_api/src/impls.rs delete mode 100644 crates/ra_ide_api/src/inlay_hints.rs delete mode 100644 crates/ra_ide_api/src/join_lines.rs delete mode 100644 crates/ra_ide_api/src/lib.rs delete mode 100644 crates/ra_ide_api/src/line_index.rs delete mode 100644 crates/ra_ide_api/src/line_index_utils.rs delete mode 100644 crates/ra_ide_api/src/marks.rs delete mode 100644 crates/ra_ide_api/src/matching_brace.rs delete mode 100644 crates/ra_ide_api/src/mock_analysis.rs delete mode 100644 crates/ra_ide_api/src/parent_module.rs delete mode 100644 crates/ra_ide_api/src/references.rs delete mode 100644 crates/ra_ide_api/src/references/classify.rs delete mode 100644 crates/ra_ide_api/src/references/name_definition.rs delete mode 100644 crates/ra_ide_api/src/references/rename.rs delete mode 100644 crates/ra_ide_api/src/references/search_scope.rs delete mode 100644 crates/ra_ide_api/src/runnables.rs delete mode 100644 crates/ra_ide_api/src/snapshots/highlighting.html delete mode 100644 crates/ra_ide_api/src/snapshots/rainbow_highlighting.html delete mode 100644 crates/ra_ide_api/src/source_change.rs delete mode 100644 crates/ra_ide_api/src/status.rs delete mode 100644 crates/ra_ide_api/src/symbol_index.rs delete mode 100644 crates/ra_ide_api/src/syntax_highlighting.rs delete mode 100644 crates/ra_ide_api/src/syntax_tree.rs delete mode 100644 crates/ra_ide_api/src/test_utils.rs delete mode 100644 crates/ra_ide_api/src/typing.rs delete mode 100644 crates/ra_ide_api/src/wasm_shims.rs (limited to 'crates') diff --git a/crates/ra_batch/Cargo.toml b/crates/ra_batch/Cargo.toml index 35626d77d..3bf351fe3 100644 --- a/crates/ra_batch/Cargo.toml +++ b/crates/ra_batch/Cargo.toml @@ -15,6 +15,6 @@ crossbeam-channel = "0.4.0" ra_vfs = "0.5.0" ra_vfs_glob = { path = "../ra_vfs_glob" } ra_db = { path = "../ra_db" } -ra_ide_api = { path = "../ra_ide_api" } +ra_ide = { path = "../ra_ide" } ra_hir = { path = "../ra_hir" } ra_project_model = { path = "../ra_project_model" } diff --git a/crates/ra_batch/src/lib.rs b/crates/ra_batch/src/lib.rs index cb389eb26..2c9645c00 100644 --- a/crates/ra_batch/src/lib.rs +++ b/crates/ra_batch/src/lib.rs @@ -6,7 +6,7 @@ use rustc_hash::FxHashMap; use crossbeam_channel::{unbounded, Receiver}; use ra_db::{CrateGraph, FileId, SourceRootId}; -use ra_ide_api::{AnalysisChange, AnalysisHost, FeatureFlags}; +use ra_ide::{AnalysisChange, AnalysisHost, FeatureFlags}; use ra_project_model::{get_rustc_cfg_options, PackageRoot, ProjectWorkspace}; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use ra_vfs_glob::RustPackageFilterBuilder; diff --git a/crates/ra_cli/Cargo.toml b/crates/ra_cli/Cargo.toml index 3bb475997..c7e0d0f0f 100644 --- a/crates/ra_cli/Cargo.toml +++ b/crates/ra_cli/Cargo.toml @@ -10,7 +10,7 @@ pico-args = "0.3.0" flexi_logger = "0.14.0" ra_syntax = { path = "../ra_syntax" } -ra_ide_api = { path = "../ra_ide_api" } +ra_ide = { path = "../ra_ide" } ra_batch = { path = "../ra_batch" } ra_hir = { path = "../ra_hir" } ra_db = { path = "../ra_db" } diff --git a/crates/ra_cli/src/analysis_bench.rs b/crates/ra_cli/src/analysis_bench.rs index 34105af57..5485a38ff 100644 --- a/crates/ra_cli/src/analysis_bench.rs +++ b/crates/ra_cli/src/analysis_bench.rs @@ -10,7 +10,7 @@ use ra_db::{ salsa::{Database, Durability}, FileId, SourceDatabaseExt, }; -use ra_ide_api::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol}; +use ra_ide::{Analysis, AnalysisChange, AnalysisHost, FilePosition, LineCol}; use crate::Result; diff --git a/crates/ra_cli/src/main.rs b/crates/ra_cli/src/main.rs index 08f353147..fe847e611 100644 --- a/crates/ra_cli/src/main.rs +++ b/crates/ra_cli/src/main.rs @@ -9,7 +9,7 @@ use std::{error::Error, fmt::Write, io::Read}; use flexi_logger::Logger; use pico_args::Arguments; -use ra_ide_api::{file_structure, Analysis}; +use ra_ide::{file_structure, Analysis}; use ra_prof::profile; use ra_syntax::{AstNode, SourceFile}; diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index e8852531b..21341b769 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs @@ -1,4 +1,4 @@ -//! ra_db defines basic database traits. The concrete DB is defined by ra_ide_api. +//! ra_db defines basic database traits. The concrete DB is defined by ra_ide. mod cancellation; mod input; pub mod fixture; diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml new file mode 100644 index 000000000..e6383dd35 --- /dev/null +++ b/crates/ra_ide/Cargo.toml @@ -0,0 +1,46 @@ +[package] +edition = "2018" +name = "ra_ide" +version = "0.1.0" +authors = ["rust-analyzer developers"] + +[lib] +doctest = false + +[features] +wasm = [] + +[dependencies] +format-buf = "1.0.0" +itertools = "0.8.0" +join_to_string = "0.1.3" +log = "0.4.5" +rayon = "1.0.2" +fst = { version = "0.3.1", default-features = false } +rustc-hash = "1.0" +unicase = "2.2.0" +superslice = "1.0.0" +rand = { version = "0.7.0", features = ["small_rng"] } +once_cell = "1.2.0" + +ra_syntax = { path = "../ra_syntax" } +ra_text_edit = { path = "../ra_text_edit" } +ra_db = { path = "../ra_db" } +ra_cfg = { path = "../ra_cfg" } +ra_fmt = { path = "../ra_fmt" } +ra_prof = { path = "../ra_prof" } +test_utils = { path = "../test_utils" } +ra_assists = { path = "../ra_assists" } + +# ra_ide should depend only on the top-level `hir` package. if you need +# something from some `hir_xxx` subpackage, reexport the API via `hir`. +hir = { path = "../ra_hir", package = "ra_hir" } + +[dev-dependencies] +insta = "0.12.0" + +[dev-dependencies.proptest] +version = "0.9.0" +# Disable `fork` feature to allow compiling on webassembly +default-features = false +features = ["std", "bit-set", "break-dead-code"] diff --git a/crates/ra_ide/src/assists.rs b/crates/ra_ide/src/assists.rs new file mode 100644 index 000000000..e00589733 --- /dev/null +++ b/crates/ra_ide/src/assists.rs @@ -0,0 +1,28 @@ +//! FIXME: write short doc here + +use ra_db::{FilePosition, FileRange}; + +use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; + +pub use ra_assists::AssistId; + +#[derive(Debug)] +pub struct Assist { + pub id: AssistId, + pub change: SourceChange, +} + +pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec { + ra_assists::assists(db, frange) + .into_iter() + .map(|(label, action)| { + let file_id = frange.file_id; + let file_edit = SourceFileEdit { file_id, edit: action.edit }; + let id = label.id; + let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( + action.cursor_position.map(|offset| FilePosition { offset, file_id }), + ); + Assist { id, change } + }) + .collect() +} diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs new file mode 100644 index 000000000..d559dc4d0 --- /dev/null +++ b/crates/ra_ide/src/call_info.rs @@ -0,0 +1,592 @@ +//! FIXME: write short doc here + +use ra_db::SourceDatabase; +use ra_syntax::{ + algo::ancestors_at_offset, + ast::{self, ArgListOwner}, + match_ast, AstNode, SyntaxNode, TextUnit, +}; +use test_utils::tested_by; + +use crate::{db::RootDatabase, CallInfo, FilePosition, FunctionSignature}; + +/// Computes parameter information for the given call expression. +pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { + let parse = db.parse(position.file_id); + let syntax = parse.tree().syntax().clone(); + + // Find the calling expression and it's NameRef + let calling_node = FnCallNode::with_node(&syntax, position.offset)?; + let name_ref = calling_node.name_ref()?; + let name_ref = hir::Source::new(position.file_id.into(), name_ref.syntax()); + + let analyzer = hir::SourceAnalyzer::new(db, name_ref, None); + let (mut call_info, has_self) = match &calling_node { + FnCallNode::CallExpr(expr) => { + //FIXME: Type::as_callable is broken + let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; + match callable_def { + hir::CallableDef::FunctionId(it) => { + let fn_def = it.into(); + (CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db)) + } + hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false), + hir::CallableDef::EnumVariantId(it) => { + (CallInfo::with_enum_variant(db, it.into())?, false) + } + } + } + FnCallNode::MethodCallExpr(expr) => { + let function = analyzer.resolve_method_call(&expr)?; + (CallInfo::with_fn(db, function), function.has_self_param(db)) + } + FnCallNode::MacroCallExpr(expr) => { + let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?; + (CallInfo::with_macro(db, macro_def)?, false) + } + }; + + // If we have a calling expression let's find which argument we are on + let num_params = call_info.parameters().len(); + + if num_params == 1 { + if !has_self { + call_info.active_parameter = Some(0); + } + } else if num_params > 1 { + // Count how many parameters into the call we are. + if let Some(arg_list) = calling_node.arg_list() { + // Number of arguments specified at the call site + let num_args_at_callsite = arg_list.args().count(); + + let arg_list_range = arg_list.syntax().text_range(); + if !arg_list_range.contains_inclusive(position.offset) { + tested_by!(call_info_bad_offset); + return None; + } + + let mut param = std::cmp::min( + num_args_at_callsite, + arg_list + .args() + .take_while(|arg| arg.syntax().text_range().end() < position.offset) + .count(), + ); + + // If we are in a method account for `self` + if has_self { + param += 1; + } + + call_info.active_parameter = Some(param); + } + } + + Some(call_info) +} + +#[derive(Debug)] +enum FnCallNode { + CallExpr(ast::CallExpr), + MethodCallExpr(ast::MethodCallExpr), + MacroCallExpr(ast::MacroCall), +} + +impl FnCallNode { + fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option { + ancestors_at_offset(syntax, offset).find_map(|node| { + match_ast! { + match node { + ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, + ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, + ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, + _ => { None }, + } + } + }) + } + + fn name_ref(&self) -> Option { + match self { + FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { + ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, + _ => return None, + }), + + FnCallNode::MethodCallExpr(call_expr) => { + call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0) + } + + FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(), + } + } + + fn arg_list(&self) -> Option { + match self { + FnCallNode::CallExpr(expr) => expr.arg_list(), + FnCallNode::MethodCallExpr(expr) => expr.arg_list(), + FnCallNode::MacroCallExpr(_) => None, + } + } +} + +impl CallInfo { + fn with_fn(db: &RootDatabase, function: hir::Function) -> Self { + let signature = FunctionSignature::from_hir(db, function); + + CallInfo { signature, active_parameter: None } + } + + fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option { + let signature = FunctionSignature::from_struct(db, st)?; + + Some(CallInfo { signature, active_parameter: None }) + } + + fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option { + let signature = FunctionSignature::from_enum_variant(db, variant)?; + + Some(CallInfo { signature, active_parameter: None }) + } + + fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option { + let signature = FunctionSignature::from_macro(db, macro_def)?; + + Some(CallInfo { signature, active_parameter: None }) + } + + fn parameters(&self) -> &[String] { + &self.signature.parameters + } +} + +#[cfg(test)] +mod tests { + use test_utils::covers; + + use crate::mock_analysis::single_file_with_position; + + use super::*; + + // These are only used when testing + impl CallInfo { + fn doc(&self) -> Option { + self.signature.doc.clone() + } + + fn label(&self) -> String { + self.signature.to_string() + } + } + + fn call_info(text: &str) -> CallInfo { + let (analysis, position) = single_file_with_position(text); + analysis.call_info(position).unwrap().unwrap() + } + + #[test] + fn test_fn_signature_two_args_firstx() { + let info = call_info( + r#"fn foo(x: u32, y: u32) -> u32 {x + y} +fn bar() { foo(<|>3, ); }"#, + ); + + assert_eq!(info.parameters(), ["x: u32", "y: u32"]); + assert_eq!(info.active_parameter, Some(0)); + } + + #[test] + fn test_fn_signature_two_args_second() { + let info = call_info( + r#"fn foo(x: u32, y: u32) -> u32 {x + y} +fn bar() { foo(3, <|>); }"#, + ); + + assert_eq!(info.parameters(), ["x: u32", "y: u32"]); + assert_eq!(info.active_parameter, Some(1)); + } + + #[test] + fn test_fn_signature_two_args_empty() { + let info = call_info( + r#"fn foo(x: u32, y: u32) -> u32 {x + y} +fn bar() { foo(<|>); }"#, + ); + + assert_eq!(info.parameters(), ["x: u32", "y: u32"]); + assert_eq!(info.active_parameter, Some(0)); + } + + #[test] + fn test_fn_signature_two_args_first_generics() { + let info = call_info( + r#"fn foo(x: T, y: U) -> u32 where T: Copy + Display, U: Debug {x + y} +fn bar() { foo(<|>3, ); }"#, + ); + + assert_eq!(info.parameters(), ["x: T", "y: U"]); + assert_eq!( + info.label(), + r#" +fn foo(x: T, y: U) -> u32 +where T: Copy + Display, + U: Debug + "# + .trim() + ); + assert_eq!(info.active_parameter, Some(0)); + } + + #[test] + fn test_fn_signature_no_params() { + let info = call_info( + r#"fn foo() -> T where T: Copy + Display {} +fn bar() { foo(<|>); }"#, + ); + + assert!(info.parameters().is_empty()); + assert_eq!( + info.label(), + r#" +fn foo() -> T +where T: Copy + Display + "# + .trim() + ); + assert!(info.active_parameter.is_none()); + } + + #[test] + fn test_fn_signature_for_impl() { + let info = call_info( + r#"struct F; impl F { pub fn new() { F{}} } +fn bar() {let _ : F = F::new(<|>);}"#, + ); + + assert!(info.parameters().is_empty()); + assert_eq!(info.active_parameter, None); + } + + #[test] + fn test_fn_signature_for_method_self() { + let info = call_info( + r#"struct F; +impl F { + pub fn new() -> F{ + F{} + } + + pub fn do_it(&self) {} +} + +fn bar() { + let f : F = F::new(); + f.do_it(<|>); +}"#, + ); + + assert_eq!(info.parameters(), ["&self"]); + assert_eq!(info.active_parameter, None); + } + + #[test] + fn test_fn_signature_for_method_with_arg() { + let info = call_info( + r#"struct F; +impl F { + pub fn new() -> F{ + F{} + } + + pub fn do_it(&self, x: i32) {} +} + +fn bar() { + let f : F = F::new(); + f.do_it(<|>); +}"#, + ); + + assert_eq!(info.parameters(), ["&self", "x: i32"]); + assert_eq!(info.active_parameter, Some(1)); + } + + #[test] + fn test_fn_signature_with_docs_simple() { + let info = call_info( + r#" +/// test +// non-doc-comment +fn foo(j: u32) -> u32 { + j +} + +fn bar() { + let _ = foo(<|>); +} +"#, + ); + + assert_eq!(info.parameters(), ["j: u32"]); + assert_eq!(info.active_parameter, Some(0)); + assert_eq!(info.label(), "fn foo(j: u32) -> u32"); + assert_eq!(info.doc().map(|it| it.into()), Some("test".to_string())); + } + + #[test] + fn test_fn_signature_with_docs() { + let info = call_info( + r#" +/// Adds one to the number given. +/// +/// # Examples +/// +/// ``` +/// let five = 5; +/// +/// assert_eq!(6, my_crate::add_one(5)); +/// ``` +pub fn add_one(x: i32) -> i32 { + x + 1 +} + +pub fn do() { + add_one(<|> +}"#, + ); + + assert_eq!(info.parameters(), ["x: i32"]); + assert_eq!(info.active_parameter, Some(0)); + assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32"); + assert_eq!( + info.doc().map(|it| it.into()), + Some( + r#"Adds one to the number given. + +# Examples + +``` +let five = 5; + +assert_eq!(6, my_crate::add_one(5)); +```"# + .to_string() + ) + ); + } + + #[test] + fn test_fn_signature_with_docs_impl() { + let info = call_info( + r#" +struct addr; +impl addr { + /// Adds one to the number given. + /// + /// # Examples + /// + /// ``` + /// let five = 5; + /// + /// assert_eq!(6, my_crate::add_one(5)); + /// ``` + pub fn add_one(x: i32) -> i32 { + x + 1 + } +} + +pub fn do_it() { + addr {}; + addr::add_one(<|>); +}"#, + ); + + assert_eq!(info.parameters(), ["x: i32"]); + assert_eq!(info.active_parameter, Some(0)); + assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32"); + assert_eq!( + info.doc().map(|it| it.into()), + Some( + r#"Adds one to the number given. + +# Examples + +``` +let five = 5; + +assert_eq!(6, my_crate::add_one(5)); +```"# + .to_string() + ) + ); + } + + #[test] + fn test_fn_signature_with_docs_from_actix() { + let info = call_info( + r#" +struct WriteHandler; + +impl WriteHandler { + /// Method is called when writer emits error. + /// + /// If this method returns `ErrorAction::Continue` writer processing + /// continues otherwise stream processing stops. + fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running { + Running::Stop + } + + /// Method is called when writer finishes. + /// + /// By default this method stops actor's `Context`. + fn finished(&mut self, ctx: &mut Self::Context) { + ctx.stop() + } +} + +pub fn foo(mut r: WriteHandler<()>) { + r.finished(<|>); +} + +"#, + ); + + assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string()); + assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]); + assert_eq!(info.active_parameter, Some(1)); + assert_eq!( + info.doc().map(|it| it.into()), + Some( + r#"Method is called when writer finishes. + +By default this method stops actor's `Context`."# + .to_string() + ) + ); + } + + #[test] + fn call_info_bad_offset() { + covers!(call_info_bad_offset); + let (analysis, position) = single_file_with_position( + r#"fn foo(x: u32, y: u32) -> u32 {x + y} + fn bar() { foo <|> (3, ); }"#, + ); + let call_info = analysis.call_info(position).unwrap(); + assert!(call_info.is_none()); + } + + #[test] + fn test_nested_method_in_lamba() { + let info = call_info( + r#"struct Foo; + +impl Foo { + fn bar(&self, _: u32) { } +} + +fn bar(_: u32) { } + +fn main() { + let foo = Foo; + std::thread::spawn(move || foo.bar(<|>)); +}"#, + ); + + assert_eq!(info.parameters(), ["&self", "_: u32"]); + assert_eq!(info.active_parameter, Some(1)); + assert_eq!(info.label(), "fn bar(&self, _: u32)"); + } + + #[test] + fn works_for_tuple_structs() { + let info = call_info( + r#" +/// A cool tuple struct +struct TS(u32, i32); +fn main() { + let s = TS(0, <|>); +}"#, + ); + + assert_eq!(info.label(), "struct TS(u32, i32) -> TS"); + assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); + assert_eq!(info.active_parameter, Some(1)); + } + + #[test] + #[should_panic] + fn cant_call_named_structs() { + let _ = call_info( + r#" +struct TS { x: u32, y: i32 } +fn main() { + let s = TS(<|>); +}"#, + ); + } + + #[test] + fn works_for_enum_variants() { + let info = call_info( + r#" +enum E { + /// A Variant + A(i32), + /// Another + B, + /// And C + C { a: i32, b: i32 } +} + +fn main() { + let a = E::A(<|>); +} + "#, + ); + + assert_eq!(info.label(), "E::A(0: i32)"); + assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string())); + assert_eq!(info.active_parameter, Some(0)); + } + + #[test] + #[should_panic] + fn cant_call_enum_records() { + let _ = call_info( + r#" +enum E { + /// A Variant + A(i32), + /// Another + B, + /// And C + C { a: i32, b: i32 } +} + +fn main() { + let a = E::C(<|>); +} + "#, + ); + } + + #[test] + fn fn_signature_for_macro() { + let info = call_info( + r#" +/// empty macro +macro_rules! foo { + () => {} +} + +fn f() { + foo!(<|>); +} + "#, + ); + + assert_eq!(info.label(), "foo!()"); + assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string())); + } +} diff --git a/crates/ra_ide/src/change.rs b/crates/ra_ide/src/change.rs new file mode 100644 index 000000000..4a76d1dd8 --- /dev/null +++ b/crates/ra_ide/src/change.rs @@ -0,0 +1,354 @@ +//! FIXME: write short doc here + +use std::{fmt, sync::Arc, time}; + +use ra_db::{ + salsa::{Database, Durability, SweepStrategy}, + CrateGraph, CrateId, FileId, RelativePathBuf, SourceDatabase, SourceDatabaseExt, SourceRoot, + SourceRootId, +}; +use ra_prof::{memory_usage, profile, Bytes}; +use ra_syntax::SourceFile; +#[cfg(not(feature = "wasm"))] +use rayon::prelude::*; +use rustc_hash::FxHashMap; + +use crate::{ + db::{DebugData, RootDatabase}, + symbol_index::{SymbolIndex, SymbolsDatabase}, +}; + +#[derive(Default)] +pub struct AnalysisChange { + new_roots: Vec<(SourceRootId, bool)>, + roots_changed: FxHashMap, + files_changed: Vec<(FileId, Arc)>, + libraries_added: Vec, + crate_graph: Option, + debug_data: DebugData, +} + +impl fmt::Debug for AnalysisChange { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + let mut d = fmt.debug_struct("AnalysisChange"); + if !self.new_roots.is_empty() { + d.field("new_roots", &self.new_roots); + } + if !self.roots_changed.is_empty() { + d.field("roots_changed", &self.roots_changed); + } + if !self.files_changed.is_empty() { + d.field("files_changed", &self.files_changed.len()); + } + if !self.libraries_added.is_empty() { + d.field("libraries_added", &self.libraries_added.len()); + } + if !self.crate_graph.is_none() { + d.field("crate_graph", &self.crate_graph); + } + d.finish() + } +} + +impl AnalysisChange { + pub fn new() -> AnalysisChange { + AnalysisChange::default() + } + + pub fn add_root(&mut self, root_id: SourceRootId, is_local: bool) { + self.new_roots.push((root_id, is_local)); + } + + pub fn add_file( + &mut self, + root_id: SourceRootId, + file_id: FileId, + path: RelativePathBuf, + text: Arc, + ) { + let file = AddFile { file_id, path, text }; + self.roots_changed.entry(root_id).or_default().added.push(file); + } + + pub fn change_file(&mut self, file_id: FileId, new_text: Arc) { + self.files_changed.push((file_id, new_text)) + } + + pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) { + let file = RemoveFile { file_id, path }; + self.roots_changed.entry(root_id).or_default().removed.push(file); + } + + pub fn add_library(&mut self, data: LibraryData) { + self.libraries_added.push(data) + } + + pub fn set_crate_graph(&mut self, graph: CrateGraph) { + self.crate_graph = Some(graph); + } + + pub fn set_debug_crate_name(&mut self, crate_id: CrateId, name: String) { + self.debug_data.crate_names.insert(crate_id, name); + } + + pub fn set_debug_root_path(&mut self, source_root_id: SourceRootId, path: String) { + self.debug_data.root_paths.insert(source_root_id, path); + } +} + +#[derive(Debug)] +struct AddFile { + file_id: FileId, + path: RelativePathBuf, + text: Arc, +} + +#[derive(Debug)] +struct RemoveFile { + file_id: FileId, + path: RelativePathBuf, +} + +#[derive(Default)] +struct RootChange { + added: Vec, + removed: Vec, +} + +impl fmt::Debug for RootChange { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fmt.debug_struct("AnalysisChange") + .field("added", &self.added.len()) + .field("removed", &self.removed.len()) + .finish() + } +} + +pub struct LibraryData { + root_id: SourceRootId, + root_change: RootChange, + symbol_index: SymbolIndex, +} + +impl fmt::Debug for LibraryData { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("LibraryData") + .field("root_id", &self.root_id) + .field("root_change", &self.root_change) + .field("n_symbols", &self.symbol_index.len()) + .finish() + } +} + +impl LibraryData { + pub fn prepare( + root_id: SourceRootId, + files: Vec<(FileId, RelativePathBuf, Arc)>, + ) -> LibraryData { + #[cfg(not(feature = "wasm"))] + let iter = files.par_iter(); + #[cfg(feature = "wasm")] + let iter = files.iter(); + + let symbol_index = SymbolIndex::for_files(iter.map(|(file_id, _, text)| { + let parse = SourceFile::parse(text); + (*file_id, parse) + })); + let mut root_change = RootChange::default(); + root_change.added = files + .into_iter() + .map(|(file_id, path, text)| AddFile { file_id, path, text }) + .collect(); + LibraryData { root_id, root_change, symbol_index } + } +} + +const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); + +impl RootDatabase { + pub(crate) fn apply_change(&mut self, change: AnalysisChange) { + let _p = profile("RootDatabase::apply_change"); + log::info!("apply_change {:?}", change); + { + let _p = profile("RootDatabase::apply_change/cancellation"); + self.salsa_runtime_mut().synthetic_write(Durability::LOW); + } + if !change.new_roots.is_empty() { + let mut local_roots = Vec::clone(&self.local_roots()); + for (root_id, is_local) in change.new_roots { + let root = if is_local { SourceRoot::new() } else { SourceRoot::new_library() }; + let durability = durability(&root); + self.set_source_root_with_durability(root_id, Arc::new(root), durability); + if is_local { + local_roots.push(root_id); + } + } + self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); + } + + for (root_id, root_change) in change.roots_changed { + self.apply_root_change(root_id, root_change); + } + for (file_id, text) in change.files_changed { + let source_root_id = self.file_source_root(file_id); + let source_root = self.source_root(source_root_id); + let durability = durability(&source_root); + self.set_file_text_with_durability(file_id, text, durability) + } + if !change.libraries_added.is_empty() { + let mut libraries = Vec::clone(&self.library_roots()); + for library in change.libraries_added { + libraries.push(library.root_id); + self.set_source_root_with_durability( + library.root_id, + Default::default(), + Durability::HIGH, + ); + self.set_library_symbols_with_durability( + library.root_id, + Arc::new(library.symbol_index), + Durability::HIGH, + ); + self.apply_root_change(library.root_id, library.root_change); + } + self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH); + } + if let Some(crate_graph) = change.crate_graph { + self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) + } + + Arc::make_mut(&mut self.debug_data).merge(change.debug_data) + } + + fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { + let mut source_root = SourceRoot::clone(&self.source_root(root_id)); + let durability = durability(&source_root); + for add_file in root_change.added { + self.set_file_text_with_durability(add_file.file_id, add_file.text, durability); + self.set_file_relative_path_with_durability( + add_file.file_id, + add_file.path.clone(), + durability, + ); + self.set_file_source_root_with_durability(add_file.file_id, root_id, durability); + source_root.insert_file(add_file.path, add_file.file_id); + } + for remove_file in root_change.removed { + self.set_file_text_with_durability(remove_file.file_id, Default::default(), durability); + source_root.remove_file(&remove_file.path); + } + self.set_source_root_with_durability(root_id, Arc::new(source_root), durability); + } + + pub(crate) fn maybe_collect_garbage(&mut self) { + if cfg!(feature = "wasm") { + return; + } + + if self.last_gc_check.elapsed() > GC_COOLDOWN { + self.last_gc_check = crate::wasm_shims::Instant::now(); + } + } + + pub(crate) fn collect_garbage(&mut self) { + if cfg!(feature = "wasm") { + return; + } + + let _p = profile("RootDatabase::collect_garbage"); + self.last_gc = crate::wasm_shims::Instant::now(); + + let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); + + self.query(ra_db::ParseQuery).sweep(sweep); + self.query(hir::db::ParseMacroQuery).sweep(sweep); + + // Macros do take significant space, but less then the syntax trees + // self.query(hir::db::MacroDefQuery).sweep(sweep); + // self.query(hir::db::MacroArgQuery).sweep(sweep); + // self.query(hir::db::MacroExpandQuery).sweep(sweep); + + self.query(hir::db::AstIdMapQuery).sweep(sweep); + + self.query(hir::db::RawItemsWithSourceMapQuery).sweep(sweep); + self.query(hir::db::BodyWithSourceMapQuery).sweep(sweep); + + self.query(hir::db::ExprScopesQuery).sweep(sweep); + self.query(hir::db::InferQuery).sweep(sweep); + self.query(hir::db::BodyQuery).sweep(sweep); + } + + pub(crate) fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> { + let mut acc: Vec<(String, Bytes)> = vec![]; + let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); + macro_rules! sweep_each_query { + ($($q:path)*) => {$( + let before = memory_usage().allocated; + self.query($q).sweep(sweep); + let after = memory_usage().allocated; + let q: $q = Default::default(); + let name = format!("{:?}", q); + acc.push((name, before - after)); + + let before = memory_usage().allocated; + self.query($q).sweep(sweep.discard_everything()); + let after = memory_usage().allocated; + let q: $q = Default::default(); + let name = format!("{:?} (deps)", q); + acc.push((name, before - after)); + )*} + } + sweep_each_query![ + ra_db::ParseQuery + ra_db::SourceRootCratesQuery + hir::db::AstIdMapQuery + hir::db::ParseMacroQuery + hir::db::MacroDefQuery + hir::db::MacroArgQuery + hir::db::MacroExpandQuery + hir::db::StructDataQuery + hir::db::EnumDataQuery + hir::db::TraitDataQuery + hir::db::RawItemsWithSourceMapQuery + hir::db::RawItemsQuery + hir::db::CrateDefMapQuery + hir::db::GenericParamsQuery + hir::db::FunctionDataQuery + hir::db::TypeAliasDataQuery + hir::db::ConstDataQuery + hir::db::StaticDataQuery + hir::db::ModuleLangItemsQuery + hir::db::CrateLangItemsQuery + hir::db::LangItemQuery + hir::db::DocumentationQuery + hir::db::ExprScopesQuery + hir::db::InferQuery + hir::db::TyQuery + hir::db::ValueTyQuery + hir::db::FieldTypesQuery + hir::db::CallableItemSignatureQuery + hir::db::GenericPredicatesQuery + hir::db::GenericDefaultsQuery + hir::db::BodyWithSourceMapQuery + hir::db::BodyQuery + hir::db::ImplsInCrateQuery + hir::db::ImplsForTraitQuery + hir::db::AssociatedTyDataQuery + hir::db::TraitDatumQuery + hir::db::StructDatumQuery + hir::db::ImplDatumQuery + hir::db::ImplDataQuery + hir::db::TraitSolveQuery + ]; + acc.sort_by_key(|it| std::cmp::Reverse(it.1)); + acc + } +} + +fn durability(source_root: &SourceRoot) -> Durability { + if source_root.is_library { + Durability::HIGH + } else { + Durability::LOW + } +} diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs new file mode 100644 index 000000000..abe1f36ce --- /dev/null +++ b/crates/ra_ide/src/completion.rs @@ -0,0 +1,77 @@ +//! FIXME: write short doc here + +mod completion_item; +mod completion_context; +mod presentation; + +mod complete_dot; +mod complete_record_literal; +mod complete_record_pattern; +mod complete_pattern; +mod complete_fn_param; +mod complete_keyword; +mod complete_snippet; +mod complete_path; +mod complete_scope; +mod complete_postfix; +mod complete_macro_in_item_position; + +use ra_db::SourceDatabase; + +#[cfg(test)] +use crate::completion::completion_item::do_completion; +use crate::{ + completion::{ + completion_context::CompletionContext, + completion_item::{CompletionKind, Completions}, + }, + db, FilePosition, +}; + +pub use crate::completion::completion_item::{ + CompletionItem, CompletionItemKind, InsertTextFormat, +}; + +/// Main entry point for completion. We run completion as a two-phase process. +/// +/// First, we look at the position and collect a so-called `CompletionContext. +/// This is a somewhat messy process, because, during completion, syntax tree is +/// incomplete and can look really weird. +/// +/// Once the context is collected, we run a series of completion routines which +/// look at the context and produce completion items. One subtlety about this +/// phase is that completion engine should not filter by the substring which is +/// already present, it should give all possible variants for the identifier at +/// the caret. In other words, for +/// +/// ```no-run +/// fn f() { +/// let foo = 92; +/// let _ = bar<|> +/// } +/// ``` +/// +/// `foo` *should* be present among the completion variants. Filtering by +/// identifier prefix/fuzzy match should be done higher in the stack, together +/// with ordering of completions (currently this is done by the client). +pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Option { + let original_parse = db.parse(position.file_id); + let ctx = CompletionContext::new(db, &original_parse, position)?; + + let mut acc = Completions::default(); + + complete_fn_param::complete_fn_param(&mut acc, &ctx); + complete_keyword::complete_expr_keyword(&mut acc, &ctx); + complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); + complete_snippet::complete_expr_snippet(&mut acc, &ctx); + complete_snippet::complete_item_snippet(&mut acc, &ctx); + complete_path::complete_path(&mut acc, &ctx); + complete_scope::complete_scope(&mut acc, &ctx); + complete_dot::complete_dot(&mut acc, &ctx); + complete_record_literal::complete_record_literal(&mut acc, &ctx); + complete_record_pattern::complete_record_pattern(&mut acc, &ctx); + complete_pattern::complete_pattern(&mut acc, &ctx); + complete_postfix::complete_postfix(&mut acc, &ctx); + complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); + Some(acc) +} diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs new file mode 100644 index 000000000..b6fe48627 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_dot.rs @@ -0,0 +1,456 @@ +//! FIXME: write short doc here + +use hir::Type; + +use crate::completion::completion_item::CompletionKind; +use crate::{ + completion::{completion_context::CompletionContext, completion_item::Completions}, + CompletionItem, +}; +use rustc_hash::FxHashSet; + +/// Complete dot accesses, i.e. fields or methods (and .await syntax). +pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { + let dot_receiver = match &ctx.dot_receiver { + Some(expr) => expr, + _ => return, + }; + + let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { + Some(ty) => ty, + _ => return, + }; + + if !ctx.is_call { + complete_fields(acc, ctx, &receiver_ty); + } + complete_methods(acc, ctx, &receiver_ty); + + // Suggest .await syntax for types that implement Future trait + if ctx.analyzer.impls_future(ctx.db, receiver_ty.into_ty()) { + CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") + .detail("expr.await") + .insert_text("await") + .add_to(acc); + } +} + +fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { + for receiver in receiver.autoderef(ctx.db) { + for (field, ty) in receiver.fields(ctx.db) { + acc.add_field(ctx, field, &ty); + } + for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { + acc.add_tuple_field(ctx, i, &ty); + } + } +} + +fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { + let mut seen_methods = FxHashSet::default(); + ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| { + if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) { + acc.add_function(ctx, func); + } + None::<()> + }); +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_ref_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn test_struct_field_completion() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A { the_field: u32 } + fn foo(a: A) { + a.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_field", + source_range: [94; 94), + delete: [94; 94), + insert: "the_field", + kind: Field, + detail: "u32", + }, + ] + "### + ); + } + + #[test] + fn test_struct_field_completion_self() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A { + /// This is the_field + the_field: (u32,) + } + impl A { + fn foo(self) { + self.<|> + } + } + ", + ), + @r###" + [ + CompletionItem { + label: "foo()", + source_range: [187; 187), + delete: [187; 187), + insert: "foo()$0", + kind: Method, + lookup: "foo", + detail: "fn foo(self)", + }, + CompletionItem { + label: "the_field", + source_range: [187; 187), + delete: [187; 187), + insert: "the_field", + kind: Field, + detail: "(u32,)", + documentation: Documentation( + "This is the_field", + ), + }, + ] + "### + ); + } + + #[test] + fn test_struct_field_completion_autoderef() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A { the_field: (u32, i32) } + impl A { + fn foo(&self) { + self.<|> + } + } + ", + ), + @r###" + [ + CompletionItem { + label: "foo()", + source_range: [126; 126), + delete: [126; 126), + insert: "foo()$0", + kind: Method, + lookup: "foo", + detail: "fn foo(&self)", + }, + CompletionItem { + label: "the_field", + source_range: [126; 126), + delete: [126; 126), + insert: "the_field", + kind: Field, + detail: "(u32, i32)", + }, + ] + "### + ); + } + + #[test] + fn test_no_struct_field_completion_for_method_call() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A { the_field: u32 } + fn foo(a: A) { + a.<|>() + } + ", + ), + @"[]" + ); + } + + #[test] + fn test_method_completion() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A {} + impl A { + fn the_method(&self) {} + } + fn foo(a: A) { + a.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_method()", + source_range: [144; 144), + delete: [144; 144), + insert: "the_method()$0", + kind: Method, + lookup: "the_method", + detail: "fn the_method(&self)", + }, + ] + "### + ); + } + + #[test] + fn test_trait_method_completion() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A {} + trait Trait { fn the_method(&self); } + impl Trait for A {} + fn foo(a: A) { + a.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_method()", + source_range: [151; 151), + delete: [151; 151), + insert: "the_method()$0", + kind: Method, + lookup: "the_method", + detail: "fn the_method(&self)", + }, + ] + "### + ); + } + + #[test] + fn test_trait_method_completion_deduplicated() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A {} + trait Trait { fn the_method(&self); } + impl Trait for T {} + fn foo(a: &A) { + a.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_method()", + source_range: [155; 155), + delete: [155; 155), + insert: "the_method()$0", + kind: Method, + lookup: "the_method", + detail: "fn the_method(&self)", + }, + ] + "### + ); + } + + #[test] + fn test_no_non_self_method() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A {} + impl A { + fn the_method() {} + } + fn foo(a: A) { + a.<|> + } + ", + ), + @"[]" + ); + } + + #[test] + fn test_method_attr_filtering() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A {} + impl A { + #[inline] + fn the_method(&self) { + let x = 1; + let y = 2; + } + } + fn foo(a: A) { + a.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "the_method()", + source_range: [249; 249), + delete: [249; 249), + insert: "the_method()$0", + kind: Method, + lookup: "the_method", + detail: "fn the_method(&self)", + }, + ] + "### + ); + } + + #[test] + fn test_tuple_field_completion() { + assert_debug_snapshot!( + do_ref_completion( + r" + fn foo() { + let b = (0, 3.14); + b.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "0", + source_range: [75; 75), + delete: [75; 75), + insert: "0", + kind: Field, + detail: "i32", + }, + CompletionItem { + label: "1", + source_range: [75; 75), + delete: [75; 75), + insert: "1", + kind: Field, + detail: "f64", + }, + ] + "### + ); + } + + #[test] + fn test_tuple_field_inference() { + assert_debug_snapshot!( + do_ref_completion( + r" + pub struct S; + impl S { + pub fn blah(&self) {} + } + + struct T(S); + + impl T { + fn foo(&self) { + // FIXME: This doesn't work without the trailing `a` as `0.` is a float + self.0.a<|> + } + } + ", + ), + @r###" + [ + CompletionItem { + label: "blah()", + source_range: [299; 300), + delete: [299; 300), + insert: "blah()$0", + kind: Method, + lookup: "blah", + detail: "pub fn blah(&self)", + }, + ] + "### + ); + } + + #[test] + fn test_completion_works_in_consts() { + assert_debug_snapshot!( + do_ref_completion( + r" + struct A { the_field: u32 } + const X: u32 = { + A { the_field: 92 }.<|> + }; + ", + ), + @r###" + [ + CompletionItem { + label: "the_field", + source_range: [106; 106), + delete: [106; 106), + insert: "the_field", + kind: Field, + detail: "u32", + }, + ] + "### + ); + } + + #[test] + fn test_completion_await_impls_future() { + assert_debug_snapshot!( + do_completion( + r###" + //- /main.rs + use std::future::*; + struct A {} + impl Future for A {} + fn foo(a: A) { + a.<|> + } + + //- /std/lib.rs + pub mod future { + pub trait Future {} + } + "###, CompletionKind::Keyword), + @r###" + [ + CompletionItem { + label: "await", + source_range: [74; 74), + delete: [74; 74), + insert: "await", + detail: "expr.await", + }, + ] + "### + ) + } +} diff --git a/crates/ra_ide/src/completion/complete_fn_param.rs b/crates/ra_ide/src/completion/complete_fn_param.rs new file mode 100644 index 000000000..502458706 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_fn_param.rs @@ -0,0 +1,136 @@ +//! FIXME: write short doc here + +use ra_syntax::{ast, match_ast, AstNode}; +use rustc_hash::FxHashMap; + +use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; + +/// Complete repeated parameters, both name and type. For example, if all +/// functions in a file have a `spam: &mut Spam` parameter, a completion with +/// `spam: &mut Spam` insert text/label and `spam` lookup string will be +/// suggested. +pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_param { + return; + } + + let mut params = FxHashMap::default(); + for node in ctx.token.parent().ancestors() { + match_ast! { + match node { + ast::SourceFile(it) => { process(it, &mut params) }, + ast::ItemList(it) => { process(it, &mut params) }, + _ => (), + } + } + } + params + .into_iter() + .filter_map(|(label, (count, param))| { + let lookup = param.pat()?.syntax().text().to_string(); + if count < 2 { + None + } else { + Some((label, lookup)) + } + }) + .for_each(|(label, lookup)| { + CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) + .lookup_by(lookup) + .add_to(acc) + }); + + fn process(node: N, params: &mut FxHashMap) { + node.functions().filter_map(|it| it.param_list()).flat_map(|it| it.params()).for_each( + |param| { + let text = param.syntax().text().to_string(); + params.entry(text).or_insert((0, param)).0 += 1; + }, + ) + } +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_magic_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Magic) + } + + #[test] + fn test_param_completion_last_param() { + assert_debug_snapshot!( + do_magic_completion( + r" + fn foo(file_id: FileId) {} + fn bar(file_id: FileId) {} + fn baz(file<|>) {} + ", + ), + @r###" + [ + CompletionItem { + label: "file_id: FileId", + source_range: [110; 114), + delete: [110; 114), + insert: "file_id: FileId", + lookup: "file_id", + }, + ] + "### + ); + } + + #[test] + fn test_param_completion_nth_param() { + assert_debug_snapshot!( + do_magic_completion( + r" + fn foo(file_id: FileId) {} + fn bar(file_id: FileId) {} + fn baz(file<|>, x: i32) {} + ", + ), + @r###" + [ + CompletionItem { + label: "file_id: FileId", + source_range: [110; 114), + delete: [110; 114), + insert: "file_id: FileId", + lookup: "file_id", + }, + ] + "### + ); + } + + #[test] + fn test_param_completion_trait_param() { + assert_debug_snapshot!( + do_magic_completion( + r" + pub(crate) trait SourceRoot { + pub fn contains(&self, file_id: FileId) -> bool; + pub fn module_map(&self) -> &ModuleMap; + pub fn lines(&self, file_id: FileId) -> &LineIndex; + pub fn syntax(&self, file<|>) + } + ", + ), + @r###" + [ + CompletionItem { + label: "file_id: FileId", + source_range: [289; 293), + delete: [289; 293), + insert: "file_id: FileId", + lookup: "file_id", + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/completion/complete_keyword.rs b/crates/ra_ide/src/completion/complete_keyword.rs new file mode 100644 index 000000000..eb7cd9ac2 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_keyword.rs @@ -0,0 +1,781 @@ +//! FIXME: write short doc here + +use ra_syntax::{ + ast::{self, LoopBodyOwner}, + match_ast, AstNode, + SyntaxKind::*, + SyntaxToken, +}; + +use crate::completion::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, +}; + +pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { + // complete keyword "crate" in use stmt + let source_range = ctx.source_range(); + match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { + (Some(_), None) => { + CompletionItem::new(CompletionKind::Keyword, source_range, "crate") + .kind(CompletionItemKind::Keyword) + .insert_text("crate::") + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); + } + (Some(_), Some(_)) => { + CompletionItem::new(CompletionKind::Keyword, source_range, "self") + .kind(CompletionItemKind::Keyword) + .add_to(acc); + CompletionItem::new(CompletionKind::Keyword, source_range, "super") + .kind(CompletionItemKind::Keyword) + .insert_text("super::") + .add_to(acc); + } + _ => {} + } +} + +fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { + CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) + .kind(CompletionItemKind::Keyword) + .insert_snippet(snippet) + .build() +} + +pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_trivial_path { + return; + } + + let fn_def = match &ctx.function_syntax { + Some(it) => it, + None => return, + }; + acc.add(keyword(ctx, "if", "if $0 {}")); + acc.add(keyword(ctx, "match", "match $0 {}")); + acc.add(keyword(ctx, "while", "while $0 {}")); + acc.add(keyword(ctx, "loop", "loop {$0}")); + + if ctx.after_if { + acc.add(keyword(ctx, "else", "else {$0}")); + acc.add(keyword(ctx, "else if", "else if $0 {}")); + } + if is_in_loop_body(&ctx.token) { + if ctx.can_be_stmt { + acc.add(keyword(ctx, "continue", "continue;")); + acc.add(keyword(ctx, "break", "break;")); + } else { + acc.add(keyword(ctx, "continue", "continue")); + acc.add(keyword(ctx, "break", "break")); + } + } + acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); +} + +fn is_in_loop_body(leaf: &SyntaxToken) -> bool { + for node in leaf.parent().ancestors() { + if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { + break; + } + let loop_body = match_ast! { + match node { + ast::ForExpr(it) => { it.loop_body() }, + ast::WhileExpr(it) => { it.loop_body() }, + ast::LoopExpr(it) => { it.loop_body() }, + _ => None, + } + }; + if let Some(body) = loop_body { + if leaf.text_range().is_subrange(&body.syntax().text_range()) { + return true; + } + } + } + false +} + +fn complete_return( + ctx: &CompletionContext, + fn_def: &ast::FnDef, + can_be_stmt: bool, +) -> Option { + let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { + (true, true) => "return $0;", + (true, false) => "return;", + (false, true) => "return $0", + (false, false) => "return", + }; + Some(keyword(ctx, "return", snip)) +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_keyword_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Keyword) + } + + #[test] + fn completes_keywords_in_use_stmt() { + assert_debug_snapshot!( + do_keyword_completion( + r" + use <|> + ", + ), + @r###" + [ + CompletionItem { + label: "crate", + source_range: [21; 21), + delete: [21; 21), + insert: "crate::", + kind: Keyword, + }, + CompletionItem { + label: "self", + source_range: [21; 21), + delete: [21; 21), + insert: "self", + kind: Keyword, + }, + CompletionItem { + label: "super", + source_range: [21; 21), + delete: [21; 21), + insert: "super::", + kind: Keyword, + }, + ] + "### + ); + + assert_debug_snapshot!( + do_keyword_completion( + r" + use a::<|> + ", + ), + @r###" + [ + CompletionItem { + label: "self", + source_range: [24; 24), + delete: [24; 24), + insert: "self", + kind: Keyword, + }, + CompletionItem { + label: "super", + source_range: [24; 24), + delete: [24; 24), + insert: "super::", + kind: Keyword, + }, + ] + "### + ); + + assert_debug_snapshot!( + do_keyword_completion( + r" + use a::{b, <|>} + ", + ), + @r###" + [ + CompletionItem { + label: "self", + source_range: [28; 28), + delete: [28; 28), + insert: "self", + kind: Keyword, + }, + CompletionItem { + label: "super", + source_range: [28; 28), + delete: [28; 28), + insert: "super::", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn completes_various_keywords_in_function() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() { + <|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [49; 49), + delete: [49; 49), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [49; 49), + delete: [49; 49), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [49; 49), + delete: [49; 49), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [49; 49), + delete: [49; 49), + insert: "return;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [49; 49), + delete: [49; 49), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn completes_else_after_if() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() { + if true { + () + } <|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "else", + source_range: [108; 108), + delete: [108; 108), + insert: "else {$0}", + kind: Keyword, + }, + CompletionItem { + label: "else if", + source_range: [108; 108), + delete: [108; 108), + insert: "else if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "if", + source_range: [108; 108), + delete: [108; 108), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [108; 108), + delete: [108; 108), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [108; 108), + delete: [108; 108), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [108; 108), + delete: [108; 108), + insert: "return;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [108; 108), + delete: [108; 108), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn test_completion_return_value() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() -> i32 { + <|> + 92 + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [56; 56), + delete: [56; 56), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [56; 56), + delete: [56; 56), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [56; 56), + delete: [56; 56), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [56; 56), + delete: [56; 56), + insert: "return $0;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [56; 56), + delete: [56; 56), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() { + <|> + 92 + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [49; 49), + delete: [49; 49), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [49; 49), + delete: [49; 49), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [49; 49), + delete: [49; 49), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [49; 49), + delete: [49; 49), + insert: "return;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [49; 49), + delete: [49; 49), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn dont_add_semi_after_return_if_not_a_statement() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() -> i32 { + match () { + () => <|> + } + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [97; 97), + delete: [97; 97), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [97; 97), + delete: [97; 97), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [97; 97), + delete: [97; 97), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [97; 97), + delete: [97; 97), + insert: "return $0", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [97; 97), + delete: [97; 97), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn last_return_in_block_has_semi() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() -> i32 { + if condition { + <|> + } + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [95; 95), + delete: [95; 95), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [95; 95), + delete: [95; 95), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [95; 95), + delete: [95; 95), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [95; 95), + delete: [95; 95), + insert: "return $0;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [95; 95), + delete: [95; 95), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() -> i32 { + if condition { + <|> + } + let x = 92; + x + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [95; 95), + delete: [95; 95), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [95; 95), + delete: [95; 95), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [95; 95), + delete: [95; 95), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [95; 95), + delete: [95; 95), + insert: "return $0;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [95; 95), + delete: [95; 95), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn completes_break_and_continue_in_loops() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() -> i32 { + loop { <|> } + } + ", + ), + @r###" + [ + CompletionItem { + label: "break", + source_range: [63; 63), + delete: [63; 63), + insert: "break;", + kind: Keyword, + }, + CompletionItem { + label: "continue", + source_range: [63; 63), + delete: [63; 63), + insert: "continue;", + kind: Keyword, + }, + CompletionItem { + label: "if", + source_range: [63; 63), + delete: [63; 63), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [63; 63), + delete: [63; 63), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [63; 63), + delete: [63; 63), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [63; 63), + delete: [63; 63), + insert: "return $0;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [63; 63), + delete: [63; 63), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + + // No completion: lambda isolates control flow + assert_debug_snapshot!( + do_keyword_completion( + r" + fn quux() -> i32 { + loop { || { <|> } } + } + ", + ), + @r###" + [ + CompletionItem { + label: "if", + source_range: [68; 68), + delete: [68; 68), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [68; 68), + delete: [68; 68), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [68; 68), + delete: [68; 68), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [68; 68), + delete: [68; 68), + insert: "return $0;", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [68; 68), + delete: [68; 68), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ); + } + + #[test] + fn no_semi_after_break_continue_in_expr() { + assert_debug_snapshot!( + do_keyword_completion( + r" + fn f() { + loop { + match () { + () => br<|> + } + } + } + ", + ), + @r###" + [ + CompletionItem { + label: "break", + source_range: [122; 124), + delete: [122; 124), + insert: "break", + kind: Keyword, + }, + CompletionItem { + label: "continue", + source_range: [122; 124), + delete: [122; 124), + insert: "continue", + kind: Keyword, + }, + CompletionItem { + label: "if", + source_range: [122; 124), + delete: [122; 124), + insert: "if $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "loop", + source_range: [122; 124), + delete: [122; 124), + insert: "loop {$0}", + kind: Keyword, + }, + CompletionItem { + label: "match", + source_range: [122; 124), + delete: [122; 124), + insert: "match $0 {}", + kind: Keyword, + }, + CompletionItem { + label: "return", + source_range: [122; 124), + delete: [122; 124), + insert: "return", + kind: Keyword, + }, + CompletionItem { + label: "while", + source_range: [122; 124), + delete: [122; 124), + insert: "while $0 {}", + kind: Keyword, + }, + ] + "### + ) + } +} diff --git a/crates/ra_ide/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs new file mode 100644 index 000000000..faadd1e3f --- /dev/null +++ b/crates/ra_ide/src/completion/complete_macro_in_item_position.rs @@ -0,0 +1,143 @@ +//! FIXME: write short doc here + +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { + // Show only macros in top level. + if ctx.is_new_item { + ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { + if let hir::ScopeDef::MacroDef(mac) = res { + acc.add_macro(ctx, Some(name.to_string()), mac); + } + }) + } +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_reference_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn completes_macros_as_item() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + macro_rules! foo { + () => {} + } + + fn foo() {} + + <|> + " + ), + @r###" + [ + CompletionItem { + label: "foo!", + source_range: [46; 46), + delete: [46; 46), + insert: "foo!($0)", + kind: Macro, + detail: "macro_rules! foo", + }, + ] + "### + ); + } + + #[test] + fn completes_vec_macros_with_square_brackets() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + /// Creates a [`Vec`] containing the arguments. + /// + /// - Create a [`Vec`] containing a given list of elements: + /// + /// ``` + /// let v = vec![1, 2, 3]; + /// assert_eq!(v[0], 1); + /// assert_eq!(v[1], 2); + /// assert_eq!(v[2], 3); + /// ``` + macro_rules! vec { + () => {} + } + + fn foo() {} + + <|> + " + ), + @r###" + [ + CompletionItem { + label: "vec!", + source_range: [280; 280), + delete: [280; 280), + insert: "vec![$0]", + kind: Macro, + detail: "macro_rules! vec", + documentation: Documentation( + "Creates a [`Vec`] containing the arguments.\n\n- Create a [`Vec`] containing a given list of elements:\n\n```\nlet v = vec![1, 2, 3];\nassert_eq!(v[0], 1);\nassert_eq!(v[1], 2);\nassert_eq!(v[2], 3);\n```", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_macros_braces_guessing() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + /// Foo + /// + /// Not call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`. + /// Call as `let _=foo! { hello world };` + macro_rules! foo { + () => {} + } + + fn main() { + <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo!", + source_range: [163; 163), + delete: [163; 163), + insert: "foo! {$0}", + kind: Macro, + detail: "macro_rules! foo", + documentation: Documentation( + "Foo\n\nNot call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.\nCall as `let _=foo! { hello world };`", + ), + }, + CompletionItem { + label: "main()", + source_range: [163; 163), + delete: [163; 163), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/completion/complete_path.rs b/crates/ra_ide/src/completion/complete_path.rs new file mode 100644 index 000000000..89e0009a1 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_path.rs @@ -0,0 +1,785 @@ +//! FIXME: write short doc here + +use hir::{Adt, Either, HasSource, PathResolution}; +use ra_syntax::AstNode; +use test_utils::tested_by; + +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { + let path = match &ctx.path_prefix { + Some(path) => path.clone(), + _ => return, + }; + let def = match ctx.analyzer.resolve_hir_path(ctx.db, &path) { + Some(PathResolution::Def(def)) => def, + _ => return, + }; + match def { + hir::ModuleDef::Module(module) => { + let module_scope = module.scope(ctx.db); + for (name, def, import) in module_scope { + if let hir::ScopeDef::ModuleDef(hir::ModuleDef::BuiltinType(..)) = def { + if ctx.use_item_syntax.is_some() { + tested_by!(dont_complete_primitive_in_use); + continue; + } + } + if Some(module) == ctx.module { + if let Some(import) = import { + if let Either::A(use_tree) = import.source(ctx.db).value { + if use_tree.syntax().text_range().contains_inclusive(ctx.offset) { + // for `use self::foo<|>`, don't suggest `foo` as a completion + tested_by!(dont_complete_current_use); + continue; + } + } + } + } + acc.add_resolution(ctx, name.to_string(), &def); + } + } + hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => { + if let hir::ModuleDef::Adt(Adt::Enum(e)) = def { + for variant in e.variants(ctx.db) { + acc.add_enum_variant(ctx, variant); + } + } + let ty = match def { + hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), + hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), + _ => unreachable!(), + }; + ctx.analyzer.iterate_path_candidates(ctx.db, &ty, None, |_ty, item| { + match item { + hir::AssocItem::Function(func) => { + if !func.has_self_param(ctx.db) { + acc.add_function(ctx, func); + } + } + hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + None::<()> + }); + // Iterate assoc types separately + // FIXME: complete T::AssocType + let krate = ctx.module.map(|m| m.krate()); + if let Some(krate) = krate { + ty.iterate_impl_items(ctx.db, krate, |item| { + match item { + hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {} + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + None::<()> + }); + } + } + hir::ModuleDef::Trait(t) => { + for item in t.items(ctx.db) { + match item { + hir::AssocItem::Function(func) => { + if !func.has_self_param(ctx.db) { + acc.add_function(ctx, func); + } + } + hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + } + } + _ => {} + }; +} + +#[cfg(test)] +mod tests { + use test_utils::covers; + + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_reference_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn dont_complete_current_use() { + covers!(dont_complete_current_use); + let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); + assert!(completions.is_empty()); + } + + #[test] + fn dont_complete_current_use_in_braces_with_glob() { + let completions = do_completion( + r" + mod foo { pub struct S; } + use self::{foo::*, bar<|>}; + ", + CompletionKind::Reference, + ); + assert_eq!(completions.len(), 2); + } + + #[test] + fn dont_complete_primitive_in_use() { + covers!(dont_complete_primitive_in_use); + let completions = do_completion(r"use self::<|>;", CompletionKind::BuiltinType); + assert!(completions.is_empty()); + } + + #[test] + fn completes_primitives() { + let completions = + do_completion(r"fn main() { let _: <|> = 92; }", CompletionKind::BuiltinType); + assert_eq!(completions.len(), 17); + } + + #[test] + fn completes_mod_with_docs() { + assert_debug_snapshot!( + do_reference_completion( + r" + use self::my<|>; + + /// Some simple + /// docs describing `mod my`. + mod my { + struct Bar; + } + " + ), + @r###" + [ + CompletionItem { + label: "my", + source_range: [27; 29), + delete: [27; 29), + insert: "my", + kind: Module, + documentation: Documentation( + "Some simple\ndocs describing `mod my`.", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_use_item_starting_with_self() { + assert_debug_snapshot!( + do_reference_completion( + r" + use self::m::<|>; + + mod m { + struct Bar; + } + " + ), + @r###" + [ + CompletionItem { + label: "Bar", + source_range: [30; 30), + delete: [30; 30), + insert: "Bar", + kind: Struct, + }, + ] + "### + ); + } + + #[test] + fn completes_use_item_starting_with_crate() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + mod foo; + struct Spam; + //- /foo.rs + use crate::Sp<|> + " + ), + @r###" + [ + CompletionItem { + label: "Spam", + source_range: [11; 13), + delete: [11; 13), + insert: "Spam", + kind: Struct, + }, + CompletionItem { + label: "foo", + source_range: [11; 13), + delete: [11; 13), + insert: "foo", + kind: Module, + }, + ] + "### + ); + } + + #[test] + fn completes_nested_use_tree() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + mod foo; + struct Spam; + //- /foo.rs + use crate::{Sp<|>}; + " + ), + @r###" + [ + CompletionItem { + label: "Spam", + source_range: [12; 14), + delete: [12; 14), + insert: "Spam", + kind: Struct, + }, + CompletionItem { + label: "foo", + source_range: [12; 14), + delete: [12; 14), + insert: "foo", + kind: Module, + }, + ] + "### + ); + } + + #[test] + fn completes_deeply_nested_use_tree() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + mod foo; + pub mod bar { + pub mod baz { + pub struct Spam; + } + } + //- /foo.rs + use crate::{bar::{baz::Sp<|>}}; + " + ), + @r###" + [ + CompletionItem { + label: "Spam", + source_range: [23; 25), + delete: [23; 25), + insert: "Spam", + kind: Struct, + }, + ] + "### + ); + } + + #[test] + fn completes_enum_variant() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + /// An enum + enum E { + /// Foo Variant + Foo, + /// Bar Variant with i32 + Bar(i32) + } + fn foo() { let _ = E::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "Bar", + source_range: [116; 116), + delete: [116; 116), + insert: "Bar", + kind: EnumVariant, + detail: "(i32)", + documentation: Documentation( + "Bar Variant with i32", + ), + }, + CompletionItem { + label: "Foo", + source_range: [116; 116), + delete: [116; 116), + insert: "Foo", + kind: EnumVariant, + detail: "()", + documentation: Documentation( + "Foo Variant", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_enum_variant_with_details() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + struct S { field: u32 } + /// An enum + enum E { + /// Foo Variant (empty) + Foo, + /// Bar Variant with i32 and u32 + Bar(i32, u32), + /// + S(S), + } + fn foo() { let _ = E::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "Bar", + source_range: [180; 180), + delete: [180; 180), + insert: "Bar", + kind: EnumVariant, + detail: "(i32, u32)", + documentation: Documentation( + "Bar Variant with i32 and u32", + ), + }, + CompletionItem { + label: "Foo", + source_range: [180; 180), + delete: [180; 180), + insert: "Foo", + kind: EnumVariant, + detail: "()", + documentation: Documentation( + "Foo Variant (empty)", + ), + }, + CompletionItem { + label: "S", + source_range: [180; 180), + delete: [180; 180), + insert: "S", + kind: EnumVariant, + detail: "(S)", + documentation: Documentation( + "", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_struct_associated_method() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + /// A Struct + struct S; + + impl S { + /// An associated method + fn m() { } + } + + fn foo() { let _ = S::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "m()", + source_range: [100; 100), + delete: [100; 100), + insert: "m()$0", + kind: Function, + lookup: "m", + detail: "fn m()", + documentation: Documentation( + "An associated method", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_struct_associated_const() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + /// A Struct + struct S; + + impl S { + /// An associated const + const C: i32 = 42; + } + + fn foo() { let _ = S::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "C", + source_range: [107; 107), + delete: [107; 107), + insert: "C", + kind: Const, + detail: "const C: i32 = 42;", + documentation: Documentation( + "An associated const", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_struct_associated_type() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + /// A Struct + struct S; + + impl S { + /// An associated type + type T = i32; + } + + fn foo() { let _ = S::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "T", + source_range: [101; 101), + delete: [101; 101), + insert: "T", + kind: TypeAlias, + detail: "type T = i32;", + documentation: Documentation( + "An associated type", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_enum_associated_method() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + /// An enum + enum S {}; + + impl S { + /// An associated method + fn m() { } + } + + fn foo() { let _ = S::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "m()", + source_range: [100; 100), + delete: [100; 100), + insert: "m()$0", + kind: Function, + lookup: "m", + detail: "fn m()", + documentation: Documentation( + "An associated method", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_union_associated_method() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + /// A union + union U {}; + + impl U { + /// An associated method + fn m() { } + } + + fn foo() { let _ = U::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "m()", + source_range: [101; 101), + delete: [101; 101), + insert: "m()$0", + kind: Function, + lookup: "m", + detail: "fn m()", + documentation: Documentation( + "An associated method", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_use_paths_across_crates() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + use foo::<|>; + + //- /foo/lib.rs + pub mod bar { + pub struct S; + } + " + ), + @r###" + [ + CompletionItem { + label: "bar", + source_range: [9; 9), + delete: [9; 9), + insert: "bar", + kind: Module, + }, + ] + "### + ); + } + + #[test] + fn completes_trait_associated_method_1() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + trait Trait { + /// A trait method + fn m(); + } + + fn foo() { let _ = Trait::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "m()", + source_range: [73; 73), + delete: [73; 73), + insert: "m()$0", + kind: Function, + lookup: "m", + detail: "fn m()", + documentation: Documentation( + "A trait method", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_trait_associated_method_2() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + trait Trait { + /// A trait method + fn m(); + } + + struct S; + impl Trait for S {} + + fn foo() { let _ = S::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "m()", + source_range: [99; 99), + delete: [99; 99), + insert: "m()$0", + kind: Function, + lookup: "m", + detail: "fn m()", + documentation: Documentation( + "A trait method", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_trait_associated_method_3() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + trait Trait { + /// A trait method + fn m(); + } + + struct S; + impl Trait for S {} + + fn foo() { let _ = ::<|> } + " + ), + @r###" + [ + CompletionItem { + label: "m()", + source_range: [110; 110), + delete: [110; 110), + insert: "m()$0", + kind: Function, + lookup: "m", + detail: "fn m()", + documentation: Documentation( + "A trait method", + ), + }, + ] + "### + ); + } + + #[test] + fn completes_type_alias() { + assert_debug_snapshot!( + do_reference_completion( + " + struct S; + impl S { fn foo() {} } + type T = S; + impl T { fn bar() {} } + + fn main() { + T::<|>; + } + " + ), + @r###" + [ + CompletionItem { + label: "bar()", + source_range: [185; 185), + delete: [185; 185), + insert: "bar()$0", + kind: Function, + lookup: "bar", + detail: "fn bar()", + }, + CompletionItem { + label: "foo()", + source_range: [185; 185), + delete: [185; 185), + insert: "foo()$0", + kind: Function, + lookup: "foo", + detail: "fn foo()", + }, + ] + "### + ); + } + + #[test] + fn completes_qualified_macros() { + assert_debug_snapshot!( + do_reference_completion( + " + #[macro_export] + macro_rules! foo { + () => {} + } + + fn main() { + let _ = crate::<|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo!", + source_range: [179; 179), + delete: [179; 179), + insert: "foo!($0)", + kind: Macro, + detail: "#[macro_export]\nmacro_rules! foo", + }, + CompletionItem { + label: "main()", + source_range: [179; 179), + delete: [179; 179), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/completion/complete_pattern.rs b/crates/ra_ide/src/completion/complete_pattern.rs new file mode 100644 index 000000000..fd03b1c40 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_pattern.rs @@ -0,0 +1,89 @@ +//! FIXME: write short doc here + +use crate::completion::{CompletionContext, Completions}; + +/// Completes constats and paths in patterns. +pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_pat_binding { + return; + } + // FIXME: ideally, we should look at the type we are matching against and + // suggest variants + auto-imports + ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { + let def = match &res { + hir::ScopeDef::ModuleDef(def) => def, + _ => return, + }; + match def { + hir::ModuleDef::Adt(hir::Adt::Enum(..)) + | hir::ModuleDef::EnumVariant(..) + | hir::ModuleDef::Const(..) + | hir::ModuleDef::Module(..) => (), + _ => return, + } + acc.add_resolution(ctx, name.to_string(), &res) + }); +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn complete(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn completes_enum_variants_and_modules() { + let completions = complete( + r" + enum E { X } + use self::E::X; + const Z: E = E::X; + mod m {} + + static FOO: E = E::X; + struct Bar { f: u32 } + + fn foo() { + match E::X { + <|> + } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "E", + source_range: [246; 246), + delete: [246; 246), + insert: "E", + kind: Enum, + }, + CompletionItem { + label: "X", + source_range: [246; 246), + delete: [246; 246), + insert: "X", + kind: EnumVariant, + }, + CompletionItem { + label: "Z", + source_range: [246; 246), + delete: [246; 246), + insert: "Z", + kind: Const, + }, + CompletionItem { + label: "m", + source_range: [246; 246), + delete: [246; 246), + insert: "m", + kind: Module, + }, + ] + "###); + } +} diff --git a/crates/ra_ide/src/completion/complete_postfix.rs b/crates/ra_ide/src/completion/complete_postfix.rs new file mode 100644 index 000000000..646a30c76 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_postfix.rs @@ -0,0 +1,282 @@ +//! FIXME: write short doc here + +use ra_syntax::{ast::AstNode, TextRange, TextUnit}; +use ra_text_edit::TextEdit; + +use crate::{ + completion::{ + completion_context::CompletionContext, + completion_item::{Builder, CompletionKind, Completions}, + }, + CompletionItem, +}; + +pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { + if ctx.db.feature_flags.get("completion.enable-postfix") == false { + return; + } + + let dot_receiver = match &ctx.dot_receiver { + Some(it) => it, + None => return, + }; + + let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { + let text = dot_receiver.syntax().text(); + let without_dot = ..text.len() - TextUnit::of_char('.'); + text.slice(without_dot).to_string() + } else { + dot_receiver.syntax().text().to_string() + }; + + let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { + Some(it) => it, + None => return, + }; + + if receiver_ty.is_bool() || receiver_ty.is_unknown() { + postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text)) + .add_to(acc); + postfix_snippet( + ctx, + "while", + "while expr {}", + &format!("while {} {{\n$0\n}}", receiver_text), + ) + .add_to(acc); + } + + postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); + + postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); + postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); + + postfix_snippet( + ctx, + "match", + "match expr {}", + &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), + ) + .add_to(acc); + + postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); + + postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) + .add_to(acc); +} + +fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { + let edit = { + let receiver_range = + ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); + let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); + TextEdit::replace(delete_range, snippet.to_string()) + }; + CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) + .detail(detail) + .snippet_edit(edit) +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + + fn do_postfix_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Postfix) + } + + #[test] + fn postfix_completion_works_for_trivial_path_expression() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + fn main() { + let bar = true; + bar.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: [89; 89), + delete: [85; 89), + insert: "Box::new(bar)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: [89; 89), + delete: [85; 89), + insert: "dbg!(bar)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "if", + source_range: [89; 89), + delete: [85; 89), + insert: "if bar {$0}", + detail: "if expr {}", + }, + CompletionItem { + label: "match", + source_range: [89; 89), + delete: [85; 89), + insert: "match bar {\n ${1:_} => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: [89; 89), + delete: [85; 89), + insert: "!bar", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: [89; 89), + delete: [85; 89), + insert: "&bar", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: [89; 89), + delete: [85; 89), + insert: "&mut bar", + detail: "&mut expr", + }, + CompletionItem { + label: "while", + source_range: [89; 89), + delete: [85; 89), + insert: "while bar {\n$0\n}", + detail: "while expr {}", + }, + ] + "### + ); + } + + #[test] + fn some_postfix_completions_ignored() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + fn main() { + let bar: u8 = 12; + bar.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: [91; 91), + delete: [87; 91), + insert: "Box::new(bar)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: [91; 91), + delete: [87; 91), + insert: "dbg!(bar)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "match", + source_range: [91; 91), + delete: [87; 91), + insert: "match bar {\n ${1:_} => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: [91; 91), + delete: [87; 91), + insert: "!bar", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: [91; 91), + delete: [87; 91), + insert: "&bar", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: [91; 91), + delete: [87; 91), + insert: "&mut bar", + detail: "&mut expr", + }, + ] + "### + ); + } + + #[test] + fn postfix_completion_works_for_ambiguous_float_literal() { + assert_debug_snapshot!( + do_postfix_completion( + r#" + fn main() { + 42.<|> + } + "#, + ), + @r###" + [ + CompletionItem { + label: "box", + source_range: [52; 52), + delete: [49; 52), + insert: "Box::new(42)", + detail: "Box::new(expr)", + }, + CompletionItem { + label: "dbg", + source_range: [52; 52), + delete: [49; 52), + insert: "dbg!(42)", + detail: "dbg!(expr)", + }, + CompletionItem { + label: "match", + source_range: [52; 52), + delete: [49; 52), + insert: "match 42 {\n ${1:_} => {$0\\},\n}", + detail: "match expr {}", + }, + CompletionItem { + label: "not", + source_range: [52; 52), + delete: [49; 52), + insert: "!42", + detail: "!expr", + }, + CompletionItem { + label: "ref", + source_range: [52; 52), + delete: [49; 52), + insert: "&42", + detail: "&expr", + }, + CompletionItem { + label: "refm", + source_range: [52; 52), + delete: [49; 52), + insert: "&mut 42", + detail: "&mut expr", + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/completion/complete_record_literal.rs b/crates/ra_ide/src/completion/complete_record_literal.rs new file mode 100644 index 000000000..577c394d2 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_record_literal.rs @@ -0,0 +1,159 @@ +//! FIXME: write short doc here + +use crate::completion::{CompletionContext, Completions}; + +/// Complete fields in fields literals. +pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { + let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| { + Some(( + ctx.analyzer.type_of(ctx.db, &it.clone().into())?, + ctx.analyzer.resolve_record_literal(it)?, + )) + }) { + Some(it) => it, + _ => return, + }; + + for (field, field_ty) in ty.variant_fields(ctx.db, variant) { + acc.add_field(ctx, field, &field_ty); + } +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn complete(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn test_record_literal_deprecated_field() { + let completions = complete( + r" + struct A { + #[deprecated] + the_field: u32, + } + fn foo() { + A { the<|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "the_field", + source_range: [142; 145), + delete: [142; 145), + insert: "the_field", + kind: Field, + detail: "u32", + deprecated: true, + }, + ] + "###); + } + + #[test] + fn test_record_literal_field() { + let completions = complete( + r" + struct A { the_field: u32 } + fn foo() { + A { the<|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "the_field", + source_range: [83; 86), + delete: [83; 86), + insert: "the_field", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_enum_variant() { + let completions = complete( + r" + enum E { + A { a: u32 } + } + fn foo() { + let _ = E::A { <|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "a", + source_range: [119; 119), + delete: [119; 119), + insert: "a", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_two_structs() { + let completions = complete( + r" + struct A { a: u32 } + struct B { b: u32 } + + fn foo() { + let _: A = B { <|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "b", + source_range: [119; 119), + delete: [119; 119), + insert: "b", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_literal_generic_struct() { + let completions = complete( + r" + struct A { a: T } + + fn foo() { + let _: A = A { <|> } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "a", + source_range: [93; 93), + delete: [93; 93), + insert: "a", + kind: Field, + detail: "u32", + }, + ] + "###); + } +} diff --git a/crates/ra_ide/src/completion/complete_record_pattern.rs b/crates/ra_ide/src/completion/complete_record_pattern.rs new file mode 100644 index 000000000..a56c7e3a1 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_record_pattern.rs @@ -0,0 +1,93 @@ +//! FIXME: write short doc here + +use crate::completion::{CompletionContext, Completions}; + +pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { + let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| { + Some(( + ctx.analyzer.type_of_pat(ctx.db, &it.clone().into())?, + ctx.analyzer.resolve_record_pattern(it)?, + )) + }) { + Some(it) => it, + _ => return, + }; + + for (field, field_ty) in ty.variant_fields(ctx.db, variant) { + acc.add_field(ctx, field, &field_ty); + } +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn complete(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn test_record_pattern_field() { + let completions = complete( + r" + struct S { foo: u32 } + + fn process(f: S) { + match f { + S { f<|>: 92 } => (), + } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "foo", + source_range: [117; 118), + delete: [117; 118), + insert: "foo", + kind: Field, + detail: "u32", + }, + ] + "###); + } + + #[test] + fn test_record_pattern_enum_variant() { + let completions = complete( + r" + enum E { + S { foo: u32, bar: () } + } + + fn process(e: E) { + match e { + E::S { <|> } => (), + } + } + ", + ); + assert_debug_snapshot!(completions, @r###" + [ + CompletionItem { + label: "bar", + source_range: [161; 161), + delete: [161; 161), + insert: "bar", + kind: Field, + detail: "()", + }, + CompletionItem { + label: "foo", + source_range: [161; 161), + delete: [161; 161), + insert: "foo", + kind: Field, + detail: "u32", + }, + ] + "###); + } +} diff --git a/crates/ra_ide/src/completion/complete_scope.rs b/crates/ra_ide/src/completion/complete_scope.rs new file mode 100644 index 000000000..d5739b58a --- /dev/null +++ b/crates/ra_ide/src/completion/complete_scope.rs @@ -0,0 +1,876 @@ +//! FIXME: write short doc here + +use ra_assists::auto_import_text_edit; +use ra_syntax::{ast, AstNode, SmolStr}; +use ra_text_edit::TextEditBuilder; +use rustc_hash::FxHashMap; + +use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; + +pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_trivial_path { + return; + } + + ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { + acc.add_resolution(ctx, name.to_string(), &res) + }); + + // auto-import + // We fetch ident from the original file, because we need to pre-filter auto-imports + if ast::NameRef::cast(ctx.token.parent()).is_some() { + let import_resolver = ImportResolver::new(); + let import_names = import_resolver.all_names(ctx.token.text()); + import_names.into_iter().for_each(|(name, path)| { + let edit = { + let mut builder = TextEditBuilder::default(); + builder.replace(ctx.source_range(), name.to_string()); + auto_import_text_edit( + &ctx.token.parent(), + &ctx.token.parent(), + &path, + &mut builder, + ); + builder.finish() + }; + + // Hack: copied this check form conv.rs beacause auto import can produce edits + // that invalidate assert in conv_with. + if edit + .as_atoms() + .iter() + .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) + .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) + { + CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + build_import_label(&name, &path), + ) + .text_edit(edit) + .add_to(acc); + } + }); + } +} + +fn build_import_label(name: &str, path: &[SmolStr]) -> String { + let mut buf = String::with_capacity(64); + buf.push_str(name); + buf.push_str(" ("); + fmt_import_path(path, &mut buf); + buf.push_str(")"); + buf +} + +fn fmt_import_path(path: &[SmolStr], buf: &mut String) { + let mut segments = path.iter(); + if let Some(s) = segments.next() { + buf.push_str(&s); + } + for s in segments { + buf.push_str("::"); + buf.push_str(&s); + } +} + +#[derive(Debug, Clone, Default)] +pub(crate) struct ImportResolver { + // todo: use fst crate or something like that + dummy_names: Vec<(SmolStr, Vec)>, +} + +impl ImportResolver { + pub(crate) fn new() -> Self { + let dummy_names = vec![ + (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]), + (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]), + (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]), + (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]), + ( + SmolStr::new("Debug"), + vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")], + ), + ( + SmolStr::new("Display"), + vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")], + ), + ( + SmolStr::new("Hash"), + vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")], + ), + ( + SmolStr::new("Hasher"), + vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")], + ), + ( + SmolStr::new("Iterator"), + vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")], + ), + ]; + + ImportResolver { dummy_names } + } + + // Returns a map of importable items filtered by name. + // The map associates item name with its full path. + // todo: should return Resolutions + pub(crate) fn all_names(&self, name: &str) -> FxHashMap> { + if name.len() > 1 { + self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect() + } else { + FxHashMap::default() + } + } +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_reference_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn completes_bindings_from_let() { + assert_debug_snapshot!( + do_reference_completion( + r" + fn quux(x: i32) { + let y = 92; + 1 + <|>; + let z = (); + } + " + ), + @r###" + [ + CompletionItem { + label: "quux(…)", + source_range: [91; 91), + delete: [91; 91), + insert: "quux($0)", + kind: Function, + lookup: "quux", + detail: "fn quux(x: i32)", + }, + CompletionItem { + label: "x", + source_range: [91; 91), + delete: [91; 91), + insert: "x", + kind: Binding, + detail: "i32", + }, + CompletionItem { + label: "y", + source_range: [91; 91), + delete: [91; 91), + insert: "y", + kind: Binding, + detail: "i32", + }, + ] + "### + ); + } + + #[test] + fn completes_bindings_from_if_let() { + assert_debug_snapshot!( + do_reference_completion( + r" + fn quux() { + if let Some(x) = foo() { + let y = 92; + }; + if let Some(a) = bar() { + let b = 62; + 1 + <|> + } + } + " + ), + @r###" + [ + CompletionItem { + label: "a", + source_range: [242; 242), + delete: [242; 242), + insert: "a", + kind: Binding, + }, + CompletionItem { + label: "b", + source_range: [242; 242), + delete: [242; 242), + insert: "b", + kind: Binding, + detail: "i32", + }, + CompletionItem { + label: "quux()", + source_range: [242; 242), + delete: [242; 242), + insert: "quux()$0", + kind: Function, + lookup: "quux", + detail: "fn quux()", + }, + ] + "### + ); + } + + #[test] + fn completes_bindings_from_for() { + assert_debug_snapshot!( + do_reference_completion( + r" + fn quux() { + for x in &[1, 2, 3] { + <|> + } + } + " + ), + @r###" + [ + CompletionItem { + label: "quux()", + source_range: [95; 95), + delete: [95; 95), + insert: "quux()$0", + kind: Function, + lookup: "quux", + detail: "fn quux()", + }, + CompletionItem { + label: "x", + source_range: [95; 95), + delete: [95; 95), + insert: "x", + kind: Binding, + }, + ] + "### + ); + } + + #[test] + fn completes_generic_params() { + assert_debug_snapshot!( + do_reference_completion( + r" + fn quux() { + <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "T", + source_range: [52; 52), + delete: [52; 52), + insert: "T", + kind: TypeParam, + }, + CompletionItem { + label: "quux()", + source_range: [52; 52), + delete: [52; 52), + insert: "quux()$0", + kind: Function, + lookup: "quux", + detail: "fn quux()", + }, + ] + "### + ); + } + + #[test] + fn completes_generic_params_in_struct() { + assert_debug_snapshot!( + do_reference_completion( + r" + struct X { + x: <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "Self", + source_range: [54; 54), + delete: [54; 54), + insert: "Self", + kind: TypeParam, + }, + CompletionItem { + label: "T", + source_range: [54; 54), + delete: [54; 54), + insert: "T", + kind: TypeParam, + }, + CompletionItem { + label: "X<…>", + source_range: [54; 54), + delete: [54; 54), + insert: "X<$0>", + kind: Struct, + lookup: "X", + }, + ] + "### + ); + } + + #[test] + fn completes_self_in_enum() { + assert_debug_snapshot!( + do_reference_completion( + r" + enum X { + Y(<|>) + } + " + ), + @r###" + [ + CompletionItem { + label: "Self", + source_range: [48; 48), + delete: [48; 48), + insert: "Self", + kind: TypeParam, + }, + CompletionItem { + label: "X", + source_range: [48; 48), + delete: [48; 48), + insert: "X", + kind: Enum, + }, + ] + "### + ); + } + + #[test] + fn completes_module_items() { + assert_debug_snapshot!( + do_reference_completion( + r" + struct Foo; + enum Baz {} + fn quux() { + <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "Baz", + source_range: [105; 105), + delete: [105; 105), + insert: "Baz", + kind: Enum, + }, + CompletionItem { + label: "Foo", + source_range: [105; 105), + delete: [105; 105), + insert: "Foo", + kind: Struct, + }, + CompletionItem { + label: "quux()", + source_range: [105; 105), + delete: [105; 105), + insert: "quux()$0", + kind: Function, + lookup: "quux", + detail: "fn quux()", + }, + ] + "### + ); + } + + #[test] + fn completes_extern_prelude() { + assert_debug_snapshot!( + do_reference_completion( + r" + //- /lib.rs + use <|>; + + //- /other_crate/lib.rs + // nothing here + " + ), + @r###" + [ + CompletionItem { + label: "other_crate", + source_range: [4; 4), + delete: [4; 4), + insert: "other_crate", + kind: Module, + }, + ] + "### + ); + } + + #[test] + fn completes_module_items_in_nested_modules() { + assert_debug_snapshot!( + do_reference_completion( + r" + struct Foo; + mod m { + struct Bar; + fn quux() { <|> } + } + " + ), + @r###" + [ + CompletionItem { + label: "Bar", + source_range: [117; 117), + delete: [117; 117), + insert: "Bar", + kind: Struct, + }, + CompletionItem { + label: "quux()", + source_range: [117; 117), + delete: [117; 117), + insert: "quux()$0", + kind: Function, + lookup: "quux", + detail: "fn quux()", + }, + ] + "### + ); + } + + #[test] + fn completes_return_type() { + assert_debug_snapshot!( + do_reference_completion( + r" + struct Foo; + fn x() -> <|> + " + ), + @r###" + [ + CompletionItem { + label: "Foo", + source_range: [55; 55), + delete: [55; 55), + insert: "Foo", + kind: Struct, + }, + CompletionItem { + label: "x()", + source_range: [55; 55), + delete: [55; 55), + insert: "x()$0", + kind: Function, + lookup: "x", + detail: "fn x()", + }, + ] + "### + ); + } + + #[test] + fn dont_show_both_completions_for_shadowing() { + assert_debug_snapshot!( + do_reference_completion( + r" + fn foo() { + let bar = 92; + { + let bar = 62; + <|> + } + } + " + ), + @r###" + [ + CompletionItem { + label: "bar", + source_range: [146; 146), + delete: [146; 146), + insert: "bar", + kind: Binding, + detail: "i32", + }, + CompletionItem { + label: "foo()", + source_range: [146; 146), + delete: [146; 146), + insert: "foo()$0", + kind: Function, + lookup: "foo", + detail: "fn foo()", + }, + ] + "### + ); + } + + #[test] + fn completes_self_in_methods() { + assert_debug_snapshot!( + do_reference_completion(r"impl S { fn foo(&self) { <|> } }"), + @r###" + [ + CompletionItem { + label: "Self", + source_range: [25; 25), + delete: [25; 25), + insert: "Self", + kind: TypeParam, + }, + CompletionItem { + label: "self", + source_range: [25; 25), + delete: [25; 25), + insert: "self", + kind: Binding, + detail: "&{unknown}", + }, + ] + "### + ); + } + + #[test] + fn completes_prelude() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + fn foo() { let x: <|> } + + //- /std/lib.rs + #[prelude_import] + use prelude::*; + + mod prelude { + struct Option; + } + " + ), + @r###" + [ + CompletionItem { + label: "Option", + source_range: [18; 18), + delete: [18; 18), + insert: "Option", + kind: Struct, + }, + CompletionItem { + label: "foo()", + source_range: [18; 18), + delete: [18; 18), + insert: "foo()$0", + kind: Function, + lookup: "foo", + detail: "fn foo()", + }, + CompletionItem { + label: "std", + source_range: [18; 18), + delete: [18; 18), + insert: "std", + kind: Module, + }, + ] + "### + ); + } + + #[test] + fn completes_std_prelude_if_core_is_defined() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + fn foo() { let x: <|> } + + //- /core/lib.rs + #[prelude_import] + use prelude::*; + + mod prelude { + struct Option; + } + + //- /std/lib.rs + #[prelude_import] + use prelude::*; + + mod prelude { + struct String; + } + " + ), + @r###" + [ + CompletionItem { + label: "String", + source_range: [18; 18), + delete: [18; 18), + insert: "String", + kind: Struct, + }, + CompletionItem { + label: "core", + source_range: [18; 18), + delete: [18; 18), + insert: "core", + kind: Module, + }, + CompletionItem { + label: "foo()", + source_range: [18; 18), + delete: [18; 18), + insert: "foo()$0", + kind: Function, + lookup: "foo", + detail: "fn foo()", + }, + CompletionItem { + label: "std", + source_range: [18; 18), + delete: [18; 18), + insert: "std", + kind: Module, + }, + ] + "### + ); + } + + #[test] + fn completes_macros_as_value() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + macro_rules! foo { + () => {} + } + + #[macro_use] + mod m1 { + macro_rules! bar { + () => {} + } + } + + mod m2 { + macro_rules! nope { + () => {} + } + + #[macro_export] + macro_rules! baz { + () => {} + } + } + + fn main() { + let v = <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "bar!", + source_range: [252; 252), + delete: [252; 252), + insert: "bar!($0)", + kind: Macro, + detail: "macro_rules! bar", + }, + CompletionItem { + label: "baz!", + source_range: [252; 252), + delete: [252; 252), + insert: "baz!($0)", + kind: Macro, + detail: "#[macro_export]\nmacro_rules! baz", + }, + CompletionItem { + label: "foo!", + source_range: [252; 252), + delete: [252; 252), + insert: "foo!($0)", + kind: Macro, + detail: "macro_rules! foo", + }, + CompletionItem { + label: "m1", + source_range: [252; 252), + delete: [252; 252), + insert: "m1", + kind: Module, + }, + CompletionItem { + label: "m2", + source_range: [252; 252), + delete: [252; 252), + insert: "m2", + kind: Module, + }, + CompletionItem { + label: "main()", + source_range: [252; 252), + delete: [252; 252), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + ] + "### + ); + } + + #[test] + fn completes_both_macro_and_value() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + macro_rules! foo { + () => {} + } + + fn foo() { + <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo!", + source_range: [49; 49), + delete: [49; 49), + insert: "foo!($0)", + kind: Macro, + detail: "macro_rules! foo", + }, + CompletionItem { + label: "foo()", + source_range: [49; 49), + delete: [49; 49), + insert: "foo()$0", + kind: Function, + lookup: "foo", + detail: "fn foo()", + }, + ] + "### + ); + } + + #[test] + fn completes_macros_as_type() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + macro_rules! foo { + () => {} + } + + fn main() { + let x: <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo!", + source_range: [57; 57), + delete: [57; 57), + insert: "foo!($0)", + kind: Macro, + detail: "macro_rules! foo", + }, + CompletionItem { + label: "main()", + source_range: [57; 57), + delete: [57; 57), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + ] + "### + ); + } + + #[test] + fn completes_macros_as_stmt() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /main.rs + macro_rules! foo { + () => {} + } + + fn main() { + <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo!", + source_range: [50; 50), + delete: [50; 50), + insert: "foo!($0)", + kind: Macro, + detail: "macro_rules! foo", + }, + CompletionItem { + label: "main()", + source_range: [50; 50), + delete: [50; 50), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/completion/complete_snippet.rs b/crates/ra_ide/src/completion/complete_snippet.rs new file mode 100644 index 000000000..1f2988b36 --- /dev/null +++ b/crates/ra_ide/src/completion/complete_snippet.rs @@ -0,0 +1,120 @@ +//! FIXME: write short doc here + +use crate::completion::{ + completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind, + CompletionKind, Completions, +}; + +fn snippet(ctx: &CompletionContext, label: &str, snippet: &str) -> Builder { + CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label) + .insert_snippet(snippet) + .kind(CompletionItemKind::Snippet) +} + +pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { + if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) { + return; + } + + snippet(ctx, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); + snippet(ctx, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); +} + +pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { + if !ctx.is_new_item { + return; + } + snippet( + ctx, + "Test function", + "\ +#[test] +fn ${1:feature}() { + $0 +}", + ) + .lookup_by("tfn") + .add_to(acc); + + snippet(ctx, "pub(crate)", "pub(crate) $0").add_to(acc); +} + +#[cfg(test)] +mod tests { + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + use insta::assert_debug_snapshot; + + fn do_snippet_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Snippet) + } + + #[test] + fn completes_snippets_in_expressions() { + assert_debug_snapshot!( + do_snippet_completion(r"fn foo(x: i32) { <|> }"), + @r###" + [ + CompletionItem { + label: "pd", + source_range: [17; 17), + delete: [17; 17), + insert: "eprintln!(\"$0 = {:?}\", $0);", + kind: Snippet, + }, + CompletionItem { + label: "ppd", + source_range: [17; 17), + delete: [17; 17), + insert: "eprintln!(\"$0 = {:#?}\", $0);", + kind: Snippet, + }, + ] + "### + ); + } + + #[test] + fn should_not_complete_snippets_in_path() { + assert_debug_snapshot!( + do_snippet_completion(r"fn foo(x: i32) { ::foo<|> }"), + @"[]" + ); + assert_debug_snapshot!( + do_snippet_completion(r"fn foo(x: i32) { ::<|> }"), + @"[]" + ); + } + + #[test] + fn completes_snippets_in_items() { + assert_debug_snapshot!( + do_snippet_completion( + r" + #[cfg(test)] + mod tests { + <|> + } + " + ), + @r###" + [ + CompletionItem { + label: "Test function", + source_range: [78; 78), + delete: [78; 78), + insert: "#[test]\nfn ${1:feature}() {\n $0\n}", + kind: Snippet, + lookup: "tfn", + }, + CompletionItem { + label: "pub(crate)", + source_range: [78; 78), + delete: [78; 78), + insert: "pub(crate) $0", + kind: Snippet, + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/completion/completion_context.rs b/crates/ra_ide/src/completion/completion_context.rs new file mode 100644 index 000000000..b8345c91d --- /dev/null +++ b/crates/ra_ide/src/completion/completion_context.rs @@ -0,0 +1,274 @@ +//! FIXME: write short doc here + +use ra_syntax::{ + algo::{find_covering_element, find_node_at_offset}, + ast, AstNode, Parse, SourceFile, + SyntaxKind::*, + SyntaxNode, SyntaxToken, TextRange, TextUnit, +}; +use ra_text_edit::AtomTextEdit; + +use crate::{db, FilePosition}; + +/// `CompletionContext` is created early during completion to figure out, where +/// exactly is the cursor, syntax-wise. +#[derive(Debug)] +pub(crate) struct CompletionContext<'a> { + pub(super) db: &'a db::RootDatabase, + pub(super) analyzer: hir::SourceAnalyzer, + pub(super) offset: TextUnit, + pub(super) token: SyntaxToken, + pub(super) module: Option, + pub(super) function_syntax: Option, + pub(super) use_item_syntax: Option, + pub(super) record_lit_syntax: Option, + pub(super) record_lit_pat: Option, + pub(super) is_param: bool, + /// If a name-binding or reference to a const in a pattern. + /// Irrefutable patterns (like let) are excluded. + pub(super) is_pat_binding: bool, + /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. + pub(super) is_trivial_path: bool, + /// If not a trivial path, the prefix (qualifier). + pub(super) path_prefix: Option, + pub(super) after_if: bool, + /// `true` if we are a statement or a last expr in the block. + pub(super) can_be_stmt: bool, + /// Something is typed at the "top" level, in module or impl/trait. + pub(super) is_new_item: bool, + /// The receiver if this is a field or method access, i.e. writing something.<|> + pub(super) dot_receiver: Option, + pub(super) dot_receiver_is_ambiguous_float_literal: bool, + /// If this is a call (method or function) in particular, i.e. the () are already there. + pub(super) is_call: bool, + pub(super) is_path_type: bool, + pub(super) has_type_args: bool, +} + +impl<'a> CompletionContext<'a> { + pub(super) fn new( + db: &'a db::RootDatabase, + original_parse: &'a Parse, + position: FilePosition, + ) -> Option> { + let src = hir::ModuleSource::from_position(db, position); + let module = hir::Module::from_definition( + db, + hir::Source { file_id: position.file_id.into(), value: src }, + ); + let token = + original_parse.tree().syntax().token_at_offset(position.offset).left_biased()?; + let analyzer = hir::SourceAnalyzer::new( + db, + hir::Source::new(position.file_id.into(), &token.parent()), + Some(position.offset), + ); + let mut ctx = CompletionContext { + db, + analyzer, + token, + offset: position.offset, + module, + function_syntax: None, + use_item_syntax: None, + record_lit_syntax: None, + record_lit_pat: None, + is_param: false, + is_pat_binding: false, + is_trivial_path: false, + path_prefix: None, + after_if: false, + can_be_stmt: false, + is_new_item: false, + dot_receiver: None, + is_call: false, + is_path_type: false, + has_type_args: false, + dot_receiver_is_ambiguous_float_literal: false, + }; + ctx.fill(&original_parse, position.offset); + Some(ctx) + } + + // The range of the identifier that is being completed. + pub(crate) fn source_range(&self) -> TextRange { + match self.token.kind() { + // workaroud when completion is triggered by trigger characters. + IDENT => self.token.text_range(), + _ => TextRange::offset_len(self.offset, 0.into()), + } + } + + fn fill(&mut self, original_parse: &'a Parse, offset: TextUnit) { + // Insert a fake ident to get a valid parse tree. We will use this file + // to determine context, though the original_file will be used for + // actual completion. + let file = { + let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); + original_parse.reparse(&edit).tree() + }; + + // First, let's try to complete a reference to some declaration. + if let Some(name_ref) = find_node_at_offset::(file.syntax(), offset) { + // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. + // See RFC#1685. + if is_node::(name_ref.syntax()) { + self.is_param = true; + return; + } + self.classify_name_ref(original_parse.tree(), name_ref); + } + + // Otherwise, see if this is a declaration. We can use heuristics to + // suggest declaration names, see `CompletionKind::Magic`. + if let Some(name) = find_node_at_offset::(file.syntax(), offset) { + if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { + let parent = bind_pat.syntax().parent(); + if parent.clone().and_then(ast::MatchArm::cast).is_some() + || parent.and_then(ast::Condition::cast).is_some() + { + self.is_pat_binding = true; + } + } + if is_node::(name.syntax()) { + self.is_param = true; + return; + } + if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { + self.record_lit_pat = + find_node_at_offset(original_parse.tree().syntax(), self.offset); + } + } + } + + fn classify_name_ref(&mut self, original_file: SourceFile, name_ref: ast::NameRef) { + let name_range = name_ref.syntax().text_range(); + if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { + self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); + } + + let top_node = name_ref + .syntax() + .ancestors() + .take_while(|it| it.text_range() == name_range) + .last() + .unwrap(); + + match top_node.parent().map(|it| it.kind()) { + Some(SOURCE_FILE) | Some(ITEM_LIST) => { + self.is_new_item = true; + return; + } + _ => (), + } + + self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); + + self.function_syntax = self + .token + .parent() + .ancestors() + .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) + .find_map(ast::FnDef::cast); + + let parent = match name_ref.syntax().parent() { + Some(it) => it, + None => return, + }; + + if let Some(segment) = ast::PathSegment::cast(parent.clone()) { + let path = segment.parent_path(); + self.is_call = path + .syntax() + .parent() + .and_then(ast::PathExpr::cast) + .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) + .is_some(); + + self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); + self.has_type_args = segment.type_arg_list().is_some(); + + if let Some(mut path) = hir::Path::from_ast(path.clone()) { + if !path.is_ident() { + path.segments.pop().unwrap(); + self.path_prefix = Some(path); + return; + } + } + + if path.qualifier().is_none() { + self.is_trivial_path = true; + + // Find either enclosing expr statement (thing with `;`) or a + // block. If block, check that we are the last expr. + self.can_be_stmt = name_ref + .syntax() + .ancestors() + .find_map(|node| { + if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { + return Some( + stmt.syntax().text_range() == name_ref.syntax().text_range(), + ); + } + if let Some(block) = ast::Block::cast(node) { + return Some( + block.expr().map(|e| e.syntax().text_range()) + == Some(name_ref.syntax().text_range()), + ); + } + None + }) + .unwrap_or(false); + + if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { + if let Some(if_expr) = + find_node_at_offset::(original_file.syntax(), off) + { + if if_expr.syntax().text_range().end() + < name_ref.syntax().text_range().start() + { + self.after_if = true; + } + } + } + } + } + if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { + // The receiver comes before the point of insertion of the fake + // ident, so it should have the same range in the non-modified file + self.dot_receiver = field_expr + .expr() + .map(|e| e.syntax().text_range()) + .and_then(|r| find_node_with_range(original_file.syntax(), r)); + self.dot_receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = + &self.dot_receiver + { + match l.kind() { + ast::LiteralKind::FloatNumber { suffix: _ } => l.token().text().ends_with('.'), + _ => false, + } + } else { + false + } + } + if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { + // As above + self.dot_receiver = method_call_expr + .expr() + .map(|e| e.syntax().text_range()) + .and_then(|r| find_node_with_range(original_file.syntax(), r)); + self.is_call = true; + } + } +} + +fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { + find_covering_element(syntax, range).ancestors().find_map(N::cast) +} + +fn is_node(node: &SyntaxNode) -> bool { + match node.ancestors().find_map(N::cast) { + None => false, + Some(n) => n.syntax().text_range() == node.text_range(), + } +} diff --git a/crates/ra_ide/src/completion/completion_item.rs b/crates/ra_ide/src/completion/completion_item.rs new file mode 100644 index 000000000..93f336370 --- /dev/null +++ b/crates/ra_ide/src/completion/completion_item.rs @@ -0,0 +1,322 @@ +//! FIXME: write short doc here + +use std::fmt; + +use hir::Documentation; +use ra_syntax::TextRange; +use ra_text_edit::TextEdit; + +/// `CompletionItem` describes a single completion variant in the editor pop-up. +/// It is basically a POD with various properties. To construct a +/// `CompletionItem`, use `new` method and the `Builder` struct. +pub struct CompletionItem { + /// Used only internally in tests, to check only specific kind of + /// completion (postfix, keyword, reference, etc). + #[allow(unused)] + completion_kind: CompletionKind, + /// Label in the completion pop up which identifies completion. + label: String, + /// Range of identifier that is being completed. + /// + /// It should be used primarily for UI, but we also use this to convert + /// genetic TextEdit into LSP's completion edit (see conv.rs). + /// + /// `source_range` must contain the completion offset. `insert_text` should + /// start with what `source_range` points to, or VSCode will filter out the + /// completion silently. + source_range: TextRange, + /// What happens when user selects this item. + /// + /// Typically, replaces `source_range` with new identifier. + text_edit: TextEdit, + insert_text_format: InsertTextFormat, + + /// What item (struct, function, etc) are we completing. + kind: Option, + + /// Lookup is used to check if completion item indeed can complete current + /// ident. + /// + /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it + /// contains `bar` sub sequence), and `quux` will rejected. + lookup: Option, + + /// Additional info to show in the UI pop up. + detail: Option, + documentation: Option, + + /// Whether this item is marked as deprecated + deprecated: bool, +} + +// We use custom debug for CompletionItem to make `insta`'s diffs more readable. +impl fmt::Debug for CompletionItem { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let mut s = f.debug_struct("CompletionItem"); + s.field("label", &self.label()).field("source_range", &self.source_range()); + if self.text_edit().as_atoms().len() == 1 { + let atom = &self.text_edit().as_atoms()[0]; + s.field("delete", &atom.delete); + s.field("insert", &atom.insert); + } else { + s.field("text_edit", &self.text_edit); + } + if let Some(kind) = self.kind().as_ref() { + s.field("kind", kind); + } + if self.lookup() != self.label() { + s.field("lookup", &self.lookup()); + } + if let Some(detail) = self.detail() { + s.field("detail", &detail); + } + if let Some(documentation) = self.documentation() { + s.field("documentation", &documentation); + } + if self.deprecated { + s.field("deprecated", &true); + } + s.finish() + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CompletionItemKind { + Snippet, + Keyword, + Module, + Function, + BuiltinType, + Struct, + Enum, + EnumVariant, + Binding, + Field, + Static, + Const, + Trait, + TypeAlias, + Method, + TypeParam, + Macro, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub(crate) enum CompletionKind { + /// Parser-based keyword completion. + Keyword, + /// Your usual "complete all valid identifiers". + Reference, + /// "Secret sauce" completions. + Magic, + Snippet, + Postfix, + BuiltinType, +} + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub enum InsertTextFormat { + PlainText, + Snippet, +} + +impl CompletionItem { + pub(crate) fn new( + completion_kind: CompletionKind, + source_range: TextRange, + label: impl Into, + ) -> Builder { + let label = label.into(); + Builder { + source_range, + completion_kind, + label, + insert_text: None, + insert_text_format: InsertTextFormat::PlainText, + detail: None, + documentation: None, + lookup: None, + kind: None, + text_edit: None, + deprecated: None, + } + } + /// What user sees in pop-up in the UI. + pub fn label(&self) -> &str { + &self.label + } + pub fn source_range(&self) -> TextRange { + self.source_range + } + + pub fn insert_text_format(&self) -> InsertTextFormat { + self.insert_text_format + } + + pub fn text_edit(&self) -> &TextEdit { + &self.text_edit + } + + /// Short one-line additional information, like a type + pub fn detail(&self) -> Option<&str> { + self.detail.as_ref().map(|it| it.as_str()) + } + /// A doc-comment + pub fn documentation(&self) -> Option { + self.documentation.clone() + } + /// What string is used for filtering. + pub fn lookup(&self) -> &str { + self.lookup.as_ref().map(|it| it.as_str()).unwrap_or_else(|| self.label()) + } + + pub fn kind(&self) -> Option { + self.kind + } + + pub fn deprecated(&self) -> bool { + self.deprecated + } +} + +/// A helper to make `CompletionItem`s. +#[must_use] +pub(crate) struct Builder { + source_range: TextRange, + completion_kind: CompletionKind, + label: String, + insert_text: Option, + insert_text_format: InsertTextFormat, + detail: Option, + documentation: Option, + lookup: Option, + kind: Option, + text_edit: Option, + deprecated: Option, +} + +impl Builder { + pub(crate) fn add_to(self, acc: &mut Completions) { + acc.add(self.build()) + } + + pub(crate) fn build(self) -> CompletionItem { + let label = self.label; + let text_edit = match self.text_edit { + Some(it) => it, + None => TextEdit::replace( + self.source_range, + self.insert_text.unwrap_or_else(|| label.clone()), + ), + }; + + CompletionItem { + source_range: self.source_range, + label, + insert_text_format: self.insert_text_format, + text_edit, + detail: self.detail, + documentation: self.documentation, + lookup: self.lookup, + kind: self.kind, + completion_kind: self.completion_kind, + deprecated: self.deprecated.unwrap_or(false), + } + } + pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { + self.lookup = Some(lookup.into()); + self + } + pub(crate) fn label(mut self, label: impl Into) -> Builder { + self.label = label.into(); + self + } + pub(crate) fn insert_text(mut self, insert_text: impl Into) -> Builder { + self.insert_text = Some(insert_text.into()); + self + } + pub(crate) fn insert_snippet(mut self, snippet: impl Into) -> Builder { + self.insert_text_format = InsertTextFormat::Snippet; + self.insert_text(snippet) + } + pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { + self.kind = Some(kind); + self + } + pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { + self.text_edit = Some(edit); + self + } + pub(crate) fn snippet_edit(mut self, edit: TextEdit) -> Builder { + self.insert_text_format = InsertTextFormat::Snippet; + self.text_edit(edit) + } + #[allow(unused)] + pub(crate) fn detail(self, detail: impl Into) -> Builder { + self.set_detail(Some(detail)) + } + pub(crate) fn set_detail(mut self, detail: Option>) -> Builder { + self.detail = detail.map(Into::into); + self + } + #[allow(unused)] + pub(crate) fn documentation(self, docs: Documentation) -> Builder { + self.set_documentation(Some(docs)) + } + pub(crate) fn set_documentation(mut self, docs: Option) -> Builder { + self.documentation = docs.map(Into::into); + self + } + pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { + self.deprecated = Some(deprecated); + self + } +} + +impl<'a> Into for Builder { + fn into(self) -> CompletionItem { + self.build() + } +} + +/// Represents an in-progress set of completions being built. +#[derive(Debug, Default)] +pub(crate) struct Completions { + buf: Vec, +} + +impl Completions { + pub(crate) fn add(&mut self, item: impl Into) { + self.buf.push(item.into()) + } + pub(crate) fn add_all(&mut self, items: I) + where + I: IntoIterator, + I::Item: Into, + { + items.into_iter().for_each(|item| self.add(item.into())) + } +} + +impl Into> for Completions { + fn into(self) -> Vec { + self.buf + } +} + +#[cfg(test)] +pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec { + use crate::completion::completions; + use crate::mock_analysis::{analysis_and_position, single_file_with_position}; + let (analysis, position) = if code.contains("//-") { + analysis_and_position(code) + } else { + single_file_with_position(code) + }; + let completions = completions(&analysis.db, position).unwrap(); + let completion_items: Vec = completions.into(); + let mut kind_completions: Vec = + completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); + kind_completions.sort_by_key(|c| c.label.clone()); + kind_completions +} diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs new file mode 100644 index 000000000..5f056730a --- /dev/null +++ b/crates/ra_ide/src/completion/presentation.rs @@ -0,0 +1,676 @@ +//! This modules takes care of rendering various definitions as completion items. + +use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, Type}; +use join_to_string::join; +use ra_syntax::ast::NameOwner; +use test_utils::tested_by; + +use crate::completion::{ + CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, +}; + +use crate::display::{const_label, function_label, macro_label, type_label}; + +impl Completions { + pub(crate) fn add_field( + &mut self, + ctx: &CompletionContext, + field: hir::StructField, + ty: &Type, + ) { + let is_deprecated = is_deprecated(field, ctx.db); + CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + field.name(ctx.db).to_string(), + ) + .kind(CompletionItemKind::Field) + .detail(ty.display(ctx.db).to_string()) + .set_documentation(field.docs(ctx.db)) + .set_deprecated(is_deprecated) + .add_to(self); + } + + pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) + .kind(CompletionItemKind::Field) + .detail(ty.display(ctx.db).to_string()) + .add_to(self); + } + + pub(crate) fn add_resolution( + &mut self, + ctx: &CompletionContext, + local_name: String, + resolution: &ScopeDef, + ) { + use hir::ModuleDef::*; + + let completion_kind = match resolution { + ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, + _ => CompletionKind::Reference, + }; + + let kind = match resolution { + ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, + ScopeDef::ModuleDef(Function(func)) => { + return self.add_function_with_name(ctx, Some(local_name), *func); + } + ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, + // FIXME: add CompletionItemKind::Union + ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, + ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, + + ScopeDef::ModuleDef(EnumVariant(..)) => CompletionItemKind::EnumVariant, + ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, + ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, + ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, + ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, + ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, + ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, + ScopeDef::Local(..) => CompletionItemKind::Binding, + // (does this need its own kind?) + ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, + ScopeDef::MacroDef(mac) => { + return self.add_macro(ctx, Some(local_name), *mac); + } + ScopeDef::Unknown => { + return self.add(CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + local_name, + )); + } + }; + + let docs = match resolution { + ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db), + ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db), + _ => None, + }; + + let mut completion_item = + CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); + if let ScopeDef::Local(local) = resolution { + let ty = local.ty(ctx.db); + if !ty.is_unknown() { + completion_item = completion_item.detail(ty.display(ctx.db).to_string()); + } + }; + + // If not an import, add parenthesis automatically. + if ctx.is_path_type + && !ctx.has_type_args + && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") + { + let has_non_default_type_params = match resolution { + ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), + ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), + _ => false, + }; + if has_non_default_type_params { + tested_by!(inserts_angle_brackets_for_generics); + completion_item = completion_item + .lookup_by(local_name.clone()) + .label(format!("{}<…>", local_name)) + .insert_snippet(format!("{}<$0>", local_name)); + } + } + + completion_item.kind(kind).set_documentation(docs).add_to(self) + } + + pub(crate) fn add_function(&mut self, ctx: &CompletionContext, func: hir::Function) { + self.add_function_with_name(ctx, None, func) + } + + fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str { + let mut votes = [0, 0, 0]; + for (idx, s) in docs.match_indices(¯o_name) { + let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); + // Ensure to match the full word + if after.starts_with('!') + && before + .chars() + .rev() + .next() + .map_or(true, |c| c != '_' && !c.is_ascii_alphanumeric()) + { + // It may have spaces before the braces like `foo! {}` + match after[1..].chars().find(|&c| !c.is_whitespace()) { + Some('{') => votes[0] += 1, + Some('[') => votes[1] += 1, + Some('(') => votes[2] += 1, + _ => {} + } + } + } + + // Insert a space before `{}`. + // We prefer the last one when some votes equal. + *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1 + } + + pub(crate) fn add_macro( + &mut self, + ctx: &CompletionContext, + name: Option, + macro_: hir::MacroDef, + ) { + let name = match name { + Some(it) => it, + None => return, + }; + + let ast_node = macro_.source(ctx.db).value; + let detail = macro_label(&ast_node); + + let docs = macro_.docs(ctx.db); + let macro_declaration = format!("{}!", name); + + let mut builder = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), ¯o_declaration) + .kind(CompletionItemKind::Macro) + .set_documentation(docs.clone()) + .set_deprecated(is_deprecated(macro_, ctx.db)) + .detail(detail); + + builder = if ctx.use_item_syntax.is_some() { + builder.insert_text(name) + } else { + let macro_braces_to_insert = + self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); + builder.insert_snippet(macro_declaration + macro_braces_to_insert) + }; + + self.add(builder); + } + + fn add_function_with_name( + &mut self, + ctx: &CompletionContext, + name: Option, + func: hir::Function, + ) { + let func_name = func.name(ctx.db); + let has_self_param = func.has_self_param(ctx.db); + let params = func.params(ctx.db); + + let name = name.unwrap_or_else(|| func_name.to_string()); + let ast_node = func.source(ctx.db).value; + let detail = function_label(&ast_node); + + let mut builder = + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) + .kind(if has_self_param { + CompletionItemKind::Method + } else { + CompletionItemKind::Function + }) + .set_documentation(func.docs(ctx.db)) + .set_deprecated(is_deprecated(func, ctx.db)) + .detail(detail); + + // Add `<>` for generic types + if ctx.use_item_syntax.is_none() + && !ctx.is_call + && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") + { + tested_by!(inserts_parens_for_function_calls); + let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { + (format!("{}()$0", func_name), format!("{}()", name)) + } else { + (format!("{}($0)", func_name), format!("{}(…)", name)) + }; + builder = builder.lookup_by(name).label(label).insert_snippet(snippet); + } + + self.add(builder) + } + + pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { + let ast_node = constant.source(ctx.db).value; + let name = match ast_node.name() { + Some(name) => name, + _ => return, + }; + let detail = const_label(&ast_node); + + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) + .kind(CompletionItemKind::Const) + .set_documentation(constant.docs(ctx.db)) + .set_deprecated(is_deprecated(constant, ctx.db)) + .detail(detail) + .add_to(self); + } + + pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { + let type_def = type_alias.source(ctx.db).value; + let name = match type_def.name() { + Some(name) => name, + _ => return, + }; + let detail = type_label(&type_def); + + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) + .kind(CompletionItemKind::TypeAlias) + .set_documentation(type_alias.docs(ctx.db)) + .set_deprecated(is_deprecated(type_alias, ctx.db)) + .detail(detail) + .add_to(self); + } + + pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) { + let is_deprecated = is_deprecated(variant, ctx.db); + let name = match variant.name(ctx.db) { + Some(it) => it, + None => return, + }; + let detail_types = variant.fields(ctx.db).into_iter().map(|field| field.ty(ctx.db)); + let detail = join(detail_types.map(|t| t.display(ctx.db).to_string())) + .separator(", ") + .surround_with("(", ")") + .to_string(); + CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) + .kind(CompletionItemKind::EnumVariant) + .set_documentation(variant.docs(ctx.db)) + .set_deprecated(is_deprecated) + .detail(detail) + .add_to(self); + } +} + +fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { + node.attrs(db).by_key("deprecated").exists() +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + use test_utils::covers; + + use crate::completion::{do_completion, CompletionItem, CompletionKind}; + + fn do_reference_completion(code: &str) -> Vec { + do_completion(code, CompletionKind::Reference) + } + + #[test] + fn sets_deprecated_flag_in_completion_items() { + assert_debug_snapshot!( + do_reference_completion( + r#" + #[deprecated] + fn something_deprecated() {} + + #[deprecated(since = "1.0.0")] + fn something_else_deprecated() {} + + fn main() { som<|> } + "#, + ), + @r###" + [ + CompletionItem { + label: "main()", + source_range: [203; 206), + delete: [203; 206), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + CompletionItem { + label: "something_deprecated()", + source_range: [203; 206), + delete: [203; 206), + insert: "something_deprecated()$0", + kind: Function, + lookup: "something_deprecated", + detail: "fn something_deprecated()", + deprecated: true, + }, + CompletionItem { + label: "something_else_deprecated()", + source_range: [203; 206), + delete: [203; 206), + insert: "something_else_deprecated()$0", + kind: Function, + lookup: "something_else_deprecated", + detail: "fn something_else_deprecated()", + deprecated: true, + }, + ] + "### + ); + } + + #[test] + fn inserts_parens_for_function_calls() { + covers!(inserts_parens_for_function_calls); + assert_debug_snapshot!( + do_reference_completion( + r" + fn no_args() {} + fn main() { no_<|> } + " + ), + @r###" + [ + CompletionItem { + label: "main()", + source_range: [61; 64), + delete: [61; 64), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + CompletionItem { + label: "no_args()", + source_range: [61; 64), + delete: [61; 64), + insert: "no_args()$0", + kind: Function, + lookup: "no_args", + detail: "fn no_args()", + }, + ] + "### + ); + assert_debug_snapshot!( + do_reference_completion( + r" + fn with_args(x: i32, y: String) {} + fn main() { with_<|> } + " + ), + @r###" + [ + CompletionItem { + label: "main()", + source_range: [80; 85), + delete: [80; 85), + insert: "main()$0", + kind: Function, + lookup: "main", + detail: "fn main()", + }, + CompletionItem { + label: "with_args(…)", + source_range: [80; 85), + delete: [80; 85), + insert: "with_args($0)", + kind: Function, + lookup: "with_args", + detail: "fn with_args(x: i32, y: String)", + }, + ] + "### + ); + assert_debug_snapshot!( + do_reference_completion( + r" + struct S {} + impl S { + fn foo(&self) {} + } + fn bar(s: &S) { + s.f<|> + } + " + ), + @r###" + [ + CompletionItem { + label: "foo()", + source_range: [163; 164), + delete: [163; 164), + insert: "foo()$0", + kind: Method, + lookup: "foo", + detail: "fn foo(&self)", + }, + ] + "### + ); + } + + #[test] + fn dont_render_function_parens_in_use_item() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + mod m { pub fn foo() {} } + use crate::m::f<|>; + " + ), + @r###" + [ + CompletionItem { + label: "foo", + source_range: [40; 41), + delete: [40; 41), + insert: "foo", + kind: Function, + detail: "pub fn foo()", + }, + ] + "### + ); + } + + #[test] + fn dont_render_function_parens_if_already_call() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + fn frobnicate() {} + fn main() { + frob<|>(); + } + " + ), + @r###" + [ + CompletionItem { + label: "frobnicate", + source_range: [35; 39), + delete: [35; 39), + insert: "frobnicate", + kind: Function, + detail: "fn frobnicate()", + }, + CompletionItem { + label: "main", + source_range: [35; 39), + delete: [35; 39), + insert: "main", + kind: Function, + detail: "fn main()", + }, + ] + "### + ); + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + struct Foo {} + impl Foo { fn new() -> Foo {} } + fn main() { + Foo::ne<|>(); + } + " + ), + @r###" + [ + CompletionItem { + label: "new", + source_range: [67; 69), + delete: [67; 69), + insert: "new", + kind: Function, + detail: "fn new() -> Foo", + }, + ] + "### + ); + } + + #[test] + fn inserts_angle_brackets_for_generics() { + covers!(inserts_angle_brackets_for_generics); + assert_debug_snapshot!( + do_reference_completion( + r" + struct Vec {} + fn foo(xs: Ve<|>) + " + ), + @r###" + [ + CompletionItem { + label: "Vec<…>", + source_range: [61; 63), + delete: [61; 63), + insert: "Vec<$0>", + kind: Struct, + lookup: "Vec", + }, + CompletionItem { + label: "foo(…)", + source_range: [61; 63), + delete: [61; 63), + insert: "foo($0)", + kind: Function, + lookup: "foo", + detail: "fn foo(xs: Ve)", + }, + ] + "### + ); + assert_debug_snapshot!( + do_reference_completion( + r" + type Vec = (T,); + fn foo(xs: Ve<|>) + " + ), + @r###" + [ + CompletionItem { + label: "Vec<…>", + source_range: [64; 66), + delete: [64; 66), + insert: "Vec<$0>", + kind: TypeAlias, + lookup: "Vec", + }, + CompletionItem { + label: "foo(…)", + source_range: [64; 66), + delete: [64; 66), + insert: "foo($0)", + kind: Function, + lookup: "foo", + detail: "fn foo(xs: Ve)", + }, + ] + "### + ); + assert_debug_snapshot!( + do_reference_completion( + r" + struct Vec {} + fn foo(xs: Ve<|>) + " + ), + @r###" + [ + CompletionItem { + label: "Vec", + source_range: [68; 70), + delete: [68; 70), + insert: "Vec", + kind: Struct, + }, + CompletionItem { + label: "foo(…)", + source_range: [68; 70), + delete: [68; 70), + insert: "foo($0)", + kind: Function, + lookup: "foo", + detail: "fn foo(xs: Ve)", + }, + ] + "### + ); + assert_debug_snapshot!( + do_reference_completion( + r" + struct Vec {} + fn foo(xs: Ve<|>) + " + ), + @r###" + [ + CompletionItem { + label: "Vec", + source_range: [61; 63), + delete: [61; 63), + insert: "Vec", + kind: Struct, + }, + CompletionItem { + label: "foo(…)", + source_range: [61; 63), + delete: [61; 63), + insert: "foo($0)", + kind: Function, + lookup: "foo", + detail: "fn foo(xs: Ve)", + }, + ] + "### + ); + } + + #[test] + fn dont_insert_macro_call_braces_in_use() { + assert_debug_snapshot!( + do_reference_completion( + r" + //- /main.rs + use foo::<|>; + + //- /foo/lib.rs + #[macro_export] + macro_rules frobnicate { + () => () + } + " + ), + @r###" + [ + CompletionItem { + label: "frobnicate!", + source_range: [9; 9), + delete: [9; 9), + insert: "frobnicate", + kind: Macro, + detail: "#[macro_export]\nmacro_rules! frobnicate", + }, + ] + "### + ) + } +} diff --git a/crates/ra_ide/src/db.rs b/crates/ra_ide/src/db.rs new file mode 100644 index 000000000..f739ebecd --- /dev/null +++ b/crates/ra_ide/src/db.rs @@ -0,0 +1,144 @@ +//! FIXME: write short doc here + +use std::sync::Arc; + +use ra_db::{ + salsa::{self, Database, Durability}, + Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, + SourceDatabase, SourceDatabaseExt, SourceRootId, +}; +use rustc_hash::FxHashMap; + +use crate::{ + symbol_index::{self, SymbolsDatabase}, + FeatureFlags, LineIndex, +}; + +#[salsa::database( + ra_db::SourceDatabaseStorage, + ra_db::SourceDatabaseExtStorage, + LineIndexDatabaseStorage, + symbol_index::SymbolsDatabaseStorage, + hir::db::InternDatabaseStorage, + hir::db::AstDatabaseStorage, + hir::db::DefDatabaseStorage, + hir::db::HirDatabaseStorage +)] +#[derive(Debug)] +pub(crate) struct RootDatabase { + runtime: salsa::Runtime, + pub(crate) feature_flags: Arc, + pub(crate) debug_data: Arc, + pub(crate) last_gc: crate::wasm_shims::Instant, + pub(crate) last_gc_check: crate::wasm_shims::Instant, +} + +impl FileLoader for RootDatabase { + fn file_text(&self, file_id: FileId) -> Arc { + FileLoaderDelegate(self).file_text(file_id) + } + fn resolve_relative_path( + &self, + anchor: FileId, + relative_path: &RelativePath, + ) -> Option { + FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + } + fn relevant_crates(&self, file_id: FileId) -> Arc> { + FileLoaderDelegate(self).relevant_crates(file_id) + } +} + +impl hir::debug::HirDebugHelper for RootDatabase { + fn crate_name(&self, krate: CrateId) -> Option { + self.debug_data.crate_names.get(&krate).cloned() + } + fn file_path(&self, file_id: FileId) -> Option { + let source_root_id = self.file_source_root(file_id); + let source_root_path = self.debug_data.root_paths.get(&source_root_id)?; + let file_path = self.file_relative_path(file_id); + Some(format!("{}/{}", source_root_path, file_path)) + } +} + +impl salsa::Database for RootDatabase { + fn salsa_runtime(&self) -> &salsa::Runtime { + &self.runtime + } + fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { + &mut self.runtime + } + fn on_propagated_panic(&self) -> ! { + Canceled::throw() + } + fn salsa_event(&self, event: impl Fn() -> salsa::Event) { + match event().kind { + salsa::EventKind::DidValidateMemoizedValue { .. } + | salsa::EventKind::WillExecute { .. } => { + self.check_canceled(); + } + _ => (), + } + } +} + +impl Default for RootDatabase { + fn default() -> RootDatabase { + RootDatabase::new(None, FeatureFlags::default()) + } +} + +impl RootDatabase { + pub fn new(lru_capacity: Option, feature_flags: FeatureFlags) -> RootDatabase { + let mut db = RootDatabase { + runtime: salsa::Runtime::default(), + last_gc: crate::wasm_shims::Instant::now(), + last_gc_check: crate::wasm_shims::Instant::now(), + feature_flags: Arc::new(feature_flags), + debug_data: Default::default(), + }; + db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); + db.set_local_roots_with_durability(Default::default(), Durability::HIGH); + db.set_library_roots_with_durability(Default::default(), Durability::HIGH); + let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); + db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity); + db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity); + db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity); + db + } +} + +impl salsa::ParallelDatabase for RootDatabase { + fn snapshot(&self) -> salsa::Snapshot { + salsa::Snapshot::new(RootDatabase { + runtime: self.runtime.snapshot(self), + last_gc: self.last_gc, + last_gc_check: self.last_gc_check, + feature_flags: Arc::clone(&self.feature_flags), + debug_data: Arc::clone(&self.debug_data), + }) + } +} + +#[salsa::query_group(LineIndexDatabaseStorage)] +pub(crate) trait LineIndexDatabase: ra_db::SourceDatabase + CheckCanceled { + fn line_index(&self, file_id: FileId) -> Arc; +} + +fn line_index(db: &impl LineIndexDatabase, file_id: FileId) -> Arc { + let text = db.file_text(file_id); + Arc::new(LineIndex::new(&*text)) +} + +#[derive(Debug, Default, Clone)] +pub(crate) struct DebugData { + pub(crate) root_paths: FxHashMap, + pub(crate) crate_names: FxHashMap, +} + +impl DebugData { + pub(crate) fn merge(&mut self, other: DebugData) { + self.root_paths.extend(other.root_paths.into_iter()); + self.crate_names.extend(other.crate_names.into_iter()); + } +} diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs new file mode 100644 index 000000000..cc1ccab4b --- /dev/null +++ b/crates/ra_ide/src/diagnostics.rs @@ -0,0 +1,652 @@ +//! FIXME: write short doc here + +use std::cell::RefCell; + +use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; +use itertools::Itertools; +use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; +use ra_prof::profile; +use ra_syntax::{ + algo, + ast::{self, make, AstNode}, + Location, SyntaxNode, TextRange, T, +}; +use ra_text_edit::{TextEdit, TextEditBuilder}; + +use crate::{db::RootDatabase, Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; + +#[derive(Debug, Copy, Clone)] +pub enum Severity { + Error, + WeakWarning, +} + +pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { + let _p = profile("diagnostics"); + let parse = db.parse(file_id); + let mut res = Vec::new(); + + res.extend(parse.errors().iter().map(|err| Diagnostic { + range: location_to_range(err.location()), + message: format!("Syntax Error: {}", err), + severity: Severity::Error, + fix: None, + })); + + for node in parse.tree().syntax().descendants() { + check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); + check_struct_shorthand_initialization(&mut res, file_id, &node); + } + let res = RefCell::new(res); + let mut sink = DiagnosticSink::new(|d| { + res.borrow_mut().push(Diagnostic { + message: d.message(), + range: d.highlight_range(), + severity: Severity::Error, + fix: None, + }) + }) + .on::(|d| { + let original_file = d.source().file_id.original_file(db); + let source_root = db.file_source_root(original_file); + let path = db + .file_relative_path(original_file) + .parent() + .unwrap_or_else(|| RelativePath::new("")) + .join(&d.candidate); + let create_file = FileSystemEdit::CreateFile { source_root, path }; + let fix = SourceChange::file_system_edit("create module", create_file); + res.borrow_mut().push(Diagnostic { + range: d.highlight_range(), + message: d.message(), + severity: Severity::Error, + fix: Some(fix), + }) + }) + .on::(|d| { + let mut field_list = d.ast(db); + for f in d.missed_fields.iter() { + let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); + field_list = field_list.append_field(&field); + } + + let mut builder = TextEditBuilder::default(); + algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); + + let fix = + SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish()); + res.borrow_mut().push(Diagnostic { + range: d.highlight_range(), + message: d.message(), + severity: Severity::Error, + fix: Some(fix), + }) + }) + .on::(|d| { + let node = d.ast(db); + let replacement = format!("Ok({})", node.syntax()); + let edit = TextEdit::replace(node.syntax().text_range(), replacement); + let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); + res.borrow_mut().push(Diagnostic { + range: d.highlight_range(), + message: d.message(), + severity: Severity::Error, + fix: Some(fix), + }) + }); + let source_file = db.parse(file_id).tree(); + let src = + hir::Source { file_id: file_id.into(), value: hir::ModuleSource::SourceFile(source_file) }; + if let Some(m) = hir::Module::from_definition(db, src) { + m.diagnostics(db, &mut sink); + }; + drop(sink); + res.into_inner() +} +fn location_to_range(location: Location) -> TextRange { + match location { + Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), + Location::Range(range) => range, + } +} + +fn check_unnecessary_braces_in_use_statement( + acc: &mut Vec, + file_id: FileId, + node: &SyntaxNode, +) -> Option<()> { + let use_tree_list = ast::UseTreeList::cast(node.clone())?; + if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { + let range = use_tree_list.syntax().text_range(); + let edit = + text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) + .unwrap_or_else(|| { + let to_replace = single_use_tree.syntax().text().to_string(); + let mut edit_builder = TextEditBuilder::default(); + edit_builder.delete(range); + edit_builder.insert(range.start(), to_replace); + edit_builder.finish() + }); + + acc.push(Diagnostic { + range, + message: "Unnecessary braces in use statement".to_string(), + severity: Severity::WeakWarning, + fix: Some(SourceChange::source_file_edit( + "Remove unnecessary braces", + SourceFileEdit { file_id, edit }, + )), + }); + } + + Some(()) +} + +fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( + single_use_tree: &ast::UseTree, +) -> Option { + let use_tree_list_node = single_use_tree.syntax().parent()?; + if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { + let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); + let end = use_tree_list_node.text_range().end(); + let range = TextRange::from_to(start, end); + return Some(TextEdit::delete(range)); + } + None +} + +fn check_struct_shorthand_initialization( + acc: &mut Vec, + file_id: FileId, + node: &SyntaxNode, +) -> Option<()> { + let record_lit = ast::RecordLit::cast(node.clone())?; + let record_field_list = record_lit.record_field_list()?; + for record_field in record_field_list.fields() { + if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { + let field_name = name_ref.syntax().text().to_string(); + let field_expr = expr.syntax().text().to_string(); + if field_name == field_expr { + let mut edit_builder = TextEditBuilder::default(); + edit_builder.delete(record_field.syntax().text_range()); + edit_builder.insert(record_field.syntax().text_range().start(), field_name); + let edit = edit_builder.finish(); + + acc.push(Diagnostic { + range: record_field.syntax().text_range(), + message: "Shorthand struct initialization".to_string(), + severity: Severity::WeakWarning, + fix: Some(SourceChange::source_file_edit( + "use struct shorthand initialization", + SourceFileEdit { file_id, edit }, + )), + }); + } + } + } + Some(()) +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + use join_to_string::join; + use ra_syntax::SourceFile; + use test_utils::assert_eq_text; + + use crate::mock_analysis::{analysis_and_position, single_file}; + + use super::*; + + type DiagnosticChecker = fn(&mut Vec, FileId, &SyntaxNode) -> Option<()>; + + fn check_not_applicable(code: &str, func: DiagnosticChecker) { + let parse = SourceFile::parse(code); + let mut diagnostics = Vec::new(); + for node in parse.tree().syntax().descendants() { + func(&mut diagnostics, FileId(0), &node); + } + assert!(diagnostics.is_empty()); + } + + fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { + let parse = SourceFile::parse(before); + let mut diagnostics = Vec::new(); + for node in parse.tree().syntax().descendants() { + func(&mut diagnostics, FileId(0), &node); + } + let diagnostic = + diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); + let mut fix = diagnostic.fix.unwrap(); + let edit = fix.source_file_edits.pop().unwrap().edit; + let actual = edit.apply(&before); + assert_eq_text!(after, &actual); + } + + /// Takes a multi-file input fixture with annotated cursor positions, + /// and checks that: + /// * a diagnostic is produced + /// * this diagnostic touches the input cursor position + /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied + fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) { + let (analysis, file_position) = analysis_and_position(fixture); + let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); + let mut fix = diagnostic.fix.unwrap(); + let edit = fix.source_file_edits.pop().unwrap().edit; + let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); + let actual = edit.apply(&target_file_contents); + + // Strip indent and empty lines from `after`, to match the behaviour of + // `parse_fixture` called from `analysis_and_position`. + let margin = fixture + .lines() + .filter(|it| it.trim_start().starts_with("//-")) + .map(|it| it.len() - it.trim_start().len()) + .next() + .expect("empty fixture"); + let after = join(after.lines().filter_map(|line| { + if line.len() > margin { + Some(&line[margin..]) + } else { + None + } + })) + .separator("\n") + .suffix("\n") + .to_string(); + + assert_eq_text!(&after, &actual); + assert!( + diagnostic.range.start() <= file_position.offset + && diagnostic.range.end() >= file_position.offset, + "diagnostic range {} does not touch cursor position {}", + diagnostic.range, + file_position.offset + ); + } + + fn check_apply_diagnostic_fix(before: &str, after: &str) { + let (analysis, file_id) = single_file(before); + let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); + let mut fix = diagnostic.fix.unwrap(); + let edit = fix.source_file_edits.pop().unwrap().edit; + let actual = edit.apply(&before); + assert_eq_text!(after, &actual); + } + + /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics + /// apply to the file containing the cursor. + fn check_no_diagnostic_for_target_file(fixture: &str) { + let (analysis, file_position) = analysis_and_position(fixture); + let diagnostics = analysis.diagnostics(file_position.file_id).unwrap(); + assert_eq!(diagnostics.len(), 0); + } + + fn check_no_diagnostic(content: &str) { + let (analysis, file_id) = single_file(content); + let diagnostics = analysis.diagnostics(file_id).unwrap(); + assert_eq!(diagnostics.len(), 0); + } + + #[test] + fn test_wrap_return_type() { + let before = r#" + //- /main.rs + use std::{string::String, result::Result::{self, Ok, Err}}; + + fn div(x: i32, y: i32) -> Result { + if y == 0 { + return Err("div by zero".into()); + } + x / y<|> + } + + //- /std/lib.rs + pub mod string { + pub struct String { } + } + pub mod result { + pub enum Result { Ok(T), Err(E) } + } + "#; + let after = r#" + use std::{string::String, result::Result::{self, Ok, Err}}; + + fn div(x: i32, y: i32) -> Result { + if y == 0 { + return Err("div by zero".into()); + } + Ok(x / y) + } + "#; + check_apply_diagnostic_fix_from_position(before, after); + } + + #[test] + fn test_wrap_return_type_handles_generic_functions() { + let before = r#" + //- /main.rs + use std::result::Result::{self, Ok, Err}; + + fn div(x: T) -> Result { + if x == 0 { + return Err(7); + } + <|>x + } + + //- /std/lib.rs + pub mod result { + pub enum Result { Ok(T), Err(E) } + } + "#; + let after = r#" + use std::result::Result::{self, Ok, Err}; + + fn div(x: T) -> Result { + if x == 0 { + return Err(7); + } + Ok(x) + } + "#; + check_apply_diagnostic_fix_from_position(before, after); + } + + #[test] + fn test_wrap_return_type_handles_type_aliases() { + let before = r#" + //- /main.rs + use std::{string::String, result::Result::{self, Ok, Err}}; + + type MyResult = Result; + + fn div(x: i32, y: i32) -> MyResult { + if y == 0 { + return Err("div by zero".into()); + } + x <|>/ y + } + + //- /std/lib.rs + pub mod string { + pub struct String { } + } + pub mod result { + pub enum Result { Ok(T), Err(E) } + } + "#; + let after = r#" + use std::{string::String, result::Result::{self, Ok, Err}}; + + type MyResult = Result; + fn div(x: i32, y: i32) -> MyResult { + if y == 0 { + return Err("div by zero".into()); + } + Ok(x / y) + } + "#; + check_apply_diagnostic_fix_from_position(before, after); + } + + #[test] + fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { + let content = r#" + //- /main.rs + use std::{string::String, result::Result::{self, Ok, Err}}; + + fn foo() -> Result { + 0<|> + } + + //- /std/lib.rs + pub mod string { + pub struct String { } + } + pub mod result { + pub enum Result { Ok(T), Err(E) } + } + "#; + check_no_diagnostic_for_target_file(content); + } + + #[test] + fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { + let content = r#" + //- /main.rs + use std::{string::String, result::Result::{self, Ok, Err}}; + + enum SomeOtherEnum { + Ok(i32), + Err(String), + } + + fn foo() -> SomeOtherEnum { + 0<|> + } + + //- /std/lib.rs + pub mod string { + pub struct String { } + } + pub mod result { + pub enum Result { Ok(T), Err(E) } + } + "#; + check_no_diagnostic_for_target_file(content); + } + + #[test] + fn test_fill_struct_fields_empty() { + let before = r" + struct TestStruct { + one: i32, + two: i64, + } + + fn test_fn() { + let s = TestStruct{}; + } + "; + let after = r" + struct TestStruct { + one: i32, + two: i64, + } + + fn test_fn() { + let s = TestStruct{ one: (), two: ()}; + } + "; + check_apply_diagnostic_fix(before, after); + } + + #[test] + fn test_fill_struct_fields_partial() { + let before = r" + struct TestStruct { + one: i32, + two: i64, + } + + fn test_fn() { + let s = TestStruct{ two: 2 }; + } + "; + let after = r" + struct TestStruct { + one: i32, + two: i64, + } + + fn test_fn() { + let s = TestStruct{ two: 2, one: () }; + } + "; + check_apply_diagnostic_fix(before, after); + } + + #[test] + fn test_fill_struct_fields_no_diagnostic() { + let content = r" + struct TestStruct { + one: i32, + two: i64, + } + + fn test_fn() { + let one = 1; + let s = TestStruct{ one, two: 2 }; + } + "; + + check_no_diagnostic(content); + } + + #[test] + fn test_fill_struct_fields_no_diagnostic_on_spread() { + let content = r" + struct TestStruct { + one: i32, + two: i64, + } + + fn test_fn() { + let one = 1; + let s = TestStruct{ ..a }; + } + "; + + check_no_diagnostic(content); + } + + #[test] + fn test_unresolved_module_diagnostic() { + let (analysis, file_id) = single_file("mod foo;"); + let diagnostics = analysis.diagnostics(file_id).unwrap(); + assert_debug_snapshot!(diagnostics, @r###" + [ + Diagnostic { + message: "unresolved module", + range: [0; 8), + fix: Some( + SourceChange { + label: "create module", + source_file_edits: [], + file_system_edits: [ + CreateFile { + source_root: SourceRootId( + 0, + ), + path: "foo.rs", + }, + ], + cursor_position: None, + }, + ), + severity: Error, + }, + ] + "###); + } + + #[test] + fn test_check_unnecessary_braces_in_use_statement() { + check_not_applicable( + " + use a; + use a::{c, d::e}; + ", + check_unnecessary_braces_in_use_statement, + ); + check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement); + check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement); + check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement); + check_apply( + "use a::{c, d::{e}};", + "use a::{c, d::e};", + check_unnecessary_braces_in_use_statement, + ); + } + + #[test] + fn test_check_struct_shorthand_initialization() { + check_not_applicable( + r#" + struct A { + a: &'static str + } + + fn main() { + A { + a: "hello" + } + } + "#, + check_struct_shorthand_initialization, + ); + + check_apply( + r#" +struct A { + a: &'static str +} + +fn main() { + let a = "haha"; + A { + a: a + } +} + "#, + r#" +struct A { + a: &'static str +} + +fn main() { + let a = "haha"; + A { + a + } +} + "#, + check_struct_shorthand_initialization, + ); + + check_apply( + r#" +struct A { + a: &'static str, + b: &'static str +} + +fn main() { + let a = "haha"; + let b = "bb"; + A { + a: a, + b + } +} + "#, + r#" +struct A { + a: &'static str, + b: &'static str +} + +fn main() { + let a = "haha"; + let b = "bb"; + A { + a, + b + } +} + "#, + check_struct_shorthand_initialization, + ); + } +} diff --git a/crates/ra_ide/src/display.rs b/crates/ra_ide/src/display.rs new file mode 100644 index 000000000..30617412a --- /dev/null +++ b/crates/ra_ide/src/display.rs @@ -0,0 +1,84 @@ +//! This module contains utilities for turning SyntaxNodes and HIR types +//! into types that may be used to render in a UI. + +mod function_signature; +mod navigation_target; +mod structure; +mod short_label; + +use ra_syntax::{ + ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, + SyntaxKind::{ATTR, COMMENT}, +}; + +pub use function_signature::FunctionSignature; +pub use navigation_target::NavigationTarget; +pub use structure::{file_structure, StructureNode}; + +pub(crate) use navigation_target::{description_from_symbol, docs_from_symbol, ToNav}; +pub(crate) use short_label::ShortLabel; + +pub(crate) fn function_label(node: &ast::FnDef) -> String { + FunctionSignature::from(node).to_string() +} + +pub(crate) fn const_label(node: &ast::ConstDef) -> String { + let label: String = node + .syntax() + .children_with_tokens() + .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) + .map(|node| node.to_string()) + .collect(); + + label.trim().to_owned() +} + +pub(crate) fn type_label(node: &ast::TypeAliasDef) -> String { + let label: String = node + .syntax() + .children_with_tokens() + .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) + .map(|node| node.to_string()) + .collect(); + + label.trim().to_owned() +} + +pub(crate) fn generic_parameters(node: &N) -> Vec { + let mut res = vec![]; + if let Some(type_params) = node.type_param_list() { + res.extend(type_params.lifetime_params().map(|p| p.syntax().text().to_string())); + res.extend(type_params.type_params().map(|p| p.syntax().text().to_string())); + } + res +} + +pub(crate) fn where_predicates(node: &N) -> Vec { + let mut res = vec![]; + if let Some(clause) = node.where_clause() { + res.extend(clause.predicates().map(|p| p.syntax().text().to_string())); + } + res +} + +pub(crate) fn macro_label(node: &ast::MacroCall) -> String { + let name = node.name().map(|name| name.syntax().text().to_string()).unwrap_or_default(); + let vis = if node.has_atom_attr("macro_export") { "#[macro_export]\n" } else { "" }; + format!("{}macro_rules! {}", vis, name) +} + +pub(crate) fn rust_code_markup>(val: CODE) -> String { + rust_code_markup_with_doc::<_, &str>(val, None) +} + +pub(crate) fn rust_code_markup_with_doc(val: CODE, doc: Option) -> String +where + CODE: AsRef, + DOC: AsRef, +{ + if let Some(doc) = doc { + format!("```rust\n{}\n```\n\n{}", val.as_ref(), doc.as_ref()) + } else { + format!("```rust\n{}\n```", val.as_ref()) + } +} diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs new file mode 100644 index 000000000..d96de4e4c --- /dev/null +++ b/crates/ra_ide/src/display/function_signature.rs @@ -0,0 +1,215 @@ +//! FIXME: write short doc here + +use std::fmt::{self, Display}; + +use hir::{Docs, Documentation, HasSource, HirDisplay}; +use join_to_string::join; +use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; +use std::convert::From; + +use crate::{ + db, + display::{generic_parameters, where_predicates}, +}; + +#[derive(Debug)] +pub enum CallableKind { + Function, + StructConstructor, + VariantConstructor, + Macro, +} + +/// Contains information about a function signature +#[derive(Debug)] +pub struct FunctionSignature { + pub kind: CallableKind, + /// Optional visibility + pub visibility: Option, + /// Name of the function + pub name: Option, + /// Documentation for the function + pub doc: Option, + /// Generic parameters + pub generic_parameters: Vec, + /// Parameters of the function + pub parameters: Vec, + /// Optional return type + pub ret_type: Option, + /// Where predicates + pub where_predicates: Vec, +} + +impl FunctionSignature { + pub(crate) fn with_doc_opt(mut self, doc: Option) -> Self { + self.doc = doc; + self + } + + pub(crate) fn from_hir(db: &db::RootDatabase, function: hir::Function) -> Self { + let doc = function.docs(db); + let ast_node = function.source(db).value; + FunctionSignature::from(&ast_node).with_doc_opt(doc) + } + + pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option { + let node: ast::StructDef = st.source(db).value; + match node.kind() { + ast::StructKind::Record(_) => return None, + _ => (), + }; + + let params = st + .fields(db) + .into_iter() + .map(|field: hir::StructField| { + let ty = field.ty(db); + format!("{}", ty.display(db)) + }) + .collect(); + + Some( + FunctionSignature { + kind: CallableKind::StructConstructor, + visibility: node.visibility().map(|n| n.syntax().text().to_string()), + name: node.name().map(|n| n.text().to_string()), + ret_type: node.name().map(|n| n.text().to_string()), + parameters: params, + generic_parameters: generic_parameters(&node), + where_predicates: where_predicates(&node), + doc: None, + } + .with_doc_opt(st.docs(db)), + ) + } + + pub(crate) fn from_enum_variant( + db: &db::RootDatabase, + variant: hir::EnumVariant, + ) -> Option { + let node: ast::EnumVariant = variant.source(db).value; + match node.kind() { + ast::StructKind::Record(_) | ast::StructKind::Unit => return None, + _ => (), + }; + + let parent_name = match variant.parent_enum(db).name(db) { + Some(name) => name.to_string(), + None => "missing".into(), + }; + + let name = format!("{}::{}", parent_name, variant.name(db).unwrap()); + + let params = variant + .fields(db) + .into_iter() + .map(|field: hir::StructField| { + let name = field.name(db); + let ty = field.ty(db); + format!("{}: {}", name, ty.display(db)) + }) + .collect(); + + Some( + FunctionSignature { + kind: CallableKind::VariantConstructor, + visibility: None, + name: Some(name), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(variant.docs(db)), + ) + } + + pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option { + let node: ast::MacroCall = macro_def.source(db).value; + + let params = vec![]; + + Some( + FunctionSignature { + kind: CallableKind::Macro, + visibility: None, + name: node.name().map(|n| n.text().to_string()), + ret_type: None, + parameters: params, + generic_parameters: vec![], + where_predicates: vec![], + doc: None, + } + .with_doc_opt(macro_def.docs(db)), + ) + } +} + +impl From<&'_ ast::FnDef> for FunctionSignature { + fn from(node: &ast::FnDef) -> FunctionSignature { + fn param_list(node: &ast::FnDef) -> Vec { + let mut res = vec![]; + if let Some(param_list) = node.param_list() { + if let Some(self_param) = param_list.self_param() { + res.push(self_param.syntax().text().to_string()) + } + + res.extend(param_list.params().map(|param| param.syntax().text().to_string())); + } + res + } + + FunctionSignature { + kind: CallableKind::Function, + visibility: node.visibility().map(|n| n.syntax().text().to_string()), + name: node.name().map(|n| n.text().to_string()), + ret_type: node + .ret_type() + .and_then(|r| r.type_ref()) + .map(|n| n.syntax().text().to_string()), + parameters: param_list(node), + generic_parameters: generic_parameters(node), + where_predicates: where_predicates(node), + // docs are processed separately + doc: None, + } + } +} + +impl Display for FunctionSignature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(t) = &self.visibility { + write!(f, "{} ", t)?; + } + + if let Some(name) = &self.name { + match self.kind { + CallableKind::Function => write!(f, "fn {}", name)?, + CallableKind::StructConstructor => write!(f, "struct {}", name)?, + CallableKind::VariantConstructor => write!(f, "{}", name)?, + CallableKind::Macro => write!(f, "{}!", name)?, + } + } + + if !self.generic_parameters.is_empty() { + join(self.generic_parameters.iter()) + .separator(", ") + .surround_with("<", ">") + .to_fmt(f)?; + } + + join(self.parameters.iter()).separator(", ").surround_with("(", ")").to_fmt(f)?; + + if let Some(t) = &self.ret_type { + write!(f, " -> {}", t)?; + } + + if !self.where_predicates.is_empty() { + write!(f, "\nwhere ")?; + join(self.where_predicates.iter()).separator(",\n ").to_fmt(f)?; + } + + Ok(()) + } +} diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs new file mode 100644 index 000000000..6ac60722b --- /dev/null +++ b/crates/ra_ide/src/display/navigation_target.rs @@ -0,0 +1,411 @@ +//! FIXME: write short doc here + +use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource, Source}; +use ra_db::{FileId, SourceDatabase}; +use ra_syntax::{ + ast::{self, DocCommentsOwner, NameOwner}, + match_ast, AstNode, SmolStr, + SyntaxKind::{self, BIND_PAT}, + TextRange, +}; + +use crate::{db::RootDatabase, expand::original_range, FileSymbol}; + +use super::short_label::ShortLabel; + +/// `NavigationTarget` represents and element in the editor's UI which you can +/// click on to navigate to a particular piece of code. +/// +/// Typically, a `NavigationTarget` corresponds to some element in the source +/// code, like a function or a struct, but this is not strictly required. +#[derive(Debug, Clone)] +pub struct NavigationTarget { + file_id: FileId, + name: SmolStr, + kind: SyntaxKind, + full_range: TextRange, + focus_range: Option, + container_name: Option, + description: Option, + docs: Option, +} + +pub(crate) trait ToNav { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget; +} + +impl NavigationTarget { + /// When `focus_range` is specified, returns it. otherwise + /// returns `full_range` + pub fn range(&self) -> TextRange { + self.focus_range.unwrap_or(self.full_range) + } + + pub fn name(&self) -> &SmolStr { + &self.name + } + + pub fn container_name(&self) -> Option<&SmolStr> { + self.container_name.as_ref() + } + + pub fn kind(&self) -> SyntaxKind { + self.kind + } + + pub fn file_id(&self) -> FileId { + self.file_id + } + + pub fn full_range(&self) -> TextRange { + self.full_range + } + + pub fn docs(&self) -> Option<&str> { + self.docs.as_ref().map(String::as_str) + } + + pub fn description(&self) -> Option<&str> { + self.description.as_ref().map(String::as_str) + } + + /// A "most interesting" range withing the `full_range`. + /// + /// Typically, `full_range` is the whole syntax node, + /// including doc comments, and `focus_range` is the range of the identifier. + pub fn focus_range(&self) -> Option { + self.focus_range + } + + pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { + let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); + if let Some(src) = module.declaration_source(db) { + let frange = original_range(db, src.as_ref().map(|it| it.syntax())); + return NavigationTarget::from_syntax( + frange.file_id, + name, + None, + frange.range, + src.value.syntax().kind(), + src.value.doc_comment_text(), + src.value.short_label(), + ); + } + module.to_nav(db) + } + + pub(crate) fn from_def( + db: &RootDatabase, + module_def: hir::ModuleDef, + ) -> Option { + let nav = match module_def { + hir::ModuleDef::Module(module) => module.to_nav(db), + hir::ModuleDef::Function(it) => it.to_nav(db), + hir::ModuleDef::Adt(it) => it.to_nav(db), + hir::ModuleDef::Const(it) => it.to_nav(db), + hir::ModuleDef::Static(it) => it.to_nav(db), + hir::ModuleDef::EnumVariant(it) => it.to_nav(db), + hir::ModuleDef::Trait(it) => it.to_nav(db), + hir::ModuleDef::TypeAlias(it) => it.to_nav(db), + hir::ModuleDef::BuiltinType(..) => { + return None; + } + }; + Some(nav) + } + + #[cfg(test)] + pub(crate) fn assert_match(&self, expected: &str) { + let actual = self.debug_render(); + test_utils::assert_eq_text!(expected.trim(), actual.trim(),); + } + + #[cfg(test)] + pub(crate) fn debug_render(&self) -> String { + let mut buf = format!( + "{} {:?} {:?} {:?}", + self.name(), + self.kind(), + self.file_id(), + self.full_range() + ); + if let Some(focus_range) = self.focus_range() { + buf.push_str(&format!(" {:?}", focus_range)) + } + if let Some(container_name) = self.container_name() { + buf.push_str(&format!(" {}", container_name)) + } + buf + } + + /// Allows `NavigationTarget` to be created from a `NameOwner` + pub(crate) fn from_named( + db: &RootDatabase, + node: Source<&dyn ast::NameOwner>, + docs: Option, + description: Option, + ) -> NavigationTarget { + //FIXME: use `_` instead of empty string + let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); + let focus_range = + node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); + let frange = original_range(db, node.map(|it| it.syntax())); + + NavigationTarget::from_syntax( + frange.file_id, + name, + focus_range, + frange.range, + node.value.syntax().kind(), + docs, + description, + ) + } + + fn from_syntax( + file_id: FileId, + name: SmolStr, + focus_range: Option, + full_range: TextRange, + kind: SyntaxKind, + docs: Option, + description: Option, + ) -> NavigationTarget { + NavigationTarget { + file_id, + name, + kind, + full_range, + focus_range, + container_name: None, + description, + docs, + } + } +} + +impl ToNav for FileSymbol { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + NavigationTarget { + file_id: self.file_id, + name: self.name.clone(), + kind: self.ptr.kind(), + full_range: self.ptr.range(), + focus_range: self.name_range, + container_name: self.container_name.clone(), + description: description_from_symbol(db, self), + docs: docs_from_symbol(db, self), + } + } +} + +pub(crate) trait ToNavFromAst {} +impl ToNavFromAst for hir::Function {} +impl ToNavFromAst for hir::Const {} +impl ToNavFromAst for hir::Static {} +impl ToNavFromAst for hir::Struct {} +impl ToNavFromAst for hir::Enum {} +impl ToNavFromAst for hir::EnumVariant {} +impl ToNavFromAst for hir::Union {} +impl ToNavFromAst for hir::TypeAlias {} +impl ToNavFromAst for hir::Trait {} + +impl ToNav for D +where + D: HasSource + ToNavFromAst + Copy, + D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, +{ + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + let src = self.source(db); + NavigationTarget::from_named( + db, + src.as_ref().map(|it| it as &dyn ast::NameOwner), + src.value.doc_comment_text(), + src.value.short_label(), + ) + } +} + +impl ToNav for hir::Module { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + let src = self.definition_source(db); + let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default(); + match &src.value { + ModuleSource::SourceFile(node) => { + let frange = original_range(db, src.with_value(node.syntax())); + + NavigationTarget::from_syntax( + frange.file_id, + name, + None, + frange.range, + node.syntax().kind(), + None, + None, + ) + } + ModuleSource::Module(node) => { + let frange = original_range(db, src.with_value(node.syntax())); + + NavigationTarget::from_syntax( + frange.file_id, + name, + None, + frange.range, + node.syntax().kind(), + node.doc_comment_text(), + node.short_label(), + ) + } + } + } +} + +impl ToNav for hir::ImplBlock { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + let src = self.source(db); + let frange = original_range(db, src.as_ref().map(|it| it.syntax())); + + NavigationTarget::from_syntax( + frange.file_id, + "impl".into(), + None, + frange.range, + src.value.syntax().kind(), + None, + None, + ) + } +} + +impl ToNav for hir::StructField { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + let src = self.source(db); + + match &src.value { + FieldSource::Named(it) => NavigationTarget::from_named( + db, + src.with_value(it), + it.doc_comment_text(), + it.short_label(), + ), + FieldSource::Pos(it) => { + let frange = original_range(db, src.with_value(it.syntax())); + NavigationTarget::from_syntax( + frange.file_id, + "".into(), + None, + frange.range, + it.syntax().kind(), + None, + None, + ) + } + } + } +} + +impl ToNav for hir::MacroDef { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + let src = self.source(db); + log::debug!("nav target {:#?}", src.value.syntax()); + NavigationTarget::from_named( + db, + src.as_ref().map(|it| it as &dyn ast::NameOwner), + src.value.doc_comment_text(), + None, + ) + } +} + +impl ToNav for hir::Adt { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + match self { + hir::Adt::Struct(it) => it.to_nav(db), + hir::Adt::Union(it) => it.to_nav(db), + hir::Adt::Enum(it) => it.to_nav(db), + } + } +} + +impl ToNav for hir::AssocItem { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + match self { + AssocItem::Function(it) => it.to_nav(db), + AssocItem::Const(it) => it.to_nav(db), + AssocItem::TypeAlias(it) => it.to_nav(db), + } + } +} + +impl ToNav for hir::Local { + fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { + let src = self.source(db); + let (full_range, focus_range) = match src.value { + Either::A(it) => { + (it.syntax().text_range(), it.name().map(|it| it.syntax().text_range())) + } + Either::B(it) => (it.syntax().text_range(), Some(it.self_kw_token().text_range())), + }; + let name = match self.name(db) { + Some(it) => it.to_string().into(), + None => "".into(), + }; + NavigationTarget { + file_id: src.file_id.original_file(db), + name, + kind: BIND_PAT, + full_range, + focus_range, + container_name: None, + description: None, + docs: None, + } + } +} + +pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option { + let parse = db.parse(symbol.file_id); + let node = symbol.ptr.to_node(parse.tree().syntax()); + + match_ast! { + match node { + ast::FnDef(it) => { it.doc_comment_text() }, + ast::StructDef(it) => { it.doc_comment_text() }, + ast::EnumDef(it) => { it.doc_comment_text() }, + ast::TraitDef(it) => { it.doc_comment_text() }, + ast::Module(it) => { it.doc_comment_text() }, + ast::TypeAliasDef(it) => { it.doc_comment_text() }, + ast::ConstDef(it) => { it.doc_comment_text() }, + ast::StaticDef(it) => { it.doc_comment_text() }, + ast::RecordFieldDef(it) => { it.doc_comment_text() }, + ast::EnumVariant(it) => { it.doc_comment_text() }, + ast::MacroCall(it) => { it.doc_comment_text() }, + _ => None, + } + } +} + +/// Get a description of a symbol. +/// +/// e.g. `struct Name`, `enum Name`, `fn Name` +pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option { + let parse = db.parse(symbol.file_id); + let node = symbol.ptr.to_node(parse.tree().syntax()); + + match_ast! { + match node { + ast::FnDef(it) => { it.short_label() }, + ast::StructDef(it) => { it.short_label() }, + ast::EnumDef(it) => { it.short_label() }, + ast::TraitDef(it) => { it.short_label() }, + ast::Module(it) => { it.short_label() }, + ast::TypeAliasDef(it) => { it.short_label() }, + ast::ConstDef(it) => { it.short_label() }, + ast::StaticDef(it) => { it.short_label() }, + ast::RecordFieldDef(it) => { it.short_label() }, + ast::EnumVariant(it) => { it.short_label() }, + _ => None, + } + } +} diff --git a/crates/ra_ide/src/display/short_label.rs b/crates/ra_ide/src/display/short_label.rs new file mode 100644 index 000000000..9ffc9b980 --- /dev/null +++ b/crates/ra_ide/src/display/short_label.rs @@ -0,0 +1,97 @@ +//! FIXME: write short doc here + +use format_buf::format; +use ra_syntax::ast::{self, AstNode, NameOwner, TypeAscriptionOwner, VisibilityOwner}; + +pub(crate) trait ShortLabel { + fn short_label(&self) -> Option; +} + +impl ShortLabel for ast::FnDef { + fn short_label(&self) -> Option { + Some(crate::display::function_label(self)) + } +} + +impl ShortLabel for ast::StructDef { + fn short_label(&self) -> Option { + short_label_from_node(self, "struct ") + } +} + +impl ShortLabel for ast::UnionDef { + fn short_label(&self) -> Option { + short_label_from_node(self, "union ") + } +} + +impl ShortLabel for ast::EnumDef { + fn short_label(&self) -> Option { + short_label_from_node(self, "enum ") + } +} + +impl ShortLabel for ast::TraitDef { + fn short_label(&self) -> Option { + short_label_from_node(self, "trait ") + } +} + +impl ShortLabel for ast::Module { + fn short_label(&self) -> Option { + short_label_from_node(self, "mod ") + } +} + +impl ShortLabel for ast::TypeAliasDef { + fn short_label(&self) -> Option { + short_label_from_node(self, "type ") + } +} + +impl ShortLabel for ast::ConstDef { + fn short_label(&self) -> Option { + short_label_from_ascribed_node(self, "const ") + } +} + +impl ShortLabel for ast::StaticDef { + fn short_label(&self) -> Option { + short_label_from_ascribed_node(self, "static ") + } +} + +impl ShortLabel for ast::RecordFieldDef { + fn short_label(&self) -> Option { + short_label_from_ascribed_node(self, "") + } +} + +impl ShortLabel for ast::EnumVariant { + fn short_label(&self) -> Option { + Some(self.name()?.text().to_string()) + } +} + +fn short_label_from_ascribed_node(node: &T, prefix: &str) -> Option +where + T: NameOwner + VisibilityOwner + TypeAscriptionOwner, +{ + let mut buf = short_label_from_node(node, prefix)?; + + if let Some(type_ref) = node.ascribed_type() { + format!(buf, ": {}", type_ref.syntax()); + } + + Some(buf) +} + +fn short_label_from_node(node: &T, label: &str) -> Option +where + T: NameOwner + VisibilityOwner, +{ + let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default(); + buf.push_str(label); + buf.push_str(node.name()?.text().as_str()); + Some(buf) +} diff --git a/crates/ra_ide/src/display/structure.rs b/crates/ra_ide/src/display/structure.rs new file mode 100644 index 000000000..a80d65ac7 --- /dev/null +++ b/crates/ra_ide/src/display/structure.rs @@ -0,0 +1,401 @@ +//! FIXME: write short doc here + +use crate::TextRange; + +use ra_syntax::{ + ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, + match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, +}; + +#[derive(Debug, Clone)] +pub struct StructureNode { + pub parent: Option, + pub label: String, + pub navigation_range: TextRange, + pub node_range: TextRange, + pub kind: SyntaxKind, + pub detail: Option, + pub deprecated: bool, +} + +pub fn file_structure(file: &SourceFile) -> Vec { + let mut res = Vec::new(); + let mut stack = Vec::new(); + + for event in file.syntax().preorder() { + match event { + WalkEvent::Enter(node) => { + if let Some(mut symbol) = structure_node(&node) { + symbol.parent = stack.last().copied(); + stack.push(res.len()); + res.push(symbol); + } + } + WalkEvent::Leave(node) => { + if structure_node(&node).is_some() { + stack.pop().unwrap(); + } + } + } + } + res +} + +fn structure_node(node: &SyntaxNode) -> Option { + fn decl(node: N) -> Option { + decl_with_detail(node, None) + } + + fn decl_with_ascription( + node: N, + ) -> Option { + let ty = node.ascribed_type(); + decl_with_type_ref(node, ty) + } + + fn decl_with_type_ref( + node: N, + type_ref: Option, + ) -> Option { + let detail = type_ref.map(|type_ref| { + let mut detail = String::new(); + collapse_ws(type_ref.syntax(), &mut detail); + detail + }); + decl_with_detail(node, detail) + } + + fn decl_with_detail( + node: N, + detail: Option, + ) -> Option { + let name = node.name()?; + + Some(StructureNode { + parent: None, + label: name.text().to_string(), + navigation_range: name.syntax().text_range(), + node_range: node.syntax().text_range(), + kind: node.syntax().kind(), + detail, + deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"), + }) + } + + fn collapse_ws(node: &SyntaxNode, output: &mut String) { + let mut can_insert_ws = false; + node.text().for_each_chunk(|chunk| { + for line in chunk.lines() { + let line = line.trim(); + if line.is_empty() { + if can_insert_ws { + output.push(' '); + can_insert_ws = false; + } + } else { + output.push_str(line); + can_insert_ws = true; + } + } + }) + } + + match_ast! { + match node { + ast::FnDef(it) => { + let mut detail = String::from("fn"); + if let Some(type_param_list) = it.type_param_list() { + collapse_ws(type_param_list.syntax(), &mut detail); + } + if let Some(param_list) = it.param_list() { + collapse_ws(param_list.syntax(), &mut detail); + } + if let Some(ret_type) = it.ret_type() { + detail.push_str(" "); + collapse_ws(ret_type.syntax(), &mut detail); + } + + decl_with_detail(it, Some(detail)) + }, + ast::StructDef(it) => { decl(it) }, + ast::EnumDef(it) => { decl(it) }, + ast::EnumVariant(it) => { decl(it) }, + ast::TraitDef(it) => { decl(it) }, + ast::Module(it) => { decl(it) }, + ast::TypeAliasDef(it) => { + let ty = it.type_ref(); + decl_with_type_ref(it, ty) + }, + ast::RecordFieldDef(it) => { decl_with_ascription(it) }, + ast::ConstDef(it) => { decl_with_ascription(it) }, + ast::StaticDef(it) => { decl_with_ascription(it) }, + ast::ImplBlock(it) => { + let target_type = it.target_type()?; + let target_trait = it.target_trait(); + let label = match target_trait { + None => format!("impl {}", target_type.syntax().text()), + Some(t) => { + format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),) + } + }; + + let node = StructureNode { + parent: None, + label, + navigation_range: target_type.syntax().text_range(), + node_range: it.syntax().text_range(), + kind: it.syntax().kind(), + detail: None, + deprecated: false, + }; + Some(node) + }, + ast::MacroCall(it) => { + let first_token = it.syntax().first_token().unwrap(); + if first_token.text().as_str() != "macro_rules" { + return None; + } + decl(it) + }, + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use insta::assert_debug_snapshot; + + #[test] + fn test_file_structure() { + let file = SourceFile::parse( + r#" +struct Foo { + x: i32 +} + +mod m { + fn bar1() {} + fn bar2(t: T) -> T {} + fn bar3(a: A, + b: B) -> Vec< + u32 + > {} +} + +enum E { X, Y(i32) } +type T = (); +static S: i32 = 92; +const C: i32 = 92; + +impl E {} + +impl fmt::Debug for E {} + +macro_rules! mc { + () => {} +} + +#[deprecated] +fn obsolete() {} + +#[deprecated(note = "for awhile")] +fn very_obsolete() {} +"#, + ) + .ok() + .unwrap(); + let structure = file_structure(&file); + assert_debug_snapshot!(structure, + @r###" + [ + StructureNode { + parent: None, + label: "Foo", + navigation_range: [8; 11), + node_range: [1; 26), + kind: STRUCT_DEF, + detail: None, + deprecated: false, + }, + StructureNode { + parent: Some( + 0, + ), + label: "x", + navigation_range: [18; 19), + node_range: [18; 24), + kind: RECORD_FIELD_DEF, + detail: Some( + "i32", + ), + deprecated: false, + }, + StructureNode { + parent: None, + label: "m", + navigation_range: [32; 33), + node_range: [28; 158), + kind: MODULE, + detail: None, + deprecated: false, + }, + StructureNode { + parent: Some( + 2, + ), + label: "bar1", + navigation_range: [43; 47), + node_range: [40; 52), + kind: FN_DEF, + detail: Some( + "fn()", + ), + deprecated: false, + }, + StructureNode { + parent: Some( + 2, + ), + label: "bar2", + navigation_range: [60; 64), + node_range: [57; 81), + kind: FN_DEF, + detail: Some( + "fn(t: T) -> T", + ), + deprecated: false, + }, + StructureNode { + parent: Some( + 2, + ), + label: "bar3", + navigation_range: [89; 93), + node_range: [86; 156), + kind: FN_DEF, + detail: Some( + "fn(a: A, b: B) -> Vec< u32 >", + ), + deprecated: false, + }, + StructureNode { + parent: None, + label: "E", + navigation_range: [165; 166), + node_range: [160; 180), + kind: ENUM_DEF, + detail: None, + deprecated: false, + }, + StructureNode { + parent: Some( + 6, + ), + label: "X", + navigation_range: [169; 170), + node_range: [169; 170), + kind: ENUM_VARIANT, + detail: None, + deprecated: false, + }, + StructureNode { + parent: Some( + 6, + ), + label: "Y", + navigation_range: [172; 173), + node_range: [172; 178), + kind: ENUM_VARIANT, + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, + label: "T", + navigation_range: [186; 187), + node_range: [181; 193), + kind: TYPE_ALIAS_DEF, + detail: Some( + "()", + ), + deprecated: false, + }, + StructureNode { + parent: None, + label: "S", + navigation_range: [201; 202), + node_range: [194; 213), + kind: STATIC_DEF, + detail: Some( + "i32", + ), + deprecated: false, + }, + StructureNode { + parent: None, + label: "C", + navigation_range: [220; 221), + node_range: [214; 232), + kind: CONST_DEF, + detail: Some( + "i32", + ), + deprecated: false, + }, + StructureNode { + parent: None, + label: "impl E", + navigation_range: [239; 240), + node_range: [234; 243), + kind: IMPL_BLOCK, + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, + label: "impl fmt::Debug for E", + navigation_range: [265; 266), + node_range: [245; 269), + kind: IMPL_BLOCK, + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, + label: "mc", + navigation_range: [284; 286), + node_range: [271; 303), + kind: MACRO_CALL, + detail: None, + deprecated: false, + }, + StructureNode { + parent: None, + label: "obsolete", + navigation_range: [322; 330), + node_range: [305; 335), + kind: FN_DEF, + detail: Some( + "fn()", + ), + deprecated: true, + }, + StructureNode { + parent: None, + label: "very_obsolete", + navigation_range: [375; 388), + node_range: [337; 393), + kind: FN_DEF, + detail: Some( + "fn()", + ), + deprecated: true, + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/expand.rs b/crates/ra_ide/src/expand.rs new file mode 100644 index 000000000..2f1abf509 --- /dev/null +++ b/crates/ra_ide/src/expand.rs @@ -0,0 +1,63 @@ +//! Utilities to work with files, produced by macros. +use std::iter::successors; + +use hir::Source; +use ra_db::FileId; +use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken}; + +use crate::{db::RootDatabase, FileRange}; + +pub(crate) fn original_range(db: &RootDatabase, node: Source<&SyntaxNode>) -> FileRange { + let expansion = match node.file_id.expansion_info(db) { + None => { + return FileRange { + file_id: node.file_id.original_file(db), + range: node.value.text_range(), + } + } + Some(it) => it, + }; + // FIXME: the following completely wrong. + // + // *First*, we should try to map first and last tokens of node, and, if that + // fails, return the range of the overall macro expansions. + // + // *Second*, we should handle recurside macro expansions + + let token = node + .value + .descendants_with_tokens() + .filter_map(|it| it.into_token()) + .find_map(|it| expansion.map_token_up(node.with_value(&it))); + + match token { + Some(it) => { + FileRange { file_id: it.file_id.original_file(db), range: it.value.text_range() } + } + None => { + FileRange { file_id: node.file_id.original_file(db), range: node.value.text_range() } + } + } +} + +pub(crate) fn descend_into_macros( + db: &RootDatabase, + file_id: FileId, + token: SyntaxToken, +) -> Source { + let src = Source::new(file_id.into(), token); + + successors(Some(src), |token| { + let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; + let tt = macro_call.token_tree()?; + if !token.value.text_range().is_subrange(&tt.syntax().text_range()) { + return None; + } + let source_analyzer = + hir::SourceAnalyzer::new(db, token.with_value(token.value.parent()).as_ref(), None); + let exp = source_analyzer.expand(db, token.with_value(¯o_call))?; + exp.map_token_down(db, token.as_ref()) + }) + .last() + .unwrap() +} diff --git a/crates/ra_ide/src/expand_macro.rs b/crates/ra_ide/src/expand_macro.rs new file mode 100644 index 000000000..abc602244 --- /dev/null +++ b/crates/ra_ide/src/expand_macro.rs @@ -0,0 +1,295 @@ +//! This modules implements "expand macro" functionality in the IDE + +use crate::{db::RootDatabase, FilePosition}; +use hir::db::AstDatabase; +use ra_db::SourceDatabase; +use rustc_hash::FxHashMap; + +use ra_syntax::{ + algo::{find_node_at_offset, replace_descendants}, + ast::{self}, + AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T, +}; + +pub struct ExpandedMacro { + pub name: String, + pub expansion: String, +} + +pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option { + let parse = db.parse(position.file_id); + let file = parse.tree(); + let name_ref = find_node_at_offset::(file.syntax(), position.offset)?; + let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?; + + let source = hir::Source::new(position.file_id.into(), mac.syntax()); + let expanded = expand_macro_recur(db, source, source.with_value(&mac))?; + + // FIXME: + // macro expansion may lose all white space information + // But we hope someday we can use ra_fmt for that + let expansion = insert_whitespaces(expanded); + Some(ExpandedMacro { name: name_ref.text().to_string(), expansion }) +} + +fn expand_macro_recur( + db: &RootDatabase, + source: hir::Source<&SyntaxNode>, + macro_call: hir::Source<&ast::MacroCall>, +) -> Option { + let analyzer = hir::SourceAnalyzer::new(db, source, None); + let expansion = analyzer.expand(db, macro_call)?; + let macro_file_id = expansion.file_id(); + let mut expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?; + + let children = expanded.descendants().filter_map(ast::MacroCall::cast); + let mut replaces = FxHashMap::default(); + + for child in children.into_iter() { + let node = hir::Source::new(macro_file_id, &child); + if let Some(new_node) = expand_macro_recur(db, source, node) { + // Replace the whole node if it is root + // `replace_descendants` will not replace the parent node + // but `SyntaxNode::descendants include itself + if expanded == *child.syntax() { + expanded = new_node; + } else { + replaces.insert(child.syntax().clone().into(), new_node.into()); + } + } + } + + Some(replace_descendants(&expanded, &replaces)) +} + +// FIXME: It would also be cool to share logic here and in the mbe tests, +// which are pretty unreadable at the moment. +fn insert_whitespaces(syn: SyntaxNode) -> String { + use SyntaxKind::*; + + let mut res = String::new(); + let mut token_iter = syn + .preorder_with_tokens() + .filter_map(|event| { + if let WalkEvent::Enter(NodeOrToken::Token(token)) = event { + Some(token) + } else { + None + } + }) + .peekable(); + + let mut indent = 0; + let mut last: Option = None; + + while let Some(token) = token_iter.next() { + let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool { + token_iter.peek().map(|it| f(it.kind())).unwrap_or(default) + }; + let is_last = |f: fn(SyntaxKind) -> bool, default| -> bool { + last.map(|it| f(it)).unwrap_or(default) + }; + + res += &match token.kind() { + k @ _ if is_text(k) && is_next(|it| !it.is_punct(), true) => { + token.text().to_string() + " " + } + L_CURLY if is_next(|it| it != R_CURLY, true) => { + indent += 1; + let leading_space = if is_last(|it| is_text(it), false) { " " } else { "" }; + format!("{}{{\n{}", leading_space, " ".repeat(indent)) + } + R_CURLY if is_last(|it| it != L_CURLY, true) => { + indent = indent.checked_sub(1).unwrap_or(0); + format!("\n{}}}", " ".repeat(indent)) + } + R_CURLY => format!("}}\n{}", " ".repeat(indent)), + T![;] => format!(";\n{}", " ".repeat(indent)), + T![->] => " -> ".to_string(), + T![=] => " = ".to_string(), + T![=>] => " => ".to_string(), + _ => token.text().to_string(), + }; + + last = Some(token.kind()); + } + + return res; + + fn is_text(k: SyntaxKind) -> bool { + k.is_keyword() || k.is_literal() || k == IDENT + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock_analysis::analysis_and_position; + use insta::assert_snapshot; + + fn check_expand_macro(fixture: &str) -> ExpandedMacro { + let (analysis, pos) = analysis_and_position(fixture); + analysis.expand_macro(pos).unwrap().unwrap() + } + + #[test] + fn macro_expand_recursive_expansion() { + let res = check_expand_macro( + r#" + //- /lib.rs + macro_rules! bar { + () => { fn b() {} } + } + macro_rules! foo { + () => { bar!(); } + } + macro_rules! baz { + () => { foo!(); } + } + f<|>oo!(); + "#, + ); + + assert_eq!(res.name, "foo"); + assert_snapshot!(res.expansion, @r###" +fn b(){} +"###); + } + + #[test] + fn macro_expand_multiple_lines() { + let res = check_expand_macro( + r#" + //- /lib.rs + macro_rules! foo { + () => { + fn some_thing() -> u32 { + let a = 0; + a + 10 + } + } + } + f<|>oo!(); + "#, + ); + + assert_eq!(res.name, "foo"); + assert_snapshot!(res.expansion, @r###" +fn some_thing() -> u32 { + let a = 0; + a+10 +} +"###); + } + + #[test] + fn macro_expand_match_ast() { + let res = check_expand_macro( + r#" + //- /lib.rs + macro_rules! match_ast { + (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + + (match ($node:expr) { + $( ast::$ast:ident($it:ident) => $res:block, )* + _ => $catch_all:expr $(,)? + }) => {{ + $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* + { $catch_all } + }}; + } + + fn main() { + mat<|>ch_ast! { + match container { + ast::TraitDef(it) => {}, + ast::ImplBlock(it) => {}, + _ => { continue }, + } + } + } + "#, + ); + + assert_eq!(res.name, "match_ast"); + assert_snapshot!(res.expansion, @r###" +{ + if let Some(it) = ast::TraitDef::cast(container.clone()){} + else if let Some(it) = ast::ImplBlock::cast(container.clone()){} + else { + { + continue + } + } +} +"###); + } + + #[test] + fn macro_expand_match_ast_inside_let_statement() { + let res = check_expand_macro( + r#" + //- /lib.rs + macro_rules! match_ast { + (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; + (match ($node:expr) {}) => {{}}; + } + + fn main() { + let p = f(|it| { + let res = mat<|>ch_ast! { match c {}}; + Some(res) + })?; + } + "#, + ); + + assert_eq!(res.name, "match_ast"); + assert_snapshot!(res.expansion, @r###"{}"###); + } + + #[test] + fn macro_expand_inner_macro_fail_to_expand() { + let res = check_expand_macro( + r#" + //- /lib.rs + macro_rules! bar { + (BAD) => {}; + } + macro_rules! foo { + () => {bar!()}; + } + + fn main() { + let res = fo<|>o!(); + } + "#, + ); + + assert_eq!(res.name, "foo"); + assert_snapshot!(res.expansion, @r###"bar!()"###); + } + + #[test] + fn macro_expand_with_dollar_crate() { + let res = check_expand_macro( + r#" + //- /lib.rs + #[macro_export] + macro_rules! bar { + () => {0}; + } + macro_rules! foo { + () => {$crate::bar!()}; + } + + fn main() { + let res = fo<|>o!(); + } + "#, + ); + + assert_eq!(res.name, "foo"); + assert_snapshot!(res.expansion, @r###"0"###); + } +} diff --git a/crates/ra_ide/src/extend_selection.rs b/crates/ra_ide/src/extend_selection.rs new file mode 100644 index 000000000..4b7bfc0b1 --- /dev/null +++ b/crates/ra_ide/src/extend_selection.rs @@ -0,0 +1,452 @@ +//! FIXME: write short doc here + +use ra_db::SourceDatabase; +use ra_syntax::{ + algo::find_covering_element, + ast::{self, AstNode, AstToken}, + Direction, NodeOrToken, + SyntaxKind::{self, *}, + SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, +}; + +use crate::{db::RootDatabase, FileRange}; + +// FIXME: restore macro support +pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { + let parse = db.parse(frange.file_id); + try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range) +} + +fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option { + let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; + let list_kinds = [ + RECORD_FIELD_PAT_LIST, + MATCH_ARM_LIST, + RECORD_FIELD_DEF_LIST, + TUPLE_FIELD_DEF_LIST, + RECORD_FIELD_LIST, + ENUM_VARIANT_LIST, + USE_TREE_LIST, + TYPE_PARAM_LIST, + TYPE_ARG_LIST, + TYPE_BOUND_LIST, + PARAM_LIST, + ARG_LIST, + ARRAY_EXPR, + TUPLE_EXPR, + WHERE_CLAUSE, + ]; + + if range.is_empty() { + let offset = range.start(); + let mut leaves = root.token_at_offset(offset); + if leaves.clone().all(|it| it.kind() == WHITESPACE) { + return Some(extend_ws(root, leaves.next()?, offset)); + } + let leaf_range = match leaves { + TokenAtOffset::None => return None, + TokenAtOffset::Single(l) => { + if string_kinds.contains(&l.kind()) { + extend_single_word_in_comment_or_string(&l, offset) + .unwrap_or_else(|| l.text_range()) + } else { + l.text_range() + } + } + TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(), + }; + return Some(leaf_range); + }; + let node = match find_covering_element(root, range) { + NodeOrToken::Token(token) => { + if token.text_range() != range { + return Some(token.text_range()); + } + if let Some(comment) = ast::Comment::cast(token.clone()) { + if let Some(range) = extend_comments(comment) { + return Some(range); + } + } + token.parent() + } + NodeOrToken::Node(node) => node, + }; + if node.text_range() != range { + return Some(node.text_range()); + } + + // Using shallowest node with same range allows us to traverse siblings. + let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap(); + + if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { + if let Some(range) = extend_list_item(&node) { + return Some(range); + } + } + + node.parent().map(|it| it.text_range()) +} + +fn extend_single_word_in_comment_or_string( + leaf: &SyntaxToken, + offset: TextUnit, +) -> Option { + let text: &str = leaf.text(); + let cursor_position: u32 = (offset - leaf.text_range().start()).into(); + + let (before, after) = text.split_at(cursor_position as usize); + + fn non_word_char(c: char) -> bool { + !(c.is_alphanumeric() || c == '_') + } + + let start_idx = before.rfind(non_word_char)? as u32; + let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32; + + let from: TextUnit = (start_idx + 1).into(); + let to: TextUnit = (cursor_position + end_idx).into(); + + let range = TextRange::from_to(from, to); + if range.is_empty() { + None + } else { + Some(range + leaf.text_range().start()) + } +} + +fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextUnit) -> TextRange { + let ws_text = ws.text(); + let suffix = TextRange::from_to(offset, ws.text_range().end()) - ws.text_range().start(); + let prefix = TextRange::from_to(ws.text_range().start(), offset) - ws.text_range().start(); + let ws_suffix = &ws_text.as_str()[suffix]; + let ws_prefix = &ws_text.as_str()[prefix]; + if ws_text.contains('\n') && !ws_suffix.contains('\n') { + if let Some(node) = ws.next_sibling_or_token() { + let start = match ws_prefix.rfind('\n') { + Some(idx) => ws.text_range().start() + TextUnit::from((idx + 1) as u32), + None => node.text_range().start(), + }; + let end = if root.text().char_at(node.text_range().end()) == Some('\n') { + node.text_range().end() + TextUnit::of_char('\n') + } else { + node.text_range().end() + }; + return TextRange::from_to(start, end); + } + } + ws.text_range() +} + +fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken { + return if priority(&r) > priority(&l) { r } else { l }; + fn priority(n: &SyntaxToken) -> usize { + match n.kind() { + WHITESPACE => 0, + IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2, + _ => 1, + } + } +} + +/// Extend list item selection to include nearby delimiter and whitespace. +fn extend_list_item(node: &SyntaxNode) -> Option { + fn is_single_line_ws(node: &SyntaxToken) -> bool { + node.kind() == WHITESPACE && !node.text().contains('\n') + } + + fn nearby_delimiter( + delimiter_kind: SyntaxKind, + node: &SyntaxNode, + dir: Direction, + ) -> Option { + node.siblings_with_tokens(dir) + .skip(1) + .skip_while(|node| match node { + NodeOrToken::Node(_) => false, + NodeOrToken::Token(it) => is_single_line_ws(it), + }) + .next() + .and_then(|it| it.into_token()) + .filter(|node| node.kind() == delimiter_kind) + } + + let delimiter = match node.kind() { + TYPE_BOUND => T![+], + _ => T![,], + }; + if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) { + return Some(TextRange::from_to( + delimiter_node.text_range().start(), + node.text_range().end(), + )); + } + if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) { + // Include any following whitespace when delimiter is after list item. + let final_node = delimiter_node + .next_sibling_or_token() + .and_then(|it| it.into_token()) + .filter(|node| is_single_line_ws(node)) + .unwrap_or(delimiter_node); + + return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); + } + + None +} + +fn extend_comments(comment: ast::Comment) -> Option { + let prev = adj_comments(&comment, Direction::Prev); + let next = adj_comments(&comment, Direction::Next); + if prev != next { + Some(TextRange::from_to( + prev.syntax().text_range().start(), + next.syntax().text_range().end(), + )) + } else { + None + } +} + +fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment { + let mut res = comment.clone(); + for element in comment.syntax().siblings_with_tokens(dir) { + let token = match element.as_token() { + None => break, + Some(token) => token, + }; + if let Some(c) = ast::Comment::cast(token.clone()) { + res = c + } else if token.kind() != WHITESPACE || token.text().contains("\n\n") { + break; + } + } + res +} + +#[cfg(test)] +mod tests { + use ra_syntax::{AstNode, SourceFile}; + use test_utils::extract_offset; + + use super::*; + + fn do_check(before: &str, afters: &[&str]) { + let (cursor, before) = extract_offset(before); + let parse = SourceFile::parse(&before); + let mut range = TextRange::offset_len(cursor, 0.into()); + for &after in afters { + range = try_extend_selection(parse.tree().syntax(), range).unwrap(); + let actual = &before[range]; + assert_eq!(after, actual); + } + } + + #[test] + fn test_extend_selection_arith() { + do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]); + } + + #[test] + fn test_extend_selection_list() { + do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]); + do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]); + do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]); + do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]); + do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]); + do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]); + + do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]); + do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]); + do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]); + + do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]); + + do_check( + r#" +const FOO: [usize; 2] = [ + 22, + <|>33, +]"#, + &["33", "33,"], + ); + + do_check( + r#" +const FOO: [usize; 2] = [ + 22 + , 33<|>, +]"#, + &["33", ", 33"], + ); + } + + #[test] + fn test_extend_selection_start_of_the_line() { + do_check( + r#" +impl S { +<|> fn foo() { + + } +}"#, + &[" fn foo() {\n\n }\n"], + ); + } + + #[test] + fn test_extend_selection_doc_comments() { + do_check( + r#" +struct A; + +/// bla +/// bla +struct B { + <|> +} + "#, + &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"], + ) + } + + #[test] + fn test_extend_selection_comments() { + do_check( + r#" +fn bar(){} + +// fn foo() { +// 1 + <|>1 +// } + +// fn foo(){} + "#, + &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"], + ); + + do_check( + r#" +// #[derive(Debug, Clone, Copy, PartialEq, Eq)] +// pub enum Direction { +// <|> Next, +// Prev +// } +"#, + &[ + "// Next,", + "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }", + ], + ); + + do_check( + r#" +/* +foo +_bar1<|>*/ +"#, + &["_bar1", "/*\nfoo\n_bar1*/"], + ); + + do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]); + + do_check(r#"/<|>/foo bar"#, &["//foo bar"]); + } + + #[test] + fn test_extend_selection_prefer_idents() { + do_check( + r#" +fn main() { foo<|>+bar;} +"#, + &["foo", "foo+bar"], + ); + do_check( + r#" +fn main() { foo+<|>bar;} +"#, + &["bar", "foo+bar"], + ); + } + + #[test] + fn test_extend_selection_prefer_lifetimes() { + do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]); + do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]); + } + + #[test] + fn test_extend_selection_select_first_word() { + do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]); + do_check( + r#" +impl S { +fn foo() { +// hel<|>lo world +} +} +"#, + &["hello", "// hello world"], + ); + } + + #[test] + fn test_extend_selection_string() { + do_check( + r#" +fn bar(){} + +" fn f<|>oo() {" +"#, + &["foo", "\" fn foo() {\""], + ); + } + + #[test] + fn test_extend_trait_bounds_list_in_where_clause() { + do_check( + r#" +fn foo() + where + R: req::Request + 'static, + R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static, + R::Result: Serialize + 'static, +"#, + &[ + "DeserializeOwned", + "DeserializeOwned + ", + "DeserializeOwned + panic::UnwindSafe + 'static", + "R::Params: DeserializeOwned + panic::UnwindSafe + 'static", + "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,", + ], + ); + do_check(r#"fn foo() where T: <|>Copy"#, &["Copy"]); + do_check(r#"fn foo() where T: <|>Copy + Display"#, &["Copy", "Copy + "]); + do_check(r#"fn foo() where T: <|>Copy +Display"#, &["Copy", "Copy +"]); + do_check(r#"fn foo() where T: <|>Copy+Display"#, &["Copy", "Copy+"]); + do_check(r#"fn foo() where T: Copy + <|>Display"#, &["Display", "+ Display"]); + do_check(r#"fn foo() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]); + do_check(r#"fn foo() where T: Copy +<|>Display"#, &["Display", "+Display"]); + } + + #[test] + fn test_extend_trait_bounds_list_inline() { + do_check(r#"fn fooCopy>() {}"#, &["Copy"]); + do_check(r#"fn fooCopy + Display>() {}"#, &["Copy", "Copy + "]); + do_check(r#"fn fooCopy +Display>() {}"#, &["Copy", "Copy +"]); + do_check(r#"fn fooCopy+Display>() {}"#, &["Copy", "Copy+"]); + do_check(r#"fn fooDisplay>() {}"#, &["Display", "+ Display"]); + do_check(r#"fn fooDisplay + Sync>() {}"#, &["Display", "+ Display"]); + do_check(r#"fn fooDisplay>() {}"#, &["Display", "+Display"]); + do_check( + r#"fn foo + Display, U: Copy>() {}"#, + &[ + "Copy", + "Copy + ", + "Copy + Display", + "T: Copy + Display", + "T: Copy + Display, ", + "", + ], + ); + } +} diff --git a/crates/ra_ide/src/feature_flags.rs b/crates/ra_ide/src/feature_flags.rs new file mode 100644 index 000000000..de4ae513d --- /dev/null +++ b/crates/ra_ide/src/feature_flags.rs @@ -0,0 +1,70 @@ +//! FIXME: write short doc here + +use rustc_hash::FxHashMap; + +/// Feature flags hold fine-grained toggles for all *user-visible* features of +/// rust-analyzer. +/// +/// The exists such that users are able to disable any annoying feature (and, +/// with many users and many features, some features are bound to be annoying +/// for some users) +/// +/// Note that we purposefully use run-time checked strings, and not something +/// checked at compile time, to keep things simple and flexible. +/// +/// Also note that, at the moment, `FeatureFlags` also store features for +/// `ra_lsp_server`. This should be benign layering violation. +#[derive(Debug)] +pub struct FeatureFlags { + flags: FxHashMap, +} + +impl FeatureFlags { + fn new(flags: &[(&str, bool)]) -> FeatureFlags { + let flags = flags + .iter() + .map(|&(name, value)| { + check_flag_name(name); + (name.to_string(), value) + }) + .collect(); + FeatureFlags { flags } + } + + pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> { + match self.flags.get_mut(flag) { + None => Err(()), + Some(slot) => { + *slot = value; + Ok(()) + } + } + } + + pub fn get(&self, flag: &str) -> bool { + match self.flags.get(flag) { + None => panic!("unknown flag: {:?}", flag), + Some(value) => *value, + } + } +} + +impl Default for FeatureFlags { + fn default() -> FeatureFlags { + FeatureFlags::new(&[ + ("lsp.diagnostics", true), + ("completion.insertion.add-call-parenthesis", true), + ("completion.enable-postfix", true), + ("notifications.workspace-loaded", true), + ]) + } +} + +fn check_flag_name(flag: &str) { + for c in flag.bytes() { + match c { + b'a'..=b'z' | b'-' | b'.' => (), + _ => panic!("flag name does not match conventions: {:?}", flag), + } + } +} diff --git a/crates/ra_ide/src/folding_ranges.rs b/crates/ra_ide/src/folding_ranges.rs new file mode 100644 index 000000000..4eeb76d14 --- /dev/null +++ b/crates/ra_ide/src/folding_ranges.rs @@ -0,0 +1,378 @@ +//! FIXME: write short doc here + +use rustc_hash::FxHashSet; + +use ra_syntax::{ + ast::{self, AstNode, AstToken, VisibilityOwner}, + Direction, NodeOrToken, SourceFile, + SyntaxKind::{self, *}, + SyntaxNode, TextRange, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum FoldKind { + Comment, + Imports, + Mods, + Block, +} + +#[derive(Debug)] +pub struct Fold { + pub range: TextRange, + pub kind: FoldKind, +} + +pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { + let mut res = vec![]; + let mut visited_comments = FxHashSet::default(); + let mut visited_imports = FxHashSet::default(); + let mut visited_mods = FxHashSet::default(); + + for element in file.syntax().descendants_with_tokens() { + // Fold items that span multiple lines + if let Some(kind) = fold_kind(element.kind()) { + let is_multiline = match &element { + NodeOrToken::Node(node) => node.text().contains_char('\n'), + NodeOrToken::Token(token) => token.text().contains('\n'), + }; + if is_multiline { + res.push(Fold { range: element.text_range(), kind }); + continue; + } + } + + match element { + NodeOrToken::Token(token) => { + // Fold groups of comments + if let Some(comment) = ast::Comment::cast(token) { + if !visited_comments.contains(&comment) { + if let Some(range) = + contiguous_range_for_comment(comment, &mut visited_comments) + { + res.push(Fold { range, kind: FoldKind::Comment }) + } + } + } + } + NodeOrToken::Node(node) => { + // Fold groups of imports + if node.kind() == USE_ITEM && !visited_imports.contains(&node) { + if let Some(range) = contiguous_range_for_group(&node, &mut visited_imports) { + res.push(Fold { range, kind: FoldKind::Imports }) + } + } + + // Fold groups of mods + if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node) + { + if let Some(range) = + contiguous_range_for_group_unless(&node, has_visibility, &mut visited_mods) + { + res.push(Fold { range, kind: FoldKind::Mods }) + } + } + } + } + } + + res +} + +fn fold_kind(kind: SyntaxKind) -> Option { + match kind { + COMMENT => Some(FoldKind::Comment), + USE_ITEM => Some(FoldKind::Imports), + RECORD_FIELD_DEF_LIST + | RECORD_FIELD_PAT_LIST + | ITEM_LIST + | EXTERN_ITEM_LIST + | USE_TREE_LIST + | BLOCK + | MATCH_ARM_LIST + | ENUM_VARIANT_LIST + | TOKEN_TREE => Some(FoldKind::Block), + _ => None, + } +} + +fn has_visibility(node: &SyntaxNode) -> bool { + ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some() +} + +fn contiguous_range_for_group( + first: &SyntaxNode, + visited: &mut FxHashSet, +) -> Option { + contiguous_range_for_group_unless(first, |_| false, visited) +} + +fn contiguous_range_for_group_unless( + first: &SyntaxNode, + unless: impl Fn(&SyntaxNode) -> bool, + visited: &mut FxHashSet, +) -> Option { + visited.insert(first.clone()); + + let mut last = first.clone(); + for element in first.siblings_with_tokens(Direction::Next) { + let node = match element { + NodeOrToken::Token(token) => { + if let Some(ws) = ast::Whitespace::cast(token) { + if !ws.spans_multiple_lines() { + // Ignore whitespace without blank lines + continue; + } + } + // There is a blank line or another token, which means that the + // group ends here + break; + } + NodeOrToken::Node(node) => node, + }; + + // Stop if we find a node that doesn't belong to the group + if node.kind() != first.kind() || unless(&node) { + break; + } + + visited.insert(node.clone()); + last = node; + } + + if first != &last { + Some(TextRange::from_to(first.text_range().start(), last.text_range().end())) + } else { + // The group consists of only one element, therefore it cannot be folded + None + } +} + +fn contiguous_range_for_comment( + first: ast::Comment, + visited: &mut FxHashSet, +) -> Option { + visited.insert(first.clone()); + + // Only fold comments of the same flavor + let group_kind = first.kind(); + if !group_kind.shape.is_line() { + return None; + } + + let mut last = first.clone(); + for element in first.syntax().siblings_with_tokens(Direction::Next) { + match element { + NodeOrToken::Token(token) => { + if let Some(ws) = ast::Whitespace::cast(token.clone()) { + if !ws.spans_multiple_lines() { + // Ignore whitespace without blank lines + continue; + } + } + if let Some(c) = ast::Comment::cast(token) { + if c.kind() == group_kind { + visited.insert(c.clone()); + last = c; + continue; + } + } + // The comment group ends because either: + // * An element of a different kind was reached + // * A comment of a different flavor was reached + break; + } + NodeOrToken::Node(_) => break, + }; + } + + if first != last { + Some(TextRange::from_to( + first.syntax().text_range().start(), + last.syntax().text_range().end(), + )) + } else { + // The group consists of only one element, therefore it cannot be folded + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use test_utils::extract_ranges; + + fn do_check(text: &str, fold_kinds: &[FoldKind]) { + let (ranges, text) = extract_ranges(text, "fold"); + let parse = SourceFile::parse(&text); + let folds = folding_ranges(&parse.tree()); + + assert_eq!( + folds.len(), + ranges.len(), + "The amount of folds is different than the expected amount" + ); + assert_eq!( + folds.len(), + fold_kinds.len(), + "The amount of fold kinds is different than the expected amount" + ); + for ((fold, range), fold_kind) in + folds.iter().zip(ranges.into_iter()).zip(fold_kinds.iter()) + { + assert_eq!(fold.range.start(), range.start()); + assert_eq!(fold.range.end(), range.end()); + assert_eq!(&fold.kind, fold_kind); + } + } + + #[test] + fn test_fold_comments() { + let text = r#" +// Hello +// this is a multiline +// comment +// + +// But this is not + +fn main() { + // We should + // also + // fold + // this one. + //! But this one is different + //! because it has another flavor + /* As does this + multiline comment */ +}"#; + + let fold_kinds = &[ + FoldKind::Comment, + FoldKind::Block, + FoldKind::Comment, + FoldKind::Comment, + FoldKind::Comment, + ]; + do_check(text, fold_kinds); + } + + #[test] + fn test_fold_imports() { + let text = r#" +use std::{ + str, + vec, + io as iop +}; + +fn main() { +}"#; + + let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_mods() { + let text = r#" + +pub mod foo; +mod after_pub; +mod after_pub_next; + +mod before_pub; +mod before_pub_next; +pub mod bar; + +mod not_folding_single; +pub mod foobar; +pub not_folding_single_next; + +#[cfg(test)] +mod with_attribute; +mod with_attribute_next; + +fn main() { +}"#; + + let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_import_groups() { + let text = r#" +use std::str; +use std::vec; +use std::io as iop; + +use std::mem; +use std::f64; + +use std::collections::HashMap; +// Some random comment +use std::collections::VecDeque; + +fn main() { +}"#; + + let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_import_and_groups() { + let text = r#" +use std::str; +use std::vec; +use std::io as iop; + +use std::mem; +use std::f64; + +use std::collections::{ + HashMap, + VecDeque, +}; +// Some random comment + +fn main() { +}"#; + + let folds = &[ + FoldKind::Imports, + FoldKind::Imports, + FoldKind::Imports, + FoldKind::Block, + FoldKind::Block, + ]; + do_check(text, folds); + } + + #[test] + fn test_folds_macros() { + let text = r#" +macro_rules! foo { + ($($tt:tt)*) => { $($tt)* } +} +"#; + + let folds = &[FoldKind::Block]; + do_check(text, folds); + } + + #[test] + fn test_fold_match_arms() { + let text = r#" +fn main() { + match 0 { + 0 => 0, + _ => 1, + } +}"#; + + let folds = &[FoldKind::Block, FoldKind::Block]; + do_check(text, folds); + } +} diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs new file mode 100644 index 000000000..c10a6c844 --- /dev/null +++ b/crates/ra_ide/src/goto_definition.rs @@ -0,0 +1,696 @@ +//! FIXME: write short doc here + +use hir::{db::AstDatabase, Source}; +use ra_syntax::{ + ast::{self, DocCommentsOwner}, + match_ast, AstNode, SyntaxNode, +}; + +use crate::{ + db::RootDatabase, + display::{ShortLabel, ToNav}, + expand::descend_into_macros, + references::{classify_name_ref, NameKind::*}, + FilePosition, NavigationTarget, RangeInfo, +}; + +pub(crate) fn goto_definition( + db: &RootDatabase, + position: FilePosition, +) -> Option>> { + let file = db.parse_or_expand(position.file_id.into())?; + let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; + let token = descend_into_macros(db, position.file_id, token); + + let res = match_ast! { + match (token.value.parent()) { + ast::NameRef(name_ref) => { + let navs = reference_definition(db, token.with_value(&name_ref)).to_vec(); + RangeInfo::new(name_ref.syntax().text_range(), navs.to_vec()) + }, + ast::Name(name) => { + let navs = name_definition(db, token.with_value(&name))?; + RangeInfo::new(name.syntax().text_range(), navs) + + }, + _ => return None, + } + }; + + Some(res) +} + +#[derive(Debug)] +pub(crate) enum ReferenceResult { + Exact(NavigationTarget), + Approximate(Vec), +} + +impl ReferenceResult { + fn to_vec(self) -> Vec { + use self::ReferenceResult::*; + match self { + Exact(target) => vec![target], + Approximate(vec) => vec, + } + } +} + +pub(crate) fn reference_definition( + db: &RootDatabase, + name_ref: Source<&ast::NameRef>, +) -> ReferenceResult { + use self::ReferenceResult::*; + + let name_kind = classify_name_ref(db, name_ref).map(|d| d.kind); + match name_kind { + Some(Macro(mac)) => return Exact(mac.to_nav(db)), + Some(Field(field)) => return Exact(field.to_nav(db)), + Some(AssocItem(assoc)) => return Exact(assoc.to_nav(db)), + Some(Def(def)) => match NavigationTarget::from_def(db, def) { + Some(nav) => return Exact(nav), + None => return Approximate(vec![]), + }, + Some(SelfType(imp)) => { + // FIXME: ideally, this should point to the type in the impl, and + // not at the whole impl. And goto **type** definition should bring + // us to the actual type + return Exact(imp.to_nav(db)); + } + Some(Local(local)) => return Exact(local.to_nav(db)), + Some(GenericParam(_)) => { + // FIXME: go to the generic param def + } + None => {} + }; + + // Fallback index based approach: + let navs = crate::symbol_index::index_resolve(db, name_ref.value) + .into_iter() + .map(|s| s.to_nav(db)) + .collect(); + Approximate(navs) +} + +pub(crate) fn name_definition( + db: &RootDatabase, + name: Source<&ast::Name>, +) -> Option> { + let parent = name.value.syntax().parent()?; + + if let Some(module) = ast::Module::cast(parent.clone()) { + if module.has_semi() { + let src = name.with_value(module); + if let Some(child_module) = hir::Module::from_declaration(db, src) { + let nav = child_module.to_nav(db); + return Some(vec![nav]); + } + } + } + + if let Some(nav) = named_target(db, name.with_value(&parent)) { + return Some(vec![nav]); + } + + None +} + +fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option { + match_ast! { + match (node.value) { + ast::StructDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::EnumDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::EnumVariant(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::FnDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::TypeAliasDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::ConstDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::StaticDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::TraitDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::RecordFieldDef(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::Module(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + it.short_label(), + )) + }, + ast::MacroCall(it) => { + Some(NavigationTarget::from_named( + db, + node.with_value(&it), + it.doc_comment_text(), + None, + )) + }, + _ => None, + } + } +} + +#[cfg(test)] +mod tests { + use test_utils::covers; + + use crate::mock_analysis::analysis_and_position; + + fn check_goto(fixture: &str, expected: &str) { + let (analysis, pos) = analysis_and_position(fixture); + + let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; + assert_eq!(navs.len(), 1); + let nav = navs.pop().unwrap(); + nav.assert_match(expected); + } + + #[test] + fn goto_definition_works_in_items() { + check_goto( + " + //- /lib.rs + struct Foo; + enum E { X(Foo<|>) } + ", + "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + ); + } + + #[test] + fn goto_definition_resolves_correct_name() { + check_goto( + " + //- /lib.rs + use a::Foo; + mod a; + mod b; + enum E { X(Foo<|>) } + //- /a.rs + struct Foo; + //- /b.rs + struct Foo; + ", + "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)", + ); + } + + #[test] + fn goto_definition_works_for_module_declaration() { + check_goto( + " + //- /lib.rs + mod <|>foo; + //- /foo.rs + // empty + ", + "foo SOURCE_FILE FileId(2) [0; 10)", + ); + + check_goto( + " + //- /lib.rs + mod <|>foo; + //- /foo/mod.rs + // empty + ", + "foo SOURCE_FILE FileId(2) [0; 10)", + ); + } + + #[test] + fn goto_definition_works_for_macros() { + covers!(goto_definition_works_for_macros); + check_goto( + " + //- /lib.rs + macro_rules! foo { + () => { + {} + }; + } + + fn bar() { + <|>foo!(); + } + ", + "foo MACRO_CALL FileId(1) [0; 50) [13; 16)", + ); + } + + #[test] + fn goto_definition_works_for_macros_from_other_crates() { + covers!(goto_definition_works_for_macros); + check_goto( + " + //- /lib.rs + use foo::foo; + fn bar() { + <|>foo!(); + } + + //- /foo/lib.rs + #[macro_export] + macro_rules! foo { + () => { + {} + }; + } + ", + "foo MACRO_CALL FileId(2) [0; 66) [29; 32)", + ); + } + + #[test] + fn goto_definition_works_for_macros_in_use_tree() { + check_goto( + " + //- /lib.rs + use foo::foo<|>; + + //- /foo/lib.rs + #[macro_export] + macro_rules! foo { + () => { + {} + }; + } + ", + "foo MACRO_CALL FileId(2) [0; 66) [29; 32)", + ); + } + + #[test] + fn goto_definition_works_for_macro_defined_fn_with_arg() { + check_goto( + " + //- /lib.rs + macro_rules! define_fn { + ($name:ident) => (fn $name() {}) + } + + define_fn!( + foo + ) + + fn bar() { + <|>foo(); + } + ", + "foo FN_DEF FileId(1) [80; 83) [80; 83)", + ); + } + + #[test] + fn goto_definition_works_for_macro_defined_fn_no_arg() { + check_goto( + " + //- /lib.rs + macro_rules! define_fn { + () => (fn foo() {}) + } + + define_fn!(); + + fn bar() { + <|>foo(); + } + ", + "foo FN_DEF FileId(1) [39; 42) [39; 42)", + ); + } + + #[test] + fn goto_definition_works_for_methods() { + covers!(goto_definition_works_for_methods); + check_goto( + " + //- /lib.rs + struct Foo; + impl Foo { + fn frobnicate(&self) { } + } + + fn bar(foo: &Foo) { + foo.frobnicate<|>(); + } + ", + "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)", + ); + } + + #[test] + fn goto_definition_works_for_fields() { + covers!(goto_definition_works_for_fields); + check_goto( + " + //- /lib.rs + struct Foo { + spam: u32, + } + + fn bar(foo: &Foo) { + foo.spam<|>; + } + ", + "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)", + ); + } + + #[test] + fn goto_definition_works_for_record_fields() { + covers!(goto_definition_works_for_record_fields); + check_goto( + " + //- /lib.rs + struct Foo { + spam: u32, + } + + fn bar() -> Foo { + Foo { + spam<|>: 0, + } + } + ", + "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)", + ); + } + + #[test] + fn goto_definition_works_for_ufcs_inherent_methods() { + check_goto( + " + //- /lib.rs + struct Foo; + impl Foo { + fn frobnicate() { } + } + + fn bar(foo: &Foo) { + Foo::frobnicate<|>(); + } + ", + "frobnicate FN_DEF FileId(1) [27; 47) [30; 40)", + ); + } + + #[test] + fn goto_definition_works_for_ufcs_trait_methods_through_traits() { + check_goto( + " + //- /lib.rs + trait Foo { + fn frobnicate(); + } + + fn bar() { + Foo::frobnicate<|>(); + } + ", + "frobnicate FN_DEF FileId(1) [16; 32) [19; 29)", + ); + } + + #[test] + fn goto_definition_works_for_ufcs_trait_methods_through_self() { + check_goto( + " + //- /lib.rs + struct Foo; + trait Trait { + fn frobnicate(); + } + impl Trait for Foo {} + + fn bar() { + Foo::frobnicate<|>(); + } + ", + "frobnicate FN_DEF FileId(1) [30; 46) [33; 43)", + ); + } + + #[test] + fn goto_definition_on_self() { + check_goto( + " + //- /lib.rs + struct Foo; + impl Foo { + pub fn new() -> Self { + Self<|> {} + } + } + ", + "impl IMPL_BLOCK FileId(1) [12; 73)", + ); + + check_goto( + " + //- /lib.rs + struct Foo; + impl Foo { + pub fn new() -> Self<|> { + Self {} + } + } + ", + "impl IMPL_BLOCK FileId(1) [12; 73)", + ); + + check_goto( + " + //- /lib.rs + enum Foo { A } + impl Foo { + pub fn new() -> Self<|> { + Foo::A + } + } + ", + "impl IMPL_BLOCK FileId(1) [15; 75)", + ); + + check_goto( + " + //- /lib.rs + enum Foo { A } + impl Foo { + pub fn thing(a: &Self<|>) { + } + } + ", + "impl IMPL_BLOCK FileId(1) [15; 62)", + ); + } + + #[test] + fn goto_definition_on_self_in_trait_impl() { + check_goto( + " + //- /lib.rs + struct Foo; + trait Make { + fn new() -> Self; + } + impl Make for Foo { + fn new() -> Self { + Self<|> {} + } + } + ", + "impl IMPL_BLOCK FileId(1) [49; 115)", + ); + + check_goto( + " + //- /lib.rs + struct Foo; + trait Make { + fn new() -> Self; + } + impl Make for Foo { + fn new() -> Self<|> { + Self {} + } + } + ", + "impl IMPL_BLOCK FileId(1) [49; 115)", + ); + } + + #[test] + fn goto_definition_works_when_used_on_definition_name_itself() { + check_goto( + " + //- /lib.rs + struct Foo<|> { value: u32 } + ", + "Foo STRUCT_DEF FileId(1) [0; 25) [7; 10)", + ); + + check_goto( + r#" + //- /lib.rs + struct Foo { + field<|>: string, + } + "#, + "field RECORD_FIELD_DEF FileId(1) [17; 30) [17; 22)", + ); + + check_goto( + " + //- /lib.rs + fn foo_test<|>() { + } + ", + "foo_test FN_DEF FileId(1) [0; 17) [3; 11)", + ); + + check_goto( + " + //- /lib.rs + enum Foo<|> { + Variant, + } + ", + "Foo ENUM_DEF FileId(1) [0; 25) [5; 8)", + ); + + check_goto( + " + //- /lib.rs + enum Foo { + Variant1, + Variant2<|>, + Variant3, + } + ", + "Variant2 ENUM_VARIANT FileId(1) [29; 37) [29; 37)", + ); + + check_goto( + r#" + //- /lib.rs + static inner<|>: &str = ""; + "#, + "inner STATIC_DEF FileId(1) [0; 24) [7; 12)", + ); + + check_goto( + r#" + //- /lib.rs + const inner<|>: &str = ""; + "#, + "inner CONST_DEF FileId(1) [0; 23) [6; 11)", + ); + + check_goto( + r#" + //- /lib.rs + type Thing<|> = Option<()>; + "#, + "Thing TYPE_ALIAS_DEF FileId(1) [0; 24) [5; 10)", + ); + + check_goto( + r#" + //- /lib.rs + trait Foo<|> { + } + "#, + "Foo TRAIT_DEF FileId(1) [0; 13) [6; 9)", + ); + + check_goto( + r#" + //- /lib.rs + mod bar<|> { + } + "#, + "bar MODULE FileId(1) [0; 11) [4; 7)", + ); + } + + #[test] + fn goto_from_macro() { + check_goto( + " + //- /lib.rs + macro_rules! id { + ($($tt:tt)*) => { $($tt)* } + } + fn foo() {} + id! { + fn bar() { + fo<|>o(); + } + } + ", + "foo FN_DEF FileId(1) [52; 63) [55; 58)", + ); + } +} diff --git a/crates/ra_ide/src/goto_type_definition.rs b/crates/ra_ide/src/goto_type_definition.rs new file mode 100644 index 000000000..992a08809 --- /dev/null +++ b/crates/ra_ide/src/goto_type_definition.rs @@ -0,0 +1,105 @@ +//! FIXME: write short doc here + +use hir::db::AstDatabase; +use ra_syntax::{ast, AstNode}; + +use crate::{ + db::RootDatabase, display::ToNav, expand::descend_into_macros, FilePosition, NavigationTarget, + RangeInfo, +}; + +pub(crate) fn goto_type_definition( + db: &RootDatabase, + position: FilePosition, +) -> Option>> { + let file = db.parse_or_expand(position.file_id.into())?; + let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; + let token = descend_into_macros(db, position.file_id, token); + + let node = token.value.ancestors().find_map(|token| { + token + .ancestors() + .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some()) + })?; + + let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None); + + let ty: hir::Type = if let Some(ty) = + ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) + { + ty + } else if let Some(ty) = ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p)) + { + ty + } else { + return None; + }; + + let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?; + + let nav = adt_def.to_nav(db); + Some(RangeInfo::new(node.text_range(), vec![nav])) +} + +#[cfg(test)] +mod tests { + use crate::mock_analysis::analysis_and_position; + + fn check_goto(fixture: &str, expected: &str) { + let (analysis, pos) = analysis_and_position(fixture); + + let mut navs = analysis.goto_type_definition(pos).unwrap().unwrap().info; + assert_eq!(navs.len(), 1); + let nav = navs.pop().unwrap(); + nav.assert_match(expected); + } + + #[test] + fn goto_type_definition_works_simple() { + check_goto( + " + //- /lib.rs + struct Foo; + fn foo() { + let f: Foo; + f<|> + } + ", + "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + ); + } + + #[test] + fn goto_type_definition_works_simple_ref() { + check_goto( + " + //- /lib.rs + struct Foo; + fn foo() { + let f: &Foo; + f<|> + } + ", + "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", + ); + } + + #[test] + fn goto_type_definition_works_through_macro() { + check_goto( + " + //- /lib.rs + macro_rules! id { + ($($tt:tt)*) => { $($tt)* } + } + struct Foo {} + id! { + fn bar() { + let f<|> = Foo {}; + } + } + ", + "Foo STRUCT_DEF FileId(1) [52; 65) [59; 62)", + ); + } +} diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs new file mode 100644 index 000000000..260a7b869 --- /dev/null +++ b/crates/ra_ide/src/hover.rs @@ -0,0 +1,730 @@ +//! FIXME: write short doc here + +use hir::{db::AstDatabase, Adt, HasSource, HirDisplay}; +use ra_db::SourceDatabase; +use ra_syntax::{ + algo::find_covering_element, + ast::{self, DocCommentsOwner}, + match_ast, AstNode, +}; + +use crate::{ + db::RootDatabase, + display::{ + description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, + rust_code_markup_with_doc, ShortLabel, + }, + expand::descend_into_macros, + references::{classify_name, classify_name_ref, NameKind, NameKind::*}, + FilePosition, FileRange, RangeInfo, +}; + +/// Contains the results when hovering over an item +#[derive(Debug, Clone)] +pub struct HoverResult { + results: Vec, + exact: bool, +} + +impl Default for HoverResult { + fn default() -> Self { + HoverResult::new() + } +} + +impl HoverResult { + pub fn new() -> HoverResult { + HoverResult { + results: Vec::new(), + // We assume exact by default + exact: true, + } + } + + pub fn extend(&mut self, item: Option) { + self.results.extend(item); + } + + pub fn is_exact(&self) -> bool { + self.exact + } + + pub fn is_empty(&self) -> bool { + self.results.is_empty() + } + + pub fn len(&self) -> usize { + self.results.len() + } + + pub fn first(&self) -> Option<&str> { + self.results.first().map(String::as_str) + } + + pub fn results(&self) -> &[String] { + &self.results + } + + /// Returns the results converted into markup + /// for displaying in a UI + pub fn to_markup(&self) -> String { + let mut markup = if !self.exact { + let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits."); + if !self.results.is_empty() { + msg.push_str(" \nThese items were found instead:"); + } + msg.push_str("\n\n---\n"); + msg + } else { + String::new() + }; + + markup.push_str(&self.results.join("\n\n---\n")); + + markup + } +} + +fn hover_text(docs: Option, desc: Option) -> Option { + match (desc, docs) { + (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)), + (None, Some(docs)) => Some(docs), + _ => None, + } +} + +fn hover_text_from_name_kind( + db: &RootDatabase, + name_kind: NameKind, + no_fallback: &mut bool, +) -> Option { + return match name_kind { + Macro(it) => { + let src = it.source(db); + hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value))) + } + Field(it) => { + let src = it.source(db); + match src.value { + hir::FieldSource::Named(it) => hover_text(it.doc_comment_text(), it.short_label()), + _ => None, + } + } + AssocItem(it) => match it { + hir::AssocItem::Function(it) => from_def_source(db, it), + hir::AssocItem::Const(it) => from_def_source(db, it), + hir::AssocItem::TypeAlias(it) => from_def_source(db, it), + }, + Def(it) => match it { + hir::ModuleDef::Module(it) => match it.definition_source(db).value { + hir::ModuleSource::Module(it) => { + hover_text(it.doc_comment_text(), it.short_label()) + } + _ => None, + }, + hir::ModuleDef::Function(it) => from_def_source(db, it), + hir::ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it), + hir::ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it), + hir::ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it), + hir::ModuleDef::EnumVariant(it) => from_def_source(db, it), + hir::ModuleDef::Const(it) => from_def_source(db, it), + hir::ModuleDef::Static(it) => from_def_source(db, it), + hir::ModuleDef::Trait(it) => from_def_source(db, it), + hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), + hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), + }, + Local(_) => { + // Hover for these shows type names + *no_fallback = true; + None + } + GenericParam(_) | SelfType(_) => { + // FIXME: Hover for generic param + None + } + }; + + fn from_def_source(db: &RootDatabase, def: D) -> Option + where + D: HasSource, + A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, + { + let src = def.source(db); + hover_text(src.value.doc_comment_text(), src.value.short_label()) + } +} + +pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { + let file = db.parse_or_expand(position.file_id.into())?; + let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; + let token = descend_into_macros(db, position.file_id, token); + + let mut res = HoverResult::new(); + + let mut range = match_ast! { + match (token.value.parent()) { + ast::NameRef(name_ref) => { + let mut no_fallback = false; + if let Some(name_kind) = + classify_name_ref(db, token.with_value(&name_ref)).map(|d| d.kind) + { + res.extend(hover_text_from_name_kind(db, name_kind, &mut no_fallback)) + } + + if res.is_empty() && !no_fallback { + // Fallback index based approach: + let symbols = crate::symbol_index::index_resolve(db, &name_ref); + for sym in symbols { + let docs = docs_from_symbol(db, &sym); + let desc = description_from_symbol(db, &sym); + res.extend(hover_text(docs, desc)); + } + } + + if !res.is_empty() { + Some(name_ref.syntax().text_range()) + } else { + None + } + }, + ast::Name(name) => { + if let Some(name_kind) = classify_name(db, token.with_value(&name)).map(|d| d.kind) { + res.extend(hover_text_from_name_kind(db, name_kind, &mut true)); + } + + if !res.is_empty() { + Some(name.syntax().text_range()) + } else { + None + } + }, + _ => None, + } + }; + + if range.is_none() { + let node = token.value.ancestors().find(|n| { + ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some() + })?; + let frange = FileRange { file_id: position.file_id, range: node.text_range() }; + res.extend(type_of(db, frange).map(rust_code_markup)); + range = Some(node.text_range()); + }; + + let range = range?; + if res.is_empty() { + return None; + } + Some(RangeInfo::new(range, res)) +} + +pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option { + let parse = db.parse(frange.file_id); + let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); + // if we picked identifier, expand to pattern/expression + let node = leaf_node + .ancestors() + .take_while(|it| it.text_range() == leaf_node.text_range()) + .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; + let analyzer = + hir::SourceAnalyzer::new(db, hir::Source::new(frange.file_id.into(), &node), None); + let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) + { + ty + } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) { + ty + } else { + return None; + }; + Some(ty.display(db).to_string()) +} + +#[cfg(test)] +mod tests { + use crate::mock_analysis::{ + analysis_and_position, single_file_with_position, single_file_with_range, + }; + use ra_syntax::TextRange; + + fn trim_markup(s: &str) -> &str { + s.trim_start_matches("```rust\n").trim_end_matches("\n```") + } + + fn trim_markup_opt(s: Option<&str>) -> Option<&str> { + s.map(trim_markup) + } + + fn check_hover_result(fixture: &str, expected: &[&str]) { + let (analysis, position) = analysis_and_position(fixture); + let hover = analysis.hover(position).unwrap().unwrap(); + let mut results = Vec::from(hover.info.results()); + results.sort(); + + for (markup, expected) in + results.iter().zip(expected.iter().chain(std::iter::repeat(&""))) + { + assert_eq!(trim_markup(&markup), *expected); + } + + assert_eq!(hover.info.len(), expected.len()); + } + + #[test] + fn hover_shows_type_of_an_expression() { + let (analysis, position) = single_file_with_position( + " + pub fn foo() -> u32 { 1 } + + fn main() { + let foo_test = foo()<|>; + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); + assert_eq!(trim_markup_opt(hover.info.first()), Some("u32")); + } + + #[test] + fn hover_shows_fn_signature() { + // Single file with result + check_hover_result( + r#" + //- /main.rs + pub fn foo() -> u32 { 1 } + + fn main() { + let foo_test = fo<|>o(); + } + "#, + &["pub fn foo() -> u32"], + ); + + // Multiple results + check_hover_result( + r#" + //- /a.rs + pub fn foo() -> u32 { 1 } + + //- /b.rs + pub fn foo() -> &str { "" } + + //- /c.rs + pub fn foo(a: u32, b: u32) {} + + //- /main.rs + mod a; + mod b; + mod c; + + fn main() { + let foo_test = fo<|>o(); + } + "#, + &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], + ); + } + + #[test] + fn hover_shows_fn_signature_with_type_params() { + check_hover_result( + r#" + //- /main.rs + pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str { } + + fn main() { + let foo_test = fo<|>o(); + } + "#, + &["pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str"], + ); + } + + #[test] + fn hover_shows_fn_signature_on_fn_name() { + check_hover_result( + r#" + //- /main.rs + pub fn foo<|>(a: u32, b: u32) -> u32 {} + + fn main() { + } + "#, + &["pub fn foo(a: u32, b: u32) -> u32"], + ); + } + + #[test] + fn hover_shows_struct_field_info() { + // Hovering over the field when instantiating + check_hover_result( + r#" + //- /main.rs + struct Foo { + field_a: u32, + } + + fn main() { + let foo = Foo { + field_a<|>: 0, + }; + } + "#, + &["field_a: u32"], + ); + + // Hovering over the field in the definition + check_hover_result( + r#" + //- /main.rs + struct Foo { + field_a<|>: u32, + } + + fn main() { + let foo = Foo { + field_a: 0, + }; + } + "#, + &["field_a: u32"], + ); + } + + #[test] + fn hover_const_static() { + check_hover_result( + r#" + //- /main.rs + const foo<|>: u32 = 0; + "#, + &["const foo: u32"], + ); + + check_hover_result( + r#" + //- /main.rs + static foo<|>: u32 = 0; + "#, + &["static foo: u32"], + ); + } + + #[test] + fn hover_some() { + let (analysis, position) = single_file_with_position( + " + enum Option { Some(T) } + use Option::Some; + + fn main() { + So<|>me(12); + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Some")); + + let (analysis, position) = single_file_with_position( + " + enum Option { Some(T) } + use Option::Some; + + fn main() { + let b<|>ar = Some(12); + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Option")); + } + + #[test] + fn hover_enum_variant() { + check_hover_result( + r#" + //- /main.rs + enum Option { + /// The None variant + Non<|>e + } + "#, + &[" +None +``` + +The None variant + " + .trim()], + ); + + check_hover_result( + r#" + //- /main.rs + enum Option { + /// The Some variant + Some(T) + } + fn main() { + let s = Option::Som<|>e(12); + } + "#, + &[" +Some +``` + +The Some variant + " + .trim()], + ); + } + + #[test] + fn hover_for_local_variable() { + let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + } + + #[test] + fn hover_for_local_variable_pat() { + let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + } + + #[test] + fn hover_local_var_edge() { + let (analysis, position) = single_file_with_position( + " +fn func(foo: i32) { if true { <|>foo; }; } +", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + } + + #[test] + fn test_type_of_for_function() { + let (analysis, range) = single_file_with_range( + " + pub fn foo() -> u32 { 1 }; + + fn main() { + let foo_test = <|>foo()<|>; + } + ", + ); + + let type_name = analysis.type_of(range).unwrap().unwrap(); + assert_eq!("u32", &type_name); + } + + #[test] + fn test_type_of_for_expr() { + let (analysis, range) = single_file_with_range( + " + fn main() { + let foo: usize = 1; + let bar = <|>1 + foo<|>; + } + ", + ); + + let type_name = analysis.type_of(range).unwrap().unwrap(); + assert_eq!("usize", &type_name); + } + + #[test] + fn test_hover_infer_associated_method_result() { + let (analysis, position) = single_file_with_position( + " + struct Thing { x: u32 } + + impl Thing { + fn new() -> Thing { + Thing { x: 0 } + } + } + + fn main() { + let foo_<|>test = Thing::new(); + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); + } + + #[test] + fn test_hover_infer_associated_method_exact() { + let (analysis, position) = single_file_with_position( + " + struct Thing { x: u32 } + + impl Thing { + fn new() -> Thing { + Thing { x: 0 } + } + } + + fn main() { + let foo_test = Thing::new<|>(); + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing")); + assert_eq!(hover.info.is_exact(), true); + } + + #[test] + fn test_hover_infer_associated_const_in_pattern() { + let (analysis, position) = single_file_with_position( + " + struct X; + impl X { + const C: u32 = 1; + } + + fn main() { + match 1 { + X::C<|> => {}, + 2 => {}, + _ => {} + }; + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); + assert_eq!(hover.info.is_exact(), true); + } + + #[test] + fn test_hover_self() { + let (analysis, position) = single_file_with_position( + " + struct Thing { x: u32 } + impl Thing { + fn new() -> Self { + Self<|> { x: 0 } + } + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); + assert_eq!(hover.info.is_exact(), true); + + /* FIXME: revive these tests + let (analysis, position) = single_file_with_position( + " + struct Thing { x: u32 } + impl Thing { + fn new() -> Self<|> { + Self { x: 0 } + } + } + ", + ); + + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); + assert_eq!(hover.info.is_exact(), true); + + let (analysis, position) = single_file_with_position( + " + enum Thing { A } + impl Thing { + pub fn new() -> Self<|> { + Thing::A + } + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); + assert_eq!(hover.info.is_exact(), true); + + let (analysis, position) = single_file_with_position( + " + enum Thing { A } + impl Thing { + pub fn thing(a: Self<|>) { + } + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); + assert_eq!(hover.info.is_exact(), true); + */ + } + + #[test] + fn test_hover_shadowing_pat() { + let (analysis, position) = single_file_with_position( + " + fn x() {} + + fn y() { + let x = 0i32; + x<|>; + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(hover.info.is_exact(), true); + } + + #[test] + fn test_hover_macro_invocation() { + let (analysis, position) = single_file_with_position( + " + macro_rules! foo { + () => {} + } + + fn f() { + fo<|>o!(); + } + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); + assert_eq!(hover.info.is_exact(), true); + } + + #[test] + fn test_hover_tuple_field() { + let (analysis, position) = single_file_with_position( + " + struct TS(String, i32<|>); + ", + ); + let hover = analysis.hover(position).unwrap().unwrap(); + assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); + assert_eq!(hover.info.is_exact(), true); + } + + #[test] + fn test_hover_through_macro() { + check_hover_result( + " + //- /lib.rs + macro_rules! id { + ($($tt:tt)*) => { $($tt)* } + } + fn foo() {} + id! { + fn bar() { + fo<|>o(); + } + } + ", + &["fn foo()"], + ); + } +} diff --git a/crates/ra_ide/src/impls.rs b/crates/ra_ide/src/impls.rs new file mode 100644 index 000000000..aa480e399 --- /dev/null +++ b/crates/ra_ide/src/impls.rs @@ -0,0 +1,206 @@ +//! FIXME: write short doc here + +use hir::{FromSource, ImplBlock}; +use ra_db::SourceDatabase; +use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; + +use crate::{db::RootDatabase, display::ToNav, FilePosition, NavigationTarget, RangeInfo}; + +pub(crate) fn goto_implementation( + db: &RootDatabase, + position: FilePosition, +) -> Option>> { + let parse = db.parse(position.file_id); + let syntax = parse.tree().syntax().clone(); + + let src = hir::ModuleSource::from_position(db, position); + let module = hir::Module::from_definition( + db, + hir::Source { file_id: position.file_id.into(), value: src }, + )?; + + if let Some(nominal_def) = find_node_at_offset::(&syntax, position.offset) { + return Some(RangeInfo::new( + nominal_def.syntax().text_range(), + impls_for_def(db, position, &nominal_def, module)?, + )); + } else if let Some(trait_def) = find_node_at_offset::(&syntax, position.offset) { + return Some(RangeInfo::new( + trait_def.syntax().text_range(), + impls_for_trait(db, position, &trait_def, module)?, + )); + } + + None +} + +fn impls_for_def( + db: &RootDatabase, + position: FilePosition, + node: &ast::NominalDef, + module: hir::Module, +) -> Option> { + let ty = match node { + ast::NominalDef::StructDef(def) => { + let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; + hir::Struct::from_source(db, src)?.ty(db) + } + ast::NominalDef::EnumDef(def) => { + let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; + hir::Enum::from_source(db, src)?.ty(db) + } + ast::NominalDef::UnionDef(def) => { + let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; + hir::Union::from_source(db, src)?.ty(db) + } + }; + + let krate = module.krate(); + let impls = ImplBlock::all_in_crate(db, krate); + + Some( + impls + .into_iter() + .filter(|impl_block| ty.is_equal_for_find_impls(&impl_block.target_ty(db))) + .map(|imp| imp.to_nav(db)) + .collect(), + ) +} + +fn impls_for_trait( + db: &RootDatabase, + position: FilePosition, + node: &ast::TraitDef, + module: hir::Module, +) -> Option> { + let src = hir::Source { file_id: position.file_id.into(), value: node.clone() }; + let tr = hir::Trait::from_source(db, src)?; + + let krate = module.krate(); + let impls = ImplBlock::for_trait(db, krate, tr); + + Some(impls.into_iter().map(|imp| imp.to_nav(db)).collect()) +} + +#[cfg(test)] +mod tests { + use crate::mock_analysis::analysis_and_position; + + fn check_goto(fixture: &str, expected: &[&str]) { + let (analysis, pos) = analysis_and_position(fixture); + + let mut navs = analysis.goto_implementation(pos).unwrap().unwrap().info; + assert_eq!(navs.len(), expected.len()); + navs.sort_by_key(|nav| (nav.file_id(), nav.full_range().start())); + navs.into_iter().enumerate().for_each(|(i, nav)| nav.assert_match(expected[i])); + } + + #[test] + fn goto_implementation_works() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + impl Foo {} + ", + &["impl IMPL_BLOCK FileId(1) [12; 23)"], + ); + } + + #[test] + fn goto_implementation_works_multiple_blocks() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + impl Foo {} + impl Foo {} + ", + &["impl IMPL_BLOCK FileId(1) [12; 23)", "impl IMPL_BLOCK FileId(1) [24; 35)"], + ); + } + + #[test] + fn goto_implementation_works_multiple_mods() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + mod a { + impl super::Foo {} + } + mod b { + impl super::Foo {} + } + ", + &["impl IMPL_BLOCK FileId(1) [24; 42)", "impl IMPL_BLOCK FileId(1) [57; 75)"], + ); + } + + #[test] + fn goto_implementation_works_multiple_files() { + check_goto( + " + //- /lib.rs + struct Foo<|>; + mod a; + mod b; + //- /a.rs + impl crate::Foo {} + //- /b.rs + impl crate::Foo {} + ", + &["impl IMPL_BLOCK FileId(2) [0; 18)", "impl IMPL_BLOCK FileId(3) [0; 18)"], + ); + } + + #[test] + fn goto_implementation_for_trait() { + check_goto( + " + //- /lib.rs + trait T<|> {} + struct Foo; + impl T for Foo {} + ", + &["impl IMPL_BLOCK FileId(1) [23; 40)"], + ); + } + + #[test] + fn goto_implementation_for_trait_multiple_files() { + check_goto( + " + //- /lib.rs + trait T<|> {}; + struct Foo; + mod a; + mod b; + //- /a.rs + impl crate::T for crate::Foo {} + //- /b.rs + impl crate::T for crate::Foo {} + ", + &["impl IMPL_BLOCK FileId(2) [0; 31)", "impl IMPL_BLOCK FileId(3) [0; 31)"], + ); + } + + #[test] + fn goto_implementation_all_impls() { + check_goto( + " + //- /lib.rs + trait T {} + struct Foo<|>; + impl Foo {} + impl T for Foo {} + impl T for &Foo {} + ", + &[ + "impl IMPL_BLOCK FileId(1) [23; 34)", + "impl IMPL_BLOCK FileId(1) [35; 52)", + "impl IMPL_BLOCK FileId(1) [53; 71)", + ], + ); + } +} diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs new file mode 100644 index 000000000..45149bf0c --- /dev/null +++ b/crates/ra_ide/src/inlay_hints.rs @@ -0,0 +1,543 @@ +//! FIXME: write short doc here + +use crate::{db::RootDatabase, FileId}; +use hir::{HirDisplay, SourceAnalyzer}; +use ra_syntax::{ + ast::{self, AstNode, TypeAscriptionOwner}, + match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, +}; + +#[derive(Debug, PartialEq, Eq)] +pub enum InlayKind { + TypeHint, +} + +#[derive(Debug)] +pub struct InlayHint { + pub range: TextRange, + pub kind: InlayKind, + pub label: SmolStr, +} + +pub(crate) fn inlay_hints( + db: &RootDatabase, + file_id: FileId, + file: &SourceFile, + max_inlay_hint_length: Option, +) -> Vec { + file.syntax() + .descendants() + .map(|node| get_inlay_hints(db, file_id, &node, max_inlay_hint_length).unwrap_or_default()) + .flatten() + .collect() +} + +fn get_inlay_hints( + db: &RootDatabase, + file_id: FileId, + node: &SyntaxNode, + max_inlay_hint_length: Option, +) -> Option> { + let analyzer = SourceAnalyzer::new(db, hir::Source::new(file_id.into(), node), None); + match_ast! { + match node { + ast::LetStmt(it) => { + if it.ascribed_type().is_some() { + return None; + } + let pat = it.pat()?; + Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length)) + }, + ast::LambdaExpr(it) => { + it.param_list().map(|param_list| { + param_list + .params() + .filter(|closure_param| closure_param.ascribed_type().is_none()) + .filter_map(|closure_param| closure_param.pat()) + .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false, max_inlay_hint_length)) + .flatten() + .collect() + }) + }, + ast::ForExpr(it) => { + let pat = it.pat()?; + Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length)) + }, + ast::IfExpr(it) => { + let pat = it.condition()?.pat()?; + Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length)) + }, + ast::WhileExpr(it) => { + let pat = it.condition()?.pat()?; + Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length)) + }, + ast::MatchArmList(it) => { + Some( + it + .arms() + .map(|match_arm| match_arm.pats()) + .flatten() + .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true, max_inlay_hint_length)) + .flatten() + .collect(), + ) + }, + _ => None, + } + } +} + +fn get_pat_type_hints( + db: &RootDatabase, + analyzer: &SourceAnalyzer, + root_pat: ast::Pat, + skip_root_pat_hint: bool, + max_inlay_hint_length: Option, +) -> Vec { + let original_pat = &root_pat.clone(); + + get_leaf_pats(root_pat) + .into_iter() + .filter(|pat| !skip_root_pat_hint || pat != original_pat) + .filter_map(|pat| { + let ty = analyzer.type_of_pat(db, &pat)?; + if ty.is_unknown() { + return None; + } + Some((pat.syntax().text_range(), ty)) + }) + .map(|(range, pat_type)| InlayHint { + range, + kind: InlayKind::TypeHint, + label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(), + }) + .collect() +} + +fn get_leaf_pats(root_pat: ast::Pat) -> Vec { + let mut pats_to_process = std::collections::VecDeque::::new(); + pats_to_process.push_back(root_pat); + + let mut leaf_pats = Vec::new(); + + while let Some(maybe_leaf_pat) = pats_to_process.pop_front() { + match &maybe_leaf_pat { + ast::Pat::BindPat(bind_pat) => { + if let Some(pat) = bind_pat.pat() { + pats_to_process.push_back(pat); + } else { + leaf_pats.push(maybe_leaf_pat); + } + } + ast::Pat::TuplePat(tuple_pat) => { + for arg_pat in tuple_pat.args() { + pats_to_process.push_back(arg_pat); + } + } + ast::Pat::RecordPat(record_pat) => { + if let Some(pat_list) = record_pat.record_field_pat_list() { + pats_to_process.extend( + pat_list + .record_field_pats() + .filter_map(|record_field_pat| { + record_field_pat + .pat() + .filter(|pat| pat.syntax().kind() != SyntaxKind::BIND_PAT) + }) + .chain(pat_list.bind_pats().map(|bind_pat| { + bind_pat.pat().unwrap_or_else(|| ast::Pat::from(bind_pat)) + })), + ); + } + } + ast::Pat::TupleStructPat(tuple_struct_pat) => { + for arg_pat in tuple_struct_pat.args() { + pats_to_process.push_back(arg_pat); + } + } + _ => (), + } + } + leaf_pats +} + +#[cfg(test)] +mod tests { + use crate::mock_analysis::single_file; + use insta::assert_debug_snapshot; + + #[test] + fn let_statement() { + let (analysis, file_id) = single_file( + r#" +#[derive(PartialEq)] +enum CustomOption { + None, + Some(T), +} + +#[derive(PartialEq)] +struct Test { + a: CustomOption, + b: u8, +} + +fn main() { + struct InnerStruct {} + + let test = 54; + let test: i32 = 33; + let mut test = 33; + let _ = 22; + let test = "test"; + let test = InnerStruct {}; + + let test = vec![222]; + let test: Vec<_> = (0..3).collect(); + let test = (0..3).collect::>(); + let test = (0..3).collect::>(); + + let mut test = Vec::new(); + test.push(333); + + let test = (42, 'a'); + let (a, (b, c, (d, e), f)) = (2, (3, 4, (6.6, 7.7), 5)); +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" + [ + InlayHint { + range: [193; 197), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [236; 244), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [275; 279), + kind: TypeHint, + label: "&str", + }, + InlayHint { + range: [539; 543), + kind: TypeHint, + label: "(i32, char)", + }, + InlayHint { + range: [566; 567), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [570; 571), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [573; 574), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [584; 585), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [577; 578), + kind: TypeHint, + label: "f64", + }, + InlayHint { + range: [580; 581), + kind: TypeHint, + label: "f64", + }, + ] + "### + ); + } + + #[test] + fn closure_parameter() { + let (analysis, file_id) = single_file( + r#" +fn main() { + let mut start = 0; + (0..2).for_each(|increment| { + start += increment; + }) +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" + [ + InlayHint { + range: [21; 30), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [57; 66), + kind: TypeHint, + label: "i32", + }, + ] + "### + ); + } + + #[test] + fn for_expression() { + let (analysis, file_id) = single_file( + r#" +fn main() { + let mut start = 0; + for increment in 0..2 { + start += increment; + } +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" + [ + InlayHint { + range: [21; 30), + kind: TypeHint, + label: "i32", + }, + InlayHint { + range: [44; 53), + kind: TypeHint, + label: "i32", + }, + ] + "### + ); + } + + #[test] + fn if_expr() { + let (analysis, file_id) = single_file( + r#" +#[derive(PartialEq)] +enum CustomOption { + None, + Some(T), +} + +#[derive(PartialEq)] +struct Test { + a: CustomOption, + b: u8, +} + +fn main() { + let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }); + if let CustomOption::None = &test {}; + if let test = &test {}; + if let CustomOption::Some(test) = &test {}; + if let CustomOption::Some(Test { a, b }) = &test {}; + if let CustomOption::Some(Test { a: x, b: y }) = &test {}; + if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {}; + if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {}; + if let CustomOption::Some(Test { b: y, .. }) = &test {}; + + if test == CustomOption::None {} +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" + [ + InlayHint { + range: [166; 170), + kind: TypeHint, + label: "CustomOption", + }, + InlayHint { + range: [334; 338), + kind: TypeHint, + label: "&Test", + }, + InlayHint { + range: [389; 390), + kind: TypeHint, + label: "&CustomOption", + }, + InlayHint { + range: [392; 393), + kind: TypeHint, + label: "&u8", + }, + InlayHint { + range: [531; 532), + kind: TypeHint, + label: "&u32", + }, + ] + "### + ); + } + + #[test] + fn while_expr() { + let (analysis, file_id) = single_file( + r#" +#[derive(PartialEq)] +enum CustomOption { + None, + Some(T), +} + +#[derive(PartialEq)] +struct Test { + a: CustomOption, + b: u8, +} + +fn main() { + let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }); + while let CustomOption::None = &test {}; + while let test = &test {}; + while let CustomOption::Some(test) = &test {}; + while let CustomOption::Some(Test { a, b }) = &test {}; + while let CustomOption::Some(Test { a: x, b: y }) = &test {}; + while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {}; + while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {}; + while let CustomOption::Some(Test { b: y, .. }) = &test {}; + + while test == CustomOption::None {} +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" + [ + InlayHint { + range: [166; 170), + kind: TypeHint, + label: "CustomOption", + }, + InlayHint { + range: [343; 347), + kind: TypeHint, + label: "&Test", + }, + InlayHint { + range: [401; 402), + kind: TypeHint, + label: "&CustomOption", + }, + InlayHint { + range: [404; 405), + kind: TypeHint, + label: "&u8", + }, + InlayHint { + range: [549; 550), + kind: TypeHint, + label: "&u32", + }, + ] + "### + ); + } + + #[test] + fn match_arm_list() { + let (analysis, file_id) = single_file( + r#" +#[derive(PartialEq)] +enum CustomOption { + None, + Some(T), +} + +#[derive(PartialEq)] +struct Test { + a: CustomOption, + b: u8, +} + +fn main() { + match CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }) { + CustomOption::None => (), + test => (), + CustomOption::Some(test) => (), + CustomOption::Some(Test { a, b }) => (), + CustomOption::Some(Test { a: x, b: y }) => (), + CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) => (), + CustomOption::Some(Test { a: CustomOption::None, b: y }) => (), + CustomOption::Some(Test { b: y, .. }) => (), + _ => {} + } +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" + [ + InlayHint { + range: [311; 315), + kind: TypeHint, + label: "Test", + }, + InlayHint { + range: [358; 359), + kind: TypeHint, + label: "CustomOption", + }, + InlayHint { + range: [361; 362), + kind: TypeHint, + label: "u8", + }, + InlayHint { + range: [484; 485), + kind: TypeHint, + label: "u32", + }, + ] + "### + ); + } + + #[test] + fn hint_truncation() { + let (analysis, file_id) = single_file( + r#" +struct Smol(T); + +struct VeryLongOuterName(T); + +fn main() { + let a = Smol(0u32); + let b = VeryLongOuterName(0usize); + let c = Smol(Smol(0u32)) +}"#, + ); + + assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###" + [ + InlayHint { + range: [74; 75), + kind: TypeHint, + label: "Smol", + }, + InlayHint { + range: [98; 99), + kind: TypeHint, + label: "VeryLongOuterName<…>", + }, + InlayHint { + range: [137; 138), + kind: TypeHint, + label: "Smol>", + }, + ] + "### + ); + } +} diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs new file mode 100644 index 000000000..7deeb3494 --- /dev/null +++ b/crates/ra_ide/src/join_lines.rs @@ -0,0 +1,611 @@ +//! FIXME: write short doc here + +use itertools::Itertools; +use ra_fmt::{compute_ws, extract_trivial_expression}; +use ra_syntax::{ + algo::{find_covering_element, non_trivia_sibling}, + ast::{self, AstNode, AstToken}, + Direction, NodeOrToken, SourceFile, + SyntaxKind::{self, WHITESPACE}, + SyntaxNode, SyntaxToken, TextRange, TextUnit, T, +}; +use ra_text_edit::{TextEdit, TextEditBuilder}; + +pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { + let range = if range.is_empty() { + let syntax = file.syntax(); + let text = syntax.text().slice(range.start()..); + let pos = match text.find_char('\n') { + None => return TextEditBuilder::default().finish(), + Some(pos) => pos, + }; + TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n')) + } else { + range + }; + + let node = match find_covering_element(file.syntax(), range) { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(token) => token.parent(), + }; + let mut edit = TextEditBuilder::default(); + for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) { + let range = match range.intersection(&token.text_range()) { + Some(range) => range, + None => continue, + } - token.text_range().start(); + let text = token.text(); + for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { + let pos: TextUnit = (pos as u32).into(); + let off = token.text_range().start() + range.start() + pos; + if !edit.invalidates_offset(off) { + remove_newline(&mut edit, &token, off); + } + } + } + + edit.finish() +} + +fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextUnit) { + if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { + // The node is either the first or the last in the file + let suff = &token.text()[TextRange::from_to( + offset - token.text_range().start() + TextUnit::of_char('\n'), + TextUnit::of_str(token.text()), + )]; + let spaces = suff.bytes().take_while(|&b| b == b' ').count(); + + edit.replace(TextRange::offset_len(offset, ((spaces + 1) as u32).into()), " ".to_string()); + return; + } + + // Special case that turns something like: + // + // ``` + // my_function({<|> + // + // }) + // ``` + // + // into `my_function()` + if join_single_expr_block(edit, token).is_some() { + return; + } + // ditto for + // + // ``` + // use foo::{<|> + // bar + // }; + // ``` + if join_single_use_tree(edit, token).is_some() { + return; + } + + // The node is between two other nodes + let prev = token.prev_sibling_or_token().unwrap(); + let next = token.next_sibling_or_token().unwrap(); + if is_trailing_comma(prev.kind(), next.kind()) { + // Removes: trailing comma, newline (incl. surrounding whitespace) + edit.delete(TextRange::from_to(prev.text_range().start(), token.text_range().end())); + } else if prev.kind() == T![,] && next.kind() == T!['}'] { + // Removes: comma, newline (incl. surrounding whitespace) + let space = if let Some(left) = prev.prev_sibling_or_token() { + compute_ws(left.kind(), next.kind()) + } else { + " " + }; + edit.replace( + TextRange::from_to(prev.text_range().start(), token.text_range().end()), + space.to_string(), + ); + } else if let (Some(_), Some(next)) = ( + prev.as_token().cloned().and_then(ast::Comment::cast), + next.as_token().cloned().and_then(ast::Comment::cast), + ) { + // Removes: newline (incl. surrounding whitespace), start of the next comment + edit.delete(TextRange::from_to( + token.text_range().start(), + next.syntax().text_range().start() + TextUnit::of_str(next.prefix()), + )); + } else { + // Remove newline but add a computed amount of whitespace characters + edit.replace(token.text_range(), compute_ws(prev.kind(), next.kind()).to_string()); + } +} + +fn has_comma_after(node: &SyntaxNode) -> bool { + match non_trivia_sibling(node.clone().into(), Direction::Next) { + Some(n) => n.kind() == T![,], + _ => false, + } +} + +fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { + let block = ast::Block::cast(token.parent())?; + let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?; + let expr = extract_trivial_expression(&block_expr)?; + + let block_range = block_expr.syntax().text_range(); + let mut buf = expr.syntax().text().to_string(); + + // Match block needs to have a comma after the block + if let Some(match_arm) = block_expr.syntax().parent().and_then(ast::MatchArm::cast) { + if !has_comma_after(match_arm.syntax()) { + buf.push(','); + } + } + + edit.replace(block_range, buf); + + Some(()) +} + +fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { + let use_tree_list = ast::UseTreeList::cast(token.parent())?; + let (tree,) = use_tree_list.use_trees().collect_tuple()?; + edit.replace(use_tree_list.syntax().text_range(), tree.syntax().text().to_string()); + Some(()) +} + +fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { + match (left, right) { + (T![,], T![')']) | (T![,], T![']']) => true, + _ => false, + } +} + +#[cfg(test)] +mod tests { + use crate::test_utils::{assert_eq_text, check_action, extract_range}; + + use super::*; + + fn check_join_lines(before: &str, after: &str) { + check_action(before, after, |file, offset| { + let range = TextRange::offset_len(offset, 0.into()); + let res = join_lines(file, range); + Some(res) + }) + } + + #[test] + fn test_join_lines_comma() { + check_join_lines( + r" +fn foo() { + <|>foo(1, + ) +} +", + r" +fn foo() { + <|>foo(1) +} +", + ); + } + + #[test] + fn test_join_lines_lambda_block() { + check_join_lines( + r" +pub fn reparse(&self, edit: &AtomTextEdit) -> File { + <|>self.incremental_reparse(edit).unwrap_or_else(|| { + self.full_reparse(edit) + }) +} +", + r" +pub fn reparse(&self, edit: &AtomTextEdit) -> File { + <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit)) +} +", + ); + } + + #[test] + fn test_join_lines_block() { + check_join_lines( + r" +fn foo() { + foo(<|>{ + 92 + }) +}", + r" +fn foo() { + foo(<|>92) +}", + ); + } + + #[test] + fn join_lines_adds_comma_for_block_in_match_arm() { + check_join_lines( + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>{ + u.foo() + } + Err(v) => v, + } +}", + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>u.foo(), + Err(v) => v, + } +}", + ); + } + + #[test] + fn join_lines_multiline_in_block() { + check_join_lines( + r" +fn foo() { + match ty { + <|> Some(ty) => { + match ty { + _ => false, + } + } + _ => true, + } +} +", + r" +fn foo() { + match ty { + <|> Some(ty) => match ty { + _ => false, + }, + _ => true, + } +} +", + ); + } + + #[test] + fn join_lines_keeps_comma_for_block_in_match_arm() { + // We already have a comma + check_join_lines( + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>{ + u.foo() + }, + Err(v) => v, + } +}", + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>u.foo(), + Err(v) => v, + } +}", + ); + + // comma with whitespace between brace and , + check_join_lines( + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>{ + u.foo() + } , + Err(v) => v, + } +}", + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>u.foo() , + Err(v) => v, + } +}", + ); + + // comma with newline between brace and , + check_join_lines( + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>{ + u.foo() + } + , + Err(v) => v, + } +}", + r" +fn foo(e: Result) { + match e { + Ok(u) => <|>u.foo() + , + Err(v) => v, + } +}", + ); + } + + #[test] + fn join_lines_keeps_comma_with_single_arg_tuple() { + // A single arg tuple + check_join_lines( + r" +fn foo() { + let x = (<|>{ + 4 + },); +}", + r" +fn foo() { + let x = (<|>4,); +}", + ); + + // single arg tuple with whitespace between brace and comma + check_join_lines( + r" +fn foo() { + let x = (<|>{ + 4 + } ,); +}", + r" +fn foo() { + let x = (<|>4 ,); +}", + ); + + // single arg tuple with newline between brace and comma + check_join_lines( + r" +fn foo() { + let x = (<|>{ + 4 + } + ,); +}", + r" +fn foo() { + let x = (<|>4 + ,); +}", + ); + } + + #[test] + fn test_join_lines_use_items_left() { + // No space after the '{' + check_join_lines( + r" +<|>use ra_syntax::{ + TextUnit, TextRange, +};", + r" +<|>use ra_syntax::{TextUnit, TextRange, +};", + ); + } + + #[test] + fn test_join_lines_use_items_right() { + // No space after the '}' + check_join_lines( + r" +use ra_syntax::{ +<|> TextUnit, TextRange +};", + r" +use ra_syntax::{ +<|> TextUnit, TextRange};", + ); + } + + #[test] + fn test_join_lines_use_items_right_comma() { + // No space after the '}' + check_join_lines( + r" +use ra_syntax::{ +<|> TextUnit, TextRange, +};", + r" +use ra_syntax::{ +<|> TextUnit, TextRange};", + ); + } + + #[test] + fn test_join_lines_use_tree() { + check_join_lines( + r" +use ra_syntax::{ + algo::<|>{ + find_token_at_offset, + }, + ast, +};", + r" +use ra_syntax::{ + algo::<|>find_token_at_offset, + ast, +};", + ); + } + + #[test] + fn test_join_lines_normal_comments() { + check_join_lines( + r" +fn foo() { + // Hello<|> + // world! +} +", + r" +fn foo() { + // Hello<|> world! +} +", + ); + } + + #[test] + fn test_join_lines_doc_comments() { + check_join_lines( + r" +fn foo() { + /// Hello<|> + /// world! +} +", + r" +fn foo() { + /// Hello<|> world! +} +", + ); + } + + #[test] + fn test_join_lines_mod_comments() { + check_join_lines( + r" +fn foo() { + //! Hello<|> + //! world! +} +", + r" +fn foo() { + //! Hello<|> world! +} +", + ); + } + + #[test] + fn test_join_lines_multiline_comments_1() { + check_join_lines( + r" +fn foo() { + // Hello<|> + /* world! */ +} +", + r" +fn foo() { + // Hello<|> world! */ +} +", + ); + } + + #[test] + fn test_join_lines_multiline_comments_2() { + check_join_lines( + r" +fn foo() { + // The<|> + /* quick + brown + fox! */ +} +", + r" +fn foo() { + // The<|> quick + brown + fox! */ +} +", + ); + } + + fn check_join_lines_sel(before: &str, after: &str) { + let (sel, before) = extract_range(before); + let parse = SourceFile::parse(&before); + let result = join_lines(&parse.tree(), sel); + let actual = result.apply(&before); + assert_eq_text!(after, &actual); + } + + #[test] + fn test_join_lines_selection_fn_args() { + check_join_lines_sel( + r" +fn foo() { + <|>foo(1, + 2, + 3, + <|>) +} + ", + r" +fn foo() { + foo(1, 2, 3) +} + ", + ); + } + + #[test] + fn test_join_lines_selection_struct() { + check_join_lines_sel( + r" +struct Foo <|>{ + f: u32, +}<|> + ", + r" +struct Foo { f: u32 } + ", + ); + } + + #[test] + fn test_join_lines_selection_dot_chain() { + check_join_lines_sel( + r" +fn foo() { + join(<|>type_params.type_params() + .filter_map(|it| it.name()) + .map(|it| it.text())<|>) +}", + r" +fn foo() { + join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text())) +}", + ); + } + + #[test] + fn test_join_lines_selection_lambda_block_body() { + check_join_lines_sel( + r" +pub fn handle_find_matching_brace() { + params.offsets + .map(|offset| <|>{ + world.analysis().matching_brace(&file, offset).unwrap_or(offset) + }<|>) + .collect(); +}", + r" +pub fn handle_find_matching_brace() { + params.offsets + .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset)) + .collect(); +}", + ); + } +} diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs new file mode 100644 index 000000000..d1bff4a76 --- /dev/null +++ b/crates/ra_ide/src/lib.rs @@ -0,0 +1,489 @@ +//! ra_ide crate provides "ide-centric" APIs for the rust-analyzer. That is, +//! it generally operates with files and text ranges, and returns results as +//! Strings, suitable for displaying to the human. +//! +//! What powers this API are the `RootDatabase` struct, which defines a `salsa` +//! database, and the `ra_hir` crate, where majority of the analysis happens. +//! However, IDE specific bits of the analysis (most notably completion) happen +//! in this crate. + +// For proving that RootDatabase is RefUnwindSafe. +#![recursion_limit = "128"] + +mod db; +pub mod mock_analysis; +mod symbol_index; +mod change; +mod source_change; +mod feature_flags; + +mod status; +mod completion; +mod runnables; +mod goto_definition; +mod goto_type_definition; +mod extend_selection; +mod hover; +mod call_info; +mod syntax_highlighting; +mod parent_module; +mod references; +mod impls; +mod assists; +mod diagnostics; +mod syntax_tree; +mod folding_ranges; +mod line_index; +mod line_index_utils; +mod join_lines; +mod typing; +mod matching_brace; +mod display; +mod inlay_hints; +mod wasm_shims; +mod expand; +mod expand_macro; + +#[cfg(test)] +mod marks; +#[cfg(test)] +mod test_utils; + +use std::sync::Arc; + +use ra_cfg::CfgOptions; +use ra_db::{ + salsa::{self, ParallelDatabase}, + CheckCanceled, Env, FileLoader, SourceDatabase, +}; +use ra_syntax::{SourceFile, TextRange, TextUnit}; + +use crate::{db::LineIndexDatabase, display::ToNav, symbol_index::FileSymbol}; + +pub use crate::{ + assists::{Assist, AssistId}, + change::{AnalysisChange, LibraryData}, + completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, + diagnostics::Severity, + display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, + expand_macro::ExpandedMacro, + feature_flags::FeatureFlags, + folding_ranges::{Fold, FoldKind}, + hover::HoverResult, + inlay_hints::{InlayHint, InlayKind}, + line_index::{LineCol, LineIndex}, + line_index_utils::translate_offset_with_edit, + references::{ReferenceSearchResult, SearchScope}, + runnables::{Runnable, RunnableKind}, + source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, + syntax_highlighting::HighlightedRange, +}; + +pub use hir::Documentation; +pub use ra_db::{ + Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, +}; + +pub type Cancelable = Result; + +#[derive(Debug)] +pub struct Diagnostic { + pub message: String, + pub range: TextRange, + pub fix: Option, + pub severity: Severity, +} + +#[derive(Debug)] +pub struct Query { + query: String, + lowercased: String, + only_types: bool, + libs: bool, + exact: bool, + limit: usize, +} + +impl Query { + pub fn new(query: String) -> Query { + let lowercased = query.to_lowercase(); + Query { + query, + lowercased, + only_types: false, + libs: false, + exact: false, + limit: usize::max_value(), + } + } + + pub fn only_types(&mut self) { + self.only_types = true; + } + + pub fn libs(&mut self) { + self.libs = true; + } + + pub fn exact(&mut self) { + self.exact = true; + } + + pub fn limit(&mut self, limit: usize) { + self.limit = limit + } +} + +/// Info associated with a text range. +#[derive(Debug)] +pub struct RangeInfo { + pub range: TextRange, + pub info: T, +} + +impl RangeInfo { + pub fn new(range: TextRange, info: T) -> RangeInfo { + RangeInfo { range, info } + } +} + +/// Contains information about a call site. Specifically the +/// `FunctionSignature`and current parameter. +#[derive(Debug)] +pub struct CallInfo { + pub signature: FunctionSignature, + pub active_parameter: Option, +} + +/// `AnalysisHost` stores the current state of the world. +#[derive(Debug)] +pub struct AnalysisHost { + db: db::RootDatabase, +} + +impl Default for AnalysisHost { + fn default() -> AnalysisHost { + AnalysisHost::new(None, FeatureFlags::default()) + } +} + +impl AnalysisHost { + pub fn new(lru_capcity: Option, feature_flags: FeatureFlags) -> AnalysisHost { + AnalysisHost { db: db::RootDatabase::new(lru_capcity, feature_flags) } + } + /// Returns a snapshot of the current state, which you can query for + /// semantic information. + pub fn analysis(&self) -> Analysis { + Analysis { db: self.db.snapshot() } + } + + pub fn feature_flags(&self) -> &FeatureFlags { + &self.db.feature_flags + } + + /// Applies changes to the current state of the world. If there are + /// outstanding snapshots, they will be canceled. + pub fn apply_change(&mut self, change: AnalysisChange) { + self.db.apply_change(change) + } + + pub fn maybe_collect_garbage(&mut self) { + self.db.maybe_collect_garbage(); + } + + pub fn collect_garbage(&mut self) { + self.db.collect_garbage(); + } + /// NB: this clears the database + pub fn per_query_memory_usage(&mut self) -> Vec<(String, ra_prof::Bytes)> { + self.db.per_query_memory_usage() + } + pub fn raw_database( + &self, + ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { + &self.db + } + pub fn raw_database_mut( + &mut self, + ) -> &mut (impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { + &mut self.db + } +} + +/// Analysis is a snapshot of a world state at a moment in time. It is the main +/// entry point for asking semantic information about the world. When the world +/// state is advanced using `AnalysisHost::apply_change` method, all existing +/// `Analysis` are canceled (most method return `Err(Canceled)`). +#[derive(Debug)] +pub struct Analysis { + db: salsa::Snapshot, +} + +// As a general design guideline, `Analysis` API are intended to be independent +// from the language server protocol. That is, when exposing some functionality +// we should think in terms of "what API makes most sense" and not in terms of +// "what types LSP uses". Although currently LSP is the only consumer of the +// API, the API should in theory be usable as a library, or via a different +// protocol. +impl Analysis { + // Creates an analysis instance for a single file, without any extenal + // dependencies, stdlib support or ability to apply changes. See + // `AnalysisHost` for creating a fully-featured analysis. + pub fn from_single_file(text: String) -> (Analysis, FileId) { + let mut host = AnalysisHost::default(); + let source_root = SourceRootId(0); + let mut change = AnalysisChange::new(); + change.add_root(source_root, true); + let mut crate_graph = CrateGraph::default(); + let file_id = FileId(0); + // FIXME: cfg options + // Default to enable test for single file. + let mut cfg_options = CfgOptions::default(); + cfg_options.insert_atom("test".into()); + crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options, Env::default()); + change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); + change.set_crate_graph(crate_graph); + host.apply_change(change); + (host.analysis(), file_id) + } + + /// Features for Analysis. + pub fn feature_flags(&self) -> &FeatureFlags { + &self.db.feature_flags + } + + /// Debug info about the current state of the analysis. + pub fn status(&self) -> Cancelable { + self.with_db(|db| status::status(&*db)) + } + + /// Gets the text of the source file. + pub fn file_text(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| db.file_text(file_id)) + } + + /// Gets the syntax tree of the file. + pub fn parse(&self, file_id: FileId) -> Cancelable { + self.with_db(|db| db.parse(file_id).tree()) + } + + /// Gets the file's `LineIndex`: data structure to convert between absolute + /// offsets and line/column representation. + pub fn file_line_index(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| db.line_index(file_id)) + } + + /// Selects the next syntactic nodes encompassing the range. + pub fn extend_selection(&self, frange: FileRange) -> Cancelable { + self.with_db(|db| extend_selection::extend_selection(db, frange)) + } + + /// Returns position of the matching brace (all types of braces are + /// supported). + pub fn matching_brace(&self, position: FilePosition) -> Cancelable> { + self.with_db(|db| { + let parse = db.parse(position.file_id); + let file = parse.tree(); + matching_brace::matching_brace(&file, position.offset) + }) + } + + /// Returns a syntax tree represented as `String`, for debug purposes. + // FIXME: use a better name here. + pub fn syntax_tree( + &self, + file_id: FileId, + text_range: Option, + ) -> Cancelable { + self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) + } + + pub fn expand_macro(&self, position: FilePosition) -> Cancelable> { + self.with_db(|db| expand_macro::expand_macro(db, position)) + } + + /// Returns an edit to remove all newlines in the range, cleaning up minor + /// stuff like trailing commas. + pub fn join_lines(&self, frange: FileRange) -> Cancelable { + self.with_db(|db| { + let parse = db.parse(frange.file_id); + let file_edit = SourceFileEdit { + file_id: frange.file_id, + edit: join_lines::join_lines(&parse.tree(), frange.range), + }; + SourceChange::source_file_edit("join lines", file_edit) + }) + } + + /// Returns an edit which should be applied when opening a new line, fixing + /// up minor stuff like continuing the comment. + pub fn on_enter(&self, position: FilePosition) -> Cancelable> { + self.with_db(|db| typing::on_enter(&db, position)) + } + + /// Returns an edit which should be applied after a character was typed. + /// + /// This is useful for some on-the-fly fixups, like adding `;` to `let =` + /// automatically. + pub fn on_char_typed( + &self, + position: FilePosition, + char_typed: char, + ) -> Cancelable> { + // Fast path to not even parse the file. + if !typing::TRIGGER_CHARS.contains(char_typed) { + return Ok(None); + } + self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) + } + + /// Returns a tree representation of symbols in the file. Useful to draw a + /// file outline. + pub fn file_structure(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| file_structure(&db.parse(file_id).tree())) + } + + /// Returns a list of the places in the file where type hints can be displayed. + pub fn inlay_hints( + &self, + file_id: FileId, + max_inlay_hint_length: Option, + ) -> Cancelable> { + self.with_db(|db| { + inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree(), max_inlay_hint_length) + }) + } + + /// Returns the set of folding ranges. + pub fn folding_ranges(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree())) + } + + /// Fuzzy searches for a symbol. + pub fn symbol_search(&self, query: Query) -> Cancelable> { + self.with_db(|db| { + symbol_index::world_symbols(db, query) + .into_iter() + .map(|s| s.to_nav(db)) + .collect::>() + }) + } + + /// Returns the definitions from the symbol at `position`. + pub fn goto_definition( + &self, + position: FilePosition, + ) -> Cancelable>>> { + self.with_db(|db| goto_definition::goto_definition(db, position)) + } + + /// Returns the impls from the symbol at `position`. + pub fn goto_implementation( + &self, + position: FilePosition, + ) -> Cancelable>>> { + self.with_db(|db| impls::goto_implementation(db, position)) + } + + /// Returns the type definitions for the symbol at `position`. + pub fn goto_type_definition( + &self, + position: FilePosition, + ) -> Cancelable>>> { + self.with_db(|db| goto_type_definition::goto_type_definition(db, position)) + } + + /// Finds all usages of the reference at point. + pub fn find_all_refs( + &self, + position: FilePosition, + search_scope: Option, + ) -> Cancelable> { + self.with_db(|db| references::find_all_refs(db, position, search_scope).map(|it| it.info)) + } + + /// Returns a short text describing element at position. + pub fn hover(&self, position: FilePosition) -> Cancelable>> { + self.with_db(|db| hover::hover(db, position)) + } + + /// Computes parameter information for the given call expression. + pub fn call_info(&self, position: FilePosition) -> Cancelable> { + self.with_db(|db| call_info::call_info(db, position)) + } + + /// Returns a `mod name;` declaration which created the current module. + pub fn parent_module(&self, position: FilePosition) -> Cancelable> { + self.with_db(|db| parent_module::parent_module(db, position)) + } + + /// Returns crates this file belongs too. + pub fn crate_for(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| parent_module::crate_for(db, file_id)) + } + + /// Returns the root file of the given crate. + pub fn crate_root(&self, crate_id: CrateId) -> Cancelable { + self.with_db(|db| db.crate_graph().crate_root(crate_id)) + } + + /// Returns the set of possible targets to run for the current file. + pub fn runnables(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| runnables::runnables(db, file_id)) + } + + /// Computes syntax highlighting for the given file. + pub fn highlight(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| syntax_highlighting::highlight(db, file_id)) + } + + /// Computes syntax highlighting for the given file. + pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable { + self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow)) + } + + /// Computes completions at the given position. + pub fn completions(&self, position: FilePosition) -> Cancelable>> { + self.with_db(|db| completion::completions(db, position).map(Into::into)) + } + + /// Computes assists (aka code actions aka intentions) for the given + /// position. + pub fn assists(&self, frange: FileRange) -> Cancelable> { + self.with_db(|db| assists::assists(db, frange)) + } + + /// Computes the set of diagnostics for the given file. + pub fn diagnostics(&self, file_id: FileId) -> Cancelable> { + self.with_db(|db| diagnostics::diagnostics(db, file_id)) + } + + /// Computes the type of the expression at the given position. + pub fn type_of(&self, frange: FileRange) -> Cancelable> { + self.with_db(|db| hover::type_of(db, frange)) + } + + /// Returns the edit required to rename reference at the position to the new + /// name. + pub fn rename( + &self, + position: FilePosition, + new_name: &str, + ) -> Cancelable>> { + self.with_db(|db| references::rename(db, position, new_name)) + } + + /// Performs an operation on that may be Canceled. + fn with_db T + std::panic::UnwindSafe, T>( + &self, + f: F, + ) -> Cancelable { + self.db.catch_canceled(f) + } +} + +#[test] +fn analysis_is_send() { + fn is_send() {} + is_send::(); +} diff --git a/crates/ra_ide/src/line_index.rs b/crates/ra_ide/src/line_index.rs new file mode 100644 index 000000000..710890d27 --- /dev/null +++ b/crates/ra_ide/src/line_index.rs @@ -0,0 +1,283 @@ +//! FIXME: write short doc here + +use crate::TextUnit; +use rustc_hash::FxHashMap; +use superslice::Ext; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct LineIndex { + pub(crate) newlines: Vec, + pub(crate) utf16_lines: FxHashMap>, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct LineCol { + /// Zero-based + pub line: u32, + /// Zero-based + pub col_utf16: u32, +} + +#[derive(Clone, Debug, Hash, PartialEq, Eq)] +pub(crate) struct Utf16Char { + pub(crate) start: TextUnit, + pub(crate) end: TextUnit, +} + +impl Utf16Char { + fn len(&self) -> TextUnit { + self.end - self.start + } +} + +impl LineIndex { + pub fn new(text: &str) -> LineIndex { + let mut utf16_lines = FxHashMap::default(); + let mut utf16_chars = Vec::new(); + + let mut newlines = vec![0.into()]; + let mut curr_row = 0.into(); + let mut curr_col = 0.into(); + let mut line = 0; + for c in text.chars() { + curr_row += TextUnit::of_char(c); + if c == '\n' { + newlines.push(curr_row); + + // Save any utf-16 characters seen in the previous line + if !utf16_chars.is_empty() { + utf16_lines.insert(line, utf16_chars); + utf16_chars = Vec::new(); + } + + // Prepare for processing the next line + curr_col = 0.into(); + line += 1; + continue; + } + + let char_len = TextUnit::of_char(c); + if char_len.to_usize() > 1 { + utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + char_len }); + } + + curr_col += char_len; + } + + // Save any utf-16 characters seen in the last line + if !utf16_chars.is_empty() { + utf16_lines.insert(line, utf16_chars); + } + + LineIndex { newlines, utf16_lines } + } + + pub fn line_col(&self, offset: TextUnit) -> LineCol { + let line = self.newlines.upper_bound(&offset) - 1; + let line_start_offset = self.newlines[line]; + let col = offset - line_start_offset; + + LineCol { line: line as u32, col_utf16: self.utf8_to_utf16_col(line as u32, col) as u32 } + } + + pub fn offset(&self, line_col: LineCol) -> TextUnit { + //FIXME: return Result + let col = self.utf16_to_utf8_col(line_col.line, line_col.col_utf16); + self.newlines[line_col.line as usize] + col + } + + fn utf8_to_utf16_col(&self, line: u32, mut col: TextUnit) -> usize { + if let Some(utf16_chars) = self.utf16_lines.get(&line) { + let mut correction = TextUnit::from_usize(0); + for c in utf16_chars { + if col >= c.end { + correction += c.len() - TextUnit::from_usize(1); + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + + col -= correction; + } + + col.to_usize() + } + + fn utf16_to_utf8_col(&self, line: u32, col: u32) -> TextUnit { + let mut col: TextUnit = col.into(); + if let Some(utf16_chars) = self.utf16_lines.get(&line) { + for c in utf16_chars { + if col >= c.start { + col += c.len() - TextUnit::from_usize(1); + } else { + // From here on, all utf16 characters come *after* the character we are mapping, + // so we don't need to take them into account + break; + } + } + } + + col + } +} + +#[cfg(test)] +/// Simple reference implementation to use in proptests +pub fn to_line_col(text: &str, offset: TextUnit) -> LineCol { + let mut res = LineCol { line: 0, col_utf16: 0 }; + for (i, c) in text.char_indices() { + if i + c.len_utf8() > offset.to_usize() { + // if it's an invalid offset, inside a multibyte char + // return as if it was at the start of the char + break; + } + if c == '\n' { + res.line += 1; + res.col_utf16 = 0; + } else { + res.col_utf16 += 1; + } + } + res +} + +#[cfg(test)] +mod test_line_index { + use super::*; + use proptest::{prelude::*, proptest}; + use ra_text_edit::test_utils::{arb_offset, arb_text}; + + #[test] + fn test_line_index() { + let text = "hello\nworld"; + let index = LineIndex::new(text); + assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 }); + assert_eq!(index.line_col(1.into()), LineCol { line: 0, col_utf16: 1 }); + assert_eq!(index.line_col(5.into()), LineCol { line: 0, col_utf16: 5 }); + assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 0 }); + assert_eq!(index.line_col(7.into()), LineCol { line: 1, col_utf16: 1 }); + assert_eq!(index.line_col(8.into()), LineCol { line: 1, col_utf16: 2 }); + assert_eq!(index.line_col(10.into()), LineCol { line: 1, col_utf16: 4 }); + assert_eq!(index.line_col(11.into()), LineCol { line: 1, col_utf16: 5 }); + assert_eq!(index.line_col(12.into()), LineCol { line: 1, col_utf16: 6 }); + + let text = "\nhello\nworld"; + let index = LineIndex::new(text); + assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 }); + assert_eq!(index.line_col(1.into()), LineCol { line: 1, col_utf16: 0 }); + assert_eq!(index.line_col(2.into()), LineCol { line: 1, col_utf16: 1 }); + assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 5 }); + assert_eq!(index.line_col(7.into()), LineCol { line: 2, col_utf16: 0 }); + } + + fn arb_text_with_offset() -> BoxedStrategy<(TextUnit, String)> { + arb_text().prop_flat_map(|text| (arb_offset(&text), Just(text))).boxed() + } + + fn to_line_col(text: &str, offset: TextUnit) -> LineCol { + let mut res = LineCol { line: 0, col_utf16: 0 }; + for (i, c) in text.char_indices() { + if i + c.len_utf8() > offset.to_usize() { + // if it's an invalid offset, inside a multibyte char + // return as if it was at the start of the char + break; + } + if c == '\n' { + res.line += 1; + res.col_utf16 = 0; + } else { + res.col_utf16 += 1; + } + } + res + } + + proptest! { + #[test] + fn test_line_index_proptest((offset, text) in arb_text_with_offset()) { + let expected = to_line_col(&text, offset); + let line_index = LineIndex::new(&text); + let actual = line_index.line_col(offset); + + assert_eq!(actual, expected); + } + } +} + +#[cfg(test)] +mod test_utf8_utf16_conv { + use super::*; + + #[test] + fn test_char_len() { + assert_eq!('メ'.len_utf8(), 3); + assert_eq!('メ'.len_utf16(), 1); + } + + #[test] + fn test_empty_index() { + let col_index = LineIndex::new( + " +const C: char = 'x'; +", + ); + assert_eq!(col_index.utf16_lines.len(), 0); + } + + #[test] + fn test_single_char() { + let col_index = LineIndex::new( + " +const C: char = 'メ'; +", + ); + + assert_eq!(col_index.utf16_lines.len(), 1); + assert_eq!(col_index.utf16_lines[&1].len(), 1); + assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); + + // UTF-8 to UTF-16, no changes + assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); + + // UTF-8 to UTF-16 + assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20); + + // UTF-16 to UTF-8, no changes + assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from(15)); + + // UTF-16 to UTF-8 + assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from(21)); + } + + #[test] + fn test_string() { + let col_index = LineIndex::new( + " +const C: char = \"メ メ\"; +", + ); + + assert_eq!(col_index.utf16_lines.len(), 1); + assert_eq!(col_index.utf16_lines[&1].len(), 2); + assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); + assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() }); + + // UTF-8 to UTF-16 + assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); + + assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19); + assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21); + + assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15); + + // UTF-16 to UTF-8 + assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from_usize(15)); + + assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextUnit::from_usize(20)); + assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from_usize(23)); + + assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15)); + } +} diff --git a/crates/ra_ide/src/line_index_utils.rs b/crates/ra_ide/src/line_index_utils.rs new file mode 100644 index 000000000..bd1e08feb --- /dev/null +++ b/crates/ra_ide/src/line_index_utils.rs @@ -0,0 +1,331 @@ +//! FIXME: write short doc here + +use crate::{line_index::Utf16Char, LineCol, LineIndex}; +use ra_syntax::{TextRange, TextUnit}; +use ra_text_edit::{AtomTextEdit, TextEdit}; + +#[derive(Debug, Clone)] +enum Step { + Newline(TextUnit), + Utf16Char(TextRange), +} + +#[derive(Debug)] +struct LineIndexStepIter<'a> { + line_index: &'a LineIndex, + next_newline_idx: usize, + utf16_chars: Option<(TextUnit, std::slice::Iter<'a, Utf16Char>)>, +} + +impl<'a> LineIndexStepIter<'a> { + fn from(line_index: &LineIndex) -> LineIndexStepIter { + let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None }; + // skip first newline since it's not real + x.next(); + x + } +} + +impl<'a> Iterator for LineIndexStepIter<'a> { + type Item = Step; + fn next(&mut self) -> Option { + self.utf16_chars + .as_mut() + .and_then(|(newline, x)| { + let x = x.next()?; + Some(Step::Utf16Char(TextRange::from_to(*newline + x.start, *newline + x.end))) + }) + .or_else(|| { + let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?; + self.utf16_chars = self + .line_index + .utf16_lines + .get(&(self.next_newline_idx as u32)) + .map(|x| (next_newline, x.iter())); + self.next_newline_idx += 1; + Some(Step::Newline(next_newline)) + }) + } +} + +#[derive(Debug)] +struct OffsetStepIter<'a> { + text: &'a str, + offset: TextUnit, +} + +impl<'a> Iterator for OffsetStepIter<'a> { + type Item = Step; + fn next(&mut self) -> Option { + let (next, next_offset) = self + .text + .char_indices() + .filter_map(|(i, c)| { + if c == '\n' { + let next_offset = self.offset + TextUnit::from_usize(i + 1); + let next = Step::Newline(next_offset); + Some((next, next_offset)) + } else { + let char_len = TextUnit::of_char(c); + if char_len.to_usize() > 1 { + let start = self.offset + TextUnit::from_usize(i); + let end = start + char_len; + let next = Step::Utf16Char(TextRange::from_to(start, end)); + let next_offset = end; + Some((next, next_offset)) + } else { + None + } + } + }) + .next()?; + let next_idx = (next_offset - self.offset).to_usize(); + self.text = &self.text[next_idx..]; + self.offset = next_offset; + Some(next) + } +} + +#[derive(Debug)] +enum NextSteps<'a> { + Use, + ReplaceMany(OffsetStepIter<'a>), + AddMany(OffsetStepIter<'a>), +} + +#[derive(Debug)] +struct TranslatedEdit<'a> { + delete: TextRange, + insert: &'a str, + diff: i64, +} + +struct Edits<'a> { + edits: &'a [AtomTextEdit], + current: Option>, + acc_diff: i64, +} + +impl<'a> Edits<'a> { + fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> { + let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 }; + x.advance_edit(); + x + } + fn advance_edit(&mut self) { + self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff); + match self.edits.split_first() { + Some((next, rest)) => { + let delete = self.translate_range(next.delete); + let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64; + self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff }); + self.edits = rest; + } + None => { + self.current = None; + } + } + } + + fn next_inserted_steps(&mut self) -> Option> { + let cur = self.current.as_ref()?; + let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert }); + self.advance_edit(); + res + } + + fn next_steps(&mut self, step: &Step) -> NextSteps { + let step_pos = match *step { + Step::Newline(n) => n, + Step::Utf16Char(r) => r.end(), + }; + match &mut self.current { + Some(edit) => { + if step_pos <= edit.delete.start() { + NextSteps::Use + } else if step_pos <= edit.delete.end() { + let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert }; + // empty slice to avoid returning steps again + edit.insert = &edit.insert[edit.insert.len()..]; + NextSteps::ReplaceMany(iter) + } else { + let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert }; + // empty slice to avoid returning steps again + edit.insert = &edit.insert[edit.insert.len()..]; + self.advance_edit(); + NextSteps::AddMany(iter) + } + } + None => NextSteps::Use, + } + } + + fn translate_range(&self, range: TextRange) -> TextRange { + if self.acc_diff == 0 { + range + } else { + let start = self.translate(range.start()); + let end = self.translate(range.end()); + TextRange::from_to(start, end) + } + } + + fn translate(&self, x: TextUnit) -> TextUnit { + if self.acc_diff == 0 { + x + } else { + TextUnit::from((x.to_usize() as i64 + self.acc_diff) as u32) + } + } + + fn translate_step(&self, x: &Step) -> Step { + if self.acc_diff == 0 { + x.clone() + } else { + match *x { + Step::Newline(n) => Step::Newline(self.translate(n)), + Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)), + } + } + } +} + +#[derive(Debug)] +struct RunningLineCol { + line: u32, + last_newline: TextUnit, + col_adjust: TextUnit, +} + +impl RunningLineCol { + fn new() -> RunningLineCol { + RunningLineCol { line: 0, last_newline: TextUnit::from(0), col_adjust: TextUnit::from(0) } + } + + fn to_line_col(&self, offset: TextUnit) -> LineCol { + LineCol { + line: self.line, + col_utf16: ((offset - self.last_newline) - self.col_adjust).into(), + } + } + + fn add_line(&mut self, newline: TextUnit) { + self.line += 1; + self.last_newline = newline; + self.col_adjust = TextUnit::from(0); + } + + fn adjust_col(&mut self, range: TextRange) { + self.col_adjust += range.len() - TextUnit::from(1); + } +} + +pub fn translate_offset_with_edit( + line_index: &LineIndex, + offset: TextUnit, + text_edit: &TextEdit, +) -> LineCol { + let mut state = Edits::from_text_edit(&text_edit); + + let mut res = RunningLineCol::new(); + + macro_rules! test_step { + ($x:ident) => { + match &$x { + Step::Newline(n) => { + if offset < *n { + return res.to_line_col(offset); + } else { + res.add_line(*n); + } + } + Step::Utf16Char(x) => { + if offset < x.end() { + // if the offset is inside a multibyte char it's invalid + // clamp it to the start of the char + let clamp = offset.min(x.start()); + return res.to_line_col(clamp); + } else { + res.adjust_col(*x); + } + } + } + }; + } + + for orig_step in LineIndexStepIter::from(line_index) { + loop { + let translated_step = state.translate_step(&orig_step); + match state.next_steps(&translated_step) { + NextSteps::Use => { + test_step!(translated_step); + break; + } + NextSteps::ReplaceMany(ns) => { + for n in ns { + test_step!(n); + } + break; + } + NextSteps::AddMany(ns) => { + for n in ns { + test_step!(n); + } + } + } + } + } + + loop { + match state.next_inserted_steps() { + None => break, + Some(ns) => { + for n in ns { + test_step!(n); + } + } + } + } + + res.to_line_col(offset) +} + +#[cfg(test)] +mod test { + use super::*; + use crate::line_index; + use proptest::{prelude::*, proptest}; + use ra_text_edit::test_utils::{arb_offset, arb_text_with_edit}; + use ra_text_edit::TextEdit; + + #[derive(Debug)] + struct ArbTextWithEditAndOffset { + text: String, + edit: TextEdit, + edited_text: String, + offset: TextUnit, + } + + fn arb_text_with_edit_and_offset() -> BoxedStrategy { + arb_text_with_edit() + .prop_flat_map(|x| { + let edited_text = x.edit.apply(&x.text); + let arb_offset = arb_offset(&edited_text); + (Just(x), Just(edited_text), arb_offset).prop_map(|(x, edited_text, offset)| { + ArbTextWithEditAndOffset { text: x.text, edit: x.edit, edited_text, offset } + }) + }) + .boxed() + } + + proptest! { + #[test] + fn test_translate_offset_with_edit(x in arb_text_with_edit_and_offset()) { + let expected = line_index::to_line_col(&x.edited_text, x.offset); + let line_index = LineIndex::new(&x.text); + let actual = translate_offset_with_edit(&line_index, x.offset, &x.edit); + + assert_eq!(actual, expected); + } + } +} diff --git a/crates/ra_ide/src/marks.rs b/crates/ra_ide/src/marks.rs new file mode 100644 index 000000000..848ae4dc7 --- /dev/null +++ b/crates/ra_ide/src/marks.rs @@ -0,0 +1,13 @@ +//! See test_utils/src/marks.rs + +test_utils::marks!( + inserts_angle_brackets_for_generics + inserts_parens_for_function_calls + goto_definition_works_for_macros + goto_definition_works_for_methods + goto_definition_works_for_fields + goto_definition_works_for_record_fields + call_info_bad_offset + dont_complete_current_use + dont_complete_primitive_in_use +); diff --git a/crates/ra_ide/src/matching_brace.rs b/crates/ra_ide/src/matching_brace.rs new file mode 100644 index 000000000..d1204fac0 --- /dev/null +++ b/crates/ra_ide/src/matching_brace.rs @@ -0,0 +1,43 @@ +//! FIXME: write short doc here + +use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextUnit, T}; + +pub fn matching_brace(file: &SourceFile, offset: TextUnit) -> Option { + const BRACES: &[SyntaxKind] = + &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; + let (brace_node, brace_idx) = file + .syntax() + .token_at_offset(offset) + .filter_map(|node| { + let idx = BRACES.iter().position(|&brace| brace == node.kind())?; + Some((node, idx)) + }) + .next()?; + let parent = brace_node.parent(); + let matching_kind = BRACES[brace_idx ^ 1]; + let matching_node = parent.children_with_tokens().find(|node| node.kind() == matching_kind)?; + Some(matching_node.text_range().start()) +} + +#[cfg(test)] +mod tests { + use test_utils::{add_cursor, assert_eq_text, extract_offset}; + + use super::*; + + #[test] + fn test_matching_brace() { + fn do_check(before: &str, after: &str) { + let (pos, before) = extract_offset(before); + let parse = SourceFile::parse(&before); + let new_pos = match matching_brace(&parse.tree(), pos) { + None => pos, + Some(pos) => pos, + }; + let actual = add_cursor(&before, new_pos); + assert_eq_text!(after, &actual); + } + + do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); + } +} diff --git a/crates/ra_ide/src/mock_analysis.rs b/crates/ra_ide/src/mock_analysis.rs new file mode 100644 index 000000000..bf8a54932 --- /dev/null +++ b/crates/ra_ide/src/mock_analysis.rs @@ -0,0 +1,149 @@ +//! FIXME: write short doc here + +use std::sync::Arc; + +use ra_cfg::CfgOptions; +use ra_db::{Env, RelativePathBuf}; +use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; + +use crate::{ + Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, + FileRange, SourceRootId, +}; + +/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis +/// from a set of in-memory files. +#[derive(Debug, Default)] +pub struct MockAnalysis { + files: Vec<(String, String)>, +} + +impl MockAnalysis { + pub fn new() -> MockAnalysis { + MockAnalysis::default() + } + /// Creates `MockAnalysis` using a fixture data in the following format: + /// + /// ```not_rust + /// //- /main.rs + /// mod foo; + /// fn main() {} + /// + /// //- /foo.rs + /// struct Baz; + /// ``` + pub fn with_files(fixture: &str) -> MockAnalysis { + let mut res = MockAnalysis::new(); + for entry in parse_fixture(fixture) { + res.add_file(&entry.meta, &entry.text); + } + res + } + + /// Same as `with_files`, but requires that a single file contains a `<|>` marker, + /// whose position is also returned. + pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) { + let mut position = None; + let mut res = MockAnalysis::new(); + for entry in parse_fixture(fixture) { + if entry.text.contains(CURSOR_MARKER) { + assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); + position = Some(res.add_file_with_position(&entry.meta, &entry.text)); + } else { + res.add_file(&entry.meta, &entry.text); + } + } + let position = position.expect("expected a marker (<|>)"); + (res, position) + } + + pub fn add_file(&mut self, path: &str, text: &str) -> FileId { + let file_id = FileId((self.files.len() + 1) as u32); + self.files.push((path.to_string(), text.to_string())); + file_id + } + pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { + let (offset, text) = extract_offset(text); + let file_id = FileId((self.files.len() + 1) as u32); + self.files.push((path.to_string(), text)); + FilePosition { file_id, offset } + } + pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { + let (range, text) = extract_range(text); + let file_id = FileId((self.files.len() + 1) as u32); + self.files.push((path.to_string(), text)); + FileRange { file_id, range } + } + pub fn id_of(&self, path: &str) -> FileId { + let (idx, _) = self + .files + .iter() + .enumerate() + .find(|(_, (p, _text))| path == p) + .expect("no file in this mock"); + FileId(idx as u32 + 1) + } + pub fn analysis_host(self) -> AnalysisHost { + let mut host = AnalysisHost::default(); + let source_root = SourceRootId(0); + let mut change = AnalysisChange::new(); + change.add_root(source_root, true); + let mut crate_graph = CrateGraph::default(); + let mut root_crate = None; + for (i, (path, contents)) in self.files.into_iter().enumerate() { + assert!(path.starts_with('/')); + let path = RelativePathBuf::from_path(&path[1..]).unwrap(); + let file_id = FileId(i as u32 + 1); + let cfg_options = CfgOptions::default(); + if path == "/lib.rs" || path == "/main.rs" { + root_crate = Some(crate_graph.add_crate_root( + file_id, + Edition2018, + cfg_options, + Env::default(), + )); + } else if path.ends_with("/lib.rs") { + let other_crate = + crate_graph.add_crate_root(file_id, Edition2018, cfg_options, Env::default()); + let crate_name = path.parent().unwrap().file_name().unwrap(); + if let Some(root_crate) = root_crate { + crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap(); + } + } + change.add_file(source_root, file_id, path, Arc::new(contents)); + } + change.set_crate_graph(crate_graph); + host.apply_change(change); + host + } + pub fn analysis(self) -> Analysis { + self.analysis_host().analysis() + } +} + +/// Creates analysis from a multi-file fixture, returns positions marked with <|>. +pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) { + let (mock, position) = MockAnalysis::with_files_and_position(fixture); + (mock.analysis(), position) +} + +/// Creates analysis for a single file. +pub fn single_file(code: &str) -> (Analysis, FileId) { + let mut mock = MockAnalysis::new(); + let file_id = mock.add_file("/main.rs", code); + (mock.analysis(), file_id) +} + +/// Creates analysis for a single file, returns position marked with <|>. +pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) { + let mut mock = MockAnalysis::new(); + let pos = mock.add_file_with_position("/main.rs", code); + (mock.analysis(), pos) +} + +/// Creates analysis for a single file, returns range marked with a pair of <|>. +pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) { + let mut mock = MockAnalysis::new(); + let pos = mock.add_file_with_range("/main.rs", code); + (mock.analysis(), pos) +} diff --git a/crates/ra_ide/src/parent_module.rs b/crates/ra_ide/src/parent_module.rs new file mode 100644 index 000000000..6027e7d54 --- /dev/null +++ b/crates/ra_ide/src/parent_module.rs @@ -0,0 +1,104 @@ +//! FIXME: write short doc here + +use ra_db::{CrateId, FileId, FilePosition}; + +use crate::{db::RootDatabase, NavigationTarget}; + +/// This returns `Vec` because a module may be included from several places. We +/// don't handle this case yet though, so the Vec has length at most one. +pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec { + let src = hir::ModuleSource::from_position(db, position); + let module = match hir::Module::from_definition( + db, + hir::Source { file_id: position.file_id.into(), value: src }, + ) { + None => return Vec::new(), + Some(it) => it, + }; + let nav = NavigationTarget::from_module_to_decl(db, module); + vec![nav] +} + +/// Returns `Vec` for the same reason as `parent_module` +pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec { + let src = hir::ModuleSource::from_file_id(db, file_id); + let module = + match hir::Module::from_definition(db, hir::Source { file_id: file_id.into(), value: src }) + { + Some(it) => it, + None => return Vec::new(), + }; + let krate = module.krate(); + vec![krate.crate_id()] +} + +#[cfg(test)] +mod tests { + use ra_cfg::CfgOptions; + use ra_db::Env; + + use crate::{ + mock_analysis::{analysis_and_position, MockAnalysis}, + AnalysisChange, CrateGraph, + Edition::Edition2018, + }; + + #[test] + fn test_resolve_parent_module() { + let (analysis, pos) = analysis_and_position( + " + //- /lib.rs + mod foo; + //- /foo.rs + <|>// empty + ", + ); + let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); + nav.assert_match("foo MODULE FileId(1) [0; 8)"); + } + + #[test] + fn test_resolve_parent_module_for_inline() { + let (analysis, pos) = analysis_and_position( + " + //- /lib.rs + mod foo { + mod bar { + mod baz { <|> } + } + } + ", + ); + let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); + nav.assert_match("baz MODULE FileId(1) [32; 44)"); + } + + #[test] + fn test_resolve_crate_root() { + let mock = MockAnalysis::with_files( + " + //- /bar.rs + mod foo; + //- /foo.rs + // empty <|> + ", + ); + let root_file = mock.id_of("/bar.rs"); + let mod_file = mock.id_of("/foo.rs"); + let mut host = mock.analysis_host(); + assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); + + let mut crate_graph = CrateGraph::default(); + let crate_id = crate_graph.add_crate_root( + root_file, + Edition2018, + CfgOptions::default(), + Env::default(), + ); + let mut change = AnalysisChange::new(); + change.set_crate_graph(crate_graph); + host.apply_change(change); + + assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]); + } +} diff --git a/crates/ra_ide/src/references.rs b/crates/ra_ide/src/references.rs new file mode 100644 index 000000000..21a1ea69e --- /dev/null +++ b/crates/ra_ide/src/references.rs @@ -0,0 +1,389 @@ +//! This module implements a reference search. +//! First, the element at the cursor position must be either an `ast::Name` +//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we +//! try to resolve the direct tree parent of this element, otherwise we +//! already have a definition and just need to get its HIR together with +//! some information that is needed for futher steps of searching. +//! After that, we collect files that might contain references and look +//! for text occurrences of the identifier. If there's an `ast::NameRef` +//! at the index that the match starts at and its tree parent is +//! resolved to the search element definition, we get a reference. + +mod classify; +mod name_definition; +mod rename; +mod search_scope; + +use hir::Source; +use once_cell::unsync::Lazy; +use ra_db::{SourceDatabase, SourceDatabaseExt}; +use ra_prof::profile; +use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; + +use crate::{ + db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, +}; + +pub(crate) use self::{ + classify::{classify_name, classify_name_ref}, + name_definition::{NameDefinition, NameKind}, + rename::rename, +}; + +pub use self::search_scope::SearchScope; + +#[derive(Debug, Clone)] +pub struct ReferenceSearchResult { + declaration: NavigationTarget, + references: Vec, +} + +impl ReferenceSearchResult { + pub fn declaration(&self) -> &NavigationTarget { + &self.declaration + } + + pub fn references(&self) -> &[FileRange] { + &self.references + } + + /// Total number of references + /// At least 1 since all valid references should + /// Have a declaration + pub fn len(&self) -> usize { + self.references.len() + 1 + } +} + +// allow turning ReferenceSearchResult into an iterator +// over FileRanges +impl IntoIterator for ReferenceSearchResult { + type Item = FileRange; + type IntoIter = std::vec::IntoIter; + + fn into_iter(mut self) -> Self::IntoIter { + let mut v = Vec::with_capacity(self.len()); + v.push(FileRange { file_id: self.declaration.file_id(), range: self.declaration.range() }); + v.append(&mut self.references); + v.into_iter() + } +} + +pub(crate) fn find_all_refs( + db: &RootDatabase, + position: FilePosition, + search_scope: Option, +) -> Option> { + let parse = db.parse(position.file_id); + let syntax = parse.tree().syntax().clone(); + let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; + + let declaration = match def.kind { + NameKind::Macro(mac) => mac.to_nav(db), + NameKind::Field(field) => field.to_nav(db), + NameKind::AssocItem(assoc) => assoc.to_nav(db), + NameKind::Def(def) => NavigationTarget::from_def(db, def)?, + NameKind::SelfType(imp) => imp.to_nav(db), + NameKind::Local(local) => local.to_nav(db), + NameKind::GenericParam(_) => return None, + }; + + let search_scope = { + let base = def.search_scope(db); + match search_scope { + None => base, + Some(scope) => base.intersection(&scope), + } + }; + + let references = process_definition(db, def, name, search_scope); + + Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) +} + +fn find_name<'a>( + db: &RootDatabase, + syntax: &SyntaxNode, + position: FilePosition, +) -> Option> { + if let Some(name) = find_node_at_offset::(&syntax, position.offset) { + let def = classify_name(db, Source::new(position.file_id.into(), &name))?; + let range = name.syntax().text_range(); + return Some(RangeInfo::new(range, (name.text().to_string(), def))); + } + let name_ref = find_node_at_offset::(&syntax, position.offset)?; + let def = classify_name_ref(db, Source::new(position.file_id.into(), &name_ref))?; + let range = name_ref.syntax().text_range(); + Some(RangeInfo::new(range, (name_ref.text().to_string(), def))) +} + +fn process_definition( + db: &RootDatabase, + def: NameDefinition, + name: String, + scope: SearchScope, +) -> Vec { + let _p = profile("process_definition"); + + let pat = name.as_str(); + let mut refs = vec![]; + + for (file_id, search_range) in scope { + let text = db.file_text(file_id); + let parse = Lazy::new(|| SourceFile::parse(&text)); + + for (idx, _) in text.match_indices(pat) { + let offset = TextUnit::from_usize(idx); + + if let Some(name_ref) = + find_node_at_offset::(parse.tree().syntax(), offset) + { + let range = name_ref.syntax().text_range(); + if let Some(search_range) = search_range { + if !range.is_subrange(&search_range) { + continue; + } + } + if let Some(d) = classify_name_ref(db, Source::new(file_id.into(), &name_ref)) { + if d == def { + refs.push(FileRange { file_id, range }); + } + } + } + } + } + refs +} + +#[cfg(test)] +mod tests { + use crate::{ + mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, + ReferenceSearchResult, SearchScope, + }; + + #[test] + fn test_find_all_refs_for_local() { + let code = r#" + fn main() { + let mut i = 1; + let j = 1; + i = i<|> + j; + + { + i = 0; + } + + i = 5; + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 5); + } + + #[test] + fn test_find_all_refs_for_param_inside() { + let code = r#" + fn foo(i : u32) -> u32 { + i<|> + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_for_fn_param() { + let code = r#" + fn foo(i<|> : u32) -> u32 { + i + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_field_name() { + let code = r#" + //- /lib.rs + struct Foo { + pub spam<|>: u32, + } + + fn main(s: Foo) { + let f = s.spam; + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_impl_item_name() { + let code = r#" + //- /lib.rs + struct Foo; + impl Foo { + fn f<|>(&self) { } + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 1); + } + + #[test] + fn test_find_all_refs_enum_var_name() { + let code = r#" + //- /lib.rs + enum Foo { + A, + B<|>, + C, + } + "#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 1); + } + + #[test] + fn test_find_all_refs_two_modules() { + let code = r#" + //- /lib.rs + pub mod foo; + pub mod bar; + + fn f() { + let i = foo::Foo { n: 5 }; + } + + //- /foo.rs + use crate::bar; + + pub struct Foo { + pub n: u32, + } + + fn f() { + let i = bar::Bar { n: 5 }; + } + + //- /bar.rs + use crate::foo; + + pub struct Bar { + pub n: u32, + } + + fn f() { + let i = foo::Foo<|> { n: 5 }; + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); + assert_eq!(refs.len(), 3); + } + + // `mod foo;` is not in the results because `foo` is an `ast::Name`. + // So, there are two references: the first one is a definition of the `foo` module, + // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. + #[test] + fn test_find_all_refs_decl_module() { + let code = r#" + //- /lib.rs + mod foo<|>; + + use foo::Foo; + + fn f() { + let i = Foo { n: 5 }; + } + + //- /foo.rs + pub struct Foo { + pub n: u32, + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_super_mod_vis() { + let code = r#" + //- /lib.rs + mod foo; + + //- /foo.rs + mod some; + use some::Foo; + + fn f() { + let i = Foo { n: 5 }; + } + + //- /foo/some.rs + pub(super) struct Foo<|> { + pub n: u32, + } + "#; + + let (analysis, pos) = analysis_and_position(code); + let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); + assert_eq!(refs.len(), 3); + } + + #[test] + fn test_find_all_refs_with_scope() { + let code = r#" + //- /lib.rs + mod foo; + mod bar; + + pub fn quux<|>() {} + + //- /foo.rs + fn f() { super::quux(); } + + //- /bar.rs + fn f() { super::quux(); } + "#; + + let (mock, pos) = MockAnalysis::with_files_and_position(code); + let bar = mock.id_of("/bar.rs"); + let analysis = mock.analysis(); + + let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); + assert_eq!(refs.len(), 3); + + let refs = + analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); + assert_eq!(refs.len(), 2); + } + + #[test] + fn test_find_all_refs_macro_def() { + let code = r#" + #[macro_export] + macro_rules! m1<|> { () => (()) } + + fn foo() { + m1(); + m1(); + }"#; + + let refs = get_all_refs(code); + assert_eq!(refs.len(), 3); + } + + fn get_all_refs(text: &str) -> ReferenceSearchResult { + let (analysis, position) = single_file_with_position(text); + analysis.find_all_refs(position, None).unwrap().unwrap() + } +} diff --git a/crates/ra_ide/src/references/classify.rs b/crates/ra_ide/src/references/classify.rs new file mode 100644 index 000000000..5cea805ec --- /dev/null +++ b/crates/ra_ide/src/references/classify.rs @@ -0,0 +1,186 @@ +//! Functions that are used to classify an element from its definition or reference. + +use hir::{FromSource, Module, ModuleSource, PathResolution, Source, SourceAnalyzer}; +use ra_prof::profile; +use ra_syntax::{ast, match_ast, AstNode}; +use test_utils::tested_by; + +use super::{ + name_definition::{from_assoc_item, from_module_def, from_struct_field}, + NameDefinition, NameKind, +}; +use crate::db::RootDatabase; + +pub(crate) fn classify_name(db: &RootDatabase, name: Source<&ast::Name>) -> Option { + let _p = profile("classify_name"); + let parent = name.value.syntax().parent()?; + + match_ast! { + match parent { + ast::BindPat(it) => { + let src = name.with_value(it); + let local = hir::Local::from_source(db, src)?; + Some(NameDefinition { + visibility: None, + container: local.module(db), + kind: NameKind::Local(local), + }) + }, + ast::RecordFieldDef(it) => { + let ast = hir::FieldSource::Named(it); + let src = name.with_value(ast); + let field = hir::StructField::from_source(db, src)?; + Some(from_struct_field(db, field)) + }, + ast::Module(it) => { + let def = { + if !it.has_semi() { + let ast = hir::ModuleSource::Module(it); + let src = name.with_value(ast); + hir::Module::from_definition(db, src) + } else { + let src = name.with_value(it); + hir::Module::from_declaration(db, src) + } + }?; + Some(from_module_def(db, def.into(), None)) + }, + ast::StructDef(it) => { + let src = name.with_value(it); + let def = hir::Struct::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::EnumDef(it) => { + let src = name.with_value(it); + let def = hir::Enum::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::TraitDef(it) => { + let src = name.with_value(it); + let def = hir::Trait::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::StaticDef(it) => { + let src = name.with_value(it); + let def = hir::Static::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::EnumVariant(it) => { + let src = name.with_value(it); + let def = hir::EnumVariant::from_source(db, src)?; + Some(from_module_def(db, def.into(), None)) + }, + ast::FnDef(it) => { + let src = name.with_value(it); + let def = hir::Function::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::ConstDef(it) => { + let src = name.with_value(it); + let def = hir::Const::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::TypeAliasDef(it) => { + let src = name.with_value(it); + let def = hir::TypeAlias::from_source(db, src)?; + if parent.parent().and_then(ast::ItemList::cast).is_some() { + Some(from_assoc_item(db, def.into())) + } else { + Some(from_module_def(db, def.into(), None)) + } + }, + ast::MacroCall(it) => { + let src = name.with_value(it); + let def = hir::MacroDef::from_source(db, src.clone())?; + + let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax())); + let module = Module::from_definition(db, src.with_value(module_src))?; + + Some(NameDefinition { + visibility: None, + container: module, + kind: NameKind::Macro(def), + }) + }, + _ => None, + } + } +} + +pub(crate) fn classify_name_ref( + db: &RootDatabase, + name_ref: Source<&ast::NameRef>, +) -> Option { + let _p = profile("classify_name_ref"); + + let parent = name_ref.value.syntax().parent()?; + let analyzer = SourceAnalyzer::new(db, name_ref.map(|it| it.syntax()), None); + + if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { + tested_by!(goto_definition_works_for_methods); + if let Some(func) = analyzer.resolve_method_call(&method_call) { + return Some(from_assoc_item(db, func.into())); + } + } + + if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { + tested_by!(goto_definition_works_for_fields); + if let Some(field) = analyzer.resolve_field(&field_expr) { + return Some(from_struct_field(db, field)); + } + } + + if let Some(record_field) = ast::RecordField::cast(parent.clone()) { + tested_by!(goto_definition_works_for_record_fields); + if let Some(field_def) = analyzer.resolve_record_field(&record_field) { + return Some(from_struct_field(db, field_def)); + } + } + + let ast = ModuleSource::from_child_node(db, name_ref.with_value(&parent)); + // FIXME: find correct container and visibility for each case + let container = Module::from_definition(db, name_ref.with_value(ast))?; + let visibility = None; + + if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { + tested_by!(goto_definition_works_for_macros); + if let Some(macro_def) = analyzer.resolve_macro_call(db, name_ref.with_value(¯o_call)) { + let kind = NameKind::Macro(macro_def); + return Some(NameDefinition { kind, container, visibility }); + } + } + + let path = name_ref.value.syntax().ancestors().find_map(ast::Path::cast)?; + let resolved = analyzer.resolve_path(db, &path)?; + match resolved { + PathResolution::Def(def) => Some(from_module_def(db, def, Some(container))), + PathResolution::AssocItem(item) => Some(from_assoc_item(db, item)), + PathResolution::Local(local) => { + let container = local.module(db); + let kind = NameKind::Local(local); + Some(NameDefinition { kind, container, visibility: None }) + } + PathResolution::GenericParam(par) => { + // FIXME: get generic param def + let kind = NameKind::GenericParam(par); + Some(NameDefinition { kind, container, visibility }) + } + PathResolution::Macro(def) => { + let kind = NameKind::Macro(def); + Some(NameDefinition { kind, container, visibility }) + } + PathResolution::SelfType(impl_block) => { + let kind = NameKind::SelfType(impl_block); + let container = impl_block.module(db); + Some(NameDefinition { kind, container, visibility }) + } + } +} diff --git a/crates/ra_ide/src/references/name_definition.rs b/crates/ra_ide/src/references/name_definition.rs new file mode 100644 index 000000000..10d3a2364 --- /dev/null +++ b/crates/ra_ide/src/references/name_definition.rs @@ -0,0 +1,83 @@ +//! `NameDefinition` keeps information about the element we want to search references for. +//! The element is represented by `NameKind`. It's located inside some `container` and +//! has a `visibility`, which defines a search scope. +//! Note that the reference search is possible for not all of the classified items. + +use hir::{ + Adt, AssocItem, GenericParam, HasSource, ImplBlock, Local, MacroDef, Module, ModuleDef, + StructField, VariantDef, +}; +use ra_syntax::{ast, ast::VisibilityOwner}; + +use crate::db::RootDatabase; + +#[derive(Debug, PartialEq, Eq)] +pub enum NameKind { + Macro(MacroDef), + Field(StructField), + AssocItem(AssocItem), + Def(ModuleDef), + SelfType(ImplBlock), + Local(Local), + GenericParam(GenericParam), +} + +#[derive(PartialEq, Eq)] +pub(crate) struct NameDefinition { + pub visibility: Option, + pub container: Module, + pub kind: NameKind, +} + +pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition { + let container = item.module(db); + let visibility = match item { + AssocItem::Function(f) => f.source(db).value.visibility(), + AssocItem::Const(c) => c.source(db).value.visibility(), + AssocItem::TypeAlias(a) => a.source(db).value.visibility(), + }; + let kind = NameKind::AssocItem(item); + NameDefinition { kind, container, visibility } +} + +pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition { + let kind = NameKind::Field(field); + let parent = field.parent_def(db); + let container = parent.module(db); + let visibility = match parent { + VariantDef::Struct(s) => s.source(db).value.visibility(), + VariantDef::Union(e) => e.source(db).value.visibility(), + VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(), + }; + NameDefinition { kind, container, visibility } +} + +pub(super) fn from_module_def( + db: &RootDatabase, + def: ModuleDef, + module: Option, +) -> NameDefinition { + let kind = NameKind::Def(def); + let (container, visibility) = match def { + ModuleDef::Module(it) => { + let container = it.parent(db).or_else(|| Some(it)).unwrap(); + let visibility = it.declaration_source(db).and_then(|s| s.value.visibility()); + (container, visibility) + } + ModuleDef::EnumVariant(it) => { + let container = it.module(db); + let visibility = it.source(db).value.parent_enum().visibility(); + (container, visibility) + } + ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()), + ModuleDef::BuiltinType(..) => (module.unwrap(), None), + }; + NameDefinition { kind, container, visibility } +} diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs new file mode 100644 index 000000000..d58496049 --- /dev/null +++ b/crates/ra_ide/src/references/rename.rs @@ -0,0 +1,328 @@ +//! FIXME: write short doc here + +use hir::ModuleSource; +use ra_db::{RelativePath, RelativePathBuf, SourceDatabase, SourceDatabaseExt}; +use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; +use ra_text_edit::TextEdit; + +use crate::{ + db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, + SourceFileEdit, TextRange, +}; + +use super::find_all_refs; + +pub(crate) fn rename( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> Option> { + let parse = db.parse(position.file_id); + if let Some((ast_name, ast_module)) = + find_name_and_module_at_offset(parse.tree().syntax(), position) + { + let range = ast_name.syntax().text_range(); + rename_mod(db, &ast_name, &ast_module, position, new_name) + .map(|info| RangeInfo::new(range, info)) + } else { + rename_reference(db, position, new_name) + } +} + +fn find_name_and_module_at_offset( + syntax: &SyntaxNode, + position: FilePosition, +) -> Option<(ast::Name, ast::Module)> { + let ast_name = find_node_at_offset::(syntax, position.offset)?; + let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; + Some((ast_name, ast_module)) +} + +fn source_edit_from_file_id_range( + file_id: FileId, + range: TextRange, + new_name: &str, +) -> SourceFileEdit { + SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } +} + +fn rename_mod( + db: &RootDatabase, + ast_name: &ast::Name, + ast_module: &ast::Module, + position: FilePosition, + new_name: &str, +) -> Option { + let mut source_file_edits = Vec::new(); + let mut file_system_edits = Vec::new(); + let module_src = hir::Source { file_id: position.file_id.into(), value: ast_module.clone() }; + if let Some(module) = hir::Module::from_declaration(db, module_src) { + let src = module.definition_source(db); + let file_id = src.file_id.original_file(db); + match src.value { + ModuleSource::SourceFile(..) => { + let mod_path: RelativePathBuf = db.file_relative_path(file_id); + // mod is defined in path/to/dir/mod.rs + let dst_path = if mod_path.file_stem() == Some("mod") { + mod_path + .parent() + .and_then(|p| p.parent()) + .or_else(|| Some(RelativePath::new(""))) + .map(|p| p.join(new_name).join("mod.rs")) + } else { + Some(mod_path.with_file_name(new_name).with_extension("rs")) + }; + if let Some(path) = dst_path { + let move_file = FileSystemEdit::MoveFile { + src: file_id, + dst_source_root: db.file_source_root(position.file_id), + dst_path: path, + }; + file_system_edits.push(move_file); + } + } + ModuleSource::Module(..) => {} + } + } + + let edit = SourceFileEdit { + file_id: position.file_id, + edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), + }; + source_file_edits.push(edit); + + Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) +} + +fn rename_reference( + db: &RootDatabase, + position: FilePosition, + new_name: &str, +) -> Option> { + let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; + + let edit = refs + .into_iter() + .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) + .collect::>(); + + if edit.is_empty() { + return None; + } + + Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + use ra_text_edit::TextEditBuilder; + use test_utils::assert_eq_text; + + use crate::{ + mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, + }; + + #[test] + fn test_rename_for_local() { + test_rename( + r#" + fn main() { + let mut i = 1; + let j = 1; + i = i<|> + j; + + { + i = 0; + } + + i = 5; + }"#, + "k", + r#" + fn main() { + let mut k = 1; + let j = 1; + k = k + j; + + { + k = 0; + } + + k = 5; + }"#, + ); + } + + #[test] + fn test_rename_for_param_inside() { + test_rename( + r#" + fn foo(i : u32) -> u32 { + i<|> + }"#, + "j", + r#" + fn foo(j : u32) -> u32 { + j + }"#, + ); + } + + #[test] + fn test_rename_refs_for_fn_param() { + test_rename( + r#" + fn foo(i<|> : u32) -> u32 { + i + }"#, + "new_name", + r#" + fn foo(new_name : u32) -> u32 { + new_name + }"#, + ); + } + + #[test] + fn test_rename_for_mut_param() { + test_rename( + r#" + fn foo(mut i<|> : u32) -> u32 { + i + }"#, + "new_name", + r#" + fn foo(mut new_name : u32) -> u32 { + new_name + }"#, + ); + } + + #[test] + fn test_rename_mod() { + let (analysis, position) = analysis_and_position( + " + //- /lib.rs + mod bar; + + //- /bar.rs + mod foo<|>; + + //- /bar/foo.rs + // emtpy + ", + ); + let new_name = "foo2"; + let source_change = analysis.rename(position, new_name).unwrap(); + assert_debug_snapshot!(&source_change, +@r###" + Some( + RangeInfo { + range: [4; 7), + info: SourceChange { + label: "rename", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 2, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [4; 7), + insert: "foo2", + }, + ], + }, + }, + ], + file_system_edits: [ + MoveFile { + src: FileId( + 3, + ), + dst_source_root: SourceRootId( + 0, + ), + dst_path: "bar/foo2.rs", + }, + ], + cursor_position: None, + }, + }, + ) + "###); + } + + #[test] + fn test_rename_mod_in_dir() { + let (analysis, position) = analysis_and_position( + " + //- /lib.rs + mod fo<|>o; + //- /foo/mod.rs + // emtpy + ", + ); + let new_name = "foo2"; + let source_change = analysis.rename(position, new_name).unwrap(); + assert_debug_snapshot!(&source_change, + @r###" + Some( + RangeInfo { + range: [4; 7), + info: SourceChange { + label: "rename", + source_file_edits: [ + SourceFileEdit { + file_id: FileId( + 1, + ), + edit: TextEdit { + atoms: [ + AtomTextEdit { + delete: [4; 7), + insert: "foo2", + }, + ], + }, + }, + ], + file_system_edits: [ + MoveFile { + src: FileId( + 2, + ), + dst_source_root: SourceRootId( + 0, + ), + dst_path: "foo2/mod.rs", + }, + ], + cursor_position: None, + }, + }, + ) + "### + ); + } + + fn test_rename(text: &str, new_name: &str, expected: &str) { + let (analysis, position) = single_file_with_position(text); + let source_change = analysis.rename(position, new_name).unwrap(); + let mut text_edit_builder = TextEditBuilder::default(); + let mut file_id: Option = None; + if let Some(change) = source_change { + for edit in change.info.source_file_edits { + file_id = Some(edit.file_id); + for atom in edit.edit.as_atoms() { + text_edit_builder.replace(atom.delete, atom.insert.clone()); + } + } + } + let result = + text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); + assert_eq_text!(expected, &*result); + } +} diff --git a/crates/ra_ide/src/references/search_scope.rs b/crates/ra_ide/src/references/search_scope.rs new file mode 100644 index 000000000..f5c9589f4 --- /dev/null +++ b/crates/ra_ide/src/references/search_scope.rs @@ -0,0 +1,145 @@ +//! Generally, `search_scope` returns files that might contain references for the element. +//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. +//! In some cases, the location of the references is known to within a `TextRange`, +//! e.g. for things like local variables. +use std::mem; + +use hir::{DefWithBody, HasSource, ModuleSource}; +use ra_db::{FileId, SourceDatabase, SourceDatabaseExt}; +use ra_prof::profile; +use ra_syntax::{AstNode, TextRange}; +use rustc_hash::FxHashMap; + +use crate::db::RootDatabase; + +use super::{NameDefinition, NameKind}; + +pub struct SearchScope { + entries: FxHashMap>, +} + +impl SearchScope { + fn new(entries: FxHashMap>) -> SearchScope { + SearchScope { entries } + } + pub fn single_file(file: FileId) -> SearchScope { + SearchScope::new(std::iter::once((file, None)).collect()) + } + pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope { + let (mut small, mut large) = (&self.entries, &other.entries); + if small.len() > large.len() { + mem::swap(&mut small, &mut large) + } + + let res = small + .iter() + .filter_map(|(file_id, r1)| { + let r2 = large.get(file_id)?; + let r = intersect_ranges(*r1, *r2)?; + Some((*file_id, r)) + }) + .collect(); + return SearchScope::new(res); + + fn intersect_ranges( + r1: Option, + r2: Option, + ) -> Option> { + match (r1, r2) { + (None, r) | (r, None) => Some(r), + (Some(r1), Some(r2)) => { + let r = r1.intersection(&r2)?; + Some(Some(r)) + } + } + } + } +} + +impl IntoIterator for SearchScope { + type Item = (FileId, Option); + type IntoIter = std::collections::hash_map::IntoIter>; + fn into_iter(self) -> Self::IntoIter { + self.entries.into_iter() + } +} + +impl NameDefinition { + pub(crate) fn search_scope(&self, db: &RootDatabase) -> SearchScope { + let _p = profile("search_scope"); + + let module_src = self.container.definition_source(db); + let file_id = module_src.file_id.original_file(db); + + if let NameKind::Local(var) = self.kind { + let range = match var.parent(db) { + DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), + DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), + DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), + }; + let mut res = FxHashMap::default(); + res.insert(file_id, Some(range)); + return SearchScope::new(res); + } + + let vis = + self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string()); + + if vis.as_str() == "pub(super)" { + if let Some(parent_module) = self.container.parent(db) { + let mut res = FxHashMap::default(); + let parent_src = parent_module.definition_source(db); + let file_id = parent_src.file_id.original_file(db); + + match parent_src.value { + ModuleSource::Module(m) => { + let range = Some(m.syntax().text_range()); + res.insert(file_id, range); + } + ModuleSource::SourceFile(_) => { + res.insert(file_id, None); + res.extend(parent_module.children(db).map(|m| { + let src = m.definition_source(db); + (src.file_id.original_file(db), None) + })); + } + } + return SearchScope::new(res); + } + } + + if vis.as_str() != "" { + let source_root_id = db.file_source_root(file_id); + let source_root = db.source_root(source_root_id); + let mut res = source_root.walk().map(|id| (id, None)).collect::>(); + + // FIXME: add "pub(in path)" + + if vis.as_str() == "pub(crate)" { + return SearchScope::new(res); + } + if vis.as_str() == "pub" { + let krate = self.container.krate(); + let crate_graph = db.crate_graph(); + for crate_id in crate_graph.iter() { + let mut crate_deps = crate_graph.dependencies(crate_id); + if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) { + let root_file = crate_graph.crate_root(crate_id); + let source_root_id = db.file_source_root(root_file); + let source_root = db.source_root(source_root_id); + res.extend(source_root.walk().map(|id| (id, None))); + } + } + return SearchScope::new(res); + } + } + + let mut res = FxHashMap::default(); + let range = match module_src.value { + ModuleSource::Module(m) => Some(m.syntax().text_range()), + ModuleSource::SourceFile(_) => None, + }; + res.insert(file_id, range); + SearchScope::new(res) + } +} diff --git a/crates/ra_ide/src/runnables.rs b/crates/ra_ide/src/runnables.rs new file mode 100644 index 000000000..8039a5164 --- /dev/null +++ b/crates/ra_ide/src/runnables.rs @@ -0,0 +1,242 @@ +//! FIXME: write short doc here + +use hir::Source; +use itertools::Itertools; +use ra_db::SourceDatabase; +use ra_syntax::{ + ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, + match_ast, SyntaxNode, TextRange, +}; + +use crate::{db::RootDatabase, FileId}; + +#[derive(Debug)] +pub struct Runnable { + pub range: TextRange, + pub kind: RunnableKind, +} + +#[derive(Debug)] +pub enum RunnableKind { + Test { name: String }, + TestMod { path: String }, + Bench { name: String }, + Bin, +} + +pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { + let parse = db.parse(file_id); + parse.tree().syntax().descendants().filter_map(|i| runnable(db, file_id, i)).collect() +} + +fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option { + match_ast! { + match item { + ast::FnDef(it) => { runnable_fn(it) }, + ast::Module(it) => { runnable_mod(db, file_id, it) }, + _ => { None }, + } + } +} + +fn runnable_fn(fn_def: ast::FnDef) -> Option { + let name = fn_def.name()?.text().clone(); + let kind = if name == "main" { + RunnableKind::Bin + } else if fn_def.has_atom_attr("test") { + RunnableKind::Test { name: name.to_string() } + } else if fn_def.has_atom_attr("bench") { + RunnableKind::Bench { name: name.to_string() } + } else { + return None; + }; + Some(Runnable { range: fn_def.syntax().text_range(), kind }) +} + +fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option { + let has_test_function = module + .item_list()? + .items() + .filter_map(|it| match it { + ast::ModuleItem::FnDef(it) => Some(it), + _ => None, + }) + .any(|f| f.has_atom_attr("test")); + if !has_test_function { + return None; + } + let range = module.syntax().text_range(); + let src = hir::ModuleSource::from_child_node(db, Source::new(file_id.into(), &module.syntax())); + let module = hir::Module::from_definition(db, Source::new(file_id.into(), src))?; + + let path = module.path_to_root(db).into_iter().rev().filter_map(|it| it.name(db)).join("::"); + Some(Runnable { range, kind: RunnableKind::TestMod { path } }) +} + +#[cfg(test)] +mod tests { + use insta::assert_debug_snapshot; + + use crate::mock_analysis::analysis_and_position; + + #[test] + fn test_runnables() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + fn main() {} + + #[test] + fn test_foo() {} + + #[test] + #[ignore] + fn test_foo() {} + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: [1; 21), + kind: Bin, + }, + Runnable { + range: [22; 46), + kind: Test { + name: "test_foo", + }, + }, + Runnable { + range: [47; 81), + kind: Test { + name: "test_foo", + }, + }, + ] + "### + ); + } + + #[test] + fn test_runnables_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod test_mod { + #[test] + fn test_foo1() {} + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: [1; 59), + kind: TestMod { + path: "test_mod", + }, + }, + Runnable { + range: [28; 57), + kind: Test { + name: "test_foo1", + }, + }, + ] + "### + ); + } + + #[test] + fn test_runnables_one_depth_layer_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod foo { + mod test_mod { + #[test] + fn test_foo1() {} + } + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: [23; 85), + kind: TestMod { + path: "foo::test_mod", + }, + }, + Runnable { + range: [46; 79), + kind: Test { + name: "test_foo1", + }, + }, + ] + "### + ); + } + + #[test] + fn test_runnables_multiple_depth_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod foo { + mod bar { + mod test_mod { + #[test] + fn test_foo1() {} + } + } + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert_debug_snapshot!(&runnables, + @r###" + [ + Runnable { + range: [41; 115), + kind: TestMod { + path: "foo::bar::test_mod", + }, + }, + Runnable { + range: [68; 105), + kind: Test { + name: "test_foo1", + }, + }, + ] + "### + ); + } + + #[test] + fn test_runnables_no_test_function_in_module() { + let (analysis, pos) = analysis_and_position( + r#" + //- /lib.rs + <|> //empty + mod test_mod { + fn foo1() {} + } + "#, + ); + let runnables = analysis.runnables(pos.file_id).unwrap(); + assert!(runnables.is_empty()) + } +} diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html new file mode 100644 index 000000000..b39c4d371 --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlighting.html @@ -0,0 +1,48 @@ + + +
#[derive(Clone, Debug)]
+struct Foo {
+    pub x: i32,
+    pub y: i32,
+}
+
+fn foo<T>() -> T {
+    unimplemented!();
+    foo::<i32>();
+}
+
+// comment
+fn main() {
+    println!("Hello, {}!", 92);
+
+    let mut vec = Vec::new();
+    if true {
+        vec.push(Foo { x: 0, y: 1 });
+    }
+    unsafe { vec.set_len(0); }
+
+    let mut x = 42;
+    let y = &mut x;
+    let z = &y;
+
+    y;
+}
\ No newline at end of file diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html new file mode 100644 index 000000000..79f11ea80 --- /dev/null +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html @@ -0,0 +1,33 @@ + + +
fn main() {
+    let hello = "hello";
+    let x = hello.to_string();
+    let y = hello.to_string();
+
+    let x = "other color please!";
+    let y = x.to_string();
+}
+
+fn bar() {
+    let mut hello = "hello";
+}
\ No newline at end of file diff --git a/crates/ra_ide/src/source_change.rs b/crates/ra_ide/src/source_change.rs new file mode 100644 index 000000000..f5f7f8807 --- /dev/null +++ b/crates/ra_ide/src/source_change.rs @@ -0,0 +1,119 @@ +//! This modules defines type to represent changes to the source code, that flow +//! from the server to the client. +//! +//! It can be viewed as a dual for `AnalysisChange`. + +use ra_db::RelativePathBuf; +use ra_text_edit::TextEdit; + +use crate::{FileId, FilePosition, SourceRootId, TextUnit}; + +#[derive(Debug)] +pub struct SourceChange { + pub label: String, + pub source_file_edits: Vec, + pub file_system_edits: Vec, + pub cursor_position: Option, +} + +impl SourceChange { + /// Creates a new SourceChange with the given label + /// from the edits. + pub(crate) fn from_edits>( + label: L, + source_file_edits: Vec, + file_system_edits: Vec, + ) -> Self { + SourceChange { + label: label.into(), + source_file_edits, + file_system_edits, + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only the given `SourceFileEdits`. + pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self { + SourceChange { + label: label.into(), + source_file_edits: edits, + file_system_edits: vec![], + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only the given `FileSystemEdits`. + pub(crate) fn file_system_edits>(label: L, edits: Vec) -> Self { + SourceChange { + label: label.into(), + source_file_edits: vec![], + file_system_edits: edits, + cursor_position: None, + } + } + + /// Creates a new SourceChange with the given label, + /// containing only a single `SourceFileEdit`. + pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self { + SourceChange::source_file_edits(label, vec![edit]) + } + + /// Creates a new SourceChange with the given label + /// from the given `FileId` and `TextEdit` + pub(crate) fn source_file_edit_from>( + label: L, + file_id: FileId, + edit: TextEdit, + ) -> Self { + SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) + } + + /// Creates a new SourceChange with the given label + /// from the given `FileId` and `TextEdit` + pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self { + SourceChange::file_system_edits(label, vec![edit]) + } + + /// Sets the cursor position to the given `FilePosition` + pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { + self.cursor_position = Some(cursor_position); + self + } + + /// Sets the cursor position to the given `FilePosition` + pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self { + self.cursor_position = cursor_position; + self + } +} + +#[derive(Debug)] +pub struct SourceFileEdit { + pub file_id: FileId, + pub edit: TextEdit, +} + +#[derive(Debug)] +pub enum FileSystemEdit { + CreateFile { source_root: SourceRootId, path: RelativePathBuf }, + MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, +} + +pub(crate) struct SingleFileChange { + pub label: String, + pub edit: TextEdit, + pub cursor_position: Option, +} + +impl SingleFileChange { + pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { + SourceChange { + label: self.label, + source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], + file_system_edits: Vec::new(), + cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), + } + } +} diff --git a/crates/ra_ide/src/status.rs b/crates/ra_ide/src/status.rs new file mode 100644 index 000000000..1bb27eb85 --- /dev/null +++ b/crates/ra_ide/src/status.rs @@ -0,0 +1,136 @@ +//! FIXME: write short doc here + +use std::{fmt, iter::FromIterator, sync::Arc}; + +use hir::MacroFile; +use ra_db::{ + salsa::{ + debug::{DebugQueryTable, TableEntry}, + Database, + }, + FileTextQuery, SourceRootId, +}; +use ra_prof::{memory_usage, Bytes}; +use ra_syntax::{ast, Parse, SyntaxNode}; + +use crate::{ + db::RootDatabase, + symbol_index::{LibrarySymbolsQuery, SymbolIndex}, + FileId, +}; + +fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { + db.query(ra_db::ParseQuery).entries::() +} +fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { + db.query(hir::db::ParseMacroQuery).entries::() +} + +pub(crate) fn status(db: &RootDatabase) -> String { + let files_stats = db.query(FileTextQuery).entries::(); + let syntax_tree_stats = syntax_tree_stats(db); + let macro_syntax_tree_stats = macro_syntax_tree_stats(db); + let symbols_stats = db.query(LibrarySymbolsQuery).entries::(); + format!( + "{}\n{}\n{}\n{} (macros)\n\n\nmemory:\n{}\ngc {:?} seconds ago", + files_stats, + symbols_stats, + syntax_tree_stats, + macro_syntax_tree_stats, + memory_usage(), + db.last_gc.elapsed().as_secs(), + ) +} + +#[derive(Default)] +struct FilesStats { + total: usize, + size: Bytes, +} + +impl fmt::Display for FilesStats { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{} ({}) files", self.total, self.size) + } +} + +impl FromIterator>> for FilesStats { + fn from_iter(iter: T) -> FilesStats + where + T: IntoIterator>>, + { + let mut res = FilesStats::default(); + for entry in iter { + res.total += 1; + res.size += entry.value.unwrap().len(); + } + res + } +} + +#[derive(Default)] +pub(crate) struct SyntaxTreeStats { + total: usize, + pub(crate) retained: usize, +} + +impl fmt::Display for SyntaxTreeStats { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{} trees, {} retained", self.total, self.retained) + } +} + +impl FromIterator>> for SyntaxTreeStats { + fn from_iter(iter: T) -> SyntaxTreeStats + where + T: IntoIterator>>, + { + let mut res = SyntaxTreeStats::default(); + for entry in iter { + res.total += 1; + res.retained += entry.value.is_some() as usize; + } + res + } +} + +impl FromIterator, M)>>> for SyntaxTreeStats { + fn from_iter(iter: T) -> SyntaxTreeStats + where + T: IntoIterator, M)>>>, + { + let mut res = SyntaxTreeStats::default(); + for entry in iter { + res.total += 1; + res.retained += entry.value.is_some() as usize; + } + res + } +} + +#[derive(Default)] +struct LibrarySymbolsStats { + total: usize, + size: Bytes, +} + +impl fmt::Display for LibrarySymbolsStats { + fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + write!(fmt, "{} ({}) symbols", self.total, self.size,) + } +} + +impl FromIterator>> for LibrarySymbolsStats { + fn from_iter(iter: T) -> LibrarySymbolsStats + where + T: IntoIterator>>, + { + let mut res = LibrarySymbolsStats::default(); + for entry in iter { + let value = entry.value.unwrap(); + res.total += value.len(); + res.size += value.memory_size(); + } + res + } +} diff --git a/crates/ra_ide/src/symbol_index.rs b/crates/ra_ide/src/symbol_index.rs new file mode 100644 index 000000000..5729eb5b3 --- /dev/null +++ b/crates/ra_ide/src/symbol_index.rs @@ -0,0 +1,405 @@ +//! This module handles fuzzy-searching of functions, structs and other symbols +//! by name across the whole workspace and dependencies. +//! +//! It works by building an incrementally-updated text-search index of all +//! symbols. The backbone of the index is the **awesome** `fst` crate by +//! @BurntSushi. +//! +//! In a nutshell, you give a set of strings to `fst`, and it builds a +//! finite state machine describing this set of strings. The strings which +//! could fuzzy-match a pattern can also be described by a finite state machine. +//! What is freaking cool is that you can now traverse both state machines in +//! lock-step to enumerate the strings which are both in the input set and +//! fuzz-match the query. Or, more formally, given two languages described by +//! FSTs, one can build a product FST which describes the intersection of the +//! languages. +//! +//! `fst` does not support cheap updating of the index, but it supports unioning +//! of state machines. So, to account for changing source code, we build an FST +//! for each library (which is assumed to never change) and an FST for each Rust +//! file in the current workspace, and run a query against the union of all +//! those FSTs. +use std::{ + fmt, + hash::{Hash, Hasher}, + mem, + sync::Arc, +}; + +use fst::{self, Streamer}; +use ra_db::{ + salsa::{self, ParallelDatabase}, + SourceDatabaseExt, SourceRootId, +}; +use ra_syntax::{ + ast::{self, NameOwner}, + match_ast, AstNode, Parse, SmolStr, SourceFile, + SyntaxKind::{self, *}, + SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent, +}; +#[cfg(not(feature = "wasm"))] +use rayon::prelude::*; + +use crate::{db::RootDatabase, FileId, Query}; + +#[salsa::query_group(SymbolsDatabaseStorage)] +pub(crate) trait SymbolsDatabase: hir::db::HirDatabase { + fn file_symbols(&self, file_id: FileId) -> Arc; + #[salsa::input] + fn library_symbols(&self, id: SourceRootId) -> Arc; + /// The set of "local" (that is, from the current workspace) roots. + /// Files in local roots are assumed to change frequently. + #[salsa::input] + fn local_roots(&self) -> Arc>; + /// The set of roots for crates.io libraries. + /// Files in libraries are assumed to never change. + #[salsa::input] + fn library_roots(&self) -> Arc>; +} + +fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc { + db.check_canceled(); + let parse = db.parse(file_id); + + let symbols = source_file_to_file_symbols(&parse.tree(), file_id); + + // FIXME: add macros here + + Arc::new(SymbolIndex::new(symbols)) +} + +pub(crate) fn world_symbols(db: &RootDatabase, query: Query) -> Vec { + /// Need to wrap Snapshot to provide `Clone` impl for `map_with` + struct Snap(salsa::Snapshot); + impl Clone for Snap { + fn clone(&self) -> Snap { + Snap(self.0.snapshot()) + } + } + + let buf: Vec> = if query.libs { + let snap = Snap(db.snapshot()); + #[cfg(not(feature = "wasm"))] + let buf = db + .library_roots() + .par_iter() + .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id)) + .collect(); + + #[cfg(feature = "wasm")] + let buf = db.library_roots().iter().map(|&lib_id| snap.0.library_symbols(lib_id)).collect(); + + buf + } else { + let mut files = Vec::new(); + for &root in db.local_roots().iter() { + let sr = db.source_root(root); + files.extend(sr.walk()) + } + + let snap = Snap(db.snapshot()); + #[cfg(not(feature = "wasm"))] + let buf = + files.par_iter().map_with(snap, |db, &file_id| db.0.file_symbols(file_id)).collect(); + + #[cfg(feature = "wasm")] + let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect(); + + buf + }; + query.search(&buf) +} + +pub(crate) fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec { + let name = name_ref.text(); + let mut query = Query::new(name.to_string()); + query.exact(); + query.limit(4); + crate::symbol_index::world_symbols(db, query) +} + +#[derive(Default)] +pub(crate) struct SymbolIndex { + symbols: Vec, + map: fst::Map, +} + +impl fmt::Debug for SymbolIndex { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish() + } +} + +impl PartialEq for SymbolIndex { + fn eq(&self, other: &SymbolIndex) -> bool { + self.symbols == other.symbols + } +} + +impl Eq for SymbolIndex {} + +impl Hash for SymbolIndex { + fn hash(&self, hasher: &mut H) { + self.symbols.hash(hasher) + } +} + +impl SymbolIndex { + fn new(mut symbols: Vec) -> SymbolIndex { + fn cmp_key<'a>(s1: &'a FileSymbol) -> impl Ord + 'a { + unicase::Ascii::new(s1.name.as_str()) + } + #[cfg(not(feature = "wasm"))] + symbols.par_sort_by(|s1, s2| cmp_key(s1).cmp(&cmp_key(s2))); + + #[cfg(feature = "wasm")] + symbols.sort_by(|s1, s2| cmp_key(s1).cmp(&cmp_key(s2))); + + let mut builder = fst::MapBuilder::memory(); + + let mut last_batch_start = 0; + + for idx in 0..symbols.len() { + if symbols.get(last_batch_start).map(cmp_key) == symbols.get(idx + 1).map(cmp_key) { + continue; + } + + let start = last_batch_start; + let end = idx + 1; + last_batch_start = end; + + let key = symbols[start].name.as_str().to_lowercase(); + let value = SymbolIndex::range_to_map_value(start, end); + + builder.insert(key, value).unwrap(); + } + + let map = fst::Map::from_bytes(builder.into_inner().unwrap()).unwrap(); + SymbolIndex { symbols, map } + } + + pub(crate) fn len(&self) -> usize { + self.symbols.len() + } + + pub(crate) fn memory_size(&self) -> usize { + self.map.as_fst().size() + self.symbols.len() * mem::size_of::() + } + + #[cfg(not(feature = "wasm"))] + pub(crate) fn for_files( + files: impl ParallelIterator)>, + ) -> SymbolIndex { + let symbols = files + .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id)) + .collect::>(); + SymbolIndex::new(symbols) + } + + #[cfg(feature = "wasm")] + pub(crate) fn for_files( + files: impl Iterator)>, + ) -> SymbolIndex { + let symbols = files + .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id)) + .collect::>(); + SymbolIndex::new(symbols) + } + + fn range_to_map_value(start: usize, end: usize) -> u64 { + debug_assert![start <= (std::u32::MAX as usize)]; + debug_assert![end <= (std::u32::MAX as usize)]; + + ((start as u64) << 32) | end as u64 + } + + fn map_value_to_range(value: u64) -> (usize, usize) { + let end = value as u32 as usize; + let start = (value >> 32) as usize; + (start, end) + } +} + +impl Query { + pub(crate) fn search(self, indices: &[Arc]) -> Vec { + let mut op = fst::map::OpBuilder::new(); + for file_symbols in indices.iter() { + let automaton = fst::automaton::Subsequence::new(&self.lowercased); + op = op.add(file_symbols.map.search(automaton)) + } + let mut stream = op.union(); + let mut res = Vec::new(); + while let Some((_, indexed_values)) = stream.next() { + if res.len() >= self.limit { + break; + } + for indexed_value in indexed_values { + let symbol_index = &indices[indexed_value.index]; + let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value); + + for symbol in &symbol_index.symbols[start..end] { + if self.only_types && !is_type(symbol.ptr.kind()) { + continue; + } + if self.exact && symbol.name != self.query { + continue; + } + res.push(symbol.clone()); + } + } + } + res + } +} + +fn is_type(kind: SyntaxKind) -> bool { + match kind { + STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => true, + _ => false, + } +} + +/// The actual data that is stored in the index. It should be as compact as +/// possible. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub(crate) struct FileSymbol { + pub(crate) file_id: FileId, + pub(crate) name: SmolStr, + pub(crate) ptr: SyntaxNodePtr, + pub(crate) name_range: Option, + pub(crate) container_name: Option, +} + +fn source_file_to_file_symbols(source_file: &SourceFile, file_id: FileId) -> Vec { + let mut symbols = Vec::new(); + let mut stack = Vec::new(); + + for event in source_file.syntax().preorder() { + match event { + WalkEvent::Enter(node) => { + if let Some(mut symbol) = to_file_symbol(&node, file_id) { + symbol.container_name = stack.last().cloned(); + + stack.push(symbol.name.clone()); + symbols.push(symbol); + } + } + + WalkEvent::Leave(node) => { + if to_symbol(&node).is_some() { + stack.pop(); + } + } + } + } + + symbols +} + +fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> { + fn decl(node: N) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> { + let name = node.name()?; + let name_range = name.syntax().text_range(); + let name = name.text().clone(); + let ptr = SyntaxNodePtr::new(node.syntax()); + + Some((name, ptr, name_range)) + } + match_ast! { + match node { + ast::FnDef(it) => { decl(it) }, + ast::StructDef(it) => { decl(it) }, + ast::EnumDef(it) => { decl(it) }, + ast::TraitDef(it) => { decl(it) }, + ast::Module(it) => { decl(it) }, + ast::TypeAliasDef(it) => { decl(it) }, + ast::ConstDef(it) => { decl(it) }, + ast::StaticDef(it) => { decl(it) }, + _ => None, + } + } +} + +fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option { + to_symbol(node).map(move |(name, ptr, name_range)| FileSymbol { + name, + ptr, + file_id, + name_range: Some(name_range), + container_name: None, + }) +} + +#[cfg(test)] +mod tests { + use crate::{display::NavigationTarget, mock_analysis::single_file, Query}; + use ra_syntax::{ + SmolStr, + SyntaxKind::{FN_DEF, STRUCT_DEF}, + }; + + #[test] + fn test_world_symbols_with_no_container() { + let code = r#" + enum FooInner { } + "#; + + let mut symbols = get_symbols_matching(code, "FooInner"); + + let s = symbols.pop().unwrap(); + + assert_eq!(s.name(), "FooInner"); + assert!(s.container_name().is_none()); + } + + #[test] + fn test_world_symbols_include_container_name() { + let code = r#" +fn foo() { + enum FooInner { } +} + "#; + + let mut symbols = get_symbols_matching(code, "FooInner"); + + let s = symbols.pop().unwrap(); + + assert_eq!(s.name(), "FooInner"); + assert_eq!(s.container_name(), Some(&SmolStr::new("foo"))); + + let code = r#" +mod foo { + struct FooInner; +} + "#; + + let mut symbols = get_symbols_matching(code, "FooInner"); + + let s = symbols.pop().unwrap(); + + assert_eq!(s.name(), "FooInner"); + assert_eq!(s.container_name(), Some(&SmolStr::new("foo"))); + } + + #[test] + fn test_world_symbols_are_case_sensitive() { + let code = r#" +fn foo() {} + +struct Foo; + "#; + + let symbols = get_symbols_matching(code, "Foo"); + + let fn_match = symbols.iter().find(|s| s.name() == "foo").map(|s| s.kind()); + let struct_match = symbols.iter().find(|s| s.name() == "Foo").map(|s| s.kind()); + + assert_eq!(fn_match, Some(FN_DEF)); + assert_eq!(struct_match, Some(STRUCT_DEF)); + } + + fn get_symbols_matching(text: &str, query: &str) -> Vec { + let (analysis, _) = single_file(text); + analysis.symbol_search(Query::new(query.into())).unwrap() + } +} diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs new file mode 100644 index 000000000..2c568a747 --- /dev/null +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -0,0 +1,342 @@ +//! FIXME: write short doc here + +use rustc_hash::{FxHashMap, FxHashSet}; + +use hir::{Name, Source}; +use ra_db::SourceDatabase; +use ra_prof::profile; +use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T}; + +use crate::{ + db::RootDatabase, + references::{ + classify_name, classify_name_ref, + NameKind::{self, *}, + }, + FileId, +}; + +#[derive(Debug)] +pub struct HighlightedRange { + pub range: TextRange, + pub tag: &'static str, + pub binding_hash: Option, +} + +fn is_control_keyword(kind: SyntaxKind) -> bool { + match kind { + T![for] + | T![loop] + | T![while] + | T![continue] + | T![break] + | T![if] + | T![else] + | T![match] + | T![return] => true, + _ => false, + } +} + +pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec { + let _p = profile("highlight"); + let parse = db.parse(file_id); + let root = parse.tree().syntax().clone(); + + fn calc_binding_hash(file_id: FileId, name: &Name, shadow_count: u32) -> u64 { + fn hash(x: T) -> u64 { + use std::{collections::hash_map::DefaultHasher, hash::Hasher}; + + let mut hasher = DefaultHasher::new(); + x.hash(&mut hasher); + hasher.finish() + } + + hash((file_id, name, shadow_count)) + } + + // Visited nodes to handle highlighting priorities + // FIXME: retain only ranges here + let mut highlighted: FxHashSet = FxHashSet::default(); + let mut bindings_shadow_count: FxHashMap = FxHashMap::default(); + + let mut res = Vec::new(); + for node in root.descendants_with_tokens() { + if highlighted.contains(&node) { + continue; + } + let mut binding_hash = None; + let tag = match node.kind() { + FN_DEF => { + bindings_shadow_count.clear(); + continue; + } + COMMENT => "comment", + STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", + ATTR => "attribute", + NAME_REF => { + if node.ancestors().any(|it| it.kind() == ATTR) { + continue; + } + + let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); + let name_kind = + classify_name_ref(db, Source::new(file_id.into(), &name_ref)).map(|d| d.kind); + + if let Some(Local(local)) = &name_kind { + if let Some(name) = local.name(db) { + let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); + binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count)) + } + }; + + name_kind.map_or("text", |it| highlight_name(db, it)) + } + NAME => { + let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap(); + let name_kind = + classify_name(db, Source::new(file_id.into(), &name)).map(|d| d.kind); + + if let Some(Local(local)) = &name_kind { + if let Some(name) = local.name(db) { + let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); + *shadow_count += 1; + binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count)) + } + }; + + match name_kind { + Some(name_kind) => highlight_name(db, name_kind), + None => name.syntax().parent().map_or("function", |x| match x.kind() { + TYPE_PARAM | STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => "type", + RECORD_FIELD_DEF => "field", + _ => "function", + }), + } + } + INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", + LIFETIME => "parameter", + T![unsafe] => "keyword.unsafe", + k if is_control_keyword(k) => "keyword.control", + k if k.is_keyword() => "keyword", + _ => { + if let Some(macro_call) = node.as_node().cloned().and_then(ast::MacroCall::cast) { + if let Some(path) = macro_call.path() { + if let Some(segment) = path.segment() { + if let Some(name_ref) = segment.name_ref() { + highlighted.insert(name_ref.syntax().clone().into()); + let range_start = name_ref.syntax().text_range().start(); + let mut range_end = name_ref.syntax().text_range().end(); + for sibling in path.syntax().siblings_with_tokens(Direction::Next) { + match sibling.kind() { + T![!] | IDENT => range_end = sibling.text_range().end(), + _ => (), + } + } + res.push(HighlightedRange { + range: TextRange::from_to(range_start, range_end), + tag: "macro", + binding_hash: None, + }) + } + } + } + } + continue; + } + }; + res.push(HighlightedRange { range: node.text_range(), tag, binding_hash }) + } + res +} + +pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { + let parse = db.parse(file_id); + + fn rainbowify(seed: u64) -> String { + use rand::prelude::*; + let mut rng = SmallRng::seed_from_u64(seed); + format!( + "hsl({h},{s}%,{l}%)", + h = rng.gen_range::(0, 361), + s = rng.gen_range::(42, 99), + l = rng.gen_range::(40, 91), + ) + } + + let mut ranges = highlight(db, file_id); + ranges.sort_by_key(|it| it.range.start()); + // quick non-optimal heuristic to intersect token ranges and highlighted ranges + let mut frontier = 0; + let mut could_intersect: Vec<&HighlightedRange> = Vec::new(); + + let mut buf = String::new(); + buf.push_str(&STYLE); + buf.push_str("
");
+    let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
+    for token in tokens {
+        could_intersect.retain(|it| token.text_range().start() <= it.range.end());
+        while let Some(r) = ranges.get(frontier) {
+            if r.range.start() <= token.text_range().end() {
+                could_intersect.push(r);
+                frontier += 1;
+            } else {
+                break;
+            }
+        }
+        let text = html_escape(&token.text());
+        let ranges = could_intersect
+            .iter()
+            .filter(|it| token.text_range().is_subrange(&it.range))
+            .collect::>();
+        if ranges.is_empty() {
+            buf.push_str(&text);
+        } else {
+            let classes = ranges.iter().map(|x| x.tag).collect::>().join(" ");
+            let binding_hash = ranges.first().and_then(|x| x.binding_hash);
+            let color = match (rainbow, binding_hash) {
+                (true, Some(hash)) => format!(
+                    " data-binding-hash=\"{}\" style=\"color: {};\"",
+                    hash,
+                    rainbowify(hash)
+                ),
+                _ => "".into(),
+            };
+            buf.push_str(&format!("{}", classes, color, text));
+        }
+    }
+    buf.push_str("
"); + buf +} + +fn highlight_name(db: &RootDatabase, name_kind: NameKind) -> &'static str { + match name_kind { + Macro(_) => "macro", + Field(_) => "field", + AssocItem(hir::AssocItem::Function(_)) => "function", + AssocItem(hir::AssocItem::Const(_)) => "constant", + AssocItem(hir::AssocItem::TypeAlias(_)) => "type", + Def(hir::ModuleDef::Module(_)) => "module", + Def(hir::ModuleDef::Function(_)) => "function", + Def(hir::ModuleDef::Adt(_)) => "type", + Def(hir::ModuleDef::EnumVariant(_)) => "constant", + Def(hir::ModuleDef::Const(_)) => "constant", + Def(hir::ModuleDef::Static(_)) => "constant", + Def(hir::ModuleDef::Trait(_)) => "type", + Def(hir::ModuleDef::TypeAlias(_)) => "type", + Def(hir::ModuleDef::BuiltinType(_)) => "type", + SelfType(_) => "type", + GenericParam(_) => "type", + Local(local) => { + if local.is_mut(db) { + "variable.mut" + } else if local.ty(db).is_mutable_reference() { + "variable.mut" + } else { + "variable" + } + } + } +} + +//FIXME: like, real html escaping +fn html_escape(text: &str) -> String { + text.replace("<", "<").replace(">", ">") +} + +const STYLE: &str = " + +"; + +#[cfg(test)] +mod tests { + use crate::mock_analysis::single_file; + use test_utils::{assert_eq_text, project_dir, read_text}; + + #[test] + fn test_highlighting() { + let (analysis, file_id) = single_file( + r#" +#[derive(Clone, Debug)] +struct Foo { + pub x: i32, + pub y: i32, +} + +fn foo() -> T { + unimplemented!(); + foo::(); +} + +// comment +fn main() { + println!("Hello, {}!", 92); + + let mut vec = Vec::new(); + if true { + vec.push(Foo { x: 0, y: 1 }); + } + unsafe { vec.set_len(0); } + + let mut x = 42; + let y = &mut x; + let z = &y; + + y; +} +"# + .trim(), + ); + let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html"); + let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); + let expected_html = &read_text(&dst_file); + std::fs::write(dst_file, &actual_html).unwrap(); + assert_eq_text!(expected_html, actual_html); + } + + #[test] + fn test_rainbow_highlighting() { + let (analysis, file_id) = single_file( + r#" +fn main() { + let hello = "hello"; + let x = hello.to_string(); + let y = hello.to_string(); + + let x = "other color please!"; + let y = x.to_string(); +} + +fn bar() { + let mut hello = "hello"; +} +"# + .trim(), + ); + let dst_file = + project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html"); + let actual_html = &analysis.highlight_as_html(file_id, true).unwrap(); + let expected_html = &read_text(&dst_file); + std::fs::write(dst_file, &actual_html).unwrap(); + assert_eq_text!(expected_html, actual_html); + } +} diff --git a/crates/ra_ide/src/syntax_tree.rs b/crates/ra_ide/src/syntax_tree.rs new file mode 100644 index 000000000..4d0f0fc47 --- /dev/null +++ b/crates/ra_ide/src/syntax_tree.rs @@ -0,0 +1,359 @@ +//! FIXME: write short doc here + +use crate::db::RootDatabase; +use ra_db::SourceDatabase; +use ra_syntax::{ + algo, AstNode, NodeOrToken, SourceFile, + SyntaxKind::{RAW_STRING, STRING}, + SyntaxToken, TextRange, +}; + +pub use ra_db::FileId; + +pub(crate) fn syntax_tree( + db: &RootDatabase, + file_id: FileId, + text_range: Option, +) -> String { + let parse = db.parse(file_id); + if let Some(text_range) = text_range { + let node = match algo::find_covering_element(parse.tree().syntax(), text_range) { + NodeOrToken::Node(node) => node, + NodeOrToken::Token(token) => { + if let Some(tree) = syntax_tree_for_string(&token, text_range) { + return tree; + } + token.parent() + } + }; + + format!("{:#?}", node) + } else { + format!("{:#?}", parse.tree().syntax()) + } +} + +/// Attempts parsing the selected contents of a string literal +/// as rust syntax and returns its syntax tree +fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option { + // When the range is inside a string + // we'll attempt parsing it as rust syntax + // to provide the syntax tree of the contents of the string + match token.kind() { + STRING | RAW_STRING => syntax_tree_for_token(token, text_range), + _ => None, + } +} + +fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option { + // Range of the full node + let node_range = node.text_range(); + let text = node.text().to_string(); + + // We start at some point inside the node + // Either we have selected the whole string + // or our selection is inside it + let start = text_range.start() - node_range.start(); + + // how many characters we have selected + let len = text_range.len().to_usize(); + + let node_len = node_range.len().to_usize(); + + let start = start.to_usize(); + + // We want to cap our length + let len = len.min(node_len); + + // Ensure our slice is inside the actual string + let end = if start + len < text.len() { start + len } else { text.len() - start }; + + let text = &text[start..end]; + + // Remove possible extra string quotes from the start + // and the end of the string + let text = text + .trim_start_matches('r') + .trim_start_matches('#') + .trim_start_matches('"') + .trim_end_matches('#') + .trim_end_matches('"') + .trim() + // Remove custom markers + .replace("<|>", ""); + + let parsed = SourceFile::parse(&text); + + // If the "file" parsed without errors, + // return its syntax + if parsed.errors().is_empty() { + return Some(format!("{:#?}", parsed.tree().syntax())); + } + + None +} + +#[cfg(test)] +mod tests { + use test_utils::assert_eq_text; + + use crate::mock_analysis::{single_file, single_file_with_range}; + + #[test] + fn test_syntax_tree_without_range() { + // Basic syntax + let (analysis, file_id) = single_file(r#"fn foo() {}"#); + let syn = analysis.syntax_tree(file_id, None).unwrap(); + + assert_eq_text!( + syn.trim(), + r#" +SOURCE_FILE@[0; 11) + FN_DEF@[0; 11) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 11) + BLOCK@[9; 11) + L_CURLY@[9; 10) "{" + R_CURLY@[10; 11) "}" +"# + .trim() + ); + + let (analysis, file_id) = single_file( + r#" +fn test() { + assert!(" + fn foo() { + } + ", ""); +}"# + .trim(), + ); + let syn = analysis.syntax_tree(file_id, None).unwrap(); + + assert_eq_text!( + syn.trim(), + r#" +SOURCE_FILE@[0; 60) + FN_DEF@[0; 60) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 7) + IDENT@[3; 7) "test" + PARAM_LIST@[7; 9) + L_PAREN@[7; 8) "(" + R_PAREN@[8; 9) ")" + WHITESPACE@[9; 10) " " + BLOCK_EXPR@[10; 60) + BLOCK@[10; 60) + L_CURLY@[10; 11) "{" + WHITESPACE@[11; 16) "\n " + EXPR_STMT@[16; 58) + MACRO_CALL@[16; 57) + PATH@[16; 22) + PATH_SEGMENT@[16; 22) + NAME_REF@[16; 22) + IDENT@[16; 22) "assert" + EXCL@[22; 23) "!" + TOKEN_TREE@[23; 57) + L_PAREN@[23; 24) "(" + STRING@[24; 52) "\"\n fn foo() {\n ..." + COMMA@[52; 53) "," + WHITESPACE@[53; 54) " " + STRING@[54; 56) "\"\"" + R_PAREN@[56; 57) ")" + SEMI@[57; 58) ";" + WHITESPACE@[58; 59) "\n" + R_CURLY@[59; 60) "}" +"# + .trim() + ); + } + + #[test] + fn test_syntax_tree_with_range() { + let (analysis, range) = single_file_with_range(r#"<|>fn foo() {}<|>"#.trim()); + let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); + + assert_eq_text!( + syn.trim(), + r#" +FN_DEF@[0; 11) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 11) + BLOCK@[9; 11) + L_CURLY@[9; 10) "{" + R_CURLY@[10; 11) "}" +"# + .trim() + ); + + let (analysis, range) = single_file_with_range( + r#"fn test() { + <|>assert!(" + fn foo() { + } + ", "");<|> +}"# + .trim(), + ); + let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); + + assert_eq_text!( + syn.trim(), + r#" +EXPR_STMT@[16; 58) + MACRO_CALL@[16; 57) + PATH@[16; 22) + PATH_SEGMENT@[16; 22) + NAME_REF@[16; 22) + IDENT@[16; 22) "assert" + EXCL@[22; 23) "!" + TOKEN_TREE@[23; 57) + L_PAREN@[23; 24) "(" + STRING@[24; 52) "\"\n fn foo() {\n ..." + COMMA@[52; 53) "," + WHITESPACE@[53; 54) " " + STRING@[54; 56) "\"\"" + R_PAREN@[56; 57) ")" + SEMI@[57; 58) ";" +"# + .trim() + ); + } + + #[test] + fn test_syntax_tree_inside_string() { + let (analysis, range) = single_file_with_range( + r#"fn test() { + assert!(" +<|>fn foo() { +}<|> +fn bar() { +} + ", ""); +}"# + .trim(), + ); + let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); + assert_eq_text!( + syn.trim(), + r#" +SOURCE_FILE@[0; 12) + FN_DEF@[0; 12) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 12) + BLOCK@[9; 12) + L_CURLY@[9; 10) "{" + WHITESPACE@[10; 11) "\n" + R_CURLY@[11; 12) "}" +"# + .trim() + ); + + // With a raw string + let (analysis, range) = single_file_with_range( + r###"fn test() { + assert!(r#" +<|>fn foo() { +}<|> +fn bar() { +} + "#, ""); +}"### + .trim(), + ); + let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); + assert_eq_text!( + syn.trim(), + r#" +SOURCE_FILE@[0; 12) + FN_DEF@[0; 12) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 12) + BLOCK@[9; 12) + L_CURLY@[9; 10) "{" + WHITESPACE@[10; 11) "\n" + R_CURLY@[11; 12) "}" +"# + .trim() + ); + + // With a raw string + let (analysis, range) = single_file_with_range( + r###"fn test() { + assert!(r<|>#" +fn foo() { +} +fn bar() { +}"<|>#, ""); +}"### + .trim(), + ); + let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); + assert_eq_text!( + syn.trim(), + r#" +SOURCE_FILE@[0; 25) + FN_DEF@[0; 12) + FN_KW@[0; 2) "fn" + WHITESPACE@[2; 3) " " + NAME@[3; 6) + IDENT@[3; 6) "foo" + PARAM_LIST@[6; 8) + L_PAREN@[6; 7) "(" + R_PAREN@[7; 8) ")" + WHITESPACE@[8; 9) " " + BLOCK_EXPR@[9; 12) + BLOCK@[9; 12) + L_CURLY@[9; 10) "{" + WHITESPACE@[10; 11) "\n" + R_CURLY@[11; 12) "}" + WHITESPACE@[12; 13) "\n" + FN_DEF@[13; 25) + FN_KW@[13; 15) "fn" + WHITESPACE@[15; 16) " " + NAME@[16; 19) + IDENT@[16; 19) "bar" + PARAM_LIST@[19; 21) + L_PAREN@[19; 20) "(" + R_PAREN@[20; 21) ")" + WHITESPACE@[21; 22) " " + BLOCK_EXPR@[22; 25) + BLOCK@[22; 25) + L_CURLY@[22; 23) "{" + WHITESPACE@[23; 24) "\n" + R_CURLY@[24; 25) "}" +"# + .trim() + ); + } +} diff --git a/crates/ra_ide/src/test_utils.rs b/crates/ra_ide/src/test_utils.rs new file mode 100644 index 000000000..8adb214d4 --- /dev/null +++ b/crates/ra_ide/src/test_utils.rs @@ -0,0 +1,21 @@ +//! FIXME: write short doc here + +use ra_syntax::{SourceFile, TextUnit}; +use ra_text_edit::TextEdit; + +pub use test_utils::*; + +pub fn check_action Option>( + before: &str, + after: &str, + f: F, +) { + let (before_cursor_pos, before) = extract_offset(before); + let file = SourceFile::parse(&before).ok().unwrap(); + let result = f(&file, before_cursor_pos).expect("code action is not applicable"); + let actual = result.apply(&before); + let actual_cursor_pos = + result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); + let actual = add_cursor(&actual, actual_cursor_pos); + assert_eq_text!(after, &actual); +} diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs new file mode 100644 index 000000000..21e5be9b3 --- /dev/null +++ b/crates/ra_ide/src/typing.rs @@ -0,0 +1,490 @@ +//! This module handles auto-magic editing actions applied together with users +//! edits. For example, if the user typed +//! +//! ```text +//! foo +//! .bar() +//! .baz() +//! | // <- cursor is here +//! ``` +//! +//! and types `.` next, we want to indent the dot. +//! +//! Language server executes such typing assists synchronously. That is, they +//! block user's typing and should be pretty fast for this reason! + +use ra_db::{FilePosition, SourceDatabase}; +use ra_fmt::leading_indent; +use ra_syntax::{ + algo::find_node_at_offset, + ast::{self, AstToken}, + AstNode, SmolStr, SourceFile, + SyntaxKind::*, + SyntaxToken, TextRange, TextUnit, TokenAtOffset, +}; +use ra_text_edit::TextEdit; + +use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit}; + +pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { + let parse = db.parse(position.file_id); + let file = parse.tree(); + let comment = file + .syntax() + .token_at_offset(position.offset) + .left_biased() + .and_then(ast::Comment::cast)?; + + if comment.kind().shape.is_block() { + return None; + } + + let prefix = comment.prefix(); + let comment_range = comment.syntax().text_range(); + if position.offset < comment_range.start() + TextUnit::of_str(prefix) + TextUnit::from(1) { + return None; + } + + // Continuing non-doc line comments (like this one :) ) is annoying + if prefix == "//" && comment_range.end() == position.offset { + return None; + } + + let indent = node_indent(&file, comment.syntax())?; + let inserted = format!("\n{}{} ", indent, prefix); + let cursor_position = position.offset + TextUnit::of_str(&inserted); + let edit = TextEdit::insert(position.offset, inserted); + + Some( + SourceChange::source_file_edit( + "on enter", + SourceFileEdit { edit, file_id: position.file_id }, + ) + .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), + ) +} + +fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { + let ws = match file.syntax().token_at_offset(token.text_range().start()) { + TokenAtOffset::Between(l, r) => { + assert!(r == *token); + l + } + TokenAtOffset::Single(n) => { + assert!(n == *token); + return Some("".into()); + } + TokenAtOffset::None => unreachable!(), + }; + if ws.kind() != WHITESPACE { + return None; + } + let text = ws.text(); + let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); + Some(text[pos..].into()) +} + +pub(crate) const TRIGGER_CHARS: &str = ".=>"; + +pub(crate) fn on_char_typed( + db: &RootDatabase, + position: FilePosition, + char_typed: char, +) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); + let file = &db.parse(position.file_id).tree(); + assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); + let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; + Some(single_file_change.into_source_change(position.file_id)) +} + +fn on_char_typed_inner( + file: &SourceFile, + offset: TextUnit, + char_typed: char, +) -> Option { + assert!(TRIGGER_CHARS.contains(char_typed)); + match char_typed { + '.' => on_dot_typed(file, offset), + '=' => on_eq_typed(file, offset), + '>' => on_arrow_typed(file, offset), + _ => unreachable!(), + } +} + +/// Returns an edit which should be applied after `=` was typed. Primarily, +/// this works when adding `let =`. +// FIXME: use a snippet completion instead of this hack here. +fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { + assert_eq!(file.syntax().text().char_at(offset), Some('=')); + let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; + if let_stmt.has_semi() { + return None; + } + if let Some(expr) = let_stmt.initializer() { + let expr_range = expr.syntax().text_range(); + if expr_range.contains(offset) && offset != expr_range.start() { + return None; + } + if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { + return None; + } + } else { + return None; + } + let offset = let_stmt.syntax().text_range().end(); + Some(SingleFileChange { + label: "add semicolon".to_string(), + edit: TextEdit::insert(offset, ";".to_string()), + cursor_position: None, + }) +} + +/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. +fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option { + assert_eq!(file.syntax().text().char_at(offset), Some('.')); + let whitespace = + file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; + + let current_indent = { + let text = whitespace.text(); + let newline = text.rfind('\n')?; + &text[newline + 1..] + }; + let current_indent_len = TextUnit::of_str(current_indent); + + // Make sure dot is a part of call chain + let field_expr = ast::FieldExpr::cast(whitespace.syntax().parent())?; + let prev_indent = leading_indent(field_expr.syntax())?; + let target_indent = format!(" {}", prev_indent); + let target_indent_len = TextUnit::of_str(&target_indent); + if current_indent_len == target_indent_len { + return None; + } + + Some(SingleFileChange { + label: "reindent dot".to_string(), + edit: TextEdit::replace( + TextRange::from_to(offset - current_indent_len, offset), + target_indent, + ), + cursor_position: Some( + offset + target_indent_len - current_indent_len + TextUnit::of_char('.'), + ), + }) +} + +/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` +fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option { + let file_text = file.syntax().text(); + assert_eq!(file_text.char_at(offset), Some('>')); + let after_arrow = offset + TextUnit::of_char('>'); + if file_text.char_at(after_arrow) != Some('{') { + return None; + } + if find_node_at_offset::(file.syntax(), offset).is_none() { + return None; + } + + Some(SingleFileChange { + label: "add space after return type".to_string(), + edit: TextEdit::insert(after_arrow, " ".to_string()), + cursor_position: Some(after_arrow), + }) +} + +#[cfg(test)] +mod tests { + use test_utils::{add_cursor, assert_eq_text, extract_offset}; + + use crate::mock_analysis::single_file; + + use super::*; + + #[test] + fn test_on_enter() { + fn apply_on_enter(before: &str) -> Option { + let (offset, before) = extract_offset(before); + let (analysis, file_id) = single_file(&before); + let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; + + assert_eq!(result.source_file_edits.len(), 1); + let actual = result.source_file_edits[0].edit.apply(&before); + let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); + Some(actual) + } + + fn do_check(before: &str, after: &str) { + let actual = apply_on_enter(before).unwrap(); + assert_eq_text!(after, &actual); + } + + fn do_check_noop(text: &str) { + assert!(apply_on_enter(text).is_none()) + } + + do_check( + r" +/// Some docs<|> +fn foo() { +} +", + r" +/// Some docs +/// <|> +fn foo() { +} +", + ); + do_check( + r" +impl S { + /// Some<|> docs. + fn foo() {} +} +", + r" +impl S { + /// Some + /// <|> docs. + fn foo() {} +} +", + ); + do_check( + r" +fn main() { + // Fix<|> me + let x = 1 + 1; +} +", + r" +fn main() { + // Fix + // <|> me + let x = 1 + 1; +} +", + ); + do_check_noop( + r" +fn main() { + // Fix me<|> + let x = 1 + 1; +} +", + ); + + do_check_noop(r"<|>//! docz"); + } + + fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { + let (offset, before) = extract_offset(before); + let edit = TextEdit::insert(offset, char_typed.to_string()); + let before = edit.apply(&before); + let parse = SourceFile::parse(&before); + on_char_typed_inner(&parse.tree(), offset, char_typed) + .map(|it| (it.edit.apply(&before), it)) + } + + fn type_char(char_typed: char, before: &str, after: &str) { + let (actual, file_change) = do_type_char(char_typed, before) + .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); + + if after.contains("<|>") { + let (offset, after) = extract_offset(after); + assert_eq_text!(&after, &actual); + assert_eq!(file_change.cursor_position, Some(offset)) + } else { + assert_eq_text!(after, &actual); + } + } + + fn type_char_noop(char_typed: char, before: &str) { + let file_change = do_type_char(char_typed, before); + assert!(file_change.is_none()) + } + + #[test] + fn test_on_eq_typed() { + // do_check(r" + // fn foo() { + // let foo =<|> + // } + // ", r" + // fn foo() { + // let foo =; + // } + // "); + type_char( + '=', + r" +fn foo() { + let foo <|> 1 + 1 +} +", + r" +fn foo() { + let foo = 1 + 1; +} +", + ); + // do_check(r" + // fn foo() { + // let foo =<|> + // let bar = 1; + // } + // ", r" + // fn foo() { + // let foo =; + // let bar = 1; + // } + // "); + } + + #[test] + fn indents_new_chain_call() { + type_char( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|> + } + ", + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + . + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|> + } + ", + ) + } + + #[test] + fn indents_new_chain_call_with_semi() { + type_char( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|>; + } + ", + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .; + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + <|>; + } + ", + ) + } + + #[test] + fn indents_continued_chain_call() { + type_char( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .first() + <|> + } + ", + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .first() + . + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + self.child_impl(db, name) + .first() + <|> + } + ", + ); + } + + #[test] + fn indents_middle_of_chain_call() { + type_char( + '.', + r" + fn source_impl() { + let var = enum_defvariant_list().unwrap() + <|> + .nth(92) + .unwrap(); + } + ", + r" + fn source_impl() { + let var = enum_defvariant_list().unwrap() + . + .nth(92) + .unwrap(); + } + ", + ); + type_char_noop( + '.', + r" + fn source_impl() { + let var = enum_defvariant_list().unwrap() + <|> + .nth(92) + .unwrap(); + } + ", + ); + } + + #[test] + fn dont_indent_freestanding_dot() { + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + <|> + } + ", + ); + type_char_noop( + '.', + r" + pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { + <|> + } + ", + ); + } + + #[test] + fn adds_space_after_return_type() { + type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") + } +} diff --git a/crates/ra_ide/src/wasm_shims.rs b/crates/ra_ide/src/wasm_shims.rs new file mode 100644 index 000000000..088cc9be4 --- /dev/null +++ b/crates/ra_ide/src/wasm_shims.rs @@ -0,0 +1,19 @@ +//! FIXME: write short doc here + +#[cfg(not(feature = "wasm"))] +pub use std::time::Instant; + +#[cfg(feature = "wasm")] +#[derive(Clone, Copy, Debug)] +pub struct Instant; + +#[cfg(feature = "wasm")] +impl Instant { + pub fn now() -> Self { + Self + } + + pub fn elapsed(&self) -> std::time::Duration { + std::time::Duration::new(0, 0) + } +} diff --git a/crates/ra_ide_api/Cargo.toml b/crates/ra_ide_api/Cargo.toml deleted file mode 100644 index 15346f388..000000000 --- a/crates/ra_ide_api/Cargo.toml +++ /dev/null @@ -1,46 +0,0 @@ -[package] -edition = "2018" -name = "ra_ide_api" -version = "0.1.0" -authors = ["rust-analyzer developers"] - -[lib] -doctest = false - -[features] -wasm = [] - -[dependencies] -format-buf = "1.0.0" -itertools = "0.8.0" -join_to_string = "0.1.3" -log = "0.4.5" -rayon = "1.0.2" -fst = { version = "0.3.1", default-features = false } -rustc-hash = "1.0" -unicase = "2.2.0" -superslice = "1.0.0" -rand = { version = "0.7.0", features = ["small_rng"] } -once_cell = "1.2.0" - -ra_syntax = { path = "../ra_syntax" } -ra_text_edit = { path = "../ra_text_edit" } -ra_db = { path = "../ra_db" } -ra_cfg = { path = "../ra_cfg" } -ra_fmt = { path = "../ra_fmt" } -ra_prof = { path = "../ra_prof" } -test_utils = { path = "../test_utils" } -ra_assists = { path = "../ra_assists" } - -# ra_ide_api should depend only on the top-level `hir` package. if you need -# something from some `hir_xxx` subpackage, reexport the API via `hir`. -hir = { path = "../ra_hir", package = "ra_hir" } - -[dev-dependencies] -insta = "0.12.0" - -[dev-dependencies.proptest] -version = "0.9.0" -# Disable `fork` feature to allow compiling on webassembly -default-features = false -features = ["std", "bit-set", "break-dead-code"] diff --git a/crates/ra_ide_api/src/assists.rs b/crates/ra_ide_api/src/assists.rs deleted file mode 100644 index e00589733..000000000 --- a/crates/ra_ide_api/src/assists.rs +++ /dev/null @@ -1,28 +0,0 @@ -//! FIXME: write short doc here - -use ra_db::{FilePosition, FileRange}; - -use crate::{db::RootDatabase, SourceChange, SourceFileEdit}; - -pub use ra_assists::AssistId; - -#[derive(Debug)] -pub struct Assist { - pub id: AssistId, - pub change: SourceChange, -} - -pub(crate) fn assists(db: &RootDatabase, frange: FileRange) -> Vec { - ra_assists::assists(db, frange) - .into_iter() - .map(|(label, action)| { - let file_id = frange.file_id; - let file_edit = SourceFileEdit { file_id, edit: action.edit }; - let id = label.id; - let change = SourceChange::source_file_edit(label.label, file_edit).with_cursor_opt( - action.cursor_position.map(|offset| FilePosition { offset, file_id }), - ); - Assist { id, change } - }) - .collect() -} diff --git a/crates/ra_ide_api/src/call_info.rs b/crates/ra_ide_api/src/call_info.rs deleted file mode 100644 index d559dc4d0..000000000 --- a/crates/ra_ide_api/src/call_info.rs +++ /dev/null @@ -1,592 +0,0 @@ -//! FIXME: write short doc here - -use ra_db::SourceDatabase; -use ra_syntax::{ - algo::ancestors_at_offset, - ast::{self, ArgListOwner}, - match_ast, AstNode, SyntaxNode, TextUnit, -}; -use test_utils::tested_by; - -use crate::{db::RootDatabase, CallInfo, FilePosition, FunctionSignature}; - -/// Computes parameter information for the given call expression. -pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option { - let parse = db.parse(position.file_id); - let syntax = parse.tree().syntax().clone(); - - // Find the calling expression and it's NameRef - let calling_node = FnCallNode::with_node(&syntax, position.offset)?; - let name_ref = calling_node.name_ref()?; - let name_ref = hir::Source::new(position.file_id.into(), name_ref.syntax()); - - let analyzer = hir::SourceAnalyzer::new(db, name_ref, None); - let (mut call_info, has_self) = match &calling_node { - FnCallNode::CallExpr(expr) => { - //FIXME: Type::as_callable is broken - let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; - match callable_def { - hir::CallableDef::FunctionId(it) => { - let fn_def = it.into(); - (CallInfo::with_fn(db, fn_def), fn_def.has_self_param(db)) - } - hir::CallableDef::StructId(it) => (CallInfo::with_struct(db, it.into())?, false), - hir::CallableDef::EnumVariantId(it) => { - (CallInfo::with_enum_variant(db, it.into())?, false) - } - } - } - FnCallNode::MethodCallExpr(expr) => { - let function = analyzer.resolve_method_call(&expr)?; - (CallInfo::with_fn(db, function), function.has_self_param(db)) - } - FnCallNode::MacroCallExpr(expr) => { - let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?; - (CallInfo::with_macro(db, macro_def)?, false) - } - }; - - // If we have a calling expression let's find which argument we are on - let num_params = call_info.parameters().len(); - - if num_params == 1 { - if !has_self { - call_info.active_parameter = Some(0); - } - } else if num_params > 1 { - // Count how many parameters into the call we are. - if let Some(arg_list) = calling_node.arg_list() { - // Number of arguments specified at the call site - let num_args_at_callsite = arg_list.args().count(); - - let arg_list_range = arg_list.syntax().text_range(); - if !arg_list_range.contains_inclusive(position.offset) { - tested_by!(call_info_bad_offset); - return None; - } - - let mut param = std::cmp::min( - num_args_at_callsite, - arg_list - .args() - .take_while(|arg| arg.syntax().text_range().end() < position.offset) - .count(), - ); - - // If we are in a method account for `self` - if has_self { - param += 1; - } - - call_info.active_parameter = Some(param); - } - } - - Some(call_info) -} - -#[derive(Debug)] -enum FnCallNode { - CallExpr(ast::CallExpr), - MethodCallExpr(ast::MethodCallExpr), - MacroCallExpr(ast::MacroCall), -} - -impl FnCallNode { - fn with_node(syntax: &SyntaxNode, offset: TextUnit) -> Option { - ancestors_at_offset(syntax, offset).find_map(|node| { - match_ast! { - match node { - ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, - ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, - ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, - _ => { None }, - } - } - }) - } - - fn name_ref(&self) -> Option { - match self { - FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { - ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, - _ => return None, - }), - - FnCallNode::MethodCallExpr(call_expr) => { - call_expr.syntax().children().filter_map(ast::NameRef::cast).nth(0) - } - - FnCallNode::MacroCallExpr(call_expr) => call_expr.path()?.segment()?.name_ref(), - } - } - - fn arg_list(&self) -> Option { - match self { - FnCallNode::CallExpr(expr) => expr.arg_list(), - FnCallNode::MethodCallExpr(expr) => expr.arg_list(), - FnCallNode::MacroCallExpr(_) => None, - } - } -} - -impl CallInfo { - fn with_fn(db: &RootDatabase, function: hir::Function) -> Self { - let signature = FunctionSignature::from_hir(db, function); - - CallInfo { signature, active_parameter: None } - } - - fn with_struct(db: &RootDatabase, st: hir::Struct) -> Option { - let signature = FunctionSignature::from_struct(db, st)?; - - Some(CallInfo { signature, active_parameter: None }) - } - - fn with_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) -> Option { - let signature = FunctionSignature::from_enum_variant(db, variant)?; - - Some(CallInfo { signature, active_parameter: None }) - } - - fn with_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option { - let signature = FunctionSignature::from_macro(db, macro_def)?; - - Some(CallInfo { signature, active_parameter: None }) - } - - fn parameters(&self) -> &[String] { - &self.signature.parameters - } -} - -#[cfg(test)] -mod tests { - use test_utils::covers; - - use crate::mock_analysis::single_file_with_position; - - use super::*; - - // These are only used when testing - impl CallInfo { - fn doc(&self) -> Option { - self.signature.doc.clone() - } - - fn label(&self) -> String { - self.signature.to_string() - } - } - - fn call_info(text: &str) -> CallInfo { - let (analysis, position) = single_file_with_position(text); - analysis.call_info(position).unwrap().unwrap() - } - - #[test] - fn test_fn_signature_two_args_firstx() { - let info = call_info( - r#"fn foo(x: u32, y: u32) -> u32 {x + y} -fn bar() { foo(<|>3, ); }"#, - ); - - assert_eq!(info.parameters(), ["x: u32", "y: u32"]); - assert_eq!(info.active_parameter, Some(0)); - } - - #[test] - fn test_fn_signature_two_args_second() { - let info = call_info( - r#"fn foo(x: u32, y: u32) -> u32 {x + y} -fn bar() { foo(3, <|>); }"#, - ); - - assert_eq!(info.parameters(), ["x: u32", "y: u32"]); - assert_eq!(info.active_parameter, Some(1)); - } - - #[test] - fn test_fn_signature_two_args_empty() { - let info = call_info( - r#"fn foo(x: u32, y: u32) -> u32 {x + y} -fn bar() { foo(<|>); }"#, - ); - - assert_eq!(info.parameters(), ["x: u32", "y: u32"]); - assert_eq!(info.active_parameter, Some(0)); - } - - #[test] - fn test_fn_signature_two_args_first_generics() { - let info = call_info( - r#"fn foo(x: T, y: U) -> u32 where T: Copy + Display, U: Debug {x + y} -fn bar() { foo(<|>3, ); }"#, - ); - - assert_eq!(info.parameters(), ["x: T", "y: U"]); - assert_eq!( - info.label(), - r#" -fn foo(x: T, y: U) -> u32 -where T: Copy + Display, - U: Debug - "# - .trim() - ); - assert_eq!(info.active_parameter, Some(0)); - } - - #[test] - fn test_fn_signature_no_params() { - let info = call_info( - r#"fn foo() -> T where T: Copy + Display {} -fn bar() { foo(<|>); }"#, - ); - - assert!(info.parameters().is_empty()); - assert_eq!( - info.label(), - r#" -fn foo() -> T -where T: Copy + Display - "# - .trim() - ); - assert!(info.active_parameter.is_none()); - } - - #[test] - fn test_fn_signature_for_impl() { - let info = call_info( - r#"struct F; impl F { pub fn new() { F{}} } -fn bar() {let _ : F = F::new(<|>);}"#, - ); - - assert!(info.parameters().is_empty()); - assert_eq!(info.active_parameter, None); - } - - #[test] - fn test_fn_signature_for_method_self() { - let info = call_info( - r#"struct F; -impl F { - pub fn new() -> F{ - F{} - } - - pub fn do_it(&self) {} -} - -fn bar() { - let f : F = F::new(); - f.do_it(<|>); -}"#, - ); - - assert_eq!(info.parameters(), ["&self"]); - assert_eq!(info.active_parameter, None); - } - - #[test] - fn test_fn_signature_for_method_with_arg() { - let info = call_info( - r#"struct F; -impl F { - pub fn new() -> F{ - F{} - } - - pub fn do_it(&self, x: i32) {} -} - -fn bar() { - let f : F = F::new(); - f.do_it(<|>); -}"#, - ); - - assert_eq!(info.parameters(), ["&self", "x: i32"]); - assert_eq!(info.active_parameter, Some(1)); - } - - #[test] - fn test_fn_signature_with_docs_simple() { - let info = call_info( - r#" -/// test -// non-doc-comment -fn foo(j: u32) -> u32 { - j -} - -fn bar() { - let _ = foo(<|>); -} -"#, - ); - - assert_eq!(info.parameters(), ["j: u32"]); - assert_eq!(info.active_parameter, Some(0)); - assert_eq!(info.label(), "fn foo(j: u32) -> u32"); - assert_eq!(info.doc().map(|it| it.into()), Some("test".to_string())); - } - - #[test] - fn test_fn_signature_with_docs() { - let info = call_info( - r#" -/// Adds one to the number given. -/// -/// # Examples -/// -/// ``` -/// let five = 5; -/// -/// assert_eq!(6, my_crate::add_one(5)); -/// ``` -pub fn add_one(x: i32) -> i32 { - x + 1 -} - -pub fn do() { - add_one(<|> -}"#, - ); - - assert_eq!(info.parameters(), ["x: i32"]); - assert_eq!(info.active_parameter, Some(0)); - assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32"); - assert_eq!( - info.doc().map(|it| it.into()), - Some( - r#"Adds one to the number given. - -# Examples - -``` -let five = 5; - -assert_eq!(6, my_crate::add_one(5)); -```"# - .to_string() - ) - ); - } - - #[test] - fn test_fn_signature_with_docs_impl() { - let info = call_info( - r#" -struct addr; -impl addr { - /// Adds one to the number given. - /// - /// # Examples - /// - /// ``` - /// let five = 5; - /// - /// assert_eq!(6, my_crate::add_one(5)); - /// ``` - pub fn add_one(x: i32) -> i32 { - x + 1 - } -} - -pub fn do_it() { - addr {}; - addr::add_one(<|>); -}"#, - ); - - assert_eq!(info.parameters(), ["x: i32"]); - assert_eq!(info.active_parameter, Some(0)); - assert_eq!(info.label(), "pub fn add_one(x: i32) -> i32"); - assert_eq!( - info.doc().map(|it| it.into()), - Some( - r#"Adds one to the number given. - -# Examples - -``` -let five = 5; - -assert_eq!(6, my_crate::add_one(5)); -```"# - .to_string() - ) - ); - } - - #[test] - fn test_fn_signature_with_docs_from_actix() { - let info = call_info( - r#" -struct WriteHandler; - -impl WriteHandler { - /// Method is called when writer emits error. - /// - /// If this method returns `ErrorAction::Continue` writer processing - /// continues otherwise stream processing stops. - fn error(&mut self, err: E, ctx: &mut Self::Context) -> Running { - Running::Stop - } - - /// Method is called when writer finishes. - /// - /// By default this method stops actor's `Context`. - fn finished(&mut self, ctx: &mut Self::Context) { - ctx.stop() - } -} - -pub fn foo(mut r: WriteHandler<()>) { - r.finished(<|>); -} - -"#, - ); - - assert_eq!(info.label(), "fn finished(&mut self, ctx: &mut Self::Context)".to_string()); - assert_eq!(info.parameters(), ["&mut self", "ctx: &mut Self::Context"]); - assert_eq!(info.active_parameter, Some(1)); - assert_eq!( - info.doc().map(|it| it.into()), - Some( - r#"Method is called when writer finishes. - -By default this method stops actor's `Context`."# - .to_string() - ) - ); - } - - #[test] - fn call_info_bad_offset() { - covers!(call_info_bad_offset); - let (analysis, position) = single_file_with_position( - r#"fn foo(x: u32, y: u32) -> u32 {x + y} - fn bar() { foo <|> (3, ); }"#, - ); - let call_info = analysis.call_info(position).unwrap(); - assert!(call_info.is_none()); - } - - #[test] - fn test_nested_method_in_lamba() { - let info = call_info( - r#"struct Foo; - -impl Foo { - fn bar(&self, _: u32) { } -} - -fn bar(_: u32) { } - -fn main() { - let foo = Foo; - std::thread::spawn(move || foo.bar(<|>)); -}"#, - ); - - assert_eq!(info.parameters(), ["&self", "_: u32"]); - assert_eq!(info.active_parameter, Some(1)); - assert_eq!(info.label(), "fn bar(&self, _: u32)"); - } - - #[test] - fn works_for_tuple_structs() { - let info = call_info( - r#" -/// A cool tuple struct -struct TS(u32, i32); -fn main() { - let s = TS(0, <|>); -}"#, - ); - - assert_eq!(info.label(), "struct TS(u32, i32) -> TS"); - assert_eq!(info.doc().map(|it| it.into()), Some("A cool tuple struct".to_string())); - assert_eq!(info.active_parameter, Some(1)); - } - - #[test] - #[should_panic] - fn cant_call_named_structs() { - let _ = call_info( - r#" -struct TS { x: u32, y: i32 } -fn main() { - let s = TS(<|>); -}"#, - ); - } - - #[test] - fn works_for_enum_variants() { - let info = call_info( - r#" -enum E { - /// A Variant - A(i32), - /// Another - B, - /// And C - C { a: i32, b: i32 } -} - -fn main() { - let a = E::A(<|>); -} - "#, - ); - - assert_eq!(info.label(), "E::A(0: i32)"); - assert_eq!(info.doc().map(|it| it.into()), Some("A Variant".to_string())); - assert_eq!(info.active_parameter, Some(0)); - } - - #[test] - #[should_panic] - fn cant_call_enum_records() { - let _ = call_info( - r#" -enum E { - /// A Variant - A(i32), - /// Another - B, - /// And C - C { a: i32, b: i32 } -} - -fn main() { - let a = E::C(<|>); -} - "#, - ); - } - - #[test] - fn fn_signature_for_macro() { - let info = call_info( - r#" -/// empty macro -macro_rules! foo { - () => {} -} - -fn f() { - foo!(<|>); -} - "#, - ); - - assert_eq!(info.label(), "foo!()"); - assert_eq!(info.doc().map(|it| it.into()), Some("empty macro".to_string())); - } -} diff --git a/crates/ra_ide_api/src/change.rs b/crates/ra_ide_api/src/change.rs deleted file mode 100644 index 4a76d1dd8..000000000 --- a/crates/ra_ide_api/src/change.rs +++ /dev/null @@ -1,354 +0,0 @@ -//! FIXME: write short doc here - -use std::{fmt, sync::Arc, time}; - -use ra_db::{ - salsa::{Database, Durability, SweepStrategy}, - CrateGraph, CrateId, FileId, RelativePathBuf, SourceDatabase, SourceDatabaseExt, SourceRoot, - SourceRootId, -}; -use ra_prof::{memory_usage, profile, Bytes}; -use ra_syntax::SourceFile; -#[cfg(not(feature = "wasm"))] -use rayon::prelude::*; -use rustc_hash::FxHashMap; - -use crate::{ - db::{DebugData, RootDatabase}, - symbol_index::{SymbolIndex, SymbolsDatabase}, -}; - -#[derive(Default)] -pub struct AnalysisChange { - new_roots: Vec<(SourceRootId, bool)>, - roots_changed: FxHashMap, - files_changed: Vec<(FileId, Arc)>, - libraries_added: Vec, - crate_graph: Option, - debug_data: DebugData, -} - -impl fmt::Debug for AnalysisChange { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - let mut d = fmt.debug_struct("AnalysisChange"); - if !self.new_roots.is_empty() { - d.field("new_roots", &self.new_roots); - } - if !self.roots_changed.is_empty() { - d.field("roots_changed", &self.roots_changed); - } - if !self.files_changed.is_empty() { - d.field("files_changed", &self.files_changed.len()); - } - if !self.libraries_added.is_empty() { - d.field("libraries_added", &self.libraries_added.len()); - } - if !self.crate_graph.is_none() { - d.field("crate_graph", &self.crate_graph); - } - d.finish() - } -} - -impl AnalysisChange { - pub fn new() -> AnalysisChange { - AnalysisChange::default() - } - - pub fn add_root(&mut self, root_id: SourceRootId, is_local: bool) { - self.new_roots.push((root_id, is_local)); - } - - pub fn add_file( - &mut self, - root_id: SourceRootId, - file_id: FileId, - path: RelativePathBuf, - text: Arc, - ) { - let file = AddFile { file_id, path, text }; - self.roots_changed.entry(root_id).or_default().added.push(file); - } - - pub fn change_file(&mut self, file_id: FileId, new_text: Arc) { - self.files_changed.push((file_id, new_text)) - } - - pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) { - let file = RemoveFile { file_id, path }; - self.roots_changed.entry(root_id).or_default().removed.push(file); - } - - pub fn add_library(&mut self, data: LibraryData) { - self.libraries_added.push(data) - } - - pub fn set_crate_graph(&mut self, graph: CrateGraph) { - self.crate_graph = Some(graph); - } - - pub fn set_debug_crate_name(&mut self, crate_id: CrateId, name: String) { - self.debug_data.crate_names.insert(crate_id, name); - } - - pub fn set_debug_root_path(&mut self, source_root_id: SourceRootId, path: String) { - self.debug_data.root_paths.insert(source_root_id, path); - } -} - -#[derive(Debug)] -struct AddFile { - file_id: FileId, - path: RelativePathBuf, - text: Arc, -} - -#[derive(Debug)] -struct RemoveFile { - file_id: FileId, - path: RelativePathBuf, -} - -#[derive(Default)] -struct RootChange { - added: Vec, - removed: Vec, -} - -impl fmt::Debug for RootChange { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - fmt.debug_struct("AnalysisChange") - .field("added", &self.added.len()) - .field("removed", &self.removed.len()) - .finish() - } -} - -pub struct LibraryData { - root_id: SourceRootId, - root_change: RootChange, - symbol_index: SymbolIndex, -} - -impl fmt::Debug for LibraryData { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("LibraryData") - .field("root_id", &self.root_id) - .field("root_change", &self.root_change) - .field("n_symbols", &self.symbol_index.len()) - .finish() - } -} - -impl LibraryData { - pub fn prepare( - root_id: SourceRootId, - files: Vec<(FileId, RelativePathBuf, Arc)>, - ) -> LibraryData { - #[cfg(not(feature = "wasm"))] - let iter = files.par_iter(); - #[cfg(feature = "wasm")] - let iter = files.iter(); - - let symbol_index = SymbolIndex::for_files(iter.map(|(file_id, _, text)| { - let parse = SourceFile::parse(text); - (*file_id, parse) - })); - let mut root_change = RootChange::default(); - root_change.added = files - .into_iter() - .map(|(file_id, path, text)| AddFile { file_id, path, text }) - .collect(); - LibraryData { root_id, root_change, symbol_index } - } -} - -const GC_COOLDOWN: time::Duration = time::Duration::from_millis(100); - -impl RootDatabase { - pub(crate) fn apply_change(&mut self, change: AnalysisChange) { - let _p = profile("RootDatabase::apply_change"); - log::info!("apply_change {:?}", change); - { - let _p = profile("RootDatabase::apply_change/cancellation"); - self.salsa_runtime_mut().synthetic_write(Durability::LOW); - } - if !change.new_roots.is_empty() { - let mut local_roots = Vec::clone(&self.local_roots()); - for (root_id, is_local) in change.new_roots { - let root = if is_local { SourceRoot::new() } else { SourceRoot::new_library() }; - let durability = durability(&root); - self.set_source_root_with_durability(root_id, Arc::new(root), durability); - if is_local { - local_roots.push(root_id); - } - } - self.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); - } - - for (root_id, root_change) in change.roots_changed { - self.apply_root_change(root_id, root_change); - } - for (file_id, text) in change.files_changed { - let source_root_id = self.file_source_root(file_id); - let source_root = self.source_root(source_root_id); - let durability = durability(&source_root); - self.set_file_text_with_durability(file_id, text, durability) - } - if !change.libraries_added.is_empty() { - let mut libraries = Vec::clone(&self.library_roots()); - for library in change.libraries_added { - libraries.push(library.root_id); - self.set_source_root_with_durability( - library.root_id, - Default::default(), - Durability::HIGH, - ); - self.set_library_symbols_with_durability( - library.root_id, - Arc::new(library.symbol_index), - Durability::HIGH, - ); - self.apply_root_change(library.root_id, library.root_change); - } - self.set_library_roots_with_durability(Arc::new(libraries), Durability::HIGH); - } - if let Some(crate_graph) = change.crate_graph { - self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) - } - - Arc::make_mut(&mut self.debug_data).merge(change.debug_data) - } - - fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { - let mut source_root = SourceRoot::clone(&self.source_root(root_id)); - let durability = durability(&source_root); - for add_file in root_change.added { - self.set_file_text_with_durability(add_file.file_id, add_file.text, durability); - self.set_file_relative_path_with_durability( - add_file.file_id, - add_file.path.clone(), - durability, - ); - self.set_file_source_root_with_durability(add_file.file_id, root_id, durability); - source_root.insert_file(add_file.path, add_file.file_id); - } - for remove_file in root_change.removed { - self.set_file_text_with_durability(remove_file.file_id, Default::default(), durability); - source_root.remove_file(&remove_file.path); - } - self.set_source_root_with_durability(root_id, Arc::new(source_root), durability); - } - - pub(crate) fn maybe_collect_garbage(&mut self) { - if cfg!(feature = "wasm") { - return; - } - - if self.last_gc_check.elapsed() > GC_COOLDOWN { - self.last_gc_check = crate::wasm_shims::Instant::now(); - } - } - - pub(crate) fn collect_garbage(&mut self) { - if cfg!(feature = "wasm") { - return; - } - - let _p = profile("RootDatabase::collect_garbage"); - self.last_gc = crate::wasm_shims::Instant::now(); - - let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); - - self.query(ra_db::ParseQuery).sweep(sweep); - self.query(hir::db::ParseMacroQuery).sweep(sweep); - - // Macros do take significant space, but less then the syntax trees - // self.query(hir::db::MacroDefQuery).sweep(sweep); - // self.query(hir::db::MacroArgQuery).sweep(sweep); - // self.query(hir::db::MacroExpandQuery).sweep(sweep); - - self.query(hir::db::AstIdMapQuery).sweep(sweep); - - self.query(hir::db::RawItemsWithSourceMapQuery).sweep(sweep); - self.query(hir::db::BodyWithSourceMapQuery).sweep(sweep); - - self.query(hir::db::ExprScopesQuery).sweep(sweep); - self.query(hir::db::InferQuery).sweep(sweep); - self.query(hir::db::BodyQuery).sweep(sweep); - } - - pub(crate) fn per_query_memory_usage(&mut self) -> Vec<(String, Bytes)> { - let mut acc: Vec<(String, Bytes)> = vec![]; - let sweep = SweepStrategy::default().discard_values().sweep_all_revisions(); - macro_rules! sweep_each_query { - ($($q:path)*) => {$( - let before = memory_usage().allocated; - self.query($q).sweep(sweep); - let after = memory_usage().allocated; - let q: $q = Default::default(); - let name = format!("{:?}", q); - acc.push((name, before - after)); - - let before = memory_usage().allocated; - self.query($q).sweep(sweep.discard_everything()); - let after = memory_usage().allocated; - let q: $q = Default::default(); - let name = format!("{:?} (deps)", q); - acc.push((name, before - after)); - )*} - } - sweep_each_query![ - ra_db::ParseQuery - ra_db::SourceRootCratesQuery - hir::db::AstIdMapQuery - hir::db::ParseMacroQuery - hir::db::MacroDefQuery - hir::db::MacroArgQuery - hir::db::MacroExpandQuery - hir::db::StructDataQuery - hir::db::EnumDataQuery - hir::db::TraitDataQuery - hir::db::RawItemsWithSourceMapQuery - hir::db::RawItemsQuery - hir::db::CrateDefMapQuery - hir::db::GenericParamsQuery - hir::db::FunctionDataQuery - hir::db::TypeAliasDataQuery - hir::db::ConstDataQuery - hir::db::StaticDataQuery - hir::db::ModuleLangItemsQuery - hir::db::CrateLangItemsQuery - hir::db::LangItemQuery - hir::db::DocumentationQuery - hir::db::ExprScopesQuery - hir::db::InferQuery - hir::db::TyQuery - hir::db::ValueTyQuery - hir::db::FieldTypesQuery - hir::db::CallableItemSignatureQuery - hir::db::GenericPredicatesQuery - hir::db::GenericDefaultsQuery - hir::db::BodyWithSourceMapQuery - hir::db::BodyQuery - hir::db::ImplsInCrateQuery - hir::db::ImplsForTraitQuery - hir::db::AssociatedTyDataQuery - hir::db::TraitDatumQuery - hir::db::StructDatumQuery - hir::db::ImplDatumQuery - hir::db::ImplDataQuery - hir::db::TraitSolveQuery - ]; - acc.sort_by_key(|it| std::cmp::Reverse(it.1)); - acc - } -} - -fn durability(source_root: &SourceRoot) -> Durability { - if source_root.is_library { - Durability::HIGH - } else { - Durability::LOW - } -} diff --git a/crates/ra_ide_api/src/completion.rs b/crates/ra_ide_api/src/completion.rs deleted file mode 100644 index abe1f36ce..000000000 --- a/crates/ra_ide_api/src/completion.rs +++ /dev/null @@ -1,77 +0,0 @@ -//! FIXME: write short doc here - -mod completion_item; -mod completion_context; -mod presentation; - -mod complete_dot; -mod complete_record_literal; -mod complete_record_pattern; -mod complete_pattern; -mod complete_fn_param; -mod complete_keyword; -mod complete_snippet; -mod complete_path; -mod complete_scope; -mod complete_postfix; -mod complete_macro_in_item_position; - -use ra_db::SourceDatabase; - -#[cfg(test)] -use crate::completion::completion_item::do_completion; -use crate::{ - completion::{ - completion_context::CompletionContext, - completion_item::{CompletionKind, Completions}, - }, - db, FilePosition, -}; - -pub use crate::completion::completion_item::{ - CompletionItem, CompletionItemKind, InsertTextFormat, -}; - -/// Main entry point for completion. We run completion as a two-phase process. -/// -/// First, we look at the position and collect a so-called `CompletionContext. -/// This is a somewhat messy process, because, during completion, syntax tree is -/// incomplete and can look really weird. -/// -/// Once the context is collected, we run a series of completion routines which -/// look at the context and produce completion items. One subtlety about this -/// phase is that completion engine should not filter by the substring which is -/// already present, it should give all possible variants for the identifier at -/// the caret. In other words, for -/// -/// ```no-run -/// fn f() { -/// let foo = 92; -/// let _ = bar<|> -/// } -/// ``` -/// -/// `foo` *should* be present among the completion variants. Filtering by -/// identifier prefix/fuzzy match should be done higher in the stack, together -/// with ordering of completions (currently this is done by the client). -pub(crate) fn completions(db: &db::RootDatabase, position: FilePosition) -> Option { - let original_parse = db.parse(position.file_id); - let ctx = CompletionContext::new(db, &original_parse, position)?; - - let mut acc = Completions::default(); - - complete_fn_param::complete_fn_param(&mut acc, &ctx); - complete_keyword::complete_expr_keyword(&mut acc, &ctx); - complete_keyword::complete_use_tree_keyword(&mut acc, &ctx); - complete_snippet::complete_expr_snippet(&mut acc, &ctx); - complete_snippet::complete_item_snippet(&mut acc, &ctx); - complete_path::complete_path(&mut acc, &ctx); - complete_scope::complete_scope(&mut acc, &ctx); - complete_dot::complete_dot(&mut acc, &ctx); - complete_record_literal::complete_record_literal(&mut acc, &ctx); - complete_record_pattern::complete_record_pattern(&mut acc, &ctx); - complete_pattern::complete_pattern(&mut acc, &ctx); - complete_postfix::complete_postfix(&mut acc, &ctx); - complete_macro_in_item_position::complete_macro_in_item_position(&mut acc, &ctx); - Some(acc) -} diff --git a/crates/ra_ide_api/src/completion/complete_dot.rs b/crates/ra_ide_api/src/completion/complete_dot.rs deleted file mode 100644 index b6fe48627..000000000 --- a/crates/ra_ide_api/src/completion/complete_dot.rs +++ /dev/null @@ -1,456 +0,0 @@ -//! FIXME: write short doc here - -use hir::Type; - -use crate::completion::completion_item::CompletionKind; -use crate::{ - completion::{completion_context::CompletionContext, completion_item::Completions}, - CompletionItem, -}; -use rustc_hash::FxHashSet; - -/// Complete dot accesses, i.e. fields or methods (and .await syntax). -pub(super) fn complete_dot(acc: &mut Completions, ctx: &CompletionContext) { - let dot_receiver = match &ctx.dot_receiver { - Some(expr) => expr, - _ => return, - }; - - let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { - Some(ty) => ty, - _ => return, - }; - - if !ctx.is_call { - complete_fields(acc, ctx, &receiver_ty); - } - complete_methods(acc, ctx, &receiver_ty); - - // Suggest .await syntax for types that implement Future trait - if ctx.analyzer.impls_future(ctx.db, receiver_ty.into_ty()) { - CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), "await") - .detail("expr.await") - .insert_text("await") - .add_to(acc); - } -} - -fn complete_fields(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { - for receiver in receiver.autoderef(ctx.db) { - for (field, ty) in receiver.fields(ctx.db) { - acc.add_field(ctx, field, &ty); - } - for (i, ty) in receiver.tuple_fields(ctx.db).into_iter().enumerate() { - acc.add_tuple_field(ctx, i, &ty); - } - } -} - -fn complete_methods(acc: &mut Completions, ctx: &CompletionContext, receiver: &Type) { - let mut seen_methods = FxHashSet::default(); - ctx.analyzer.iterate_method_candidates(ctx.db, receiver, None, |_ty, func| { - if func.has_self_param(ctx.db) && seen_methods.insert(func.name(ctx.db)) { - acc.add_function(ctx, func); - } - None::<()> - }); -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_ref_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn test_struct_field_completion() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A { the_field: u32 } - fn foo(a: A) { - a.<|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "the_field", - source_range: [94; 94), - delete: [94; 94), - insert: "the_field", - kind: Field, - detail: "u32", - }, - ] - "### - ); - } - - #[test] - fn test_struct_field_completion_self() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A { - /// This is the_field - the_field: (u32,) - } - impl A { - fn foo(self) { - self.<|> - } - } - ", - ), - @r###" - [ - CompletionItem { - label: "foo()", - source_range: [187; 187), - delete: [187; 187), - insert: "foo()$0", - kind: Method, - lookup: "foo", - detail: "fn foo(self)", - }, - CompletionItem { - label: "the_field", - source_range: [187; 187), - delete: [187; 187), - insert: "the_field", - kind: Field, - detail: "(u32,)", - documentation: Documentation( - "This is the_field", - ), - }, - ] - "### - ); - } - - #[test] - fn test_struct_field_completion_autoderef() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A { the_field: (u32, i32) } - impl A { - fn foo(&self) { - self.<|> - } - } - ", - ), - @r###" - [ - CompletionItem { - label: "foo()", - source_range: [126; 126), - delete: [126; 126), - insert: "foo()$0", - kind: Method, - lookup: "foo", - detail: "fn foo(&self)", - }, - CompletionItem { - label: "the_field", - source_range: [126; 126), - delete: [126; 126), - insert: "the_field", - kind: Field, - detail: "(u32, i32)", - }, - ] - "### - ); - } - - #[test] - fn test_no_struct_field_completion_for_method_call() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A { the_field: u32 } - fn foo(a: A) { - a.<|>() - } - ", - ), - @"[]" - ); - } - - #[test] - fn test_method_completion() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A {} - impl A { - fn the_method(&self) {} - } - fn foo(a: A) { - a.<|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "the_method()", - source_range: [144; 144), - delete: [144; 144), - insert: "the_method()$0", - kind: Method, - lookup: "the_method", - detail: "fn the_method(&self)", - }, - ] - "### - ); - } - - #[test] - fn test_trait_method_completion() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A {} - trait Trait { fn the_method(&self); } - impl Trait for A {} - fn foo(a: A) { - a.<|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "the_method()", - source_range: [151; 151), - delete: [151; 151), - insert: "the_method()$0", - kind: Method, - lookup: "the_method", - detail: "fn the_method(&self)", - }, - ] - "### - ); - } - - #[test] - fn test_trait_method_completion_deduplicated() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A {} - trait Trait { fn the_method(&self); } - impl Trait for T {} - fn foo(a: &A) { - a.<|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "the_method()", - source_range: [155; 155), - delete: [155; 155), - insert: "the_method()$0", - kind: Method, - lookup: "the_method", - detail: "fn the_method(&self)", - }, - ] - "### - ); - } - - #[test] - fn test_no_non_self_method() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A {} - impl A { - fn the_method() {} - } - fn foo(a: A) { - a.<|> - } - ", - ), - @"[]" - ); - } - - #[test] - fn test_method_attr_filtering() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A {} - impl A { - #[inline] - fn the_method(&self) { - let x = 1; - let y = 2; - } - } - fn foo(a: A) { - a.<|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "the_method()", - source_range: [249; 249), - delete: [249; 249), - insert: "the_method()$0", - kind: Method, - lookup: "the_method", - detail: "fn the_method(&self)", - }, - ] - "### - ); - } - - #[test] - fn test_tuple_field_completion() { - assert_debug_snapshot!( - do_ref_completion( - r" - fn foo() { - let b = (0, 3.14); - b.<|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "0", - source_range: [75; 75), - delete: [75; 75), - insert: "0", - kind: Field, - detail: "i32", - }, - CompletionItem { - label: "1", - source_range: [75; 75), - delete: [75; 75), - insert: "1", - kind: Field, - detail: "f64", - }, - ] - "### - ); - } - - #[test] - fn test_tuple_field_inference() { - assert_debug_snapshot!( - do_ref_completion( - r" - pub struct S; - impl S { - pub fn blah(&self) {} - } - - struct T(S); - - impl T { - fn foo(&self) { - // FIXME: This doesn't work without the trailing `a` as `0.` is a float - self.0.a<|> - } - } - ", - ), - @r###" - [ - CompletionItem { - label: "blah()", - source_range: [299; 300), - delete: [299; 300), - insert: "blah()$0", - kind: Method, - lookup: "blah", - detail: "pub fn blah(&self)", - }, - ] - "### - ); - } - - #[test] - fn test_completion_works_in_consts() { - assert_debug_snapshot!( - do_ref_completion( - r" - struct A { the_field: u32 } - const X: u32 = { - A { the_field: 92 }.<|> - }; - ", - ), - @r###" - [ - CompletionItem { - label: "the_field", - source_range: [106; 106), - delete: [106; 106), - insert: "the_field", - kind: Field, - detail: "u32", - }, - ] - "### - ); - } - - #[test] - fn test_completion_await_impls_future() { - assert_debug_snapshot!( - do_completion( - r###" - //- /main.rs - use std::future::*; - struct A {} - impl Future for A {} - fn foo(a: A) { - a.<|> - } - - //- /std/lib.rs - pub mod future { - pub trait Future {} - } - "###, CompletionKind::Keyword), - @r###" - [ - CompletionItem { - label: "await", - source_range: [74; 74), - delete: [74; 74), - insert: "await", - detail: "expr.await", - }, - ] - "### - ) - } -} diff --git a/crates/ra_ide_api/src/completion/complete_fn_param.rs b/crates/ra_ide_api/src/completion/complete_fn_param.rs deleted file mode 100644 index 502458706..000000000 --- a/crates/ra_ide_api/src/completion/complete_fn_param.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! FIXME: write short doc here - -use ra_syntax::{ast, match_ast, AstNode}; -use rustc_hash::FxHashMap; - -use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; - -/// Complete repeated parameters, both name and type. For example, if all -/// functions in a file have a `spam: &mut Spam` parameter, a completion with -/// `spam: &mut Spam` insert text/label and `spam` lookup string will be -/// suggested. -pub(super) fn complete_fn_param(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_param { - return; - } - - let mut params = FxHashMap::default(); - for node in ctx.token.parent().ancestors() { - match_ast! { - match node { - ast::SourceFile(it) => { process(it, &mut params) }, - ast::ItemList(it) => { process(it, &mut params) }, - _ => (), - } - } - } - params - .into_iter() - .filter_map(|(label, (count, param))| { - let lookup = param.pat()?.syntax().text().to_string(); - if count < 2 { - None - } else { - Some((label, lookup)) - } - }) - .for_each(|(label, lookup)| { - CompletionItem::new(CompletionKind::Magic, ctx.source_range(), label) - .lookup_by(lookup) - .add_to(acc) - }); - - fn process(node: N, params: &mut FxHashMap) { - node.functions().filter_map(|it| it.param_list()).flat_map(|it| it.params()).for_each( - |param| { - let text = param.syntax().text().to_string(); - params.entry(text).or_insert((0, param)).0 += 1; - }, - ) - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_magic_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Magic) - } - - #[test] - fn test_param_completion_last_param() { - assert_debug_snapshot!( - do_magic_completion( - r" - fn foo(file_id: FileId) {} - fn bar(file_id: FileId) {} - fn baz(file<|>) {} - ", - ), - @r###" - [ - CompletionItem { - label: "file_id: FileId", - source_range: [110; 114), - delete: [110; 114), - insert: "file_id: FileId", - lookup: "file_id", - }, - ] - "### - ); - } - - #[test] - fn test_param_completion_nth_param() { - assert_debug_snapshot!( - do_magic_completion( - r" - fn foo(file_id: FileId) {} - fn bar(file_id: FileId) {} - fn baz(file<|>, x: i32) {} - ", - ), - @r###" - [ - CompletionItem { - label: "file_id: FileId", - source_range: [110; 114), - delete: [110; 114), - insert: "file_id: FileId", - lookup: "file_id", - }, - ] - "### - ); - } - - #[test] - fn test_param_completion_trait_param() { - assert_debug_snapshot!( - do_magic_completion( - r" - pub(crate) trait SourceRoot { - pub fn contains(&self, file_id: FileId) -> bool; - pub fn module_map(&self) -> &ModuleMap; - pub fn lines(&self, file_id: FileId) -> &LineIndex; - pub fn syntax(&self, file<|>) - } - ", - ), - @r###" - [ - CompletionItem { - label: "file_id: FileId", - source_range: [289; 293), - delete: [289; 293), - insert: "file_id: FileId", - lookup: "file_id", - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_keyword.rs b/crates/ra_ide_api/src/completion/complete_keyword.rs deleted file mode 100644 index eb7cd9ac2..000000000 --- a/crates/ra_ide_api/src/completion/complete_keyword.rs +++ /dev/null @@ -1,781 +0,0 @@ -//! FIXME: write short doc here - -use ra_syntax::{ - ast::{self, LoopBodyOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, -}; - -use crate::completion::{ - CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, -}; - -pub(super) fn complete_use_tree_keyword(acc: &mut Completions, ctx: &CompletionContext) { - // complete keyword "crate" in use stmt - let source_range = ctx.source_range(); - match (ctx.use_item_syntax.as_ref(), ctx.path_prefix.as_ref()) { - (Some(_), None) => { - CompletionItem::new(CompletionKind::Keyword, source_range, "crate") - .kind(CompletionItemKind::Keyword) - .insert_text("crate::") - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, source_range, "self") - .kind(CompletionItemKind::Keyword) - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, source_range, "super") - .kind(CompletionItemKind::Keyword) - .insert_text("super::") - .add_to(acc); - } - (Some(_), Some(_)) => { - CompletionItem::new(CompletionKind::Keyword, source_range, "self") - .kind(CompletionItemKind::Keyword) - .add_to(acc); - CompletionItem::new(CompletionKind::Keyword, source_range, "super") - .kind(CompletionItemKind::Keyword) - .insert_text("super::") - .add_to(acc); - } - _ => {} - } -} - -fn keyword(ctx: &CompletionContext, kw: &str, snippet: &str) -> CompletionItem { - CompletionItem::new(CompletionKind::Keyword, ctx.source_range(), kw) - .kind(CompletionItemKind::Keyword) - .insert_snippet(snippet) - .build() -} - -pub(super) fn complete_expr_keyword(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path { - return; - } - - let fn_def = match &ctx.function_syntax { - Some(it) => it, - None => return, - }; - acc.add(keyword(ctx, "if", "if $0 {}")); - acc.add(keyword(ctx, "match", "match $0 {}")); - acc.add(keyword(ctx, "while", "while $0 {}")); - acc.add(keyword(ctx, "loop", "loop {$0}")); - - if ctx.after_if { - acc.add(keyword(ctx, "else", "else {$0}")); - acc.add(keyword(ctx, "else if", "else if $0 {}")); - } - if is_in_loop_body(&ctx.token) { - if ctx.can_be_stmt { - acc.add(keyword(ctx, "continue", "continue;")); - acc.add(keyword(ctx, "break", "break;")); - } else { - acc.add(keyword(ctx, "continue", "continue")); - acc.add(keyword(ctx, "break", "break")); - } - } - acc.add_all(complete_return(ctx, &fn_def, ctx.can_be_stmt)); -} - -fn is_in_loop_body(leaf: &SyntaxToken) -> bool { - for node in leaf.parent().ancestors() { - if node.kind() == FN_DEF || node.kind() == LAMBDA_EXPR { - break; - } - let loop_body = match_ast! { - match node { - ast::ForExpr(it) => { it.loop_body() }, - ast::WhileExpr(it) => { it.loop_body() }, - ast::LoopExpr(it) => { it.loop_body() }, - _ => None, - } - }; - if let Some(body) = loop_body { - if leaf.text_range().is_subrange(&body.syntax().text_range()) { - return true; - } - } - } - false -} - -fn complete_return( - ctx: &CompletionContext, - fn_def: &ast::FnDef, - can_be_stmt: bool, -) -> Option { - let snip = match (can_be_stmt, fn_def.ret_type().is_some()) { - (true, true) => "return $0;", - (true, false) => "return;", - (false, true) => "return $0", - (false, false) => "return", - }; - Some(keyword(ctx, "return", snip)) -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_keyword_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Keyword) - } - - #[test] - fn completes_keywords_in_use_stmt() { - assert_debug_snapshot!( - do_keyword_completion( - r" - use <|> - ", - ), - @r###" - [ - CompletionItem { - label: "crate", - source_range: [21; 21), - delete: [21; 21), - insert: "crate::", - kind: Keyword, - }, - CompletionItem { - label: "self", - source_range: [21; 21), - delete: [21; 21), - insert: "self", - kind: Keyword, - }, - CompletionItem { - label: "super", - source_range: [21; 21), - delete: [21; 21), - insert: "super::", - kind: Keyword, - }, - ] - "### - ); - - assert_debug_snapshot!( - do_keyword_completion( - r" - use a::<|> - ", - ), - @r###" - [ - CompletionItem { - label: "self", - source_range: [24; 24), - delete: [24; 24), - insert: "self", - kind: Keyword, - }, - CompletionItem { - label: "super", - source_range: [24; 24), - delete: [24; 24), - insert: "super::", - kind: Keyword, - }, - ] - "### - ); - - assert_debug_snapshot!( - do_keyword_completion( - r" - use a::{b, <|>} - ", - ), - @r###" - [ - CompletionItem { - label: "self", - source_range: [28; 28), - delete: [28; 28), - insert: "self", - kind: Keyword, - }, - CompletionItem { - label: "super", - source_range: [28; 28), - delete: [28; 28), - insert: "super::", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn completes_various_keywords_in_function() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() { - <|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [49; 49), - delete: [49; 49), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [49; 49), - delete: [49; 49), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [49; 49), - delete: [49; 49), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [49; 49), - delete: [49; 49), - insert: "return;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [49; 49), - delete: [49; 49), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn completes_else_after_if() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() { - if true { - () - } <|> - } - ", - ), - @r###" - [ - CompletionItem { - label: "else", - source_range: [108; 108), - delete: [108; 108), - insert: "else {$0}", - kind: Keyword, - }, - CompletionItem { - label: "else if", - source_range: [108; 108), - delete: [108; 108), - insert: "else if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "if", - source_range: [108; 108), - delete: [108; 108), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [108; 108), - delete: [108; 108), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [108; 108), - delete: [108; 108), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [108; 108), - delete: [108; 108), - insert: "return;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [108; 108), - delete: [108; 108), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn test_completion_return_value() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - <|> - 92 - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [56; 56), - delete: [56; 56), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [56; 56), - delete: [56; 56), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [56; 56), - delete: [56; 56), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [56; 56), - delete: [56; 56), - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [56; 56), - delete: [56; 56), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() { - <|> - 92 - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [49; 49), - delete: [49; 49), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [49; 49), - delete: [49; 49), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [49; 49), - delete: [49; 49), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [49; 49), - delete: [49; 49), - insert: "return;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [49; 49), - delete: [49; 49), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn dont_add_semi_after_return_if_not_a_statement() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - match () { - () => <|> - } - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [97; 97), - delete: [97; 97), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [97; 97), - delete: [97; 97), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [97; 97), - delete: [97; 97), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [97; 97), - delete: [97; 97), - insert: "return $0", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [97; 97), - delete: [97; 97), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn last_return_in_block_has_semi() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - if condition { - <|> - } - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [95; 95), - delete: [95; 95), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [95; 95), - delete: [95; 95), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [95; 95), - delete: [95; 95), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [95; 95), - delete: [95; 95), - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [95; 95), - delete: [95; 95), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - if condition { - <|> - } - let x = 92; - x - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [95; 95), - delete: [95; 95), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [95; 95), - delete: [95; 95), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [95; 95), - delete: [95; 95), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [95; 95), - delete: [95; 95), - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [95; 95), - delete: [95; 95), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn completes_break_and_continue_in_loops() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - loop { <|> } - } - ", - ), - @r###" - [ - CompletionItem { - label: "break", - source_range: [63; 63), - delete: [63; 63), - insert: "break;", - kind: Keyword, - }, - CompletionItem { - label: "continue", - source_range: [63; 63), - delete: [63; 63), - insert: "continue;", - kind: Keyword, - }, - CompletionItem { - label: "if", - source_range: [63; 63), - delete: [63; 63), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [63; 63), - delete: [63; 63), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [63; 63), - delete: [63; 63), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [63; 63), - delete: [63; 63), - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [63; 63), - delete: [63; 63), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - - // No completion: lambda isolates control flow - assert_debug_snapshot!( - do_keyword_completion( - r" - fn quux() -> i32 { - loop { || { <|> } } - } - ", - ), - @r###" - [ - CompletionItem { - label: "if", - source_range: [68; 68), - delete: [68; 68), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [68; 68), - delete: [68; 68), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [68; 68), - delete: [68; 68), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [68; 68), - delete: [68; 68), - insert: "return $0;", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [68; 68), - delete: [68; 68), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ); - } - - #[test] - fn no_semi_after_break_continue_in_expr() { - assert_debug_snapshot!( - do_keyword_completion( - r" - fn f() { - loop { - match () { - () => br<|> - } - } - } - ", - ), - @r###" - [ - CompletionItem { - label: "break", - source_range: [122; 124), - delete: [122; 124), - insert: "break", - kind: Keyword, - }, - CompletionItem { - label: "continue", - source_range: [122; 124), - delete: [122; 124), - insert: "continue", - kind: Keyword, - }, - CompletionItem { - label: "if", - source_range: [122; 124), - delete: [122; 124), - insert: "if $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "loop", - source_range: [122; 124), - delete: [122; 124), - insert: "loop {$0}", - kind: Keyword, - }, - CompletionItem { - label: "match", - source_range: [122; 124), - delete: [122; 124), - insert: "match $0 {}", - kind: Keyword, - }, - CompletionItem { - label: "return", - source_range: [122; 124), - delete: [122; 124), - insert: "return", - kind: Keyword, - }, - CompletionItem { - label: "while", - source_range: [122; 124), - delete: [122; 124), - insert: "while $0 {}", - kind: Keyword, - }, - ] - "### - ) - } -} diff --git a/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs b/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs deleted file mode 100644 index faadd1e3f..000000000 --- a/crates/ra_ide_api/src/completion/complete_macro_in_item_position.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{CompletionContext, Completions}; - -pub(super) fn complete_macro_in_item_position(acc: &mut Completions, ctx: &CompletionContext) { - // Show only macros in top level. - if ctx.is_new_item { - ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { - if let hir::ScopeDef::MacroDef(mac) = res { - acc.add_macro(ctx, Some(name.to_string()), mac); - } - }) - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_reference_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn completes_macros_as_item() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - macro_rules! foo { - () => {} - } - - fn foo() {} - - <|> - " - ), - @r###" - [ - CompletionItem { - label: "foo!", - source_range: [46; 46), - delete: [46; 46), - insert: "foo!($0)", - kind: Macro, - detail: "macro_rules! foo", - }, - ] - "### - ); - } - - #[test] - fn completes_vec_macros_with_square_brackets() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - /// Creates a [`Vec`] containing the arguments. - /// - /// - Create a [`Vec`] containing a given list of elements: - /// - /// ``` - /// let v = vec![1, 2, 3]; - /// assert_eq!(v[0], 1); - /// assert_eq!(v[1], 2); - /// assert_eq!(v[2], 3); - /// ``` - macro_rules! vec { - () => {} - } - - fn foo() {} - - <|> - " - ), - @r###" - [ - CompletionItem { - label: "vec!", - source_range: [280; 280), - delete: [280; 280), - insert: "vec![$0]", - kind: Macro, - detail: "macro_rules! vec", - documentation: Documentation( - "Creates a [`Vec`] containing the arguments.\n\n- Create a [`Vec`] containing a given list of elements:\n\n```\nlet v = vec![1, 2, 3];\nassert_eq!(v[0], 1);\nassert_eq!(v[1], 2);\nassert_eq!(v[2], 3);\n```", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_macros_braces_guessing() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - /// Foo - /// - /// Not call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`. - /// Call as `let _=foo! { hello world };` - macro_rules! foo { - () => {} - } - - fn main() { - <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "foo!", - source_range: [163; 163), - delete: [163; 163), - insert: "foo! {$0}", - kind: Macro, - detail: "macro_rules! foo", - documentation: Documentation( - "Foo\n\nNot call `fooo!()` `fooo!()`, or `_foo![]` `_foo![]`.\nCall as `let _=foo! { hello world };`", - ), - }, - CompletionItem { - label: "main()", - source_range: [163; 163), - delete: [163; 163), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_path.rs b/crates/ra_ide_api/src/completion/complete_path.rs deleted file mode 100644 index 89e0009a1..000000000 --- a/crates/ra_ide_api/src/completion/complete_path.rs +++ /dev/null @@ -1,785 +0,0 @@ -//! FIXME: write short doc here - -use hir::{Adt, Either, HasSource, PathResolution}; -use ra_syntax::AstNode; -use test_utils::tested_by; - -use crate::completion::{CompletionContext, Completions}; - -pub(super) fn complete_path(acc: &mut Completions, ctx: &CompletionContext) { - let path = match &ctx.path_prefix { - Some(path) => path.clone(), - _ => return, - }; - let def = match ctx.analyzer.resolve_hir_path(ctx.db, &path) { - Some(PathResolution::Def(def)) => def, - _ => return, - }; - match def { - hir::ModuleDef::Module(module) => { - let module_scope = module.scope(ctx.db); - for (name, def, import) in module_scope { - if let hir::ScopeDef::ModuleDef(hir::ModuleDef::BuiltinType(..)) = def { - if ctx.use_item_syntax.is_some() { - tested_by!(dont_complete_primitive_in_use); - continue; - } - } - if Some(module) == ctx.module { - if let Some(import) = import { - if let Either::A(use_tree) = import.source(ctx.db).value { - if use_tree.syntax().text_range().contains_inclusive(ctx.offset) { - // for `use self::foo<|>`, don't suggest `foo` as a completion - tested_by!(dont_complete_current_use); - continue; - } - } - } - } - acc.add_resolution(ctx, name.to_string(), &def); - } - } - hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => { - if let hir::ModuleDef::Adt(Adt::Enum(e)) = def { - for variant in e.variants(ctx.db) { - acc.add_enum_variant(ctx, variant); - } - } - let ty = match def { - hir::ModuleDef::Adt(adt) => adt.ty(ctx.db), - hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), - _ => unreachable!(), - }; - ctx.analyzer.iterate_path_candidates(ctx.db, &ty, None, |_ty, item| { - match item { - hir::AssocItem::Function(func) => { - if !func.has_self_param(ctx.db) { - acc.add_function(ctx, func); - } - } - hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), - hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), - } - None::<()> - }); - // Iterate assoc types separately - // FIXME: complete T::AssocType - let krate = ctx.module.map(|m| m.krate()); - if let Some(krate) = krate { - ty.iterate_impl_items(ctx.db, krate, |item| { - match item { - hir::AssocItem::Function(_) | hir::AssocItem::Const(_) => {} - hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), - } - None::<()> - }); - } - } - hir::ModuleDef::Trait(t) => { - for item in t.items(ctx.db) { - match item { - hir::AssocItem::Function(func) => { - if !func.has_self_param(ctx.db) { - acc.add_function(ctx, func); - } - } - hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), - hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), - } - } - } - _ => {} - }; -} - -#[cfg(test)] -mod tests { - use test_utils::covers; - - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_reference_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn dont_complete_current_use() { - covers!(dont_complete_current_use); - let completions = do_completion(r"use self::foo<|>;", CompletionKind::Reference); - assert!(completions.is_empty()); - } - - #[test] - fn dont_complete_current_use_in_braces_with_glob() { - let completions = do_completion( - r" - mod foo { pub struct S; } - use self::{foo::*, bar<|>}; - ", - CompletionKind::Reference, - ); - assert_eq!(completions.len(), 2); - } - - #[test] - fn dont_complete_primitive_in_use() { - covers!(dont_complete_primitive_in_use); - let completions = do_completion(r"use self::<|>;", CompletionKind::BuiltinType); - assert!(completions.is_empty()); - } - - #[test] - fn completes_primitives() { - let completions = - do_completion(r"fn main() { let _: <|> = 92; }", CompletionKind::BuiltinType); - assert_eq!(completions.len(), 17); - } - - #[test] - fn completes_mod_with_docs() { - assert_debug_snapshot!( - do_reference_completion( - r" - use self::my<|>; - - /// Some simple - /// docs describing `mod my`. - mod my { - struct Bar; - } - " - ), - @r###" - [ - CompletionItem { - label: "my", - source_range: [27; 29), - delete: [27; 29), - insert: "my", - kind: Module, - documentation: Documentation( - "Some simple\ndocs describing `mod my`.", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_use_item_starting_with_self() { - assert_debug_snapshot!( - do_reference_completion( - r" - use self::m::<|>; - - mod m { - struct Bar; - } - " - ), - @r###" - [ - CompletionItem { - label: "Bar", - source_range: [30; 30), - delete: [30; 30), - insert: "Bar", - kind: Struct, - }, - ] - "### - ); - } - - #[test] - fn completes_use_item_starting_with_crate() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::Sp<|> - " - ), - @r###" - [ - CompletionItem { - label: "Spam", - source_range: [11; 13), - delete: [11; 13), - insert: "Spam", - kind: Struct, - }, - CompletionItem { - label: "foo", - source_range: [11; 13), - delete: [11; 13), - insert: "foo", - kind: Module, - }, - ] - "### - ); - } - - #[test] - fn completes_nested_use_tree() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - mod foo; - struct Spam; - //- /foo.rs - use crate::{Sp<|>}; - " - ), - @r###" - [ - CompletionItem { - label: "Spam", - source_range: [12; 14), - delete: [12; 14), - insert: "Spam", - kind: Struct, - }, - CompletionItem { - label: "foo", - source_range: [12; 14), - delete: [12; 14), - insert: "foo", - kind: Module, - }, - ] - "### - ); - } - - #[test] - fn completes_deeply_nested_use_tree() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - mod foo; - pub mod bar { - pub mod baz { - pub struct Spam; - } - } - //- /foo.rs - use crate::{bar::{baz::Sp<|>}}; - " - ), - @r###" - [ - CompletionItem { - label: "Spam", - source_range: [23; 25), - delete: [23; 25), - insert: "Spam", - kind: Struct, - }, - ] - "### - ); - } - - #[test] - fn completes_enum_variant() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - /// An enum - enum E { - /// Foo Variant - Foo, - /// Bar Variant with i32 - Bar(i32) - } - fn foo() { let _ = E::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "Bar", - source_range: [116; 116), - delete: [116; 116), - insert: "Bar", - kind: EnumVariant, - detail: "(i32)", - documentation: Documentation( - "Bar Variant with i32", - ), - }, - CompletionItem { - label: "Foo", - source_range: [116; 116), - delete: [116; 116), - insert: "Foo", - kind: EnumVariant, - detail: "()", - documentation: Documentation( - "Foo Variant", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_enum_variant_with_details() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - struct S { field: u32 } - /// An enum - enum E { - /// Foo Variant (empty) - Foo, - /// Bar Variant with i32 and u32 - Bar(i32, u32), - /// - S(S), - } - fn foo() { let _ = E::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "Bar", - source_range: [180; 180), - delete: [180; 180), - insert: "Bar", - kind: EnumVariant, - detail: "(i32, u32)", - documentation: Documentation( - "Bar Variant with i32 and u32", - ), - }, - CompletionItem { - label: "Foo", - source_range: [180; 180), - delete: [180; 180), - insert: "Foo", - kind: EnumVariant, - detail: "()", - documentation: Documentation( - "Foo Variant (empty)", - ), - }, - CompletionItem { - label: "S", - source_range: [180; 180), - delete: [180; 180), - insert: "S", - kind: EnumVariant, - detail: "(S)", - documentation: Documentation( - "", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_struct_associated_method() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - /// A Struct - struct S; - - impl S { - /// An associated method - fn m() { } - } - - fn foo() { let _ = S::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "m()", - source_range: [100; 100), - delete: [100; 100), - insert: "m()$0", - kind: Function, - lookup: "m", - detail: "fn m()", - documentation: Documentation( - "An associated method", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_struct_associated_const() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - /// A Struct - struct S; - - impl S { - /// An associated const - const C: i32 = 42; - } - - fn foo() { let _ = S::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "C", - source_range: [107; 107), - delete: [107; 107), - insert: "C", - kind: Const, - detail: "const C: i32 = 42;", - documentation: Documentation( - "An associated const", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_struct_associated_type() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - /// A Struct - struct S; - - impl S { - /// An associated type - type T = i32; - } - - fn foo() { let _ = S::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "T", - source_range: [101; 101), - delete: [101; 101), - insert: "T", - kind: TypeAlias, - detail: "type T = i32;", - documentation: Documentation( - "An associated type", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_enum_associated_method() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - /// An enum - enum S {}; - - impl S { - /// An associated method - fn m() { } - } - - fn foo() { let _ = S::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "m()", - source_range: [100; 100), - delete: [100; 100), - insert: "m()$0", - kind: Function, - lookup: "m", - detail: "fn m()", - documentation: Documentation( - "An associated method", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_union_associated_method() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - /// A union - union U {}; - - impl U { - /// An associated method - fn m() { } - } - - fn foo() { let _ = U::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "m()", - source_range: [101; 101), - delete: [101; 101), - insert: "m()$0", - kind: Function, - lookup: "m", - detail: "fn m()", - documentation: Documentation( - "An associated method", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_use_paths_across_crates() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - use foo::<|>; - - //- /foo/lib.rs - pub mod bar { - pub struct S; - } - " - ), - @r###" - [ - CompletionItem { - label: "bar", - source_range: [9; 9), - delete: [9; 9), - insert: "bar", - kind: Module, - }, - ] - "### - ); - } - - #[test] - fn completes_trait_associated_method_1() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - trait Trait { - /// A trait method - fn m(); - } - - fn foo() { let _ = Trait::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "m()", - source_range: [73; 73), - delete: [73; 73), - insert: "m()$0", - kind: Function, - lookup: "m", - detail: "fn m()", - documentation: Documentation( - "A trait method", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_trait_associated_method_2() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - trait Trait { - /// A trait method - fn m(); - } - - struct S; - impl Trait for S {} - - fn foo() { let _ = S::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "m()", - source_range: [99; 99), - delete: [99; 99), - insert: "m()$0", - kind: Function, - lookup: "m", - detail: "fn m()", - documentation: Documentation( - "A trait method", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_trait_associated_method_3() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - trait Trait { - /// A trait method - fn m(); - } - - struct S; - impl Trait for S {} - - fn foo() { let _ = ::<|> } - " - ), - @r###" - [ - CompletionItem { - label: "m()", - source_range: [110; 110), - delete: [110; 110), - insert: "m()$0", - kind: Function, - lookup: "m", - detail: "fn m()", - documentation: Documentation( - "A trait method", - ), - }, - ] - "### - ); - } - - #[test] - fn completes_type_alias() { - assert_debug_snapshot!( - do_reference_completion( - " - struct S; - impl S { fn foo() {} } - type T = S; - impl T { fn bar() {} } - - fn main() { - T::<|>; - } - " - ), - @r###" - [ - CompletionItem { - label: "bar()", - source_range: [185; 185), - delete: [185; 185), - insert: "bar()$0", - kind: Function, - lookup: "bar", - detail: "fn bar()", - }, - CompletionItem { - label: "foo()", - source_range: [185; 185), - delete: [185; 185), - insert: "foo()$0", - kind: Function, - lookup: "foo", - detail: "fn foo()", - }, - ] - "### - ); - } - - #[test] - fn completes_qualified_macros() { - assert_debug_snapshot!( - do_reference_completion( - " - #[macro_export] - macro_rules! foo { - () => {} - } - - fn main() { - let _ = crate::<|> - } - " - ), - @r###" - [ - CompletionItem { - label: "foo!", - source_range: [179; 179), - delete: [179; 179), - insert: "foo!($0)", - kind: Macro, - detail: "#[macro_export]\nmacro_rules! foo", - }, - CompletionItem { - label: "main()", - source_range: [179; 179), - delete: [179; 179), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_pattern.rs b/crates/ra_ide_api/src/completion/complete_pattern.rs deleted file mode 100644 index fd03b1c40..000000000 --- a/crates/ra_ide_api/src/completion/complete_pattern.rs +++ /dev/null @@ -1,89 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{CompletionContext, Completions}; - -/// Completes constats and paths in patterns. -pub(super) fn complete_pattern(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_pat_binding { - return; - } - // FIXME: ideally, we should look at the type we are matching against and - // suggest variants + auto-imports - ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { - let def = match &res { - hir::ScopeDef::ModuleDef(def) => def, - _ => return, - }; - match def { - hir::ModuleDef::Adt(hir::Adt::Enum(..)) - | hir::ModuleDef::EnumVariant(..) - | hir::ModuleDef::Const(..) - | hir::ModuleDef::Module(..) => (), - _ => return, - } - acc.add_resolution(ctx, name.to_string(), &res) - }); -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn complete(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn completes_enum_variants_and_modules() { - let completions = complete( - r" - enum E { X } - use self::E::X; - const Z: E = E::X; - mod m {} - - static FOO: E = E::X; - struct Bar { f: u32 } - - fn foo() { - match E::X { - <|> - } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "E", - source_range: [246; 246), - delete: [246; 246), - insert: "E", - kind: Enum, - }, - CompletionItem { - label: "X", - source_range: [246; 246), - delete: [246; 246), - insert: "X", - kind: EnumVariant, - }, - CompletionItem { - label: "Z", - source_range: [246; 246), - delete: [246; 246), - insert: "Z", - kind: Const, - }, - CompletionItem { - label: "m", - source_range: [246; 246), - delete: [246; 246), - insert: "m", - kind: Module, - }, - ] - "###); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_postfix.rs b/crates/ra_ide_api/src/completion/complete_postfix.rs deleted file mode 100644 index 646a30c76..000000000 --- a/crates/ra_ide_api/src/completion/complete_postfix.rs +++ /dev/null @@ -1,282 +0,0 @@ -//! FIXME: write short doc here - -use ra_syntax::{ast::AstNode, TextRange, TextUnit}; -use ra_text_edit::TextEdit; - -use crate::{ - completion::{ - completion_context::CompletionContext, - completion_item::{Builder, CompletionKind, Completions}, - }, - CompletionItem, -}; - -pub(super) fn complete_postfix(acc: &mut Completions, ctx: &CompletionContext) { - if ctx.db.feature_flags.get("completion.enable-postfix") == false { - return; - } - - let dot_receiver = match &ctx.dot_receiver { - Some(it) => it, - None => return, - }; - - let receiver_text = if ctx.dot_receiver_is_ambiguous_float_literal { - let text = dot_receiver.syntax().text(); - let without_dot = ..text.len() - TextUnit::of_char('.'); - text.slice(without_dot).to_string() - } else { - dot_receiver.syntax().text().to_string() - }; - - let receiver_ty = match ctx.analyzer.type_of(ctx.db, &dot_receiver) { - Some(it) => it, - None => return, - }; - - if receiver_ty.is_bool() || receiver_ty.is_unknown() { - postfix_snippet(ctx, "if", "if expr {}", &format!("if {} {{$0}}", receiver_text)) - .add_to(acc); - postfix_snippet( - ctx, - "while", - "while expr {}", - &format!("while {} {{\n$0\n}}", receiver_text), - ) - .add_to(acc); - } - - postfix_snippet(ctx, "not", "!expr", &format!("!{}", receiver_text)).add_to(acc); - - postfix_snippet(ctx, "ref", "&expr", &format!("&{}", receiver_text)).add_to(acc); - postfix_snippet(ctx, "refm", "&mut expr", &format!("&mut {}", receiver_text)).add_to(acc); - - postfix_snippet( - ctx, - "match", - "match expr {}", - &format!("match {} {{\n ${{1:_}} => {{$0\\}},\n}}", receiver_text), - ) - .add_to(acc); - - postfix_snippet(ctx, "dbg", "dbg!(expr)", &format!("dbg!({})", receiver_text)).add_to(acc); - - postfix_snippet(ctx, "box", "Box::new(expr)", &format!("Box::new({})", receiver_text)) - .add_to(acc); -} - -fn postfix_snippet(ctx: &CompletionContext, label: &str, detail: &str, snippet: &str) -> Builder { - let edit = { - let receiver_range = - ctx.dot_receiver.as_ref().expect("no receiver available").syntax().text_range(); - let delete_range = TextRange::from_to(receiver_range.start(), ctx.source_range().end()); - TextEdit::replace(delete_range, snippet.to_string()) - }; - CompletionItem::new(CompletionKind::Postfix, ctx.source_range(), label) - .detail(detail) - .snippet_edit(edit) -} - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - - fn do_postfix_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Postfix) - } - - #[test] - fn postfix_completion_works_for_trivial_path_expression() { - assert_debug_snapshot!( - do_postfix_completion( - r#" - fn main() { - let bar = true; - bar.<|> - } - "#, - ), - @r###" - [ - CompletionItem { - label: "box", - source_range: [89; 89), - delete: [85; 89), - insert: "Box::new(bar)", - detail: "Box::new(expr)", - }, - CompletionItem { - label: "dbg", - source_range: [89; 89), - delete: [85; 89), - insert: "dbg!(bar)", - detail: "dbg!(expr)", - }, - CompletionItem { - label: "if", - source_range: [89; 89), - delete: [85; 89), - insert: "if bar {$0}", - detail: "if expr {}", - }, - CompletionItem { - label: "match", - source_range: [89; 89), - delete: [85; 89), - insert: "match bar {\n ${1:_} => {$0\\},\n}", - detail: "match expr {}", - }, - CompletionItem { - label: "not", - source_range: [89; 89), - delete: [85; 89), - insert: "!bar", - detail: "!expr", - }, - CompletionItem { - label: "ref", - source_range: [89; 89), - delete: [85; 89), - insert: "&bar", - detail: "&expr", - }, - CompletionItem { - label: "refm", - source_range: [89; 89), - delete: [85; 89), - insert: "&mut bar", - detail: "&mut expr", - }, - CompletionItem { - label: "while", - source_range: [89; 89), - delete: [85; 89), - insert: "while bar {\n$0\n}", - detail: "while expr {}", - }, - ] - "### - ); - } - - #[test] - fn some_postfix_completions_ignored() { - assert_debug_snapshot!( - do_postfix_completion( - r#" - fn main() { - let bar: u8 = 12; - bar.<|> - } - "#, - ), - @r###" - [ - CompletionItem { - label: "box", - source_range: [91; 91), - delete: [87; 91), - insert: "Box::new(bar)", - detail: "Box::new(expr)", - }, - CompletionItem { - label: "dbg", - source_range: [91; 91), - delete: [87; 91), - insert: "dbg!(bar)", - detail: "dbg!(expr)", - }, - CompletionItem { - label: "match", - source_range: [91; 91), - delete: [87; 91), - insert: "match bar {\n ${1:_} => {$0\\},\n}", - detail: "match expr {}", - }, - CompletionItem { - label: "not", - source_range: [91; 91), - delete: [87; 91), - insert: "!bar", - detail: "!expr", - }, - CompletionItem { - label: "ref", - source_range: [91; 91), - delete: [87; 91), - insert: "&bar", - detail: "&expr", - }, - CompletionItem { - label: "refm", - source_range: [91; 91), - delete: [87; 91), - insert: "&mut bar", - detail: "&mut expr", - }, - ] - "### - ); - } - - #[test] - fn postfix_completion_works_for_ambiguous_float_literal() { - assert_debug_snapshot!( - do_postfix_completion( - r#" - fn main() { - 42.<|> - } - "#, - ), - @r###" - [ - CompletionItem { - label: "box", - source_range: [52; 52), - delete: [49; 52), - insert: "Box::new(42)", - detail: "Box::new(expr)", - }, - CompletionItem { - label: "dbg", - source_range: [52; 52), - delete: [49; 52), - insert: "dbg!(42)", - detail: "dbg!(expr)", - }, - CompletionItem { - label: "match", - source_range: [52; 52), - delete: [49; 52), - insert: "match 42 {\n ${1:_} => {$0\\},\n}", - detail: "match expr {}", - }, - CompletionItem { - label: "not", - source_range: [52; 52), - delete: [49; 52), - insert: "!42", - detail: "!expr", - }, - CompletionItem { - label: "ref", - source_range: [52; 52), - delete: [49; 52), - insert: "&42", - detail: "&expr", - }, - CompletionItem { - label: "refm", - source_range: [52; 52), - delete: [49; 52), - insert: "&mut 42", - detail: "&mut expr", - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_record_literal.rs b/crates/ra_ide_api/src/completion/complete_record_literal.rs deleted file mode 100644 index 577c394d2..000000000 --- a/crates/ra_ide_api/src/completion/complete_record_literal.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{CompletionContext, Completions}; - -/// Complete fields in fields literals. -pub(super) fn complete_record_literal(acc: &mut Completions, ctx: &CompletionContext) { - let (ty, variant) = match ctx.record_lit_syntax.as_ref().and_then(|it| { - Some(( - ctx.analyzer.type_of(ctx.db, &it.clone().into())?, - ctx.analyzer.resolve_record_literal(it)?, - )) - }) { - Some(it) => it, - _ => return, - }; - - for (field, field_ty) in ty.variant_fields(ctx.db, variant) { - acc.add_field(ctx, field, &field_ty); - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn complete(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn test_record_literal_deprecated_field() { - let completions = complete( - r" - struct A { - #[deprecated] - the_field: u32, - } - fn foo() { - A { the<|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "the_field", - source_range: [142; 145), - delete: [142; 145), - insert: "the_field", - kind: Field, - detail: "u32", - deprecated: true, - }, - ] - "###); - } - - #[test] - fn test_record_literal_field() { - let completions = complete( - r" - struct A { the_field: u32 } - fn foo() { - A { the<|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "the_field", - source_range: [83; 86), - delete: [83; 86), - insert: "the_field", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_enum_variant() { - let completions = complete( - r" - enum E { - A { a: u32 } - } - fn foo() { - let _ = E::A { <|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "a", - source_range: [119; 119), - delete: [119; 119), - insert: "a", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_two_structs() { - let completions = complete( - r" - struct A { a: u32 } - struct B { b: u32 } - - fn foo() { - let _: A = B { <|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "b", - source_range: [119; 119), - delete: [119; 119), - insert: "b", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_literal_generic_struct() { - let completions = complete( - r" - struct A { a: T } - - fn foo() { - let _: A = A { <|> } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "a", - source_range: [93; 93), - delete: [93; 93), - insert: "a", - kind: Field, - detail: "u32", - }, - ] - "###); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_record_pattern.rs b/crates/ra_ide_api/src/completion/complete_record_pattern.rs deleted file mode 100644 index a56c7e3a1..000000000 --- a/crates/ra_ide_api/src/completion/complete_record_pattern.rs +++ /dev/null @@ -1,93 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{CompletionContext, Completions}; - -pub(super) fn complete_record_pattern(acc: &mut Completions, ctx: &CompletionContext) { - let (ty, variant) = match ctx.record_lit_pat.as_ref().and_then(|it| { - Some(( - ctx.analyzer.type_of_pat(ctx.db, &it.clone().into())?, - ctx.analyzer.resolve_record_pattern(it)?, - )) - }) { - Some(it) => it, - _ => return, - }; - - for (field, field_ty) in ty.variant_fields(ctx.db, variant) { - acc.add_field(ctx, field, &field_ty); - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn complete(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn test_record_pattern_field() { - let completions = complete( - r" - struct S { foo: u32 } - - fn process(f: S) { - match f { - S { f<|>: 92 } => (), - } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "foo", - source_range: [117; 118), - delete: [117; 118), - insert: "foo", - kind: Field, - detail: "u32", - }, - ] - "###); - } - - #[test] - fn test_record_pattern_enum_variant() { - let completions = complete( - r" - enum E { - S { foo: u32, bar: () } - } - - fn process(e: E) { - match e { - E::S { <|> } => (), - } - } - ", - ); - assert_debug_snapshot!(completions, @r###" - [ - CompletionItem { - label: "bar", - source_range: [161; 161), - delete: [161; 161), - insert: "bar", - kind: Field, - detail: "()", - }, - CompletionItem { - label: "foo", - source_range: [161; 161), - delete: [161; 161), - insert: "foo", - kind: Field, - detail: "u32", - }, - ] - "###); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_scope.rs b/crates/ra_ide_api/src/completion/complete_scope.rs deleted file mode 100644 index d5739b58a..000000000 --- a/crates/ra_ide_api/src/completion/complete_scope.rs +++ /dev/null @@ -1,876 +0,0 @@ -//! FIXME: write short doc here - -use ra_assists::auto_import_text_edit; -use ra_syntax::{ast, AstNode, SmolStr}; -use ra_text_edit::TextEditBuilder; -use rustc_hash::FxHashMap; - -use crate::completion::{CompletionContext, CompletionItem, CompletionKind, Completions}; - -pub(super) fn complete_scope(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_trivial_path { - return; - } - - ctx.analyzer.process_all_names(ctx.db, &mut |name, res| { - acc.add_resolution(ctx, name.to_string(), &res) - }); - - // auto-import - // We fetch ident from the original file, because we need to pre-filter auto-imports - if ast::NameRef::cast(ctx.token.parent()).is_some() { - let import_resolver = ImportResolver::new(); - let import_names = import_resolver.all_names(ctx.token.text()); - import_names.into_iter().for_each(|(name, path)| { - let edit = { - let mut builder = TextEditBuilder::default(); - builder.replace(ctx.source_range(), name.to_string()); - auto_import_text_edit( - &ctx.token.parent(), - &ctx.token.parent(), - &path, - &mut builder, - ); - builder.finish() - }; - - // Hack: copied this check form conv.rs beacause auto import can produce edits - // that invalidate assert in conv_with. - if edit - .as_atoms() - .iter() - .filter(|atom| !ctx.source_range().is_subrange(&atom.delete)) - .all(|atom| ctx.source_range().intersection(&atom.delete).is_none()) - { - CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - build_import_label(&name, &path), - ) - .text_edit(edit) - .add_to(acc); - } - }); - } -} - -fn build_import_label(name: &str, path: &[SmolStr]) -> String { - let mut buf = String::with_capacity(64); - buf.push_str(name); - buf.push_str(" ("); - fmt_import_path(path, &mut buf); - buf.push_str(")"); - buf -} - -fn fmt_import_path(path: &[SmolStr], buf: &mut String) { - let mut segments = path.iter(); - if let Some(s) = segments.next() { - buf.push_str(&s); - } - for s in segments { - buf.push_str("::"); - buf.push_str(&s); - } -} - -#[derive(Debug, Clone, Default)] -pub(crate) struct ImportResolver { - // todo: use fst crate or something like that - dummy_names: Vec<(SmolStr, Vec)>, -} - -impl ImportResolver { - pub(crate) fn new() -> Self { - let dummy_names = vec![ - (SmolStr::new("fmt"), vec![SmolStr::new("std"), SmolStr::new("fmt")]), - (SmolStr::new("io"), vec![SmolStr::new("std"), SmolStr::new("io")]), - (SmolStr::new("iter"), vec![SmolStr::new("std"), SmolStr::new("iter")]), - (SmolStr::new("hash"), vec![SmolStr::new("std"), SmolStr::new("hash")]), - ( - SmolStr::new("Debug"), - vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Debug")], - ), - ( - SmolStr::new("Display"), - vec![SmolStr::new("std"), SmolStr::new("fmt"), SmolStr::new("Display")], - ), - ( - SmolStr::new("Hash"), - vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hash")], - ), - ( - SmolStr::new("Hasher"), - vec![SmolStr::new("std"), SmolStr::new("hash"), SmolStr::new("Hasher")], - ), - ( - SmolStr::new("Iterator"), - vec![SmolStr::new("std"), SmolStr::new("iter"), SmolStr::new("Iterator")], - ), - ]; - - ImportResolver { dummy_names } - } - - // Returns a map of importable items filtered by name. - // The map associates item name with its full path. - // todo: should return Resolutions - pub(crate) fn all_names(&self, name: &str) -> FxHashMap> { - if name.len() > 1 { - self.dummy_names.iter().filter(|(n, _)| n.contains(name)).cloned().collect() - } else { - FxHashMap::default() - } - } -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_reference_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn completes_bindings_from_let() { - assert_debug_snapshot!( - do_reference_completion( - r" - fn quux(x: i32) { - let y = 92; - 1 + <|>; - let z = (); - } - " - ), - @r###" - [ - CompletionItem { - label: "quux(…)", - source_range: [91; 91), - delete: [91; 91), - insert: "quux($0)", - kind: Function, - lookup: "quux", - detail: "fn quux(x: i32)", - }, - CompletionItem { - label: "x", - source_range: [91; 91), - delete: [91; 91), - insert: "x", - kind: Binding, - detail: "i32", - }, - CompletionItem { - label: "y", - source_range: [91; 91), - delete: [91; 91), - insert: "y", - kind: Binding, - detail: "i32", - }, - ] - "### - ); - } - - #[test] - fn completes_bindings_from_if_let() { - assert_debug_snapshot!( - do_reference_completion( - r" - fn quux() { - if let Some(x) = foo() { - let y = 92; - }; - if let Some(a) = bar() { - let b = 62; - 1 + <|> - } - } - " - ), - @r###" - [ - CompletionItem { - label: "a", - source_range: [242; 242), - delete: [242; 242), - insert: "a", - kind: Binding, - }, - CompletionItem { - label: "b", - source_range: [242; 242), - delete: [242; 242), - insert: "b", - kind: Binding, - detail: "i32", - }, - CompletionItem { - label: "quux()", - source_range: [242; 242), - delete: [242; 242), - insert: "quux()$0", - kind: Function, - lookup: "quux", - detail: "fn quux()", - }, - ] - "### - ); - } - - #[test] - fn completes_bindings_from_for() { - assert_debug_snapshot!( - do_reference_completion( - r" - fn quux() { - for x in &[1, 2, 3] { - <|> - } - } - " - ), - @r###" - [ - CompletionItem { - label: "quux()", - source_range: [95; 95), - delete: [95; 95), - insert: "quux()$0", - kind: Function, - lookup: "quux", - detail: "fn quux()", - }, - CompletionItem { - label: "x", - source_range: [95; 95), - delete: [95; 95), - insert: "x", - kind: Binding, - }, - ] - "### - ); - } - - #[test] - fn completes_generic_params() { - assert_debug_snapshot!( - do_reference_completion( - r" - fn quux() { - <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "T", - source_range: [52; 52), - delete: [52; 52), - insert: "T", - kind: TypeParam, - }, - CompletionItem { - label: "quux()", - source_range: [52; 52), - delete: [52; 52), - insert: "quux()$0", - kind: Function, - lookup: "quux", - detail: "fn quux()", - }, - ] - "### - ); - } - - #[test] - fn completes_generic_params_in_struct() { - assert_debug_snapshot!( - do_reference_completion( - r" - struct X { - x: <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "Self", - source_range: [54; 54), - delete: [54; 54), - insert: "Self", - kind: TypeParam, - }, - CompletionItem { - label: "T", - source_range: [54; 54), - delete: [54; 54), - insert: "T", - kind: TypeParam, - }, - CompletionItem { - label: "X<…>", - source_range: [54; 54), - delete: [54; 54), - insert: "X<$0>", - kind: Struct, - lookup: "X", - }, - ] - "### - ); - } - - #[test] - fn completes_self_in_enum() { - assert_debug_snapshot!( - do_reference_completion( - r" - enum X { - Y(<|>) - } - " - ), - @r###" - [ - CompletionItem { - label: "Self", - source_range: [48; 48), - delete: [48; 48), - insert: "Self", - kind: TypeParam, - }, - CompletionItem { - label: "X", - source_range: [48; 48), - delete: [48; 48), - insert: "X", - kind: Enum, - }, - ] - "### - ); - } - - #[test] - fn completes_module_items() { - assert_debug_snapshot!( - do_reference_completion( - r" - struct Foo; - enum Baz {} - fn quux() { - <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "Baz", - source_range: [105; 105), - delete: [105; 105), - insert: "Baz", - kind: Enum, - }, - CompletionItem { - label: "Foo", - source_range: [105; 105), - delete: [105; 105), - insert: "Foo", - kind: Struct, - }, - CompletionItem { - label: "quux()", - source_range: [105; 105), - delete: [105; 105), - insert: "quux()$0", - kind: Function, - lookup: "quux", - detail: "fn quux()", - }, - ] - "### - ); - } - - #[test] - fn completes_extern_prelude() { - assert_debug_snapshot!( - do_reference_completion( - r" - //- /lib.rs - use <|>; - - //- /other_crate/lib.rs - // nothing here - " - ), - @r###" - [ - CompletionItem { - label: "other_crate", - source_range: [4; 4), - delete: [4; 4), - insert: "other_crate", - kind: Module, - }, - ] - "### - ); - } - - #[test] - fn completes_module_items_in_nested_modules() { - assert_debug_snapshot!( - do_reference_completion( - r" - struct Foo; - mod m { - struct Bar; - fn quux() { <|> } - } - " - ), - @r###" - [ - CompletionItem { - label: "Bar", - source_range: [117; 117), - delete: [117; 117), - insert: "Bar", - kind: Struct, - }, - CompletionItem { - label: "quux()", - source_range: [117; 117), - delete: [117; 117), - insert: "quux()$0", - kind: Function, - lookup: "quux", - detail: "fn quux()", - }, - ] - "### - ); - } - - #[test] - fn completes_return_type() { - assert_debug_snapshot!( - do_reference_completion( - r" - struct Foo; - fn x() -> <|> - " - ), - @r###" - [ - CompletionItem { - label: "Foo", - source_range: [55; 55), - delete: [55; 55), - insert: "Foo", - kind: Struct, - }, - CompletionItem { - label: "x()", - source_range: [55; 55), - delete: [55; 55), - insert: "x()$0", - kind: Function, - lookup: "x", - detail: "fn x()", - }, - ] - "### - ); - } - - #[test] - fn dont_show_both_completions_for_shadowing() { - assert_debug_snapshot!( - do_reference_completion( - r" - fn foo() { - let bar = 92; - { - let bar = 62; - <|> - } - } - " - ), - @r###" - [ - CompletionItem { - label: "bar", - source_range: [146; 146), - delete: [146; 146), - insert: "bar", - kind: Binding, - detail: "i32", - }, - CompletionItem { - label: "foo()", - source_range: [146; 146), - delete: [146; 146), - insert: "foo()$0", - kind: Function, - lookup: "foo", - detail: "fn foo()", - }, - ] - "### - ); - } - - #[test] - fn completes_self_in_methods() { - assert_debug_snapshot!( - do_reference_completion(r"impl S { fn foo(&self) { <|> } }"), - @r###" - [ - CompletionItem { - label: "Self", - source_range: [25; 25), - delete: [25; 25), - insert: "Self", - kind: TypeParam, - }, - CompletionItem { - label: "self", - source_range: [25; 25), - delete: [25; 25), - insert: "self", - kind: Binding, - detail: "&{unknown}", - }, - ] - "### - ); - } - - #[test] - fn completes_prelude() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - fn foo() { let x: <|> } - - //- /std/lib.rs - #[prelude_import] - use prelude::*; - - mod prelude { - struct Option; - } - " - ), - @r###" - [ - CompletionItem { - label: "Option", - source_range: [18; 18), - delete: [18; 18), - insert: "Option", - kind: Struct, - }, - CompletionItem { - label: "foo()", - source_range: [18; 18), - delete: [18; 18), - insert: "foo()$0", - kind: Function, - lookup: "foo", - detail: "fn foo()", - }, - CompletionItem { - label: "std", - source_range: [18; 18), - delete: [18; 18), - insert: "std", - kind: Module, - }, - ] - "### - ); - } - - #[test] - fn completes_std_prelude_if_core_is_defined() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - fn foo() { let x: <|> } - - //- /core/lib.rs - #[prelude_import] - use prelude::*; - - mod prelude { - struct Option; - } - - //- /std/lib.rs - #[prelude_import] - use prelude::*; - - mod prelude { - struct String; - } - " - ), - @r###" - [ - CompletionItem { - label: "String", - source_range: [18; 18), - delete: [18; 18), - insert: "String", - kind: Struct, - }, - CompletionItem { - label: "core", - source_range: [18; 18), - delete: [18; 18), - insert: "core", - kind: Module, - }, - CompletionItem { - label: "foo()", - source_range: [18; 18), - delete: [18; 18), - insert: "foo()$0", - kind: Function, - lookup: "foo", - detail: "fn foo()", - }, - CompletionItem { - label: "std", - source_range: [18; 18), - delete: [18; 18), - insert: "std", - kind: Module, - }, - ] - "### - ); - } - - #[test] - fn completes_macros_as_value() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - macro_rules! foo { - () => {} - } - - #[macro_use] - mod m1 { - macro_rules! bar { - () => {} - } - } - - mod m2 { - macro_rules! nope { - () => {} - } - - #[macro_export] - macro_rules! baz { - () => {} - } - } - - fn main() { - let v = <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "bar!", - source_range: [252; 252), - delete: [252; 252), - insert: "bar!($0)", - kind: Macro, - detail: "macro_rules! bar", - }, - CompletionItem { - label: "baz!", - source_range: [252; 252), - delete: [252; 252), - insert: "baz!($0)", - kind: Macro, - detail: "#[macro_export]\nmacro_rules! baz", - }, - CompletionItem { - label: "foo!", - source_range: [252; 252), - delete: [252; 252), - insert: "foo!($0)", - kind: Macro, - detail: "macro_rules! foo", - }, - CompletionItem { - label: "m1", - source_range: [252; 252), - delete: [252; 252), - insert: "m1", - kind: Module, - }, - CompletionItem { - label: "m2", - source_range: [252; 252), - delete: [252; 252), - insert: "m2", - kind: Module, - }, - CompletionItem { - label: "main()", - source_range: [252; 252), - delete: [252; 252), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - ] - "### - ); - } - - #[test] - fn completes_both_macro_and_value() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - macro_rules! foo { - () => {} - } - - fn foo() { - <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "foo!", - source_range: [49; 49), - delete: [49; 49), - insert: "foo!($0)", - kind: Macro, - detail: "macro_rules! foo", - }, - CompletionItem { - label: "foo()", - source_range: [49; 49), - delete: [49; 49), - insert: "foo()$0", - kind: Function, - lookup: "foo", - detail: "fn foo()", - }, - ] - "### - ); - } - - #[test] - fn completes_macros_as_type() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - macro_rules! foo { - () => {} - } - - fn main() { - let x: <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "foo!", - source_range: [57; 57), - delete: [57; 57), - insert: "foo!($0)", - kind: Macro, - detail: "macro_rules! foo", - }, - CompletionItem { - label: "main()", - source_range: [57; 57), - delete: [57; 57), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - ] - "### - ); - } - - #[test] - fn completes_macros_as_stmt() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /main.rs - macro_rules! foo { - () => {} - } - - fn main() { - <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "foo!", - source_range: [50; 50), - delete: [50; 50), - insert: "foo!($0)", - kind: Macro, - detail: "macro_rules! foo", - }, - CompletionItem { - label: "main()", - source_range: [50; 50), - delete: [50; 50), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/completion/complete_snippet.rs b/crates/ra_ide_api/src/completion/complete_snippet.rs deleted file mode 100644 index 1f2988b36..000000000 --- a/crates/ra_ide_api/src/completion/complete_snippet.rs +++ /dev/null @@ -1,120 +0,0 @@ -//! FIXME: write short doc here - -use crate::completion::{ - completion_item::Builder, CompletionContext, CompletionItem, CompletionItemKind, - CompletionKind, Completions, -}; - -fn snippet(ctx: &CompletionContext, label: &str, snippet: &str) -> Builder { - CompletionItem::new(CompletionKind::Snippet, ctx.source_range(), label) - .insert_snippet(snippet) - .kind(CompletionItemKind::Snippet) -} - -pub(super) fn complete_expr_snippet(acc: &mut Completions, ctx: &CompletionContext) { - if !(ctx.is_trivial_path && ctx.function_syntax.is_some()) { - return; - } - - snippet(ctx, "pd", "eprintln!(\"$0 = {:?}\", $0);").add_to(acc); - snippet(ctx, "ppd", "eprintln!(\"$0 = {:#?}\", $0);").add_to(acc); -} - -pub(super) fn complete_item_snippet(acc: &mut Completions, ctx: &CompletionContext) { - if !ctx.is_new_item { - return; - } - snippet( - ctx, - "Test function", - "\ -#[test] -fn ${1:feature}() { - $0 -}", - ) - .lookup_by("tfn") - .add_to(acc); - - snippet(ctx, "pub(crate)", "pub(crate) $0").add_to(acc); -} - -#[cfg(test)] -mod tests { - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - use insta::assert_debug_snapshot; - - fn do_snippet_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Snippet) - } - - #[test] - fn completes_snippets_in_expressions() { - assert_debug_snapshot!( - do_snippet_completion(r"fn foo(x: i32) { <|> }"), - @r###" - [ - CompletionItem { - label: "pd", - source_range: [17; 17), - delete: [17; 17), - insert: "eprintln!(\"$0 = {:?}\", $0);", - kind: Snippet, - }, - CompletionItem { - label: "ppd", - source_range: [17; 17), - delete: [17; 17), - insert: "eprintln!(\"$0 = {:#?}\", $0);", - kind: Snippet, - }, - ] - "### - ); - } - - #[test] - fn should_not_complete_snippets_in_path() { - assert_debug_snapshot!( - do_snippet_completion(r"fn foo(x: i32) { ::foo<|> }"), - @"[]" - ); - assert_debug_snapshot!( - do_snippet_completion(r"fn foo(x: i32) { ::<|> }"), - @"[]" - ); - } - - #[test] - fn completes_snippets_in_items() { - assert_debug_snapshot!( - do_snippet_completion( - r" - #[cfg(test)] - mod tests { - <|> - } - " - ), - @r###" - [ - CompletionItem { - label: "Test function", - source_range: [78; 78), - delete: [78; 78), - insert: "#[test]\nfn ${1:feature}() {\n $0\n}", - kind: Snippet, - lookup: "tfn", - }, - CompletionItem { - label: "pub(crate)", - source_range: [78; 78), - delete: [78; 78), - insert: "pub(crate) $0", - kind: Snippet, - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/completion/completion_context.rs b/crates/ra_ide_api/src/completion/completion_context.rs deleted file mode 100644 index b8345c91d..000000000 --- a/crates/ra_ide_api/src/completion/completion_context.rs +++ /dev/null @@ -1,274 +0,0 @@ -//! FIXME: write short doc here - -use ra_syntax::{ - algo::{find_covering_element, find_node_at_offset}, - ast, AstNode, Parse, SourceFile, - SyntaxKind::*, - SyntaxNode, SyntaxToken, TextRange, TextUnit, -}; -use ra_text_edit::AtomTextEdit; - -use crate::{db, FilePosition}; - -/// `CompletionContext` is created early during completion to figure out, where -/// exactly is the cursor, syntax-wise. -#[derive(Debug)] -pub(crate) struct CompletionContext<'a> { - pub(super) db: &'a db::RootDatabase, - pub(super) analyzer: hir::SourceAnalyzer, - pub(super) offset: TextUnit, - pub(super) token: SyntaxToken, - pub(super) module: Option, - pub(super) function_syntax: Option, - pub(super) use_item_syntax: Option, - pub(super) record_lit_syntax: Option, - pub(super) record_lit_pat: Option, - pub(super) is_param: bool, - /// If a name-binding or reference to a const in a pattern. - /// Irrefutable patterns (like let) are excluded. - pub(super) is_pat_binding: bool, - /// A single-indent path, like `foo`. `::foo` should not be considered a trivial path. - pub(super) is_trivial_path: bool, - /// If not a trivial path, the prefix (qualifier). - pub(super) path_prefix: Option, - pub(super) after_if: bool, - /// `true` if we are a statement or a last expr in the block. - pub(super) can_be_stmt: bool, - /// Something is typed at the "top" level, in module or impl/trait. - pub(super) is_new_item: bool, - /// The receiver if this is a field or method access, i.e. writing something.<|> - pub(super) dot_receiver: Option, - pub(super) dot_receiver_is_ambiguous_float_literal: bool, - /// If this is a call (method or function) in particular, i.e. the () are already there. - pub(super) is_call: bool, - pub(super) is_path_type: bool, - pub(super) has_type_args: bool, -} - -impl<'a> CompletionContext<'a> { - pub(super) fn new( - db: &'a db::RootDatabase, - original_parse: &'a Parse, - position: FilePosition, - ) -> Option> { - let src = hir::ModuleSource::from_position(db, position); - let module = hir::Module::from_definition( - db, - hir::Source { file_id: position.file_id.into(), value: src }, - ); - let token = - original_parse.tree().syntax().token_at_offset(position.offset).left_biased()?; - let analyzer = hir::SourceAnalyzer::new( - db, - hir::Source::new(position.file_id.into(), &token.parent()), - Some(position.offset), - ); - let mut ctx = CompletionContext { - db, - analyzer, - token, - offset: position.offset, - module, - function_syntax: None, - use_item_syntax: None, - record_lit_syntax: None, - record_lit_pat: None, - is_param: false, - is_pat_binding: false, - is_trivial_path: false, - path_prefix: None, - after_if: false, - can_be_stmt: false, - is_new_item: false, - dot_receiver: None, - is_call: false, - is_path_type: false, - has_type_args: false, - dot_receiver_is_ambiguous_float_literal: false, - }; - ctx.fill(&original_parse, position.offset); - Some(ctx) - } - - // The range of the identifier that is being completed. - pub(crate) fn source_range(&self) -> TextRange { - match self.token.kind() { - // workaroud when completion is triggered by trigger characters. - IDENT => self.token.text_range(), - _ => TextRange::offset_len(self.offset, 0.into()), - } - } - - fn fill(&mut self, original_parse: &'a Parse, offset: TextUnit) { - // Insert a fake ident to get a valid parse tree. We will use this file - // to determine context, though the original_file will be used for - // actual completion. - let file = { - let edit = AtomTextEdit::insert(offset, "intellijRulezz".to_string()); - original_parse.reparse(&edit).tree() - }; - - // First, let's try to complete a reference to some declaration. - if let Some(name_ref) = find_node_at_offset::(file.syntax(), offset) { - // Special case, `trait T { fn foo(i_am_a_name_ref) {} }`. - // See RFC#1685. - if is_node::(name_ref.syntax()) { - self.is_param = true; - return; - } - self.classify_name_ref(original_parse.tree(), name_ref); - } - - // Otherwise, see if this is a declaration. We can use heuristics to - // suggest declaration names, see `CompletionKind::Magic`. - if let Some(name) = find_node_at_offset::(file.syntax(), offset) { - if let Some(bind_pat) = name.syntax().ancestors().find_map(ast::BindPat::cast) { - let parent = bind_pat.syntax().parent(); - if parent.clone().and_then(ast::MatchArm::cast).is_some() - || parent.and_then(ast::Condition::cast).is_some() - { - self.is_pat_binding = true; - } - } - if is_node::(name.syntax()) { - self.is_param = true; - return; - } - if name.syntax().ancestors().find_map(ast::RecordFieldPatList::cast).is_some() { - self.record_lit_pat = - find_node_at_offset(original_parse.tree().syntax(), self.offset); - } - } - } - - fn classify_name_ref(&mut self, original_file: SourceFile, name_ref: ast::NameRef) { - let name_range = name_ref.syntax().text_range(); - if name_ref.syntax().parent().and_then(ast::RecordField::cast).is_some() { - self.record_lit_syntax = find_node_at_offset(original_file.syntax(), self.offset); - } - - let top_node = name_ref - .syntax() - .ancestors() - .take_while(|it| it.text_range() == name_range) - .last() - .unwrap(); - - match top_node.parent().map(|it| it.kind()) { - Some(SOURCE_FILE) | Some(ITEM_LIST) => { - self.is_new_item = true; - return; - } - _ => (), - } - - self.use_item_syntax = self.token.parent().ancestors().find_map(ast::UseItem::cast); - - self.function_syntax = self - .token - .parent() - .ancestors() - .take_while(|it| it.kind() != SOURCE_FILE && it.kind() != MODULE) - .find_map(ast::FnDef::cast); - - let parent = match name_ref.syntax().parent() { - Some(it) => it, - None => return, - }; - - if let Some(segment) = ast::PathSegment::cast(parent.clone()) { - let path = segment.parent_path(); - self.is_call = path - .syntax() - .parent() - .and_then(ast::PathExpr::cast) - .and_then(|it| it.syntax().parent().and_then(ast::CallExpr::cast)) - .is_some(); - - self.is_path_type = path.syntax().parent().and_then(ast::PathType::cast).is_some(); - self.has_type_args = segment.type_arg_list().is_some(); - - if let Some(mut path) = hir::Path::from_ast(path.clone()) { - if !path.is_ident() { - path.segments.pop().unwrap(); - self.path_prefix = Some(path); - return; - } - } - - if path.qualifier().is_none() { - self.is_trivial_path = true; - - // Find either enclosing expr statement (thing with `;`) or a - // block. If block, check that we are the last expr. - self.can_be_stmt = name_ref - .syntax() - .ancestors() - .find_map(|node| { - if let Some(stmt) = ast::ExprStmt::cast(node.clone()) { - return Some( - stmt.syntax().text_range() == name_ref.syntax().text_range(), - ); - } - if let Some(block) = ast::Block::cast(node) { - return Some( - block.expr().map(|e| e.syntax().text_range()) - == Some(name_ref.syntax().text_range()), - ); - } - None - }) - .unwrap_or(false); - - if let Some(off) = name_ref.syntax().text_range().start().checked_sub(2.into()) { - if let Some(if_expr) = - find_node_at_offset::(original_file.syntax(), off) - { - if if_expr.syntax().text_range().end() - < name_ref.syntax().text_range().start() - { - self.after_if = true; - } - } - } - } - } - if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { - // The receiver comes before the point of insertion of the fake - // ident, so it should have the same range in the non-modified file - self.dot_receiver = field_expr - .expr() - .map(|e| e.syntax().text_range()) - .and_then(|r| find_node_with_range(original_file.syntax(), r)); - self.dot_receiver_is_ambiguous_float_literal = if let Some(ast::Expr::Literal(l)) = - &self.dot_receiver - { - match l.kind() { - ast::LiteralKind::FloatNumber { suffix: _ } => l.token().text().ends_with('.'), - _ => false, - } - } else { - false - } - } - if let Some(method_call_expr) = ast::MethodCallExpr::cast(parent) { - // As above - self.dot_receiver = method_call_expr - .expr() - .map(|e| e.syntax().text_range()) - .and_then(|r| find_node_with_range(original_file.syntax(), r)); - self.is_call = true; - } - } -} - -fn find_node_with_range(syntax: &SyntaxNode, range: TextRange) -> Option { - find_covering_element(syntax, range).ancestors().find_map(N::cast) -} - -fn is_node(node: &SyntaxNode) -> bool { - match node.ancestors().find_map(N::cast) { - None => false, - Some(n) => n.syntax().text_range() == node.text_range(), - } -} diff --git a/crates/ra_ide_api/src/completion/completion_item.rs b/crates/ra_ide_api/src/completion/completion_item.rs deleted file mode 100644 index 93f336370..000000000 --- a/crates/ra_ide_api/src/completion/completion_item.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! FIXME: write short doc here - -use std::fmt; - -use hir::Documentation; -use ra_syntax::TextRange; -use ra_text_edit::TextEdit; - -/// `CompletionItem` describes a single completion variant in the editor pop-up. -/// It is basically a POD with various properties. To construct a -/// `CompletionItem`, use `new` method and the `Builder` struct. -pub struct CompletionItem { - /// Used only internally in tests, to check only specific kind of - /// completion (postfix, keyword, reference, etc). - #[allow(unused)] - completion_kind: CompletionKind, - /// Label in the completion pop up which identifies completion. - label: String, - /// Range of identifier that is being completed. - /// - /// It should be used primarily for UI, but we also use this to convert - /// genetic TextEdit into LSP's completion edit (see conv.rs). - /// - /// `source_range` must contain the completion offset. `insert_text` should - /// start with what `source_range` points to, or VSCode will filter out the - /// completion silently. - source_range: TextRange, - /// What happens when user selects this item. - /// - /// Typically, replaces `source_range` with new identifier. - text_edit: TextEdit, - insert_text_format: InsertTextFormat, - - /// What item (struct, function, etc) are we completing. - kind: Option, - - /// Lookup is used to check if completion item indeed can complete current - /// ident. - /// - /// That is, in `foo.bar<|>` lookup of `abracadabra` will be accepted (it - /// contains `bar` sub sequence), and `quux` will rejected. - lookup: Option, - - /// Additional info to show in the UI pop up. - detail: Option, - documentation: Option, - - /// Whether this item is marked as deprecated - deprecated: bool, -} - -// We use custom debug for CompletionItem to make `insta`'s diffs more readable. -impl fmt::Debug for CompletionItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let mut s = f.debug_struct("CompletionItem"); - s.field("label", &self.label()).field("source_range", &self.source_range()); - if self.text_edit().as_atoms().len() == 1 { - let atom = &self.text_edit().as_atoms()[0]; - s.field("delete", &atom.delete); - s.field("insert", &atom.insert); - } else { - s.field("text_edit", &self.text_edit); - } - if let Some(kind) = self.kind().as_ref() { - s.field("kind", kind); - } - if self.lookup() != self.label() { - s.field("lookup", &self.lookup()); - } - if let Some(detail) = self.detail() { - s.field("detail", &detail); - } - if let Some(documentation) = self.documentation() { - s.field("documentation", &documentation); - } - if self.deprecated { - s.field("deprecated", &true); - } - s.finish() - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum CompletionItemKind { - Snippet, - Keyword, - Module, - Function, - BuiltinType, - Struct, - Enum, - EnumVariant, - Binding, - Field, - Static, - Const, - Trait, - TypeAlias, - Method, - TypeParam, - Macro, -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub(crate) enum CompletionKind { - /// Parser-based keyword completion. - Keyword, - /// Your usual "complete all valid identifiers". - Reference, - /// "Secret sauce" completions. - Magic, - Snippet, - Postfix, - BuiltinType, -} - -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -pub enum InsertTextFormat { - PlainText, - Snippet, -} - -impl CompletionItem { - pub(crate) fn new( - completion_kind: CompletionKind, - source_range: TextRange, - label: impl Into, - ) -> Builder { - let label = label.into(); - Builder { - source_range, - completion_kind, - label, - insert_text: None, - insert_text_format: InsertTextFormat::PlainText, - detail: None, - documentation: None, - lookup: None, - kind: None, - text_edit: None, - deprecated: None, - } - } - /// What user sees in pop-up in the UI. - pub fn label(&self) -> &str { - &self.label - } - pub fn source_range(&self) -> TextRange { - self.source_range - } - - pub fn insert_text_format(&self) -> InsertTextFormat { - self.insert_text_format - } - - pub fn text_edit(&self) -> &TextEdit { - &self.text_edit - } - - /// Short one-line additional information, like a type - pub fn detail(&self) -> Option<&str> { - self.detail.as_ref().map(|it| it.as_str()) - } - /// A doc-comment - pub fn documentation(&self) -> Option { - self.documentation.clone() - } - /// What string is used for filtering. - pub fn lookup(&self) -> &str { - self.lookup.as_ref().map(|it| it.as_str()).unwrap_or_else(|| self.label()) - } - - pub fn kind(&self) -> Option { - self.kind - } - - pub fn deprecated(&self) -> bool { - self.deprecated - } -} - -/// A helper to make `CompletionItem`s. -#[must_use] -pub(crate) struct Builder { - source_range: TextRange, - completion_kind: CompletionKind, - label: String, - insert_text: Option, - insert_text_format: InsertTextFormat, - detail: Option, - documentation: Option, - lookup: Option, - kind: Option, - text_edit: Option, - deprecated: Option, -} - -impl Builder { - pub(crate) fn add_to(self, acc: &mut Completions) { - acc.add(self.build()) - } - - pub(crate) fn build(self) -> CompletionItem { - let label = self.label; - let text_edit = match self.text_edit { - Some(it) => it, - None => TextEdit::replace( - self.source_range, - self.insert_text.unwrap_or_else(|| label.clone()), - ), - }; - - CompletionItem { - source_range: self.source_range, - label, - insert_text_format: self.insert_text_format, - text_edit, - detail: self.detail, - documentation: self.documentation, - lookup: self.lookup, - kind: self.kind, - completion_kind: self.completion_kind, - deprecated: self.deprecated.unwrap_or(false), - } - } - pub(crate) fn lookup_by(mut self, lookup: impl Into) -> Builder { - self.lookup = Some(lookup.into()); - self - } - pub(crate) fn label(mut self, label: impl Into) -> Builder { - self.label = label.into(); - self - } - pub(crate) fn insert_text(mut self, insert_text: impl Into) -> Builder { - self.insert_text = Some(insert_text.into()); - self - } - pub(crate) fn insert_snippet(mut self, snippet: impl Into) -> Builder { - self.insert_text_format = InsertTextFormat::Snippet; - self.insert_text(snippet) - } - pub(crate) fn kind(mut self, kind: CompletionItemKind) -> Builder { - self.kind = Some(kind); - self - } - pub(crate) fn text_edit(mut self, edit: TextEdit) -> Builder { - self.text_edit = Some(edit); - self - } - pub(crate) fn snippet_edit(mut self, edit: TextEdit) -> Builder { - self.insert_text_format = InsertTextFormat::Snippet; - self.text_edit(edit) - } - #[allow(unused)] - pub(crate) fn detail(self, detail: impl Into) -> Builder { - self.set_detail(Some(detail)) - } - pub(crate) fn set_detail(mut self, detail: Option>) -> Builder { - self.detail = detail.map(Into::into); - self - } - #[allow(unused)] - pub(crate) fn documentation(self, docs: Documentation) -> Builder { - self.set_documentation(Some(docs)) - } - pub(crate) fn set_documentation(mut self, docs: Option) -> Builder { - self.documentation = docs.map(Into::into); - self - } - pub(crate) fn set_deprecated(mut self, deprecated: bool) -> Builder { - self.deprecated = Some(deprecated); - self - } -} - -impl<'a> Into for Builder { - fn into(self) -> CompletionItem { - self.build() - } -} - -/// Represents an in-progress set of completions being built. -#[derive(Debug, Default)] -pub(crate) struct Completions { - buf: Vec, -} - -impl Completions { - pub(crate) fn add(&mut self, item: impl Into) { - self.buf.push(item.into()) - } - pub(crate) fn add_all(&mut self, items: I) - where - I: IntoIterator, - I::Item: Into, - { - items.into_iter().for_each(|item| self.add(item.into())) - } -} - -impl Into> for Completions { - fn into(self) -> Vec { - self.buf - } -} - -#[cfg(test)] -pub(crate) fn do_completion(code: &str, kind: CompletionKind) -> Vec { - use crate::completion::completions; - use crate::mock_analysis::{analysis_and_position, single_file_with_position}; - let (analysis, position) = if code.contains("//-") { - analysis_and_position(code) - } else { - single_file_with_position(code) - }; - let completions = completions(&analysis.db, position).unwrap(); - let completion_items: Vec = completions.into(); - let mut kind_completions: Vec = - completion_items.into_iter().filter(|c| c.completion_kind == kind).collect(); - kind_completions.sort_by_key(|c| c.label.clone()); - kind_completions -} diff --git a/crates/ra_ide_api/src/completion/presentation.rs b/crates/ra_ide_api/src/completion/presentation.rs deleted file mode 100644 index 5f056730a..000000000 --- a/crates/ra_ide_api/src/completion/presentation.rs +++ /dev/null @@ -1,676 +0,0 @@ -//! This modules takes care of rendering various definitions as completion items. - -use hir::{db::HirDatabase, Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, Type}; -use join_to_string::join; -use ra_syntax::ast::NameOwner; -use test_utils::tested_by; - -use crate::completion::{ - CompletionContext, CompletionItem, CompletionItemKind, CompletionKind, Completions, -}; - -use crate::display::{const_label, function_label, macro_label, type_label}; - -impl Completions { - pub(crate) fn add_field( - &mut self, - ctx: &CompletionContext, - field: hir::StructField, - ty: &Type, - ) { - let is_deprecated = is_deprecated(field, ctx.db); - CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - field.name(ctx.db).to_string(), - ) - .kind(CompletionItemKind::Field) - .detail(ty.display(ctx.db).to_string()) - .set_documentation(field.docs(ctx.db)) - .set_deprecated(is_deprecated) - .add_to(self); - } - - pub(crate) fn add_tuple_field(&mut self, ctx: &CompletionContext, field: usize, ty: &Type) { - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), field.to_string()) - .kind(CompletionItemKind::Field) - .detail(ty.display(ctx.db).to_string()) - .add_to(self); - } - - pub(crate) fn add_resolution( - &mut self, - ctx: &CompletionContext, - local_name: String, - resolution: &ScopeDef, - ) { - use hir::ModuleDef::*; - - let completion_kind = match resolution { - ScopeDef::ModuleDef(BuiltinType(..)) => CompletionKind::BuiltinType, - _ => CompletionKind::Reference, - }; - - let kind = match resolution { - ScopeDef::ModuleDef(Module(..)) => CompletionItemKind::Module, - ScopeDef::ModuleDef(Function(func)) => { - return self.add_function_with_name(ctx, Some(local_name), *func); - } - ScopeDef::ModuleDef(Adt(hir::Adt::Struct(_))) => CompletionItemKind::Struct, - // FIXME: add CompletionItemKind::Union - ScopeDef::ModuleDef(Adt(hir::Adt::Union(_))) => CompletionItemKind::Struct, - ScopeDef::ModuleDef(Adt(hir::Adt::Enum(_))) => CompletionItemKind::Enum, - - ScopeDef::ModuleDef(EnumVariant(..)) => CompletionItemKind::EnumVariant, - ScopeDef::ModuleDef(Const(..)) => CompletionItemKind::Const, - ScopeDef::ModuleDef(Static(..)) => CompletionItemKind::Static, - ScopeDef::ModuleDef(Trait(..)) => CompletionItemKind::Trait, - ScopeDef::ModuleDef(TypeAlias(..)) => CompletionItemKind::TypeAlias, - ScopeDef::ModuleDef(BuiltinType(..)) => CompletionItemKind::BuiltinType, - ScopeDef::GenericParam(..) => CompletionItemKind::TypeParam, - ScopeDef::Local(..) => CompletionItemKind::Binding, - // (does this need its own kind?) - ScopeDef::AdtSelfType(..) | ScopeDef::ImplSelfType(..) => CompletionItemKind::TypeParam, - ScopeDef::MacroDef(mac) => { - return self.add_macro(ctx, Some(local_name), *mac); - } - ScopeDef::Unknown => { - return self.add(CompletionItem::new( - CompletionKind::Reference, - ctx.source_range(), - local_name, - )); - } - }; - - let docs = match resolution { - ScopeDef::ModuleDef(Module(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Adt(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(EnumVariant(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Const(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Static(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(Trait(it)) => it.docs(ctx.db), - ScopeDef::ModuleDef(TypeAlias(it)) => it.docs(ctx.db), - _ => None, - }; - - let mut completion_item = - CompletionItem::new(completion_kind, ctx.source_range(), local_name.clone()); - if let ScopeDef::Local(local) = resolution { - let ty = local.ty(ctx.db); - if !ty.is_unknown() { - completion_item = completion_item.detail(ty.display(ctx.db).to_string()); - } - }; - - // If not an import, add parenthesis automatically. - if ctx.is_path_type - && !ctx.has_type_args - && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") - { - let has_non_default_type_params = match resolution { - ScopeDef::ModuleDef(Adt(it)) => it.has_non_default_type_params(ctx.db), - ScopeDef::ModuleDef(TypeAlias(it)) => it.has_non_default_type_params(ctx.db), - _ => false, - }; - if has_non_default_type_params { - tested_by!(inserts_angle_brackets_for_generics); - completion_item = completion_item - .lookup_by(local_name.clone()) - .label(format!("{}<…>", local_name)) - .insert_snippet(format!("{}<$0>", local_name)); - } - } - - completion_item.kind(kind).set_documentation(docs).add_to(self) - } - - pub(crate) fn add_function(&mut self, ctx: &CompletionContext, func: hir::Function) { - self.add_function_with_name(ctx, None, func) - } - - fn guess_macro_braces(&self, macro_name: &str, docs: &str) -> &'static str { - let mut votes = [0, 0, 0]; - for (idx, s) in docs.match_indices(¯o_name) { - let (before, after) = (&docs[..idx], &docs[idx + s.len()..]); - // Ensure to match the full word - if after.starts_with('!') - && before - .chars() - .rev() - .next() - .map_or(true, |c| c != '_' && !c.is_ascii_alphanumeric()) - { - // It may have spaces before the braces like `foo! {}` - match after[1..].chars().find(|&c| !c.is_whitespace()) { - Some('{') => votes[0] += 1, - Some('[') => votes[1] += 1, - Some('(') => votes[2] += 1, - _ => {} - } - } - } - - // Insert a space before `{}`. - // We prefer the last one when some votes equal. - *votes.iter().zip(&[" {$0}", "[$0]", "($0)"]).max_by_key(|&(&vote, _)| vote).unwrap().1 - } - - pub(crate) fn add_macro( - &mut self, - ctx: &CompletionContext, - name: Option, - macro_: hir::MacroDef, - ) { - let name = match name { - Some(it) => it, - None => return, - }; - - let ast_node = macro_.source(ctx.db).value; - let detail = macro_label(&ast_node); - - let docs = macro_.docs(ctx.db); - let macro_declaration = format!("{}!", name); - - let mut builder = - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), ¯o_declaration) - .kind(CompletionItemKind::Macro) - .set_documentation(docs.clone()) - .set_deprecated(is_deprecated(macro_, ctx.db)) - .detail(detail); - - builder = if ctx.use_item_syntax.is_some() { - builder.insert_text(name) - } else { - let macro_braces_to_insert = - self.guess_macro_braces(&name, docs.as_ref().map_or("", |s| s.as_str())); - builder.insert_snippet(macro_declaration + macro_braces_to_insert) - }; - - self.add(builder); - } - - fn add_function_with_name( - &mut self, - ctx: &CompletionContext, - name: Option, - func: hir::Function, - ) { - let func_name = func.name(ctx.db); - let has_self_param = func.has_self_param(ctx.db); - let params = func.params(ctx.db); - - let name = name.unwrap_or_else(|| func_name.to_string()); - let ast_node = func.source(ctx.db).value; - let detail = function_label(&ast_node); - - let mut builder = - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) - .kind(if has_self_param { - CompletionItemKind::Method - } else { - CompletionItemKind::Function - }) - .set_documentation(func.docs(ctx.db)) - .set_deprecated(is_deprecated(func, ctx.db)) - .detail(detail); - - // Add `<>` for generic types - if ctx.use_item_syntax.is_none() - && !ctx.is_call - && ctx.db.feature_flags.get("completion.insertion.add-call-parenthesis") - { - tested_by!(inserts_parens_for_function_calls); - let (snippet, label) = if params.is_empty() || has_self_param && params.len() == 1 { - (format!("{}()$0", func_name), format!("{}()", name)) - } else { - (format!("{}($0)", func_name), format!("{}(…)", name)) - }; - builder = builder.lookup_by(name).label(label).insert_snippet(snippet); - } - - self.add(builder) - } - - pub(crate) fn add_const(&mut self, ctx: &CompletionContext, constant: hir::Const) { - let ast_node = constant.source(ctx.db).value; - let name = match ast_node.name() { - Some(name) => name, - _ => return, - }; - let detail = const_label(&ast_node); - - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) - .kind(CompletionItemKind::Const) - .set_documentation(constant.docs(ctx.db)) - .set_deprecated(is_deprecated(constant, ctx.db)) - .detail(detail) - .add_to(self); - } - - pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir::TypeAlias) { - let type_def = type_alias.source(ctx.db).value; - let name = match type_def.name() { - Some(name) => name, - _ => return, - }; - let detail = type_label(&type_def); - - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.text().to_string()) - .kind(CompletionItemKind::TypeAlias) - .set_documentation(type_alias.docs(ctx.db)) - .set_deprecated(is_deprecated(type_alias, ctx.db)) - .detail(detail) - .add_to(self); - } - - pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) { - let is_deprecated = is_deprecated(variant, ctx.db); - let name = match variant.name(ctx.db) { - Some(it) => it, - None => return, - }; - let detail_types = variant.fields(ctx.db).into_iter().map(|field| field.ty(ctx.db)); - let detail = join(detail_types.map(|t| t.display(ctx.db).to_string())) - .separator(", ") - .surround_with("(", ")") - .to_string(); - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.to_string()) - .kind(CompletionItemKind::EnumVariant) - .set_documentation(variant.docs(ctx.db)) - .set_deprecated(is_deprecated) - .detail(detail) - .add_to(self); - } -} - -fn is_deprecated(node: impl HasAttrs, db: &impl HirDatabase) -> bool { - node.attrs(db).by_key("deprecated").exists() -} - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - use test_utils::covers; - - use crate::completion::{do_completion, CompletionItem, CompletionKind}; - - fn do_reference_completion(code: &str) -> Vec { - do_completion(code, CompletionKind::Reference) - } - - #[test] - fn sets_deprecated_flag_in_completion_items() { - assert_debug_snapshot!( - do_reference_completion( - r#" - #[deprecated] - fn something_deprecated() {} - - #[deprecated(since = "1.0.0")] - fn something_else_deprecated() {} - - fn main() { som<|> } - "#, - ), - @r###" - [ - CompletionItem { - label: "main()", - source_range: [203; 206), - delete: [203; 206), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - CompletionItem { - label: "something_deprecated()", - source_range: [203; 206), - delete: [203; 206), - insert: "something_deprecated()$0", - kind: Function, - lookup: "something_deprecated", - detail: "fn something_deprecated()", - deprecated: true, - }, - CompletionItem { - label: "something_else_deprecated()", - source_range: [203; 206), - delete: [203; 206), - insert: "something_else_deprecated()$0", - kind: Function, - lookup: "something_else_deprecated", - detail: "fn something_else_deprecated()", - deprecated: true, - }, - ] - "### - ); - } - - #[test] - fn inserts_parens_for_function_calls() { - covers!(inserts_parens_for_function_calls); - assert_debug_snapshot!( - do_reference_completion( - r" - fn no_args() {} - fn main() { no_<|> } - " - ), - @r###" - [ - CompletionItem { - label: "main()", - source_range: [61; 64), - delete: [61; 64), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - CompletionItem { - label: "no_args()", - source_range: [61; 64), - delete: [61; 64), - insert: "no_args()$0", - kind: Function, - lookup: "no_args", - detail: "fn no_args()", - }, - ] - "### - ); - assert_debug_snapshot!( - do_reference_completion( - r" - fn with_args(x: i32, y: String) {} - fn main() { with_<|> } - " - ), - @r###" - [ - CompletionItem { - label: "main()", - source_range: [80; 85), - delete: [80; 85), - insert: "main()$0", - kind: Function, - lookup: "main", - detail: "fn main()", - }, - CompletionItem { - label: "with_args(…)", - source_range: [80; 85), - delete: [80; 85), - insert: "with_args($0)", - kind: Function, - lookup: "with_args", - detail: "fn with_args(x: i32, y: String)", - }, - ] - "### - ); - assert_debug_snapshot!( - do_reference_completion( - r" - struct S {} - impl S { - fn foo(&self) {} - } - fn bar(s: &S) { - s.f<|> - } - " - ), - @r###" - [ - CompletionItem { - label: "foo()", - source_range: [163; 164), - delete: [163; 164), - insert: "foo()$0", - kind: Method, - lookup: "foo", - detail: "fn foo(&self)", - }, - ] - "### - ); - } - - #[test] - fn dont_render_function_parens_in_use_item() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - mod m { pub fn foo() {} } - use crate::m::f<|>; - " - ), - @r###" - [ - CompletionItem { - label: "foo", - source_range: [40; 41), - delete: [40; 41), - insert: "foo", - kind: Function, - detail: "pub fn foo()", - }, - ] - "### - ); - } - - #[test] - fn dont_render_function_parens_if_already_call() { - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - fn frobnicate() {} - fn main() { - frob<|>(); - } - " - ), - @r###" - [ - CompletionItem { - label: "frobnicate", - source_range: [35; 39), - delete: [35; 39), - insert: "frobnicate", - kind: Function, - detail: "fn frobnicate()", - }, - CompletionItem { - label: "main", - source_range: [35; 39), - delete: [35; 39), - insert: "main", - kind: Function, - detail: "fn main()", - }, - ] - "### - ); - assert_debug_snapshot!( - do_reference_completion( - " - //- /lib.rs - struct Foo {} - impl Foo { fn new() -> Foo {} } - fn main() { - Foo::ne<|>(); - } - " - ), - @r###" - [ - CompletionItem { - label: "new", - source_range: [67; 69), - delete: [67; 69), - insert: "new", - kind: Function, - detail: "fn new() -> Foo", - }, - ] - "### - ); - } - - #[test] - fn inserts_angle_brackets_for_generics() { - covers!(inserts_angle_brackets_for_generics); - assert_debug_snapshot!( - do_reference_completion( - r" - struct Vec {} - fn foo(xs: Ve<|>) - " - ), - @r###" - [ - CompletionItem { - label: "Vec<…>", - source_range: [61; 63), - delete: [61; 63), - insert: "Vec<$0>", - kind: Struct, - lookup: "Vec", - }, - CompletionItem { - label: "foo(…)", - source_range: [61; 63), - delete: [61; 63), - insert: "foo($0)", - kind: Function, - lookup: "foo", - detail: "fn foo(xs: Ve)", - }, - ] - "### - ); - assert_debug_snapshot!( - do_reference_completion( - r" - type Vec = (T,); - fn foo(xs: Ve<|>) - " - ), - @r###" - [ - CompletionItem { - label: "Vec<…>", - source_range: [64; 66), - delete: [64; 66), - insert: "Vec<$0>", - kind: TypeAlias, - lookup: "Vec", - }, - CompletionItem { - label: "foo(…)", - source_range: [64; 66), - delete: [64; 66), - insert: "foo($0)", - kind: Function, - lookup: "foo", - detail: "fn foo(xs: Ve)", - }, - ] - "### - ); - assert_debug_snapshot!( - do_reference_completion( - r" - struct Vec {} - fn foo(xs: Ve<|>) - " - ), - @r###" - [ - CompletionItem { - label: "Vec", - source_range: [68; 70), - delete: [68; 70), - insert: "Vec", - kind: Struct, - }, - CompletionItem { - label: "foo(…)", - source_range: [68; 70), - delete: [68; 70), - insert: "foo($0)", - kind: Function, - lookup: "foo", - detail: "fn foo(xs: Ve)", - }, - ] - "### - ); - assert_debug_snapshot!( - do_reference_completion( - r" - struct Vec {} - fn foo(xs: Ve<|>) - " - ), - @r###" - [ - CompletionItem { - label: "Vec", - source_range: [61; 63), - delete: [61; 63), - insert: "Vec", - kind: Struct, - }, - CompletionItem { - label: "foo(…)", - source_range: [61; 63), - delete: [61; 63), - insert: "foo($0)", - kind: Function, - lookup: "foo", - detail: "fn foo(xs: Ve)", - }, - ] - "### - ); - } - - #[test] - fn dont_insert_macro_call_braces_in_use() { - assert_debug_snapshot!( - do_reference_completion( - r" - //- /main.rs - use foo::<|>; - - //- /foo/lib.rs - #[macro_export] - macro_rules frobnicate { - () => () - } - " - ), - @r###" - [ - CompletionItem { - label: "frobnicate!", - source_range: [9; 9), - delete: [9; 9), - insert: "frobnicate", - kind: Macro, - detail: "#[macro_export]\nmacro_rules! frobnicate", - }, - ] - "### - ) - } -} diff --git a/crates/ra_ide_api/src/db.rs b/crates/ra_ide_api/src/db.rs deleted file mode 100644 index f739ebecd..000000000 --- a/crates/ra_ide_api/src/db.rs +++ /dev/null @@ -1,144 +0,0 @@ -//! FIXME: write short doc here - -use std::sync::Arc; - -use ra_db::{ - salsa::{self, Database, Durability}, - Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, - SourceDatabase, SourceDatabaseExt, SourceRootId, -}; -use rustc_hash::FxHashMap; - -use crate::{ - symbol_index::{self, SymbolsDatabase}, - FeatureFlags, LineIndex, -}; - -#[salsa::database( - ra_db::SourceDatabaseStorage, - ra_db::SourceDatabaseExtStorage, - LineIndexDatabaseStorage, - symbol_index::SymbolsDatabaseStorage, - hir::db::InternDatabaseStorage, - hir::db::AstDatabaseStorage, - hir::db::DefDatabaseStorage, - hir::db::HirDatabaseStorage -)] -#[derive(Debug)] -pub(crate) struct RootDatabase { - runtime: salsa::Runtime, - pub(crate) feature_flags: Arc, - pub(crate) debug_data: Arc, - pub(crate) last_gc: crate::wasm_shims::Instant, - pub(crate) last_gc_check: crate::wasm_shims::Instant, -} - -impl FileLoader for RootDatabase { - fn file_text(&self, file_id: FileId) -> Arc { - FileLoaderDelegate(self).file_text(file_id) - } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) - } - fn relevant_crates(&self, file_id: FileId) -> Arc> { - FileLoaderDelegate(self).relevant_crates(file_id) - } -} - -impl hir::debug::HirDebugHelper for RootDatabase { - fn crate_name(&self, krate: CrateId) -> Option { - self.debug_data.crate_names.get(&krate).cloned() - } - fn file_path(&self, file_id: FileId) -> Option { - let source_root_id = self.file_source_root(file_id); - let source_root_path = self.debug_data.root_paths.get(&source_root_id)?; - let file_path = self.file_relative_path(file_id); - Some(format!("{}/{}", source_root_path, file_path)) - } -} - -impl salsa::Database for RootDatabase { - fn salsa_runtime(&self) -> &salsa::Runtime { - &self.runtime - } - fn salsa_runtime_mut(&mut self) -> &mut salsa::Runtime { - &mut self.runtime - } - fn on_propagated_panic(&self) -> ! { - Canceled::throw() - } - fn salsa_event(&self, event: impl Fn() -> salsa::Event) { - match event().kind { - salsa::EventKind::DidValidateMemoizedValue { .. } - | salsa::EventKind::WillExecute { .. } => { - self.check_canceled(); - } - _ => (), - } - } -} - -impl Default for RootDatabase { - fn default() -> RootDatabase { - RootDatabase::new(None, FeatureFlags::default()) - } -} - -impl RootDatabase { - pub fn new(lru_capacity: Option, feature_flags: FeatureFlags) -> RootDatabase { - let mut db = RootDatabase { - runtime: salsa::Runtime::default(), - last_gc: crate::wasm_shims::Instant::now(), - last_gc_check: crate::wasm_shims::Instant::now(), - feature_flags: Arc::new(feature_flags), - debug_data: Default::default(), - }; - db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); - db.set_local_roots_with_durability(Default::default(), Durability::HIGH); - db.set_library_roots_with_durability(Default::default(), Durability::HIGH); - let lru_capacity = lru_capacity.unwrap_or(ra_db::DEFAULT_LRU_CAP); - db.query_mut(ra_db::ParseQuery).set_lru_capacity(lru_capacity); - db.query_mut(hir::db::ParseMacroQuery).set_lru_capacity(lru_capacity); - db.query_mut(hir::db::MacroExpandQuery).set_lru_capacity(lru_capacity); - db - } -} - -impl salsa::ParallelDatabase for RootDatabase { - fn snapshot(&self) -> salsa::Snapshot { - salsa::Snapshot::new(RootDatabase { - runtime: self.runtime.snapshot(self), - last_gc: self.last_gc, - last_gc_check: self.last_gc_check, - feature_flags: Arc::clone(&self.feature_flags), - debug_data: Arc::clone(&self.debug_data), - }) - } -} - -#[salsa::query_group(LineIndexDatabaseStorage)] -pub(crate) trait LineIndexDatabase: ra_db::SourceDatabase + CheckCanceled { - fn line_index(&self, file_id: FileId) -> Arc; -} - -fn line_index(db: &impl LineIndexDatabase, file_id: FileId) -> Arc { - let text = db.file_text(file_id); - Arc::new(LineIndex::new(&*text)) -} - -#[derive(Debug, Default, Clone)] -pub(crate) struct DebugData { - pub(crate) root_paths: FxHashMap, - pub(crate) crate_names: FxHashMap, -} - -impl DebugData { - pub(crate) fn merge(&mut self, other: DebugData) { - self.root_paths.extend(other.root_paths.into_iter()); - self.crate_names.extend(other.crate_names.into_iter()); - } -} diff --git a/crates/ra_ide_api/src/diagnostics.rs b/crates/ra_ide_api/src/diagnostics.rs deleted file mode 100644 index cc1ccab4b..000000000 --- a/crates/ra_ide_api/src/diagnostics.rs +++ /dev/null @@ -1,652 +0,0 @@ -//! FIXME: write short doc here - -use std::cell::RefCell; - -use hir::diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}; -use itertools::Itertools; -use ra_db::{RelativePath, SourceDatabase, SourceDatabaseExt}; -use ra_prof::profile; -use ra_syntax::{ - algo, - ast::{self, make, AstNode}, - Location, SyntaxNode, TextRange, T, -}; -use ra_text_edit::{TextEdit, TextEditBuilder}; - -use crate::{db::RootDatabase, Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit}; - -#[derive(Debug, Copy, Clone)] -pub enum Severity { - Error, - WeakWarning, -} - -pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec { - let _p = profile("diagnostics"); - let parse = db.parse(file_id); - let mut res = Vec::new(); - - res.extend(parse.errors().iter().map(|err| Diagnostic { - range: location_to_range(err.location()), - message: format!("Syntax Error: {}", err), - severity: Severity::Error, - fix: None, - })); - - for node in parse.tree().syntax().descendants() { - check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); - check_struct_shorthand_initialization(&mut res, file_id, &node); - } - let res = RefCell::new(res); - let mut sink = DiagnosticSink::new(|d| { - res.borrow_mut().push(Diagnostic { - message: d.message(), - range: d.highlight_range(), - severity: Severity::Error, - fix: None, - }) - }) - .on::(|d| { - let original_file = d.source().file_id.original_file(db); - let source_root = db.file_source_root(original_file); - let path = db - .file_relative_path(original_file) - .parent() - .unwrap_or_else(|| RelativePath::new("")) - .join(&d.candidate); - let create_file = FileSystemEdit::CreateFile { source_root, path }; - let fix = SourceChange::file_system_edit("create module", create_file); - res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), - message: d.message(), - severity: Severity::Error, - fix: Some(fix), - }) - }) - .on::(|d| { - let mut field_list = d.ast(db); - for f in d.missed_fields.iter() { - let field = make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); - field_list = field_list.append_field(&field); - } - - let mut builder = TextEditBuilder::default(); - algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); - - let fix = - SourceChange::source_file_edit_from("fill struct fields", file_id, builder.finish()); - res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), - message: d.message(), - severity: Severity::Error, - fix: Some(fix), - }) - }) - .on::(|d| { - let node = d.ast(db); - let replacement = format!("Ok({})", node.syntax()); - let edit = TextEdit::replace(node.syntax().text_range(), replacement); - let fix = SourceChange::source_file_edit_from("wrap with ok", file_id, edit); - res.borrow_mut().push(Diagnostic { - range: d.highlight_range(), - message: d.message(), - severity: Severity::Error, - fix: Some(fix), - }) - }); - let source_file = db.parse(file_id).tree(); - let src = - hir::Source { file_id: file_id.into(), value: hir::ModuleSource::SourceFile(source_file) }; - if let Some(m) = hir::Module::from_definition(db, src) { - m.diagnostics(db, &mut sink); - }; - drop(sink); - res.into_inner() -} -fn location_to_range(location: Location) -> TextRange { - match location { - Location::Offset(offset) => TextRange::offset_len(offset, 1.into()), - Location::Range(range) => range, - } -} - -fn check_unnecessary_braces_in_use_statement( - acc: &mut Vec, - file_id: FileId, - node: &SyntaxNode, -) -> Option<()> { - let use_tree_list = ast::UseTreeList::cast(node.clone())?; - if let Some((single_use_tree,)) = use_tree_list.use_trees().collect_tuple() { - let range = use_tree_list.syntax().text_range(); - let edit = - text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(&single_use_tree) - .unwrap_or_else(|| { - let to_replace = single_use_tree.syntax().text().to_string(); - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(range); - edit_builder.insert(range.start(), to_replace); - edit_builder.finish() - }); - - acc.push(Diagnostic { - range, - message: "Unnecessary braces in use statement".to_string(), - severity: Severity::WeakWarning, - fix: Some(SourceChange::source_file_edit( - "Remove unnecessary braces", - SourceFileEdit { file_id, edit }, - )), - }); - } - - Some(()) -} - -fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( - single_use_tree: &ast::UseTree, -) -> Option { - let use_tree_list_node = single_use_tree.syntax().parent()?; - if single_use_tree.path()?.segment()?.syntax().first_child_or_token()?.kind() == T![self] { - let start = use_tree_list_node.prev_sibling_or_token()?.text_range().start(); - let end = use_tree_list_node.text_range().end(); - let range = TextRange::from_to(start, end); - return Some(TextEdit::delete(range)); - } - None -} - -fn check_struct_shorthand_initialization( - acc: &mut Vec, - file_id: FileId, - node: &SyntaxNode, -) -> Option<()> { - let record_lit = ast::RecordLit::cast(node.clone())?; - let record_field_list = record_lit.record_field_list()?; - for record_field in record_field_list.fields() { - if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { - let field_name = name_ref.syntax().text().to_string(); - let field_expr = expr.syntax().text().to_string(); - if field_name == field_expr { - let mut edit_builder = TextEditBuilder::default(); - edit_builder.delete(record_field.syntax().text_range()); - edit_builder.insert(record_field.syntax().text_range().start(), field_name); - let edit = edit_builder.finish(); - - acc.push(Diagnostic { - range: record_field.syntax().text_range(), - message: "Shorthand struct initialization".to_string(), - severity: Severity::WeakWarning, - fix: Some(SourceChange::source_file_edit( - "use struct shorthand initialization", - SourceFileEdit { file_id, edit }, - )), - }); - } - } - } - Some(()) -} - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - use join_to_string::join; - use ra_syntax::SourceFile; - use test_utils::assert_eq_text; - - use crate::mock_analysis::{analysis_and_position, single_file}; - - use super::*; - - type DiagnosticChecker = fn(&mut Vec, FileId, &SyntaxNode) -> Option<()>; - - fn check_not_applicable(code: &str, func: DiagnosticChecker) { - let parse = SourceFile::parse(code); - let mut diagnostics = Vec::new(); - for node in parse.tree().syntax().descendants() { - func(&mut diagnostics, FileId(0), &node); - } - assert!(diagnostics.is_empty()); - } - - fn check_apply(before: &str, after: &str, func: DiagnosticChecker) { - let parse = SourceFile::parse(before); - let mut diagnostics = Vec::new(); - for node in parse.tree().syntax().descendants() { - func(&mut diagnostics, FileId(0), &node); - } - let diagnostic = - diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before)); - let mut fix = diagnostic.fix.unwrap(); - let edit = fix.source_file_edits.pop().unwrap().edit; - let actual = edit.apply(&before); - assert_eq_text!(after, &actual); - } - - /// Takes a multi-file input fixture with annotated cursor positions, - /// and checks that: - /// * a diagnostic is produced - /// * this diagnostic touches the input cursor position - /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied - fn check_apply_diagnostic_fix_from_position(fixture: &str, after: &str) { - let (analysis, file_position) = analysis_and_position(fixture); - let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); - let mut fix = diagnostic.fix.unwrap(); - let edit = fix.source_file_edits.pop().unwrap().edit; - let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); - let actual = edit.apply(&target_file_contents); - - // Strip indent and empty lines from `after`, to match the behaviour of - // `parse_fixture` called from `analysis_and_position`. - let margin = fixture - .lines() - .filter(|it| it.trim_start().starts_with("//-")) - .map(|it| it.len() - it.trim_start().len()) - .next() - .expect("empty fixture"); - let after = join(after.lines().filter_map(|line| { - if line.len() > margin { - Some(&line[margin..]) - } else { - None - } - })) - .separator("\n") - .suffix("\n") - .to_string(); - - assert_eq_text!(&after, &actual); - assert!( - diagnostic.range.start() <= file_position.offset - && diagnostic.range.end() >= file_position.offset, - "diagnostic range {} does not touch cursor position {}", - diagnostic.range, - file_position.offset - ); - } - - fn check_apply_diagnostic_fix(before: &str, after: &str) { - let (analysis, file_id) = single_file(before); - let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap(); - let mut fix = diagnostic.fix.unwrap(); - let edit = fix.source_file_edits.pop().unwrap().edit; - let actual = edit.apply(&before); - assert_eq_text!(after, &actual); - } - - /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics - /// apply to the file containing the cursor. - fn check_no_diagnostic_for_target_file(fixture: &str) { - let (analysis, file_position) = analysis_and_position(fixture); - let diagnostics = analysis.diagnostics(file_position.file_id).unwrap(); - assert_eq!(diagnostics.len(), 0); - } - - fn check_no_diagnostic(content: &str) { - let (analysis, file_id) = single_file(content); - let diagnostics = analysis.diagnostics(file_id).unwrap(); - assert_eq!(diagnostics.len(), 0); - } - - #[test] - fn test_wrap_return_type() { - let before = r#" - //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; - - fn div(x: i32, y: i32) -> Result { - if y == 0 { - return Err("div by zero".into()); - } - x / y<|> - } - - //- /std/lib.rs - pub mod string { - pub struct String { } - } - pub mod result { - pub enum Result { Ok(T), Err(E) } - } - "#; - let after = r#" - use std::{string::String, result::Result::{self, Ok, Err}}; - - fn div(x: i32, y: i32) -> Result { - if y == 0 { - return Err("div by zero".into()); - } - Ok(x / y) - } - "#; - check_apply_diagnostic_fix_from_position(before, after); - } - - #[test] - fn test_wrap_return_type_handles_generic_functions() { - let before = r#" - //- /main.rs - use std::result::Result::{self, Ok, Err}; - - fn div(x: T) -> Result { - if x == 0 { - return Err(7); - } - <|>x - } - - //- /std/lib.rs - pub mod result { - pub enum Result { Ok(T), Err(E) } - } - "#; - let after = r#" - use std::result::Result::{self, Ok, Err}; - - fn div(x: T) -> Result { - if x == 0 { - return Err(7); - } - Ok(x) - } - "#; - check_apply_diagnostic_fix_from_position(before, after); - } - - #[test] - fn test_wrap_return_type_handles_type_aliases() { - let before = r#" - //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; - - type MyResult = Result; - - fn div(x: i32, y: i32) -> MyResult { - if y == 0 { - return Err("div by zero".into()); - } - x <|>/ y - } - - //- /std/lib.rs - pub mod string { - pub struct String { } - } - pub mod result { - pub enum Result { Ok(T), Err(E) } - } - "#; - let after = r#" - use std::{string::String, result::Result::{self, Ok, Err}}; - - type MyResult = Result; - fn div(x: i32, y: i32) -> MyResult { - if y == 0 { - return Err("div by zero".into()); - } - Ok(x / y) - } - "#; - check_apply_diagnostic_fix_from_position(before, after); - } - - #[test] - fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { - let content = r#" - //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; - - fn foo() -> Result { - 0<|> - } - - //- /std/lib.rs - pub mod string { - pub struct String { } - } - pub mod result { - pub enum Result { Ok(T), Err(E) } - } - "#; - check_no_diagnostic_for_target_file(content); - } - - #[test] - fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { - let content = r#" - //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; - - enum SomeOtherEnum { - Ok(i32), - Err(String), - } - - fn foo() -> SomeOtherEnum { - 0<|> - } - - //- /std/lib.rs - pub mod string { - pub struct String { } - } - pub mod result { - pub enum Result { Ok(T), Err(E) } - } - "#; - check_no_diagnostic_for_target_file(content); - } - - #[test] - fn test_fill_struct_fields_empty() { - let before = r" - struct TestStruct { - one: i32, - two: i64, - } - - fn test_fn() { - let s = TestStruct{}; - } - "; - let after = r" - struct TestStruct { - one: i32, - two: i64, - } - - fn test_fn() { - let s = TestStruct{ one: (), two: ()}; - } - "; - check_apply_diagnostic_fix(before, after); - } - - #[test] - fn test_fill_struct_fields_partial() { - let before = r" - struct TestStruct { - one: i32, - two: i64, - } - - fn test_fn() { - let s = TestStruct{ two: 2 }; - } - "; - let after = r" - struct TestStruct { - one: i32, - two: i64, - } - - fn test_fn() { - let s = TestStruct{ two: 2, one: () }; - } - "; - check_apply_diagnostic_fix(before, after); - } - - #[test] - fn test_fill_struct_fields_no_diagnostic() { - let content = r" - struct TestStruct { - one: i32, - two: i64, - } - - fn test_fn() { - let one = 1; - let s = TestStruct{ one, two: 2 }; - } - "; - - check_no_diagnostic(content); - } - - #[test] - fn test_fill_struct_fields_no_diagnostic_on_spread() { - let content = r" - struct TestStruct { - one: i32, - two: i64, - } - - fn test_fn() { - let one = 1; - let s = TestStruct{ ..a }; - } - "; - - check_no_diagnostic(content); - } - - #[test] - fn test_unresolved_module_diagnostic() { - let (analysis, file_id) = single_file("mod foo;"); - let diagnostics = analysis.diagnostics(file_id).unwrap(); - assert_debug_snapshot!(diagnostics, @r###" - [ - Diagnostic { - message: "unresolved module", - range: [0; 8), - fix: Some( - SourceChange { - label: "create module", - source_file_edits: [], - file_system_edits: [ - CreateFile { - source_root: SourceRootId( - 0, - ), - path: "foo.rs", - }, - ], - cursor_position: None, - }, - ), - severity: Error, - }, - ] - "###); - } - - #[test] - fn test_check_unnecessary_braces_in_use_statement() { - check_not_applicable( - " - use a; - use a::{c, d::e}; - ", - check_unnecessary_braces_in_use_statement, - ); - check_apply("use {b};", "use b;", check_unnecessary_braces_in_use_statement); - check_apply("use a::{c};", "use a::c;", check_unnecessary_braces_in_use_statement); - check_apply("use a::{self};", "use a;", check_unnecessary_braces_in_use_statement); - check_apply( - "use a::{c, d::{e}};", - "use a::{c, d::e};", - check_unnecessary_braces_in_use_statement, - ); - } - - #[test] - fn test_check_struct_shorthand_initialization() { - check_not_applicable( - r#" - struct A { - a: &'static str - } - - fn main() { - A { - a: "hello" - } - } - "#, - check_struct_shorthand_initialization, - ); - - check_apply( - r#" -struct A { - a: &'static str -} - -fn main() { - let a = "haha"; - A { - a: a - } -} - "#, - r#" -struct A { - a: &'static str -} - -fn main() { - let a = "haha"; - A { - a - } -} - "#, - check_struct_shorthand_initialization, - ); - - check_apply( - r#" -struct A { - a: &'static str, - b: &'static str -} - -fn main() { - let a = "haha"; - let b = "bb"; - A { - a: a, - b - } -} - "#, - r#" -struct A { - a: &'static str, - b: &'static str -} - -fn main() { - let a = "haha"; - let b = "bb"; - A { - a, - b - } -} - "#, - check_struct_shorthand_initialization, - ); - } -} diff --git a/crates/ra_ide_api/src/display.rs b/crates/ra_ide_api/src/display.rs deleted file mode 100644 index 30617412a..000000000 --- a/crates/ra_ide_api/src/display.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! This module contains utilities for turning SyntaxNodes and HIR types -//! into types that may be used to render in a UI. - -mod function_signature; -mod navigation_target; -mod structure; -mod short_label; - -use ra_syntax::{ - ast::{self, AstNode, AttrsOwner, NameOwner, TypeParamsOwner}, - SyntaxKind::{ATTR, COMMENT}, -}; - -pub use function_signature::FunctionSignature; -pub use navigation_target::NavigationTarget; -pub use structure::{file_structure, StructureNode}; - -pub(crate) use navigation_target::{description_from_symbol, docs_from_symbol, ToNav}; -pub(crate) use short_label::ShortLabel; - -pub(crate) fn function_label(node: &ast::FnDef) -> String { - FunctionSignature::from(node).to_string() -} - -pub(crate) fn const_label(node: &ast::ConstDef) -> String { - let label: String = node - .syntax() - .children_with_tokens() - .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) - .map(|node| node.to_string()) - .collect(); - - label.trim().to_owned() -} - -pub(crate) fn type_label(node: &ast::TypeAliasDef) -> String { - let label: String = node - .syntax() - .children_with_tokens() - .filter(|child| !(child.kind() == COMMENT || child.kind() == ATTR)) - .map(|node| node.to_string()) - .collect(); - - label.trim().to_owned() -} - -pub(crate) fn generic_parameters(node: &N) -> Vec { - let mut res = vec![]; - if let Some(type_params) = node.type_param_list() { - res.extend(type_params.lifetime_params().map(|p| p.syntax().text().to_string())); - res.extend(type_params.type_params().map(|p| p.syntax().text().to_string())); - } - res -} - -pub(crate) fn where_predicates(node: &N) -> Vec { - let mut res = vec![]; - if let Some(clause) = node.where_clause() { - res.extend(clause.predicates().map(|p| p.syntax().text().to_string())); - } - res -} - -pub(crate) fn macro_label(node: &ast::MacroCall) -> String { - let name = node.name().map(|name| name.syntax().text().to_string()).unwrap_or_default(); - let vis = if node.has_atom_attr("macro_export") { "#[macro_export]\n" } else { "" }; - format!("{}macro_rules! {}", vis, name) -} - -pub(crate) fn rust_code_markup>(val: CODE) -> String { - rust_code_markup_with_doc::<_, &str>(val, None) -} - -pub(crate) fn rust_code_markup_with_doc(val: CODE, doc: Option) -> String -where - CODE: AsRef, - DOC: AsRef, -{ - if let Some(doc) = doc { - format!("```rust\n{}\n```\n\n{}", val.as_ref(), doc.as_ref()) - } else { - format!("```rust\n{}\n```", val.as_ref()) - } -} diff --git a/crates/ra_ide_api/src/display/function_signature.rs b/crates/ra_ide_api/src/display/function_signature.rs deleted file mode 100644 index d96de4e4c..000000000 --- a/crates/ra_ide_api/src/display/function_signature.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! FIXME: write short doc here - -use std::fmt::{self, Display}; - -use hir::{Docs, Documentation, HasSource, HirDisplay}; -use join_to_string::join; -use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; -use std::convert::From; - -use crate::{ - db, - display::{generic_parameters, where_predicates}, -}; - -#[derive(Debug)] -pub enum CallableKind { - Function, - StructConstructor, - VariantConstructor, - Macro, -} - -/// Contains information about a function signature -#[derive(Debug)] -pub struct FunctionSignature { - pub kind: CallableKind, - /// Optional visibility - pub visibility: Option, - /// Name of the function - pub name: Option, - /// Documentation for the function - pub doc: Option, - /// Generic parameters - pub generic_parameters: Vec, - /// Parameters of the function - pub parameters: Vec, - /// Optional return type - pub ret_type: Option, - /// Where predicates - pub where_predicates: Vec, -} - -impl FunctionSignature { - pub(crate) fn with_doc_opt(mut self, doc: Option) -> Self { - self.doc = doc; - self - } - - pub(crate) fn from_hir(db: &db::RootDatabase, function: hir::Function) -> Self { - let doc = function.docs(db); - let ast_node = function.source(db).value; - FunctionSignature::from(&ast_node).with_doc_opt(doc) - } - - pub(crate) fn from_struct(db: &db::RootDatabase, st: hir::Struct) -> Option { - let node: ast::StructDef = st.source(db).value; - match node.kind() { - ast::StructKind::Record(_) => return None, - _ => (), - }; - - let params = st - .fields(db) - .into_iter() - .map(|field: hir::StructField| { - let ty = field.ty(db); - format!("{}", ty.display(db)) - }) - .collect(); - - Some( - FunctionSignature { - kind: CallableKind::StructConstructor, - visibility: node.visibility().map(|n| n.syntax().text().to_string()), - name: node.name().map(|n| n.text().to_string()), - ret_type: node.name().map(|n| n.text().to_string()), - parameters: params, - generic_parameters: generic_parameters(&node), - where_predicates: where_predicates(&node), - doc: None, - } - .with_doc_opt(st.docs(db)), - ) - } - - pub(crate) fn from_enum_variant( - db: &db::RootDatabase, - variant: hir::EnumVariant, - ) -> Option { - let node: ast::EnumVariant = variant.source(db).value; - match node.kind() { - ast::StructKind::Record(_) | ast::StructKind::Unit => return None, - _ => (), - }; - - let parent_name = match variant.parent_enum(db).name(db) { - Some(name) => name.to_string(), - None => "missing".into(), - }; - - let name = format!("{}::{}", parent_name, variant.name(db).unwrap()); - - let params = variant - .fields(db) - .into_iter() - .map(|field: hir::StructField| { - let name = field.name(db); - let ty = field.ty(db); - format!("{}: {}", name, ty.display(db)) - }) - .collect(); - - Some( - FunctionSignature { - kind: CallableKind::VariantConstructor, - visibility: None, - name: Some(name), - ret_type: None, - parameters: params, - generic_parameters: vec![], - where_predicates: vec![], - doc: None, - } - .with_doc_opt(variant.docs(db)), - ) - } - - pub(crate) fn from_macro(db: &db::RootDatabase, macro_def: hir::MacroDef) -> Option { - let node: ast::MacroCall = macro_def.source(db).value; - - let params = vec![]; - - Some( - FunctionSignature { - kind: CallableKind::Macro, - visibility: None, - name: node.name().map(|n| n.text().to_string()), - ret_type: None, - parameters: params, - generic_parameters: vec![], - where_predicates: vec![], - doc: None, - } - .with_doc_opt(macro_def.docs(db)), - ) - } -} - -impl From<&'_ ast::FnDef> for FunctionSignature { - fn from(node: &ast::FnDef) -> FunctionSignature { - fn param_list(node: &ast::FnDef) -> Vec { - let mut res = vec![]; - if let Some(param_list) = node.param_list() { - if let Some(self_param) = param_list.self_param() { - res.push(self_param.syntax().text().to_string()) - } - - res.extend(param_list.params().map(|param| param.syntax().text().to_string())); - } - res - } - - FunctionSignature { - kind: CallableKind::Function, - visibility: node.visibility().map(|n| n.syntax().text().to_string()), - name: node.name().map(|n| n.text().to_string()), - ret_type: node - .ret_type() - .and_then(|r| r.type_ref()) - .map(|n| n.syntax().text().to_string()), - parameters: param_list(node), - generic_parameters: generic_parameters(node), - where_predicates: where_predicates(node), - // docs are processed separately - doc: None, - } - } -} - -impl Display for FunctionSignature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(t) = &self.visibility { - write!(f, "{} ", t)?; - } - - if let Some(name) = &self.name { - match self.kind { - CallableKind::Function => write!(f, "fn {}", name)?, - CallableKind::StructConstructor => write!(f, "struct {}", name)?, - CallableKind::VariantConstructor => write!(f, "{}", name)?, - CallableKind::Macro => write!(f, "{}!", name)?, - } - } - - if !self.generic_parameters.is_empty() { - join(self.generic_parameters.iter()) - .separator(", ") - .surround_with("<", ">") - .to_fmt(f)?; - } - - join(self.parameters.iter()).separator(", ").surround_with("(", ")").to_fmt(f)?; - - if let Some(t) = &self.ret_type { - write!(f, " -> {}", t)?; - } - - if !self.where_predicates.is_empty() { - write!(f, "\nwhere ")?; - join(self.where_predicates.iter()).separator(",\n ").to_fmt(f)?; - } - - Ok(()) - } -} diff --git a/crates/ra_ide_api/src/display/navigation_target.rs b/crates/ra_ide_api/src/display/navigation_target.rs deleted file mode 100644 index 6ac60722b..000000000 --- a/crates/ra_ide_api/src/display/navigation_target.rs +++ /dev/null @@ -1,411 +0,0 @@ -//! FIXME: write short doc here - -use hir::{AssocItem, Either, FieldSource, HasSource, ModuleSource, Source}; -use ra_db::{FileId, SourceDatabase}; -use ra_syntax::{ - ast::{self, DocCommentsOwner, NameOwner}, - match_ast, AstNode, SmolStr, - SyntaxKind::{self, BIND_PAT}, - TextRange, -}; - -use crate::{db::RootDatabase, expand::original_range, FileSymbol}; - -use super::short_label::ShortLabel; - -/// `NavigationTarget` represents and element in the editor's UI which you can -/// click on to navigate to a particular piece of code. -/// -/// Typically, a `NavigationTarget` corresponds to some element in the source -/// code, like a function or a struct, but this is not strictly required. -#[derive(Debug, Clone)] -pub struct NavigationTarget { - file_id: FileId, - name: SmolStr, - kind: SyntaxKind, - full_range: TextRange, - focus_range: Option, - container_name: Option, - description: Option, - docs: Option, -} - -pub(crate) trait ToNav { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget; -} - -impl NavigationTarget { - /// When `focus_range` is specified, returns it. otherwise - /// returns `full_range` - pub fn range(&self) -> TextRange { - self.focus_range.unwrap_or(self.full_range) - } - - pub fn name(&self) -> &SmolStr { - &self.name - } - - pub fn container_name(&self) -> Option<&SmolStr> { - self.container_name.as_ref() - } - - pub fn kind(&self) -> SyntaxKind { - self.kind - } - - pub fn file_id(&self) -> FileId { - self.file_id - } - - pub fn full_range(&self) -> TextRange { - self.full_range - } - - pub fn docs(&self) -> Option<&str> { - self.docs.as_ref().map(String::as_str) - } - - pub fn description(&self) -> Option<&str> { - self.description.as_ref().map(String::as_str) - } - - /// A "most interesting" range withing the `full_range`. - /// - /// Typically, `full_range` is the whole syntax node, - /// including doc comments, and `focus_range` is the range of the identifier. - pub fn focus_range(&self) -> Option { - self.focus_range - } - - pub(crate) fn from_module_to_decl(db: &RootDatabase, module: hir::Module) -> NavigationTarget { - let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); - if let Some(src) = module.declaration_source(db) { - let frange = original_range(db, src.as_ref().map(|it| it.syntax())); - return NavigationTarget::from_syntax( - frange.file_id, - name, - None, - frange.range, - src.value.syntax().kind(), - src.value.doc_comment_text(), - src.value.short_label(), - ); - } - module.to_nav(db) - } - - pub(crate) fn from_def( - db: &RootDatabase, - module_def: hir::ModuleDef, - ) -> Option { - let nav = match module_def { - hir::ModuleDef::Module(module) => module.to_nav(db), - hir::ModuleDef::Function(it) => it.to_nav(db), - hir::ModuleDef::Adt(it) => it.to_nav(db), - hir::ModuleDef::Const(it) => it.to_nav(db), - hir::ModuleDef::Static(it) => it.to_nav(db), - hir::ModuleDef::EnumVariant(it) => it.to_nav(db), - hir::ModuleDef::Trait(it) => it.to_nav(db), - hir::ModuleDef::TypeAlias(it) => it.to_nav(db), - hir::ModuleDef::BuiltinType(..) => { - return None; - } - }; - Some(nav) - } - - #[cfg(test)] - pub(crate) fn assert_match(&self, expected: &str) { - let actual = self.debug_render(); - test_utils::assert_eq_text!(expected.trim(), actual.trim(),); - } - - #[cfg(test)] - pub(crate) fn debug_render(&self) -> String { - let mut buf = format!( - "{} {:?} {:?} {:?}", - self.name(), - self.kind(), - self.file_id(), - self.full_range() - ); - if let Some(focus_range) = self.focus_range() { - buf.push_str(&format!(" {:?}", focus_range)) - } - if let Some(container_name) = self.container_name() { - buf.push_str(&format!(" {}", container_name)) - } - buf - } - - /// Allows `NavigationTarget` to be created from a `NameOwner` - pub(crate) fn from_named( - db: &RootDatabase, - node: Source<&dyn ast::NameOwner>, - docs: Option, - description: Option, - ) -> NavigationTarget { - //FIXME: use `_` instead of empty string - let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); - let focus_range = - node.value.name().map(|it| original_range(db, node.with_value(it.syntax())).range); - let frange = original_range(db, node.map(|it| it.syntax())); - - NavigationTarget::from_syntax( - frange.file_id, - name, - focus_range, - frange.range, - node.value.syntax().kind(), - docs, - description, - ) - } - - fn from_syntax( - file_id: FileId, - name: SmolStr, - focus_range: Option, - full_range: TextRange, - kind: SyntaxKind, - docs: Option, - description: Option, - ) -> NavigationTarget { - NavigationTarget { - file_id, - name, - kind, - full_range, - focus_range, - container_name: None, - description, - docs, - } - } -} - -impl ToNav for FileSymbol { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - NavigationTarget { - file_id: self.file_id, - name: self.name.clone(), - kind: self.ptr.kind(), - full_range: self.ptr.range(), - focus_range: self.name_range, - container_name: self.container_name.clone(), - description: description_from_symbol(db, self), - docs: docs_from_symbol(db, self), - } - } -} - -pub(crate) trait ToNavFromAst {} -impl ToNavFromAst for hir::Function {} -impl ToNavFromAst for hir::Const {} -impl ToNavFromAst for hir::Static {} -impl ToNavFromAst for hir::Struct {} -impl ToNavFromAst for hir::Enum {} -impl ToNavFromAst for hir::EnumVariant {} -impl ToNavFromAst for hir::Union {} -impl ToNavFromAst for hir::TypeAlias {} -impl ToNavFromAst for hir::Trait {} - -impl ToNav for D -where - D: HasSource + ToNavFromAst + Copy, - D::Ast: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, -{ - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - let src = self.source(db); - NavigationTarget::from_named( - db, - src.as_ref().map(|it| it as &dyn ast::NameOwner), - src.value.doc_comment_text(), - src.value.short_label(), - ) - } -} - -impl ToNav for hir::Module { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - let src = self.definition_source(db); - let name = self.name(db).map(|it| it.to_string().into()).unwrap_or_default(); - match &src.value { - ModuleSource::SourceFile(node) => { - let frange = original_range(db, src.with_value(node.syntax())); - - NavigationTarget::from_syntax( - frange.file_id, - name, - None, - frange.range, - node.syntax().kind(), - None, - None, - ) - } - ModuleSource::Module(node) => { - let frange = original_range(db, src.with_value(node.syntax())); - - NavigationTarget::from_syntax( - frange.file_id, - name, - None, - frange.range, - node.syntax().kind(), - node.doc_comment_text(), - node.short_label(), - ) - } - } - } -} - -impl ToNav for hir::ImplBlock { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - let src = self.source(db); - let frange = original_range(db, src.as_ref().map(|it| it.syntax())); - - NavigationTarget::from_syntax( - frange.file_id, - "impl".into(), - None, - frange.range, - src.value.syntax().kind(), - None, - None, - ) - } -} - -impl ToNav for hir::StructField { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - let src = self.source(db); - - match &src.value { - FieldSource::Named(it) => NavigationTarget::from_named( - db, - src.with_value(it), - it.doc_comment_text(), - it.short_label(), - ), - FieldSource::Pos(it) => { - let frange = original_range(db, src.with_value(it.syntax())); - NavigationTarget::from_syntax( - frange.file_id, - "".into(), - None, - frange.range, - it.syntax().kind(), - None, - None, - ) - } - } - } -} - -impl ToNav for hir::MacroDef { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - let src = self.source(db); - log::debug!("nav target {:#?}", src.value.syntax()); - NavigationTarget::from_named( - db, - src.as_ref().map(|it| it as &dyn ast::NameOwner), - src.value.doc_comment_text(), - None, - ) - } -} - -impl ToNav for hir::Adt { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - match self { - hir::Adt::Struct(it) => it.to_nav(db), - hir::Adt::Union(it) => it.to_nav(db), - hir::Adt::Enum(it) => it.to_nav(db), - } - } -} - -impl ToNav for hir::AssocItem { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - match self { - AssocItem::Function(it) => it.to_nav(db), - AssocItem::Const(it) => it.to_nav(db), - AssocItem::TypeAlias(it) => it.to_nav(db), - } - } -} - -impl ToNav for hir::Local { - fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { - let src = self.source(db); - let (full_range, focus_range) = match src.value { - Either::A(it) => { - (it.syntax().text_range(), it.name().map(|it| it.syntax().text_range())) - } - Either::B(it) => (it.syntax().text_range(), Some(it.self_kw_token().text_range())), - }; - let name = match self.name(db) { - Some(it) => it.to_string().into(), - None => "".into(), - }; - NavigationTarget { - file_id: src.file_id.original_file(db), - name, - kind: BIND_PAT, - full_range, - focus_range, - container_name: None, - description: None, - docs: None, - } - } -} - -pub(crate) fn docs_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option { - let parse = db.parse(symbol.file_id); - let node = symbol.ptr.to_node(parse.tree().syntax()); - - match_ast! { - match node { - ast::FnDef(it) => { it.doc_comment_text() }, - ast::StructDef(it) => { it.doc_comment_text() }, - ast::EnumDef(it) => { it.doc_comment_text() }, - ast::TraitDef(it) => { it.doc_comment_text() }, - ast::Module(it) => { it.doc_comment_text() }, - ast::TypeAliasDef(it) => { it.doc_comment_text() }, - ast::ConstDef(it) => { it.doc_comment_text() }, - ast::StaticDef(it) => { it.doc_comment_text() }, - ast::RecordFieldDef(it) => { it.doc_comment_text() }, - ast::EnumVariant(it) => { it.doc_comment_text() }, - ast::MacroCall(it) => { it.doc_comment_text() }, - _ => None, - } - } -} - -/// Get a description of a symbol. -/// -/// e.g. `struct Name`, `enum Name`, `fn Name` -pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> Option { - let parse = db.parse(symbol.file_id); - let node = symbol.ptr.to_node(parse.tree().syntax()); - - match_ast! { - match node { - ast::FnDef(it) => { it.short_label() }, - ast::StructDef(it) => { it.short_label() }, - ast::EnumDef(it) => { it.short_label() }, - ast::TraitDef(it) => { it.short_label() }, - ast::Module(it) => { it.short_label() }, - ast::TypeAliasDef(it) => { it.short_label() }, - ast::ConstDef(it) => { it.short_label() }, - ast::StaticDef(it) => { it.short_label() }, - ast::RecordFieldDef(it) => { it.short_label() }, - ast::EnumVariant(it) => { it.short_label() }, - _ => None, - } - } -} diff --git a/crates/ra_ide_api/src/display/short_label.rs b/crates/ra_ide_api/src/display/short_label.rs deleted file mode 100644 index 9ffc9b980..000000000 --- a/crates/ra_ide_api/src/display/short_label.rs +++ /dev/null @@ -1,97 +0,0 @@ -//! FIXME: write short doc here - -use format_buf::format; -use ra_syntax::ast::{self, AstNode, NameOwner, TypeAscriptionOwner, VisibilityOwner}; - -pub(crate) trait ShortLabel { - fn short_label(&self) -> Option; -} - -impl ShortLabel for ast::FnDef { - fn short_label(&self) -> Option { - Some(crate::display::function_label(self)) - } -} - -impl ShortLabel for ast::StructDef { - fn short_label(&self) -> Option { - short_label_from_node(self, "struct ") - } -} - -impl ShortLabel for ast::UnionDef { - fn short_label(&self) -> Option { - short_label_from_node(self, "union ") - } -} - -impl ShortLabel for ast::EnumDef { - fn short_label(&self) -> Option { - short_label_from_node(self, "enum ") - } -} - -impl ShortLabel for ast::TraitDef { - fn short_label(&self) -> Option { - short_label_from_node(self, "trait ") - } -} - -impl ShortLabel for ast::Module { - fn short_label(&self) -> Option { - short_label_from_node(self, "mod ") - } -} - -impl ShortLabel for ast::TypeAliasDef { - fn short_label(&self) -> Option { - short_label_from_node(self, "type ") - } -} - -impl ShortLabel for ast::ConstDef { - fn short_label(&self) -> Option { - short_label_from_ascribed_node(self, "const ") - } -} - -impl ShortLabel for ast::StaticDef { - fn short_label(&self) -> Option { - short_label_from_ascribed_node(self, "static ") - } -} - -impl ShortLabel for ast::RecordFieldDef { - fn short_label(&self) -> Option { - short_label_from_ascribed_node(self, "") - } -} - -impl ShortLabel for ast::EnumVariant { - fn short_label(&self) -> Option { - Some(self.name()?.text().to_string()) - } -} - -fn short_label_from_ascribed_node(node: &T, prefix: &str) -> Option -where - T: NameOwner + VisibilityOwner + TypeAscriptionOwner, -{ - let mut buf = short_label_from_node(node, prefix)?; - - if let Some(type_ref) = node.ascribed_type() { - format!(buf, ": {}", type_ref.syntax()); - } - - Some(buf) -} - -fn short_label_from_node(node: &T, label: &str) -> Option -where - T: NameOwner + VisibilityOwner, -{ - let mut buf = node.visibility().map(|v| format!("{} ", v.syntax())).unwrap_or_default(); - buf.push_str(label); - buf.push_str(node.name()?.text().as_str()); - Some(buf) -} diff --git a/crates/ra_ide_api/src/display/structure.rs b/crates/ra_ide_api/src/display/structure.rs deleted file mode 100644 index a80d65ac7..000000000 --- a/crates/ra_ide_api/src/display/structure.rs +++ /dev/null @@ -1,401 +0,0 @@ -//! FIXME: write short doc here - -use crate::TextRange; - -use ra_syntax::{ - ast::{self, AttrsOwner, NameOwner, TypeAscriptionOwner, TypeParamsOwner}, - match_ast, AstNode, SourceFile, SyntaxKind, SyntaxNode, WalkEvent, -}; - -#[derive(Debug, Clone)] -pub struct StructureNode { - pub parent: Option, - pub label: String, - pub navigation_range: TextRange, - pub node_range: TextRange, - pub kind: SyntaxKind, - pub detail: Option, - pub deprecated: bool, -} - -pub fn file_structure(file: &SourceFile) -> Vec { - let mut res = Vec::new(); - let mut stack = Vec::new(); - - for event in file.syntax().preorder() { - match event { - WalkEvent::Enter(node) => { - if let Some(mut symbol) = structure_node(&node) { - symbol.parent = stack.last().copied(); - stack.push(res.len()); - res.push(symbol); - } - } - WalkEvent::Leave(node) => { - if structure_node(&node).is_some() { - stack.pop().unwrap(); - } - } - } - } - res -} - -fn structure_node(node: &SyntaxNode) -> Option { - fn decl(node: N) -> Option { - decl_with_detail(node, None) - } - - fn decl_with_ascription( - node: N, - ) -> Option { - let ty = node.ascribed_type(); - decl_with_type_ref(node, ty) - } - - fn decl_with_type_ref( - node: N, - type_ref: Option, - ) -> Option { - let detail = type_ref.map(|type_ref| { - let mut detail = String::new(); - collapse_ws(type_ref.syntax(), &mut detail); - detail - }); - decl_with_detail(node, detail) - } - - fn decl_with_detail( - node: N, - detail: Option, - ) -> Option { - let name = node.name()?; - - Some(StructureNode { - parent: None, - label: name.text().to_string(), - navigation_range: name.syntax().text_range(), - node_range: node.syntax().text_range(), - kind: node.syntax().kind(), - detail, - deprecated: node.attrs().filter_map(|x| x.simple_name()).any(|x| x == "deprecated"), - }) - } - - fn collapse_ws(node: &SyntaxNode, output: &mut String) { - let mut can_insert_ws = false; - node.text().for_each_chunk(|chunk| { - for line in chunk.lines() { - let line = line.trim(); - if line.is_empty() { - if can_insert_ws { - output.push(' '); - can_insert_ws = false; - } - } else { - output.push_str(line); - can_insert_ws = true; - } - } - }) - } - - match_ast! { - match node { - ast::FnDef(it) => { - let mut detail = String::from("fn"); - if let Some(type_param_list) = it.type_param_list() { - collapse_ws(type_param_list.syntax(), &mut detail); - } - if let Some(param_list) = it.param_list() { - collapse_ws(param_list.syntax(), &mut detail); - } - if let Some(ret_type) = it.ret_type() { - detail.push_str(" "); - collapse_ws(ret_type.syntax(), &mut detail); - } - - decl_with_detail(it, Some(detail)) - }, - ast::StructDef(it) => { decl(it) }, - ast::EnumDef(it) => { decl(it) }, - ast::EnumVariant(it) => { decl(it) }, - ast::TraitDef(it) => { decl(it) }, - ast::Module(it) => { decl(it) }, - ast::TypeAliasDef(it) => { - let ty = it.type_ref(); - decl_with_type_ref(it, ty) - }, - ast::RecordFieldDef(it) => { decl_with_ascription(it) }, - ast::ConstDef(it) => { decl_with_ascription(it) }, - ast::StaticDef(it) => { decl_with_ascription(it) }, - ast::ImplBlock(it) => { - let target_type = it.target_type()?; - let target_trait = it.target_trait(); - let label = match target_trait { - None => format!("impl {}", target_type.syntax().text()), - Some(t) => { - format!("impl {} for {}", t.syntax().text(), target_type.syntax().text(),) - } - }; - - let node = StructureNode { - parent: None, - label, - navigation_range: target_type.syntax().text_range(), - node_range: it.syntax().text_range(), - kind: it.syntax().kind(), - detail: None, - deprecated: false, - }; - Some(node) - }, - ast::MacroCall(it) => { - let first_token = it.syntax().first_token().unwrap(); - if first_token.text().as_str() != "macro_rules" { - return None; - } - decl(it) - }, - _ => None, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use insta::assert_debug_snapshot; - - #[test] - fn test_file_structure() { - let file = SourceFile::parse( - r#" -struct Foo { - x: i32 -} - -mod m { - fn bar1() {} - fn bar2(t: T) -> T {} - fn bar3(a: A, - b: B) -> Vec< - u32 - > {} -} - -enum E { X, Y(i32) } -type T = (); -static S: i32 = 92; -const C: i32 = 92; - -impl E {} - -impl fmt::Debug for E {} - -macro_rules! mc { - () => {} -} - -#[deprecated] -fn obsolete() {} - -#[deprecated(note = "for awhile")] -fn very_obsolete() {} -"#, - ) - .ok() - .unwrap(); - let structure = file_structure(&file); - assert_debug_snapshot!(structure, - @r###" - [ - StructureNode { - parent: None, - label: "Foo", - navigation_range: [8; 11), - node_range: [1; 26), - kind: STRUCT_DEF, - detail: None, - deprecated: false, - }, - StructureNode { - parent: Some( - 0, - ), - label: "x", - navigation_range: [18; 19), - node_range: [18; 24), - kind: RECORD_FIELD_DEF, - detail: Some( - "i32", - ), - deprecated: false, - }, - StructureNode { - parent: None, - label: "m", - navigation_range: [32; 33), - node_range: [28; 158), - kind: MODULE, - detail: None, - deprecated: false, - }, - StructureNode { - parent: Some( - 2, - ), - label: "bar1", - navigation_range: [43; 47), - node_range: [40; 52), - kind: FN_DEF, - detail: Some( - "fn()", - ), - deprecated: false, - }, - StructureNode { - parent: Some( - 2, - ), - label: "bar2", - navigation_range: [60; 64), - node_range: [57; 81), - kind: FN_DEF, - detail: Some( - "fn(t: T) -> T", - ), - deprecated: false, - }, - StructureNode { - parent: Some( - 2, - ), - label: "bar3", - navigation_range: [89; 93), - node_range: [86; 156), - kind: FN_DEF, - detail: Some( - "fn(a: A, b: B) -> Vec< u32 >", - ), - deprecated: false, - }, - StructureNode { - parent: None, - label: "E", - navigation_range: [165; 166), - node_range: [160; 180), - kind: ENUM_DEF, - detail: None, - deprecated: false, - }, - StructureNode { - parent: Some( - 6, - ), - label: "X", - navigation_range: [169; 170), - node_range: [169; 170), - kind: ENUM_VARIANT, - detail: None, - deprecated: false, - }, - StructureNode { - parent: Some( - 6, - ), - label: "Y", - navigation_range: [172; 173), - node_range: [172; 178), - kind: ENUM_VARIANT, - detail: None, - deprecated: false, - }, - StructureNode { - parent: None, - label: "T", - navigation_range: [186; 187), - node_range: [181; 193), - kind: TYPE_ALIAS_DEF, - detail: Some( - "()", - ), - deprecated: false, - }, - StructureNode { - parent: None, - label: "S", - navigation_range: [201; 202), - node_range: [194; 213), - kind: STATIC_DEF, - detail: Some( - "i32", - ), - deprecated: false, - }, - StructureNode { - parent: None, - label: "C", - navigation_range: [220; 221), - node_range: [214; 232), - kind: CONST_DEF, - detail: Some( - "i32", - ), - deprecated: false, - }, - StructureNode { - parent: None, - label: "impl E", - navigation_range: [239; 240), - node_range: [234; 243), - kind: IMPL_BLOCK, - detail: None, - deprecated: false, - }, - StructureNode { - parent: None, - label: "impl fmt::Debug for E", - navigation_range: [265; 266), - node_range: [245; 269), - kind: IMPL_BLOCK, - detail: None, - deprecated: false, - }, - StructureNode { - parent: None, - label: "mc", - navigation_range: [284; 286), - node_range: [271; 303), - kind: MACRO_CALL, - detail: None, - deprecated: false, - }, - StructureNode { - parent: None, - label: "obsolete", - navigation_range: [322; 330), - node_range: [305; 335), - kind: FN_DEF, - detail: Some( - "fn()", - ), - deprecated: true, - }, - StructureNode { - parent: None, - label: "very_obsolete", - navigation_range: [375; 388), - node_range: [337; 393), - kind: FN_DEF, - detail: Some( - "fn()", - ), - deprecated: true, - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/expand.rs b/crates/ra_ide_api/src/expand.rs deleted file mode 100644 index 2f1abf509..000000000 --- a/crates/ra_ide_api/src/expand.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! Utilities to work with files, produced by macros. -use std::iter::successors; - -use hir::Source; -use ra_db::FileId; -use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken}; - -use crate::{db::RootDatabase, FileRange}; - -pub(crate) fn original_range(db: &RootDatabase, node: Source<&SyntaxNode>) -> FileRange { - let expansion = match node.file_id.expansion_info(db) { - None => { - return FileRange { - file_id: node.file_id.original_file(db), - range: node.value.text_range(), - } - } - Some(it) => it, - }; - // FIXME: the following completely wrong. - // - // *First*, we should try to map first and last tokens of node, and, if that - // fails, return the range of the overall macro expansions. - // - // *Second*, we should handle recurside macro expansions - - let token = node - .value - .descendants_with_tokens() - .filter_map(|it| it.into_token()) - .find_map(|it| expansion.map_token_up(node.with_value(&it))); - - match token { - Some(it) => { - FileRange { file_id: it.file_id.original_file(db), range: it.value.text_range() } - } - None => { - FileRange { file_id: node.file_id.original_file(db), range: node.value.text_range() } - } - } -} - -pub(crate) fn descend_into_macros( - db: &RootDatabase, - file_id: FileId, - token: SyntaxToken, -) -> Source { - let src = Source::new(file_id.into(), token); - - successors(Some(src), |token| { - let macro_call = token.value.ancestors().find_map(ast::MacroCall::cast)?; - let tt = macro_call.token_tree()?; - if !token.value.text_range().is_subrange(&tt.syntax().text_range()) { - return None; - } - let source_analyzer = - hir::SourceAnalyzer::new(db, token.with_value(token.value.parent()).as_ref(), None); - let exp = source_analyzer.expand(db, token.with_value(¯o_call))?; - exp.map_token_down(db, token.as_ref()) - }) - .last() - .unwrap() -} diff --git a/crates/ra_ide_api/src/expand_macro.rs b/crates/ra_ide_api/src/expand_macro.rs deleted file mode 100644 index abc602244..000000000 --- a/crates/ra_ide_api/src/expand_macro.rs +++ /dev/null @@ -1,295 +0,0 @@ -//! This modules implements "expand macro" functionality in the IDE - -use crate::{db::RootDatabase, FilePosition}; -use hir::db::AstDatabase; -use ra_db::SourceDatabase; -use rustc_hash::FxHashMap; - -use ra_syntax::{ - algo::{find_node_at_offset, replace_descendants}, - ast::{self}, - AstNode, NodeOrToken, SyntaxKind, SyntaxNode, WalkEvent, T, -}; - -pub struct ExpandedMacro { - pub name: String, - pub expansion: String, -} - -pub(crate) fn expand_macro(db: &RootDatabase, position: FilePosition) -> Option { - let parse = db.parse(position.file_id); - let file = parse.tree(); - let name_ref = find_node_at_offset::(file.syntax(), position.offset)?; - let mac = name_ref.syntax().ancestors().find_map(ast::MacroCall::cast)?; - - let source = hir::Source::new(position.file_id.into(), mac.syntax()); - let expanded = expand_macro_recur(db, source, source.with_value(&mac))?; - - // FIXME: - // macro expansion may lose all white space information - // But we hope someday we can use ra_fmt for that - let expansion = insert_whitespaces(expanded); - Some(ExpandedMacro { name: name_ref.text().to_string(), expansion }) -} - -fn expand_macro_recur( - db: &RootDatabase, - source: hir::Source<&SyntaxNode>, - macro_call: hir::Source<&ast::MacroCall>, -) -> Option { - let analyzer = hir::SourceAnalyzer::new(db, source, None); - let expansion = analyzer.expand(db, macro_call)?; - let macro_file_id = expansion.file_id(); - let mut expanded: SyntaxNode = db.parse_or_expand(macro_file_id)?; - - let children = expanded.descendants().filter_map(ast::MacroCall::cast); - let mut replaces = FxHashMap::default(); - - for child in children.into_iter() { - let node = hir::Source::new(macro_file_id, &child); - if let Some(new_node) = expand_macro_recur(db, source, node) { - // Replace the whole node if it is root - // `replace_descendants` will not replace the parent node - // but `SyntaxNode::descendants include itself - if expanded == *child.syntax() { - expanded = new_node; - } else { - replaces.insert(child.syntax().clone().into(), new_node.into()); - } - } - } - - Some(replace_descendants(&expanded, &replaces)) -} - -// FIXME: It would also be cool to share logic here and in the mbe tests, -// which are pretty unreadable at the moment. -fn insert_whitespaces(syn: SyntaxNode) -> String { - use SyntaxKind::*; - - let mut res = String::new(); - let mut token_iter = syn - .preorder_with_tokens() - .filter_map(|event| { - if let WalkEvent::Enter(NodeOrToken::Token(token)) = event { - Some(token) - } else { - None - } - }) - .peekable(); - - let mut indent = 0; - let mut last: Option = None; - - while let Some(token) = token_iter.next() { - let mut is_next = |f: fn(SyntaxKind) -> bool, default| -> bool { - token_iter.peek().map(|it| f(it.kind())).unwrap_or(default) - }; - let is_last = |f: fn(SyntaxKind) -> bool, default| -> bool { - last.map(|it| f(it)).unwrap_or(default) - }; - - res += &match token.kind() { - k @ _ if is_text(k) && is_next(|it| !it.is_punct(), true) => { - token.text().to_string() + " " - } - L_CURLY if is_next(|it| it != R_CURLY, true) => { - indent += 1; - let leading_space = if is_last(|it| is_text(it), false) { " " } else { "" }; - format!("{}{{\n{}", leading_space, " ".repeat(indent)) - } - R_CURLY if is_last(|it| it != L_CURLY, true) => { - indent = indent.checked_sub(1).unwrap_or(0); - format!("\n{}}}", " ".repeat(indent)) - } - R_CURLY => format!("}}\n{}", " ".repeat(indent)), - T![;] => format!(";\n{}", " ".repeat(indent)), - T![->] => " -> ".to_string(), - T![=] => " = ".to_string(), - T![=>] => " => ".to_string(), - _ => token.text().to_string(), - }; - - last = Some(token.kind()); - } - - return res; - - fn is_text(k: SyntaxKind) -> bool { - k.is_keyword() || k.is_literal() || k == IDENT - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mock_analysis::analysis_and_position; - use insta::assert_snapshot; - - fn check_expand_macro(fixture: &str) -> ExpandedMacro { - let (analysis, pos) = analysis_and_position(fixture); - analysis.expand_macro(pos).unwrap().unwrap() - } - - #[test] - fn macro_expand_recursive_expansion() { - let res = check_expand_macro( - r#" - //- /lib.rs - macro_rules! bar { - () => { fn b() {} } - } - macro_rules! foo { - () => { bar!(); } - } - macro_rules! baz { - () => { foo!(); } - } - f<|>oo!(); - "#, - ); - - assert_eq!(res.name, "foo"); - assert_snapshot!(res.expansion, @r###" -fn b(){} -"###); - } - - #[test] - fn macro_expand_multiple_lines() { - let res = check_expand_macro( - r#" - //- /lib.rs - macro_rules! foo { - () => { - fn some_thing() -> u32 { - let a = 0; - a + 10 - } - } - } - f<|>oo!(); - "#, - ); - - assert_eq!(res.name, "foo"); - assert_snapshot!(res.expansion, @r###" -fn some_thing() -> u32 { - let a = 0; - a+10 -} -"###); - } - - #[test] - fn macro_expand_match_ast() { - let res = check_expand_macro( - r#" - //- /lib.rs - macro_rules! match_ast { - (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; - - (match ($node:expr) { - $( ast::$ast:ident($it:ident) => $res:block, )* - _ => $catch_all:expr $(,)? - }) => {{ - $( if let Some($it) = ast::$ast::cast($node.clone()) $res else )* - { $catch_all } - }}; - } - - fn main() { - mat<|>ch_ast! { - match container { - ast::TraitDef(it) => {}, - ast::ImplBlock(it) => {}, - _ => { continue }, - } - } - } - "#, - ); - - assert_eq!(res.name, "match_ast"); - assert_snapshot!(res.expansion, @r###" -{ - if let Some(it) = ast::TraitDef::cast(container.clone()){} - else if let Some(it) = ast::ImplBlock::cast(container.clone()){} - else { - { - continue - } - } -} -"###); - } - - #[test] - fn macro_expand_match_ast_inside_let_statement() { - let res = check_expand_macro( - r#" - //- /lib.rs - macro_rules! match_ast { - (match $node:ident { $($tt:tt)* }) => { match_ast!(match ($node) { $($tt)* }) }; - (match ($node:expr) {}) => {{}}; - } - - fn main() { - let p = f(|it| { - let res = mat<|>ch_ast! { match c {}}; - Some(res) - })?; - } - "#, - ); - - assert_eq!(res.name, "match_ast"); - assert_snapshot!(res.expansion, @r###"{}"###); - } - - #[test] - fn macro_expand_inner_macro_fail_to_expand() { - let res = check_expand_macro( - r#" - //- /lib.rs - macro_rules! bar { - (BAD) => {}; - } - macro_rules! foo { - () => {bar!()}; - } - - fn main() { - let res = fo<|>o!(); - } - "#, - ); - - assert_eq!(res.name, "foo"); - assert_snapshot!(res.expansion, @r###"bar!()"###); - } - - #[test] - fn macro_expand_with_dollar_crate() { - let res = check_expand_macro( - r#" - //- /lib.rs - #[macro_export] - macro_rules! bar { - () => {0}; - } - macro_rules! foo { - () => {$crate::bar!()}; - } - - fn main() { - let res = fo<|>o!(); - } - "#, - ); - - assert_eq!(res.name, "foo"); - assert_snapshot!(res.expansion, @r###"0"###); - } -} diff --git a/crates/ra_ide_api/src/extend_selection.rs b/crates/ra_ide_api/src/extend_selection.rs deleted file mode 100644 index 4b7bfc0b1..000000000 --- a/crates/ra_ide_api/src/extend_selection.rs +++ /dev/null @@ -1,452 +0,0 @@ -//! FIXME: write short doc here - -use ra_db::SourceDatabase; -use ra_syntax::{ - algo::find_covering_element, - ast::{self, AstNode, AstToken}, - Direction, NodeOrToken, - SyntaxKind::{self, *}, - SyntaxNode, SyntaxToken, TextRange, TextUnit, TokenAtOffset, T, -}; - -use crate::{db::RootDatabase, FileRange}; - -// FIXME: restore macro support -pub(crate) fn extend_selection(db: &RootDatabase, frange: FileRange) -> TextRange { - let parse = db.parse(frange.file_id); - try_extend_selection(parse.tree().syntax(), frange.range).unwrap_or(frange.range) -} - -fn try_extend_selection(root: &SyntaxNode, range: TextRange) -> Option { - let string_kinds = [COMMENT, STRING, RAW_STRING, BYTE_STRING, RAW_BYTE_STRING]; - let list_kinds = [ - RECORD_FIELD_PAT_LIST, - MATCH_ARM_LIST, - RECORD_FIELD_DEF_LIST, - TUPLE_FIELD_DEF_LIST, - RECORD_FIELD_LIST, - ENUM_VARIANT_LIST, - USE_TREE_LIST, - TYPE_PARAM_LIST, - TYPE_ARG_LIST, - TYPE_BOUND_LIST, - PARAM_LIST, - ARG_LIST, - ARRAY_EXPR, - TUPLE_EXPR, - WHERE_CLAUSE, - ]; - - if range.is_empty() { - let offset = range.start(); - let mut leaves = root.token_at_offset(offset); - if leaves.clone().all(|it| it.kind() == WHITESPACE) { - return Some(extend_ws(root, leaves.next()?, offset)); - } - let leaf_range = match leaves { - TokenAtOffset::None => return None, - TokenAtOffset::Single(l) => { - if string_kinds.contains(&l.kind()) { - extend_single_word_in_comment_or_string(&l, offset) - .unwrap_or_else(|| l.text_range()) - } else { - l.text_range() - } - } - TokenAtOffset::Between(l, r) => pick_best(l, r).text_range(), - }; - return Some(leaf_range); - }; - let node = match find_covering_element(root, range) { - NodeOrToken::Token(token) => { - if token.text_range() != range { - return Some(token.text_range()); - } - if let Some(comment) = ast::Comment::cast(token.clone()) { - if let Some(range) = extend_comments(comment) { - return Some(range); - } - } - token.parent() - } - NodeOrToken::Node(node) => node, - }; - if node.text_range() != range { - return Some(node.text_range()); - } - - // Using shallowest node with same range allows us to traverse siblings. - let node = node.ancestors().take_while(|n| n.text_range() == node.text_range()).last().unwrap(); - - if node.parent().map(|n| list_kinds.contains(&n.kind())) == Some(true) { - if let Some(range) = extend_list_item(&node) { - return Some(range); - } - } - - node.parent().map(|it| it.text_range()) -} - -fn extend_single_word_in_comment_or_string( - leaf: &SyntaxToken, - offset: TextUnit, -) -> Option { - let text: &str = leaf.text(); - let cursor_position: u32 = (offset - leaf.text_range().start()).into(); - - let (before, after) = text.split_at(cursor_position as usize); - - fn non_word_char(c: char) -> bool { - !(c.is_alphanumeric() || c == '_') - } - - let start_idx = before.rfind(non_word_char)? as u32; - let end_idx = after.find(non_word_char).unwrap_or_else(|| after.len()) as u32; - - let from: TextUnit = (start_idx + 1).into(); - let to: TextUnit = (cursor_position + end_idx).into(); - - let range = TextRange::from_to(from, to); - if range.is_empty() { - None - } else { - Some(range + leaf.text_range().start()) - } -} - -fn extend_ws(root: &SyntaxNode, ws: SyntaxToken, offset: TextUnit) -> TextRange { - let ws_text = ws.text(); - let suffix = TextRange::from_to(offset, ws.text_range().end()) - ws.text_range().start(); - let prefix = TextRange::from_to(ws.text_range().start(), offset) - ws.text_range().start(); - let ws_suffix = &ws_text.as_str()[suffix]; - let ws_prefix = &ws_text.as_str()[prefix]; - if ws_text.contains('\n') && !ws_suffix.contains('\n') { - if let Some(node) = ws.next_sibling_or_token() { - let start = match ws_prefix.rfind('\n') { - Some(idx) => ws.text_range().start() + TextUnit::from((idx + 1) as u32), - None => node.text_range().start(), - }; - let end = if root.text().char_at(node.text_range().end()) == Some('\n') { - node.text_range().end() + TextUnit::of_char('\n') - } else { - node.text_range().end() - }; - return TextRange::from_to(start, end); - } - } - ws.text_range() -} - -fn pick_best<'a>(l: SyntaxToken, r: SyntaxToken) -> SyntaxToken { - return if priority(&r) > priority(&l) { r } else { l }; - fn priority(n: &SyntaxToken) -> usize { - match n.kind() { - WHITESPACE => 0, - IDENT | T![self] | T![super] | T![crate] | LIFETIME => 2, - _ => 1, - } - } -} - -/// Extend list item selection to include nearby delimiter and whitespace. -fn extend_list_item(node: &SyntaxNode) -> Option { - fn is_single_line_ws(node: &SyntaxToken) -> bool { - node.kind() == WHITESPACE && !node.text().contains('\n') - } - - fn nearby_delimiter( - delimiter_kind: SyntaxKind, - node: &SyntaxNode, - dir: Direction, - ) -> Option { - node.siblings_with_tokens(dir) - .skip(1) - .skip_while(|node| match node { - NodeOrToken::Node(_) => false, - NodeOrToken::Token(it) => is_single_line_ws(it), - }) - .next() - .and_then(|it| it.into_token()) - .filter(|node| node.kind() == delimiter_kind) - } - - let delimiter = match node.kind() { - TYPE_BOUND => T![+], - _ => T![,], - }; - if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Prev) { - return Some(TextRange::from_to( - delimiter_node.text_range().start(), - node.text_range().end(), - )); - } - if let Some(delimiter_node) = nearby_delimiter(delimiter, node, Direction::Next) { - // Include any following whitespace when delimiter is after list item. - let final_node = delimiter_node - .next_sibling_or_token() - .and_then(|it| it.into_token()) - .filter(|node| is_single_line_ws(node)) - .unwrap_or(delimiter_node); - - return Some(TextRange::from_to(node.text_range().start(), final_node.text_range().end())); - } - - None -} - -fn extend_comments(comment: ast::Comment) -> Option { - let prev = adj_comments(&comment, Direction::Prev); - let next = adj_comments(&comment, Direction::Next); - if prev != next { - Some(TextRange::from_to( - prev.syntax().text_range().start(), - next.syntax().text_range().end(), - )) - } else { - None - } -} - -fn adj_comments(comment: &ast::Comment, dir: Direction) -> ast::Comment { - let mut res = comment.clone(); - for element in comment.syntax().siblings_with_tokens(dir) { - let token = match element.as_token() { - None => break, - Some(token) => token, - }; - if let Some(c) = ast::Comment::cast(token.clone()) { - res = c - } else if token.kind() != WHITESPACE || token.text().contains("\n\n") { - break; - } - } - res -} - -#[cfg(test)] -mod tests { - use ra_syntax::{AstNode, SourceFile}; - use test_utils::extract_offset; - - use super::*; - - fn do_check(before: &str, afters: &[&str]) { - let (cursor, before) = extract_offset(before); - let parse = SourceFile::parse(&before); - let mut range = TextRange::offset_len(cursor, 0.into()); - for &after in afters { - range = try_extend_selection(parse.tree().syntax(), range).unwrap(); - let actual = &before[range]; - assert_eq!(after, actual); - } - } - - #[test] - fn test_extend_selection_arith() { - do_check(r#"fn foo() { <|>1 + 1 }"#, &["1", "1 + 1", "{ 1 + 1 }"]); - } - - #[test] - fn test_extend_selection_list() { - do_check(r#"fn foo(<|>x: i32) {}"#, &["x", "x: i32"]); - do_check(r#"fn foo(<|>x: i32, y: i32) {}"#, &["x", "x: i32", "x: i32, "]); - do_check(r#"fn foo(<|>x: i32,y: i32) {}"#, &["x", "x: i32", "x: i32,"]); - do_check(r#"fn foo(x: i32, <|>y: i32) {}"#, &["y", "y: i32", ", y: i32"]); - do_check(r#"fn foo(x: i32, <|>y: i32, ) {}"#, &["y", "y: i32", ", y: i32"]); - do_check(r#"fn foo(x: i32,<|>y: i32) {}"#, &["y", "y: i32", ",y: i32"]); - - do_check(r#"const FOO: [usize; 2] = [ 22<|> , 33];"#, &["22", "22 , "]); - do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|>];"#, &["33", ", 33"]); - do_check(r#"const FOO: [usize; 2] = [ 22 , 33<|> ,];"#, &["33", ", 33"]); - - do_check(r#"fn main() { (1, 2<|>) }"#, &["2", ", 2", "(1, 2)"]); - - do_check( - r#" -const FOO: [usize; 2] = [ - 22, - <|>33, -]"#, - &["33", "33,"], - ); - - do_check( - r#" -const FOO: [usize; 2] = [ - 22 - , 33<|>, -]"#, - &["33", ", 33"], - ); - } - - #[test] - fn test_extend_selection_start_of_the_line() { - do_check( - r#" -impl S { -<|> fn foo() { - - } -}"#, - &[" fn foo() {\n\n }\n"], - ); - } - - #[test] - fn test_extend_selection_doc_comments() { - do_check( - r#" -struct A; - -/// bla -/// bla -struct B { - <|> -} - "#, - &["\n \n", "{\n \n}", "/// bla\n/// bla\nstruct B {\n \n}"], - ) - } - - #[test] - fn test_extend_selection_comments() { - do_check( - r#" -fn bar(){} - -// fn foo() { -// 1 + <|>1 -// } - -// fn foo(){} - "#, - &["1", "// 1 + 1", "// fn foo() {\n// 1 + 1\n// }"], - ); - - do_check( - r#" -// #[derive(Debug, Clone, Copy, PartialEq, Eq)] -// pub enum Direction { -// <|> Next, -// Prev -// } -"#, - &[ - "// Next,", - "// #[derive(Debug, Clone, Copy, PartialEq, Eq)]\n// pub enum Direction {\n// Next,\n// Prev\n// }", - ], - ); - - do_check( - r#" -/* -foo -_bar1<|>*/ -"#, - &["_bar1", "/*\nfoo\n_bar1*/"], - ); - - do_check(r#"//!<|>foo_2 bar"#, &["foo_2", "//!foo_2 bar"]); - - do_check(r#"/<|>/foo bar"#, &["//foo bar"]); - } - - #[test] - fn test_extend_selection_prefer_idents() { - do_check( - r#" -fn main() { foo<|>+bar;} -"#, - &["foo", "foo+bar"], - ); - do_check( - r#" -fn main() { foo+<|>bar;} -"#, - &["bar", "foo+bar"], - ); - } - - #[test] - fn test_extend_selection_prefer_lifetimes() { - do_check(r#"fn foo<<|>'a>() {}"#, &["'a", "<'a>"]); - do_check(r#"fn foo<'a<|>>() {}"#, &["'a", "<'a>"]); - } - - #[test] - fn test_extend_selection_select_first_word() { - do_check(r#"// foo bar b<|>az quxx"#, &["baz", "// foo bar baz quxx"]); - do_check( - r#" -impl S { -fn foo() { -// hel<|>lo world -} -} -"#, - &["hello", "// hello world"], - ); - } - - #[test] - fn test_extend_selection_string() { - do_check( - r#" -fn bar(){} - -" fn f<|>oo() {" -"#, - &["foo", "\" fn foo() {\""], - ); - } - - #[test] - fn test_extend_trait_bounds_list_in_where_clause() { - do_check( - r#" -fn foo() - where - R: req::Request + 'static, - R::Params: DeserializeOwned<|> + panic::UnwindSafe + 'static, - R::Result: Serialize + 'static, -"#, - &[ - "DeserializeOwned", - "DeserializeOwned + ", - "DeserializeOwned + panic::UnwindSafe + 'static", - "R::Params: DeserializeOwned + panic::UnwindSafe + 'static", - "R::Params: DeserializeOwned + panic::UnwindSafe + 'static,", - ], - ); - do_check(r#"fn foo() where T: <|>Copy"#, &["Copy"]); - do_check(r#"fn foo() where T: <|>Copy + Display"#, &["Copy", "Copy + "]); - do_check(r#"fn foo() where T: <|>Copy +Display"#, &["Copy", "Copy +"]); - do_check(r#"fn foo() where T: <|>Copy+Display"#, &["Copy", "Copy+"]); - do_check(r#"fn foo() where T: Copy + <|>Display"#, &["Display", "+ Display"]); - do_check(r#"fn foo() where T: Copy + <|>Display + Sync"#, &["Display", "+ Display"]); - do_check(r#"fn foo() where T: Copy +<|>Display"#, &["Display", "+Display"]); - } - - #[test] - fn test_extend_trait_bounds_list_inline() { - do_check(r#"fn fooCopy>() {}"#, &["Copy"]); - do_check(r#"fn fooCopy + Display>() {}"#, &["Copy", "Copy + "]); - do_check(r#"fn fooCopy +Display>() {}"#, &["Copy", "Copy +"]); - do_check(r#"fn fooCopy+Display>() {}"#, &["Copy", "Copy+"]); - do_check(r#"fn fooDisplay>() {}"#, &["Display", "+ Display"]); - do_check(r#"fn fooDisplay + Sync>() {}"#, &["Display", "+ Display"]); - do_check(r#"fn fooDisplay>() {}"#, &["Display", "+Display"]); - do_check( - r#"fn foo + Display, U: Copy>() {}"#, - &[ - "Copy", - "Copy + ", - "Copy + Display", - "T: Copy + Display", - "T: Copy + Display, ", - "", - ], - ); - } -} diff --git a/crates/ra_ide_api/src/feature_flags.rs b/crates/ra_ide_api/src/feature_flags.rs deleted file mode 100644 index de4ae513d..000000000 --- a/crates/ra_ide_api/src/feature_flags.rs +++ /dev/null @@ -1,70 +0,0 @@ -//! FIXME: write short doc here - -use rustc_hash::FxHashMap; - -/// Feature flags hold fine-grained toggles for all *user-visible* features of -/// rust-analyzer. -/// -/// The exists such that users are able to disable any annoying feature (and, -/// with many users and many features, some features are bound to be annoying -/// for some users) -/// -/// Note that we purposefully use run-time checked strings, and not something -/// checked at compile time, to keep things simple and flexible. -/// -/// Also note that, at the moment, `FeatureFlags` also store features for -/// `ra_lsp_server`. This should be benign layering violation. -#[derive(Debug)] -pub struct FeatureFlags { - flags: FxHashMap, -} - -impl FeatureFlags { - fn new(flags: &[(&str, bool)]) -> FeatureFlags { - let flags = flags - .iter() - .map(|&(name, value)| { - check_flag_name(name); - (name.to_string(), value) - }) - .collect(); - FeatureFlags { flags } - } - - pub fn set(&mut self, flag: &str, value: bool) -> Result<(), ()> { - match self.flags.get_mut(flag) { - None => Err(()), - Some(slot) => { - *slot = value; - Ok(()) - } - } - } - - pub fn get(&self, flag: &str) -> bool { - match self.flags.get(flag) { - None => panic!("unknown flag: {:?}", flag), - Some(value) => *value, - } - } -} - -impl Default for FeatureFlags { - fn default() -> FeatureFlags { - FeatureFlags::new(&[ - ("lsp.diagnostics", true), - ("completion.insertion.add-call-parenthesis", true), - ("completion.enable-postfix", true), - ("notifications.workspace-loaded", true), - ]) - } -} - -fn check_flag_name(flag: &str) { - for c in flag.bytes() { - match c { - b'a'..=b'z' | b'-' | b'.' => (), - _ => panic!("flag name does not match conventions: {:?}", flag), - } - } -} diff --git a/crates/ra_ide_api/src/folding_ranges.rs b/crates/ra_ide_api/src/folding_ranges.rs deleted file mode 100644 index 4eeb76d14..000000000 --- a/crates/ra_ide_api/src/folding_ranges.rs +++ /dev/null @@ -1,378 +0,0 @@ -//! FIXME: write short doc here - -use rustc_hash::FxHashSet; - -use ra_syntax::{ - ast::{self, AstNode, AstToken, VisibilityOwner}, - Direction, NodeOrToken, SourceFile, - SyntaxKind::{self, *}, - SyntaxNode, TextRange, -}; - -#[derive(Debug, PartialEq, Eq)] -pub enum FoldKind { - Comment, - Imports, - Mods, - Block, -} - -#[derive(Debug)] -pub struct Fold { - pub range: TextRange, - pub kind: FoldKind, -} - -pub(crate) fn folding_ranges(file: &SourceFile) -> Vec { - let mut res = vec![]; - let mut visited_comments = FxHashSet::default(); - let mut visited_imports = FxHashSet::default(); - let mut visited_mods = FxHashSet::default(); - - for element in file.syntax().descendants_with_tokens() { - // Fold items that span multiple lines - if let Some(kind) = fold_kind(element.kind()) { - let is_multiline = match &element { - NodeOrToken::Node(node) => node.text().contains_char('\n'), - NodeOrToken::Token(token) => token.text().contains('\n'), - }; - if is_multiline { - res.push(Fold { range: element.text_range(), kind }); - continue; - } - } - - match element { - NodeOrToken::Token(token) => { - // Fold groups of comments - if let Some(comment) = ast::Comment::cast(token) { - if !visited_comments.contains(&comment) { - if let Some(range) = - contiguous_range_for_comment(comment, &mut visited_comments) - { - res.push(Fold { range, kind: FoldKind::Comment }) - } - } - } - } - NodeOrToken::Node(node) => { - // Fold groups of imports - if node.kind() == USE_ITEM && !visited_imports.contains(&node) { - if let Some(range) = contiguous_range_for_group(&node, &mut visited_imports) { - res.push(Fold { range, kind: FoldKind::Imports }) - } - } - - // Fold groups of mods - if node.kind() == MODULE && !has_visibility(&node) && !visited_mods.contains(&node) - { - if let Some(range) = - contiguous_range_for_group_unless(&node, has_visibility, &mut visited_mods) - { - res.push(Fold { range, kind: FoldKind::Mods }) - } - } - } - } - } - - res -} - -fn fold_kind(kind: SyntaxKind) -> Option { - match kind { - COMMENT => Some(FoldKind::Comment), - USE_ITEM => Some(FoldKind::Imports), - RECORD_FIELD_DEF_LIST - | RECORD_FIELD_PAT_LIST - | ITEM_LIST - | EXTERN_ITEM_LIST - | USE_TREE_LIST - | BLOCK - | MATCH_ARM_LIST - | ENUM_VARIANT_LIST - | TOKEN_TREE => Some(FoldKind::Block), - _ => None, - } -} - -fn has_visibility(node: &SyntaxNode) -> bool { - ast::Module::cast(node.clone()).and_then(|m| m.visibility()).is_some() -} - -fn contiguous_range_for_group( - first: &SyntaxNode, - visited: &mut FxHashSet, -) -> Option { - contiguous_range_for_group_unless(first, |_| false, visited) -} - -fn contiguous_range_for_group_unless( - first: &SyntaxNode, - unless: impl Fn(&SyntaxNode) -> bool, - visited: &mut FxHashSet, -) -> Option { - visited.insert(first.clone()); - - let mut last = first.clone(); - for element in first.siblings_with_tokens(Direction::Next) { - let node = match element { - NodeOrToken::Token(token) => { - if let Some(ws) = ast::Whitespace::cast(token) { - if !ws.spans_multiple_lines() { - // Ignore whitespace without blank lines - continue; - } - } - // There is a blank line or another token, which means that the - // group ends here - break; - } - NodeOrToken::Node(node) => node, - }; - - // Stop if we find a node that doesn't belong to the group - if node.kind() != first.kind() || unless(&node) { - break; - } - - visited.insert(node.clone()); - last = node; - } - - if first != &last { - Some(TextRange::from_to(first.text_range().start(), last.text_range().end())) - } else { - // The group consists of only one element, therefore it cannot be folded - None - } -} - -fn contiguous_range_for_comment( - first: ast::Comment, - visited: &mut FxHashSet, -) -> Option { - visited.insert(first.clone()); - - // Only fold comments of the same flavor - let group_kind = first.kind(); - if !group_kind.shape.is_line() { - return None; - } - - let mut last = first.clone(); - for element in first.syntax().siblings_with_tokens(Direction::Next) { - match element { - NodeOrToken::Token(token) => { - if let Some(ws) = ast::Whitespace::cast(token.clone()) { - if !ws.spans_multiple_lines() { - // Ignore whitespace without blank lines - continue; - } - } - if let Some(c) = ast::Comment::cast(token) { - if c.kind() == group_kind { - visited.insert(c.clone()); - last = c; - continue; - } - } - // The comment group ends because either: - // * An element of a different kind was reached - // * A comment of a different flavor was reached - break; - } - NodeOrToken::Node(_) => break, - }; - } - - if first != last { - Some(TextRange::from_to( - first.syntax().text_range().start(), - last.syntax().text_range().end(), - )) - } else { - // The group consists of only one element, therefore it cannot be folded - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - use test_utils::extract_ranges; - - fn do_check(text: &str, fold_kinds: &[FoldKind]) { - let (ranges, text) = extract_ranges(text, "fold"); - let parse = SourceFile::parse(&text); - let folds = folding_ranges(&parse.tree()); - - assert_eq!( - folds.len(), - ranges.len(), - "The amount of folds is different than the expected amount" - ); - assert_eq!( - folds.len(), - fold_kinds.len(), - "The amount of fold kinds is different than the expected amount" - ); - for ((fold, range), fold_kind) in - folds.iter().zip(ranges.into_iter()).zip(fold_kinds.iter()) - { - assert_eq!(fold.range.start(), range.start()); - assert_eq!(fold.range.end(), range.end()); - assert_eq!(&fold.kind, fold_kind); - } - } - - #[test] - fn test_fold_comments() { - let text = r#" -// Hello -// this is a multiline -// comment -// - -// But this is not - -fn main() { - // We should - // also - // fold - // this one. - //! But this one is different - //! because it has another flavor - /* As does this - multiline comment */ -}"#; - - let fold_kinds = &[ - FoldKind::Comment, - FoldKind::Block, - FoldKind::Comment, - FoldKind::Comment, - FoldKind::Comment, - ]; - do_check(text, fold_kinds); - } - - #[test] - fn test_fold_imports() { - let text = r#" -use std::{ - str, - vec, - io as iop -}; - -fn main() { -}"#; - - let folds = &[FoldKind::Imports, FoldKind::Block, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_mods() { - let text = r#" - -pub mod foo; -mod after_pub; -mod after_pub_next; - -mod before_pub; -mod before_pub_next; -pub mod bar; - -mod not_folding_single; -pub mod foobar; -pub not_folding_single_next; - -#[cfg(test)] -mod with_attribute; -mod with_attribute_next; - -fn main() { -}"#; - - let folds = &[FoldKind::Mods, FoldKind::Mods, FoldKind::Mods, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_import_groups() { - let text = r#" -use std::str; -use std::vec; -use std::io as iop; - -use std::mem; -use std::f64; - -use std::collections::HashMap; -// Some random comment -use std::collections::VecDeque; - -fn main() { -}"#; - - let folds = &[FoldKind::Imports, FoldKind::Imports, FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_import_and_groups() { - let text = r#" -use std::str; -use std::vec; -use std::io as iop; - -use std::mem; -use std::f64; - -use std::collections::{ - HashMap, - VecDeque, -}; -// Some random comment - -fn main() { -}"#; - - let folds = &[ - FoldKind::Imports, - FoldKind::Imports, - FoldKind::Imports, - FoldKind::Block, - FoldKind::Block, - ]; - do_check(text, folds); - } - - #[test] - fn test_folds_macros() { - let text = r#" -macro_rules! foo { - ($($tt:tt)*) => { $($tt)* } -} -"#; - - let folds = &[FoldKind::Block]; - do_check(text, folds); - } - - #[test] - fn test_fold_match_arms() { - let text = r#" -fn main() { - match 0 { - 0 => 0, - _ => 1, - } -}"#; - - let folds = &[FoldKind::Block, FoldKind::Block]; - do_check(text, folds); - } -} diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs deleted file mode 100644 index c10a6c844..000000000 --- a/crates/ra_ide_api/src/goto_definition.rs +++ /dev/null @@ -1,696 +0,0 @@ -//! FIXME: write short doc here - -use hir::{db::AstDatabase, Source}; -use ra_syntax::{ - ast::{self, DocCommentsOwner}, - match_ast, AstNode, SyntaxNode, -}; - -use crate::{ - db::RootDatabase, - display::{ShortLabel, ToNav}, - expand::descend_into_macros, - references::{classify_name_ref, NameKind::*}, - FilePosition, NavigationTarget, RangeInfo, -}; - -pub(crate) fn goto_definition( - db: &RootDatabase, - position: FilePosition, -) -> Option>> { - let file = db.parse_or_expand(position.file_id.into())?; - let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; - let token = descend_into_macros(db, position.file_id, token); - - let res = match_ast! { - match (token.value.parent()) { - ast::NameRef(name_ref) => { - let navs = reference_definition(db, token.with_value(&name_ref)).to_vec(); - RangeInfo::new(name_ref.syntax().text_range(), navs.to_vec()) - }, - ast::Name(name) => { - let navs = name_definition(db, token.with_value(&name))?; - RangeInfo::new(name.syntax().text_range(), navs) - - }, - _ => return None, - } - }; - - Some(res) -} - -#[derive(Debug)] -pub(crate) enum ReferenceResult { - Exact(NavigationTarget), - Approximate(Vec), -} - -impl ReferenceResult { - fn to_vec(self) -> Vec { - use self::ReferenceResult::*; - match self { - Exact(target) => vec![target], - Approximate(vec) => vec, - } - } -} - -pub(crate) fn reference_definition( - db: &RootDatabase, - name_ref: Source<&ast::NameRef>, -) -> ReferenceResult { - use self::ReferenceResult::*; - - let name_kind = classify_name_ref(db, name_ref).map(|d| d.kind); - match name_kind { - Some(Macro(mac)) => return Exact(mac.to_nav(db)), - Some(Field(field)) => return Exact(field.to_nav(db)), - Some(AssocItem(assoc)) => return Exact(assoc.to_nav(db)), - Some(Def(def)) => match NavigationTarget::from_def(db, def) { - Some(nav) => return Exact(nav), - None => return Approximate(vec![]), - }, - Some(SelfType(imp)) => { - // FIXME: ideally, this should point to the type in the impl, and - // not at the whole impl. And goto **type** definition should bring - // us to the actual type - return Exact(imp.to_nav(db)); - } - Some(Local(local)) => return Exact(local.to_nav(db)), - Some(GenericParam(_)) => { - // FIXME: go to the generic param def - } - None => {} - }; - - // Fallback index based approach: - let navs = crate::symbol_index::index_resolve(db, name_ref.value) - .into_iter() - .map(|s| s.to_nav(db)) - .collect(); - Approximate(navs) -} - -pub(crate) fn name_definition( - db: &RootDatabase, - name: Source<&ast::Name>, -) -> Option> { - let parent = name.value.syntax().parent()?; - - if let Some(module) = ast::Module::cast(parent.clone()) { - if module.has_semi() { - let src = name.with_value(module); - if let Some(child_module) = hir::Module::from_declaration(db, src) { - let nav = child_module.to_nav(db); - return Some(vec![nav]); - } - } - } - - if let Some(nav) = named_target(db, name.with_value(&parent)) { - return Some(vec![nav]); - } - - None -} - -fn named_target(db: &RootDatabase, node: Source<&SyntaxNode>) -> Option { - match_ast! { - match (node.value) { - ast::StructDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::EnumDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::EnumVariant(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::FnDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::TypeAliasDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::ConstDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::StaticDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::TraitDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::RecordFieldDef(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::Module(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - it.short_label(), - )) - }, - ast::MacroCall(it) => { - Some(NavigationTarget::from_named( - db, - node.with_value(&it), - it.doc_comment_text(), - None, - )) - }, - _ => None, - } - } -} - -#[cfg(test)] -mod tests { - use test_utils::covers; - - use crate::mock_analysis::analysis_and_position; - - fn check_goto(fixture: &str, expected: &str) { - let (analysis, pos) = analysis_and_position(fixture); - - let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; - assert_eq!(navs.len(), 1); - let nav = navs.pop().unwrap(); - nav.assert_match(expected); - } - - #[test] - fn goto_definition_works_in_items() { - check_goto( - " - //- /lib.rs - struct Foo; - enum E { X(Foo<|>) } - ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", - ); - } - - #[test] - fn goto_definition_resolves_correct_name() { - check_goto( - " - //- /lib.rs - use a::Foo; - mod a; - mod b; - enum E { X(Foo<|>) } - //- /a.rs - struct Foo; - //- /b.rs - struct Foo; - ", - "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)", - ); - } - - #[test] - fn goto_definition_works_for_module_declaration() { - check_goto( - " - //- /lib.rs - mod <|>foo; - //- /foo.rs - // empty - ", - "foo SOURCE_FILE FileId(2) [0; 10)", - ); - - check_goto( - " - //- /lib.rs - mod <|>foo; - //- /foo/mod.rs - // empty - ", - "foo SOURCE_FILE FileId(2) [0; 10)", - ); - } - - #[test] - fn goto_definition_works_for_macros() { - covers!(goto_definition_works_for_macros); - check_goto( - " - //- /lib.rs - macro_rules! foo { - () => { - {} - }; - } - - fn bar() { - <|>foo!(); - } - ", - "foo MACRO_CALL FileId(1) [0; 50) [13; 16)", - ); - } - - #[test] - fn goto_definition_works_for_macros_from_other_crates() { - covers!(goto_definition_works_for_macros); - check_goto( - " - //- /lib.rs - use foo::foo; - fn bar() { - <|>foo!(); - } - - //- /foo/lib.rs - #[macro_export] - macro_rules! foo { - () => { - {} - }; - } - ", - "foo MACRO_CALL FileId(2) [0; 66) [29; 32)", - ); - } - - #[test] - fn goto_definition_works_for_macros_in_use_tree() { - check_goto( - " - //- /lib.rs - use foo::foo<|>; - - //- /foo/lib.rs - #[macro_export] - macro_rules! foo { - () => { - {} - }; - } - ", - "foo MACRO_CALL FileId(2) [0; 66) [29; 32)", - ); - } - - #[test] - fn goto_definition_works_for_macro_defined_fn_with_arg() { - check_goto( - " - //- /lib.rs - macro_rules! define_fn { - ($name:ident) => (fn $name() {}) - } - - define_fn!( - foo - ) - - fn bar() { - <|>foo(); - } - ", - "foo FN_DEF FileId(1) [80; 83) [80; 83)", - ); - } - - #[test] - fn goto_definition_works_for_macro_defined_fn_no_arg() { - check_goto( - " - //- /lib.rs - macro_rules! define_fn { - () => (fn foo() {}) - } - - define_fn!(); - - fn bar() { - <|>foo(); - } - ", - "foo FN_DEF FileId(1) [39; 42) [39; 42)", - ); - } - - #[test] - fn goto_definition_works_for_methods() { - covers!(goto_definition_works_for_methods); - check_goto( - " - //- /lib.rs - struct Foo; - impl Foo { - fn frobnicate(&self) { } - } - - fn bar(foo: &Foo) { - foo.frobnicate<|>(); - } - ", - "frobnicate FN_DEF FileId(1) [27; 52) [30; 40)", - ); - } - - #[test] - fn goto_definition_works_for_fields() { - covers!(goto_definition_works_for_fields); - check_goto( - " - //- /lib.rs - struct Foo { - spam: u32, - } - - fn bar(foo: &Foo) { - foo.spam<|>; - } - ", - "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)", - ); - } - - #[test] - fn goto_definition_works_for_record_fields() { - covers!(goto_definition_works_for_record_fields); - check_goto( - " - //- /lib.rs - struct Foo { - spam: u32, - } - - fn bar() -> Foo { - Foo { - spam<|>: 0, - } - } - ", - "spam RECORD_FIELD_DEF FileId(1) [17; 26) [17; 21)", - ); - } - - #[test] - fn goto_definition_works_for_ufcs_inherent_methods() { - check_goto( - " - //- /lib.rs - struct Foo; - impl Foo { - fn frobnicate() { } - } - - fn bar(foo: &Foo) { - Foo::frobnicate<|>(); - } - ", - "frobnicate FN_DEF FileId(1) [27; 47) [30; 40)", - ); - } - - #[test] - fn goto_definition_works_for_ufcs_trait_methods_through_traits() { - check_goto( - " - //- /lib.rs - trait Foo { - fn frobnicate(); - } - - fn bar() { - Foo::frobnicate<|>(); - } - ", - "frobnicate FN_DEF FileId(1) [16; 32) [19; 29)", - ); - } - - #[test] - fn goto_definition_works_for_ufcs_trait_methods_through_self() { - check_goto( - " - //- /lib.rs - struct Foo; - trait Trait { - fn frobnicate(); - } - impl Trait for Foo {} - - fn bar() { - Foo::frobnicate<|>(); - } - ", - "frobnicate FN_DEF FileId(1) [30; 46) [33; 43)", - ); - } - - #[test] - fn goto_definition_on_self() { - check_goto( - " - //- /lib.rs - struct Foo; - impl Foo { - pub fn new() -> Self { - Self<|> {} - } - } - ", - "impl IMPL_BLOCK FileId(1) [12; 73)", - ); - - check_goto( - " - //- /lib.rs - struct Foo; - impl Foo { - pub fn new() -> Self<|> { - Self {} - } - } - ", - "impl IMPL_BLOCK FileId(1) [12; 73)", - ); - - check_goto( - " - //- /lib.rs - enum Foo { A } - impl Foo { - pub fn new() -> Self<|> { - Foo::A - } - } - ", - "impl IMPL_BLOCK FileId(1) [15; 75)", - ); - - check_goto( - " - //- /lib.rs - enum Foo { A } - impl Foo { - pub fn thing(a: &Self<|>) { - } - } - ", - "impl IMPL_BLOCK FileId(1) [15; 62)", - ); - } - - #[test] - fn goto_definition_on_self_in_trait_impl() { - check_goto( - " - //- /lib.rs - struct Foo; - trait Make { - fn new() -> Self; - } - impl Make for Foo { - fn new() -> Self { - Self<|> {} - } - } - ", - "impl IMPL_BLOCK FileId(1) [49; 115)", - ); - - check_goto( - " - //- /lib.rs - struct Foo; - trait Make { - fn new() -> Self; - } - impl Make for Foo { - fn new() -> Self<|> { - Self {} - } - } - ", - "impl IMPL_BLOCK FileId(1) [49; 115)", - ); - } - - #[test] - fn goto_definition_works_when_used_on_definition_name_itself() { - check_goto( - " - //- /lib.rs - struct Foo<|> { value: u32 } - ", - "Foo STRUCT_DEF FileId(1) [0; 25) [7; 10)", - ); - - check_goto( - r#" - //- /lib.rs - struct Foo { - field<|>: string, - } - "#, - "field RECORD_FIELD_DEF FileId(1) [17; 30) [17; 22)", - ); - - check_goto( - " - //- /lib.rs - fn foo_test<|>() { - } - ", - "foo_test FN_DEF FileId(1) [0; 17) [3; 11)", - ); - - check_goto( - " - //- /lib.rs - enum Foo<|> { - Variant, - } - ", - "Foo ENUM_DEF FileId(1) [0; 25) [5; 8)", - ); - - check_goto( - " - //- /lib.rs - enum Foo { - Variant1, - Variant2<|>, - Variant3, - } - ", - "Variant2 ENUM_VARIANT FileId(1) [29; 37) [29; 37)", - ); - - check_goto( - r#" - //- /lib.rs - static inner<|>: &str = ""; - "#, - "inner STATIC_DEF FileId(1) [0; 24) [7; 12)", - ); - - check_goto( - r#" - //- /lib.rs - const inner<|>: &str = ""; - "#, - "inner CONST_DEF FileId(1) [0; 23) [6; 11)", - ); - - check_goto( - r#" - //- /lib.rs - type Thing<|> = Option<()>; - "#, - "Thing TYPE_ALIAS_DEF FileId(1) [0; 24) [5; 10)", - ); - - check_goto( - r#" - //- /lib.rs - trait Foo<|> { - } - "#, - "Foo TRAIT_DEF FileId(1) [0; 13) [6; 9)", - ); - - check_goto( - r#" - //- /lib.rs - mod bar<|> { - } - "#, - "bar MODULE FileId(1) [0; 11) [4; 7)", - ); - } - - #[test] - fn goto_from_macro() { - check_goto( - " - //- /lib.rs - macro_rules! id { - ($($tt:tt)*) => { $($tt)* } - } - fn foo() {} - id! { - fn bar() { - fo<|>o(); - } - } - ", - "foo FN_DEF FileId(1) [52; 63) [55; 58)", - ); - } -} diff --git a/crates/ra_ide_api/src/goto_type_definition.rs b/crates/ra_ide_api/src/goto_type_definition.rs deleted file mode 100644 index 992a08809..000000000 --- a/crates/ra_ide_api/src/goto_type_definition.rs +++ /dev/null @@ -1,105 +0,0 @@ -//! FIXME: write short doc here - -use hir::db::AstDatabase; -use ra_syntax::{ast, AstNode}; - -use crate::{ - db::RootDatabase, display::ToNav, expand::descend_into_macros, FilePosition, NavigationTarget, - RangeInfo, -}; - -pub(crate) fn goto_type_definition( - db: &RootDatabase, - position: FilePosition, -) -> Option>> { - let file = db.parse_or_expand(position.file_id.into())?; - let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; - let token = descend_into_macros(db, position.file_id, token); - - let node = token.value.ancestors().find_map(|token| { - token - .ancestors() - .find(|n| ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some()) - })?; - - let analyzer = hir::SourceAnalyzer::new(db, token.with_value(&node), None); - - let ty: hir::Type = if let Some(ty) = - ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) - { - ty - } else if let Some(ty) = ast::Pat::cast(node.clone()).and_then(|p| analyzer.type_of_pat(db, &p)) - { - ty - } else { - return None; - }; - - let adt_def = ty.autoderef(db).find_map(|ty| ty.as_adt())?; - - let nav = adt_def.to_nav(db); - Some(RangeInfo::new(node.text_range(), vec![nav])) -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::analysis_and_position; - - fn check_goto(fixture: &str, expected: &str) { - let (analysis, pos) = analysis_and_position(fixture); - - let mut navs = analysis.goto_type_definition(pos).unwrap().unwrap().info; - assert_eq!(navs.len(), 1); - let nav = navs.pop().unwrap(); - nav.assert_match(expected); - } - - #[test] - fn goto_type_definition_works_simple() { - check_goto( - " - //- /lib.rs - struct Foo; - fn foo() { - let f: Foo; - f<|> - } - ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", - ); - } - - #[test] - fn goto_type_definition_works_simple_ref() { - check_goto( - " - //- /lib.rs - struct Foo; - fn foo() { - let f: &Foo; - f<|> - } - ", - "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)", - ); - } - - #[test] - fn goto_type_definition_works_through_macro() { - check_goto( - " - //- /lib.rs - macro_rules! id { - ($($tt:tt)*) => { $($tt)* } - } - struct Foo {} - id! { - fn bar() { - let f<|> = Foo {}; - } - } - ", - "Foo STRUCT_DEF FileId(1) [52; 65) [59; 62)", - ); - } -} diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs deleted file mode 100644 index 260a7b869..000000000 --- a/crates/ra_ide_api/src/hover.rs +++ /dev/null @@ -1,730 +0,0 @@ -//! FIXME: write short doc here - -use hir::{db::AstDatabase, Adt, HasSource, HirDisplay}; -use ra_db::SourceDatabase; -use ra_syntax::{ - algo::find_covering_element, - ast::{self, DocCommentsOwner}, - match_ast, AstNode, -}; - -use crate::{ - db::RootDatabase, - display::{ - description_from_symbol, docs_from_symbol, macro_label, rust_code_markup, - rust_code_markup_with_doc, ShortLabel, - }, - expand::descend_into_macros, - references::{classify_name, classify_name_ref, NameKind, NameKind::*}, - FilePosition, FileRange, RangeInfo, -}; - -/// Contains the results when hovering over an item -#[derive(Debug, Clone)] -pub struct HoverResult { - results: Vec, - exact: bool, -} - -impl Default for HoverResult { - fn default() -> Self { - HoverResult::new() - } -} - -impl HoverResult { - pub fn new() -> HoverResult { - HoverResult { - results: Vec::new(), - // We assume exact by default - exact: true, - } - } - - pub fn extend(&mut self, item: Option) { - self.results.extend(item); - } - - pub fn is_exact(&self) -> bool { - self.exact - } - - pub fn is_empty(&self) -> bool { - self.results.is_empty() - } - - pub fn len(&self) -> usize { - self.results.len() - } - - pub fn first(&self) -> Option<&str> { - self.results.first().map(String::as_str) - } - - pub fn results(&self) -> &[String] { - &self.results - } - - /// Returns the results converted into markup - /// for displaying in a UI - pub fn to_markup(&self) -> String { - let mut markup = if !self.exact { - let mut msg = String::from("Failed to exactly resolve the symbol. This is probably because rust_analyzer does not yet support traits."); - if !self.results.is_empty() { - msg.push_str(" \nThese items were found instead:"); - } - msg.push_str("\n\n---\n"); - msg - } else { - String::new() - }; - - markup.push_str(&self.results.join("\n\n---\n")); - - markup - } -} - -fn hover_text(docs: Option, desc: Option) -> Option { - match (desc, docs) { - (Some(desc), docs) => Some(rust_code_markup_with_doc(desc, docs)), - (None, Some(docs)) => Some(docs), - _ => None, - } -} - -fn hover_text_from_name_kind( - db: &RootDatabase, - name_kind: NameKind, - no_fallback: &mut bool, -) -> Option { - return match name_kind { - Macro(it) => { - let src = it.source(db); - hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value))) - } - Field(it) => { - let src = it.source(db); - match src.value { - hir::FieldSource::Named(it) => hover_text(it.doc_comment_text(), it.short_label()), - _ => None, - } - } - AssocItem(it) => match it { - hir::AssocItem::Function(it) => from_def_source(db, it), - hir::AssocItem::Const(it) => from_def_source(db, it), - hir::AssocItem::TypeAlias(it) => from_def_source(db, it), - }, - Def(it) => match it { - hir::ModuleDef::Module(it) => match it.definition_source(db).value { - hir::ModuleSource::Module(it) => { - hover_text(it.doc_comment_text(), it.short_label()) - } - _ => None, - }, - hir::ModuleDef::Function(it) => from_def_source(db, it), - hir::ModuleDef::Adt(Adt::Struct(it)) => from_def_source(db, it), - hir::ModuleDef::Adt(Adt::Union(it)) => from_def_source(db, it), - hir::ModuleDef::Adt(Adt::Enum(it)) => from_def_source(db, it), - hir::ModuleDef::EnumVariant(it) => from_def_source(db, it), - hir::ModuleDef::Const(it) => from_def_source(db, it), - hir::ModuleDef::Static(it) => from_def_source(db, it), - hir::ModuleDef::Trait(it) => from_def_source(db, it), - hir::ModuleDef::TypeAlias(it) => from_def_source(db, it), - hir::ModuleDef::BuiltinType(it) => Some(it.to_string()), - }, - Local(_) => { - // Hover for these shows type names - *no_fallback = true; - None - } - GenericParam(_) | SelfType(_) => { - // FIXME: Hover for generic param - None - } - }; - - fn from_def_source(db: &RootDatabase, def: D) -> Option - where - D: HasSource, - A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, - { - let src = def.source(db); - hover_text(src.value.doc_comment_text(), src.value.short_label()) - } -} - -pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option> { - let file = db.parse_or_expand(position.file_id.into())?; - let token = file.token_at_offset(position.offset).filter(|it| !it.kind().is_trivia()).next()?; - let token = descend_into_macros(db, position.file_id, token); - - let mut res = HoverResult::new(); - - let mut range = match_ast! { - match (token.value.parent()) { - ast::NameRef(name_ref) => { - let mut no_fallback = false; - if let Some(name_kind) = - classify_name_ref(db, token.with_value(&name_ref)).map(|d| d.kind) - { - res.extend(hover_text_from_name_kind(db, name_kind, &mut no_fallback)) - } - - if res.is_empty() && !no_fallback { - // Fallback index based approach: - let symbols = crate::symbol_index::index_resolve(db, &name_ref); - for sym in symbols { - let docs = docs_from_symbol(db, &sym); - let desc = description_from_symbol(db, &sym); - res.extend(hover_text(docs, desc)); - } - } - - if !res.is_empty() { - Some(name_ref.syntax().text_range()) - } else { - None - } - }, - ast::Name(name) => { - if let Some(name_kind) = classify_name(db, token.with_value(&name)).map(|d| d.kind) { - res.extend(hover_text_from_name_kind(db, name_kind, &mut true)); - } - - if !res.is_empty() { - Some(name.syntax().text_range()) - } else { - None - } - }, - _ => None, - } - }; - - if range.is_none() { - let node = token.value.ancestors().find(|n| { - ast::Expr::cast(n.clone()).is_some() || ast::Pat::cast(n.clone()).is_some() - })?; - let frange = FileRange { file_id: position.file_id, range: node.text_range() }; - res.extend(type_of(db, frange).map(rust_code_markup)); - range = Some(node.text_range()); - }; - - let range = range?; - if res.is_empty() { - return None; - } - Some(RangeInfo::new(range, res)) -} - -pub(crate) fn type_of(db: &RootDatabase, frange: FileRange) -> Option { - let parse = db.parse(frange.file_id); - let leaf_node = find_covering_element(parse.tree().syntax(), frange.range); - // if we picked identifier, expand to pattern/expression - let node = leaf_node - .ancestors() - .take_while(|it| it.text_range() == leaf_node.text_range()) - .find(|it| ast::Expr::cast(it.clone()).is_some() || ast::Pat::cast(it.clone()).is_some())?; - let analyzer = - hir::SourceAnalyzer::new(db, hir::Source::new(frange.file_id.into(), &node), None); - let ty = if let Some(ty) = ast::Expr::cast(node.clone()).and_then(|e| analyzer.type_of(db, &e)) - { - ty - } else if let Some(ty) = ast::Pat::cast(node).and_then(|p| analyzer.type_of_pat(db, &p)) { - ty - } else { - return None; - }; - Some(ty.display(db).to_string()) -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::{ - analysis_and_position, single_file_with_position, single_file_with_range, - }; - use ra_syntax::TextRange; - - fn trim_markup(s: &str) -> &str { - s.trim_start_matches("```rust\n").trim_end_matches("\n```") - } - - fn trim_markup_opt(s: Option<&str>) -> Option<&str> { - s.map(trim_markup) - } - - fn check_hover_result(fixture: &str, expected: &[&str]) { - let (analysis, position) = analysis_and_position(fixture); - let hover = analysis.hover(position).unwrap().unwrap(); - let mut results = Vec::from(hover.info.results()); - results.sort(); - - for (markup, expected) in - results.iter().zip(expected.iter().chain(std::iter::repeat(&""))) - { - assert_eq!(trim_markup(&markup), *expected); - } - - assert_eq!(hover.info.len(), expected.len()); - } - - #[test] - fn hover_shows_type_of_an_expression() { - let (analysis, position) = single_file_with_position( - " - pub fn foo() -> u32 { 1 } - - fn main() { - let foo_test = foo()<|>; - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(hover.range, TextRange::from_to(95.into(), 100.into())); - assert_eq!(trim_markup_opt(hover.info.first()), Some("u32")); - } - - #[test] - fn hover_shows_fn_signature() { - // Single file with result - check_hover_result( - r#" - //- /main.rs - pub fn foo() -> u32 { 1 } - - fn main() { - let foo_test = fo<|>o(); - } - "#, - &["pub fn foo() -> u32"], - ); - - // Multiple results - check_hover_result( - r#" - //- /a.rs - pub fn foo() -> u32 { 1 } - - //- /b.rs - pub fn foo() -> &str { "" } - - //- /c.rs - pub fn foo(a: u32, b: u32) {} - - //- /main.rs - mod a; - mod b; - mod c; - - fn main() { - let foo_test = fo<|>o(); - } - "#, - &["pub fn foo() -> &str", "pub fn foo() -> u32", "pub fn foo(a: u32, b: u32)"], - ); - } - - #[test] - fn hover_shows_fn_signature_with_type_params() { - check_hover_result( - r#" - //- /main.rs - pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str { } - - fn main() { - let foo_test = fo<|>o(); - } - "#, - &["pub fn foo<'a, T: AsRef>(b: &'a T) -> &'a str"], - ); - } - - #[test] - fn hover_shows_fn_signature_on_fn_name() { - check_hover_result( - r#" - //- /main.rs - pub fn foo<|>(a: u32, b: u32) -> u32 {} - - fn main() { - } - "#, - &["pub fn foo(a: u32, b: u32) -> u32"], - ); - } - - #[test] - fn hover_shows_struct_field_info() { - // Hovering over the field when instantiating - check_hover_result( - r#" - //- /main.rs - struct Foo { - field_a: u32, - } - - fn main() { - let foo = Foo { - field_a<|>: 0, - }; - } - "#, - &["field_a: u32"], - ); - - // Hovering over the field in the definition - check_hover_result( - r#" - //- /main.rs - struct Foo { - field_a<|>: u32, - } - - fn main() { - let foo = Foo { - field_a: 0, - }; - } - "#, - &["field_a: u32"], - ); - } - - #[test] - fn hover_const_static() { - check_hover_result( - r#" - //- /main.rs - const foo<|>: u32 = 0; - "#, - &["const foo: u32"], - ); - - check_hover_result( - r#" - //- /main.rs - static foo<|>: u32 = 0; - "#, - &["static foo: u32"], - ); - } - - #[test] - fn hover_some() { - let (analysis, position) = single_file_with_position( - " - enum Option { Some(T) } - use Option::Some; - - fn main() { - So<|>me(12); - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Some")); - - let (analysis, position) = single_file_with_position( - " - enum Option { Some(T) } - use Option::Some; - - fn main() { - let b<|>ar = Some(12); - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Option")); - } - - #[test] - fn hover_enum_variant() { - check_hover_result( - r#" - //- /main.rs - enum Option { - /// The None variant - Non<|>e - } - "#, - &[" -None -``` - -The None variant - " - .trim()], - ); - - check_hover_result( - r#" - //- /main.rs - enum Option { - /// The Some variant - Some(T) - } - fn main() { - let s = Option::Som<|>e(12); - } - "#, - &[" -Some -``` - -The Some variant - " - .trim()], - ); - } - - #[test] - fn hover_for_local_variable() { - let (analysis, position) = single_file_with_position("fn func(foo: i32) { fo<|>o; }"); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); - } - - #[test] - fn hover_for_local_variable_pat() { - let (analysis, position) = single_file_with_position("fn func(fo<|>o: i32) {}"); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); - } - - #[test] - fn hover_local_var_edge() { - let (analysis, position) = single_file_with_position( - " -fn func(foo: i32) { if true { <|>foo; }; } -", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); - } - - #[test] - fn test_type_of_for_function() { - let (analysis, range) = single_file_with_range( - " - pub fn foo() -> u32 { 1 }; - - fn main() { - let foo_test = <|>foo()<|>; - } - ", - ); - - let type_name = analysis.type_of(range).unwrap().unwrap(); - assert_eq!("u32", &type_name); - } - - #[test] - fn test_type_of_for_expr() { - let (analysis, range) = single_file_with_range( - " - fn main() { - let foo: usize = 1; - let bar = <|>1 + foo<|>; - } - ", - ); - - let type_name = analysis.type_of(range).unwrap().unwrap(); - assert_eq!("usize", &type_name); - } - - #[test] - fn test_hover_infer_associated_method_result() { - let (analysis, position) = single_file_with_position( - " - struct Thing { x: u32 } - - impl Thing { - fn new() -> Thing { - Thing { x: 0 } - } - } - - fn main() { - let foo_<|>test = Thing::new(); - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); - } - - #[test] - fn test_hover_infer_associated_method_exact() { - let (analysis, position) = single_file_with_position( - " - struct Thing { x: u32 } - - impl Thing { - fn new() -> Thing { - Thing { x: 0 } - } - } - - fn main() { - let foo_test = Thing::new<|>(); - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("fn new() -> Thing")); - assert_eq!(hover.info.is_exact(), true); - } - - #[test] - fn test_hover_infer_associated_const_in_pattern() { - let (analysis, position) = single_file_with_position( - " - struct X; - impl X { - const C: u32 = 1; - } - - fn main() { - match 1 { - X::C<|> => {}, - 2 => {}, - _ => {} - }; - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("const C: u32")); - assert_eq!(hover.info.is_exact(), true); - } - - #[test] - fn test_hover_self() { - let (analysis, position) = single_file_with_position( - " - struct Thing { x: u32 } - impl Thing { - fn new() -> Self { - Self<|> { x: 0 } - } - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); - assert_eq!(hover.info.is_exact(), true); - - /* FIXME: revive these tests - let (analysis, position) = single_file_with_position( - " - struct Thing { x: u32 } - impl Thing { - fn new() -> Self<|> { - Self { x: 0 } - } - } - ", - ); - - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("Thing")); - assert_eq!(hover.info.is_exact(), true); - - let (analysis, position) = single_file_with_position( - " - enum Thing { A } - impl Thing { - pub fn new() -> Self<|> { - Thing::A - } - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); - assert_eq!(hover.info.is_exact(), true); - - let (analysis, position) = single_file_with_position( - " - enum Thing { A } - impl Thing { - pub fn thing(a: Self<|>) { - } - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("enum Thing")); - assert_eq!(hover.info.is_exact(), true); - */ - } - - #[test] - fn test_hover_shadowing_pat() { - let (analysis, position) = single_file_with_position( - " - fn x() {} - - fn y() { - let x = 0i32; - x<|>; - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); - assert_eq!(hover.info.is_exact(), true); - } - - #[test] - fn test_hover_macro_invocation() { - let (analysis, position) = single_file_with_position( - " - macro_rules! foo { - () => {} - } - - fn f() { - fo<|>o!(); - } - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("macro_rules! foo")); - assert_eq!(hover.info.is_exact(), true); - } - - #[test] - fn test_hover_tuple_field() { - let (analysis, position) = single_file_with_position( - " - struct TS(String, i32<|>); - ", - ); - let hover = analysis.hover(position).unwrap().unwrap(); - assert_eq!(trim_markup_opt(hover.info.first()), Some("i32")); - assert_eq!(hover.info.is_exact(), true); - } - - #[test] - fn test_hover_through_macro() { - check_hover_result( - " - //- /lib.rs - macro_rules! id { - ($($tt:tt)*) => { $($tt)* } - } - fn foo() {} - id! { - fn bar() { - fo<|>o(); - } - } - ", - &["fn foo()"], - ); - } -} diff --git a/crates/ra_ide_api/src/impls.rs b/crates/ra_ide_api/src/impls.rs deleted file mode 100644 index aa480e399..000000000 --- a/crates/ra_ide_api/src/impls.rs +++ /dev/null @@ -1,206 +0,0 @@ -//! FIXME: write short doc here - -use hir::{FromSource, ImplBlock}; -use ra_db::SourceDatabase; -use ra_syntax::{algo::find_node_at_offset, ast, AstNode}; - -use crate::{db::RootDatabase, display::ToNav, FilePosition, NavigationTarget, RangeInfo}; - -pub(crate) fn goto_implementation( - db: &RootDatabase, - position: FilePosition, -) -> Option>> { - let parse = db.parse(position.file_id); - let syntax = parse.tree().syntax().clone(); - - let src = hir::ModuleSource::from_position(db, position); - let module = hir::Module::from_definition( - db, - hir::Source { file_id: position.file_id.into(), value: src }, - )?; - - if let Some(nominal_def) = find_node_at_offset::(&syntax, position.offset) { - return Some(RangeInfo::new( - nominal_def.syntax().text_range(), - impls_for_def(db, position, &nominal_def, module)?, - )); - } else if let Some(trait_def) = find_node_at_offset::(&syntax, position.offset) { - return Some(RangeInfo::new( - trait_def.syntax().text_range(), - impls_for_trait(db, position, &trait_def, module)?, - )); - } - - None -} - -fn impls_for_def( - db: &RootDatabase, - position: FilePosition, - node: &ast::NominalDef, - module: hir::Module, -) -> Option> { - let ty = match node { - ast::NominalDef::StructDef(def) => { - let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; - hir::Struct::from_source(db, src)?.ty(db) - } - ast::NominalDef::EnumDef(def) => { - let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; - hir::Enum::from_source(db, src)?.ty(db) - } - ast::NominalDef::UnionDef(def) => { - let src = hir::Source { file_id: position.file_id.into(), value: def.clone() }; - hir::Union::from_source(db, src)?.ty(db) - } - }; - - let krate = module.krate(); - let impls = ImplBlock::all_in_crate(db, krate); - - Some( - impls - .into_iter() - .filter(|impl_block| ty.is_equal_for_find_impls(&impl_block.target_ty(db))) - .map(|imp| imp.to_nav(db)) - .collect(), - ) -} - -fn impls_for_trait( - db: &RootDatabase, - position: FilePosition, - node: &ast::TraitDef, - module: hir::Module, -) -> Option> { - let src = hir::Source { file_id: position.file_id.into(), value: node.clone() }; - let tr = hir::Trait::from_source(db, src)?; - - let krate = module.krate(); - let impls = ImplBlock::for_trait(db, krate, tr); - - Some(impls.into_iter().map(|imp| imp.to_nav(db)).collect()) -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::analysis_and_position; - - fn check_goto(fixture: &str, expected: &[&str]) { - let (analysis, pos) = analysis_and_position(fixture); - - let mut navs = analysis.goto_implementation(pos).unwrap().unwrap().info; - assert_eq!(navs.len(), expected.len()); - navs.sort_by_key(|nav| (nav.file_id(), nav.full_range().start())); - navs.into_iter().enumerate().for_each(|(i, nav)| nav.assert_match(expected[i])); - } - - #[test] - fn goto_implementation_works() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - impl Foo {} - ", - &["impl IMPL_BLOCK FileId(1) [12; 23)"], - ); - } - - #[test] - fn goto_implementation_works_multiple_blocks() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - impl Foo {} - impl Foo {} - ", - &["impl IMPL_BLOCK FileId(1) [12; 23)", "impl IMPL_BLOCK FileId(1) [24; 35)"], - ); - } - - #[test] - fn goto_implementation_works_multiple_mods() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - mod a { - impl super::Foo {} - } - mod b { - impl super::Foo {} - } - ", - &["impl IMPL_BLOCK FileId(1) [24; 42)", "impl IMPL_BLOCK FileId(1) [57; 75)"], - ); - } - - #[test] - fn goto_implementation_works_multiple_files() { - check_goto( - " - //- /lib.rs - struct Foo<|>; - mod a; - mod b; - //- /a.rs - impl crate::Foo {} - //- /b.rs - impl crate::Foo {} - ", - &["impl IMPL_BLOCK FileId(2) [0; 18)", "impl IMPL_BLOCK FileId(3) [0; 18)"], - ); - } - - #[test] - fn goto_implementation_for_trait() { - check_goto( - " - //- /lib.rs - trait T<|> {} - struct Foo; - impl T for Foo {} - ", - &["impl IMPL_BLOCK FileId(1) [23; 40)"], - ); - } - - #[test] - fn goto_implementation_for_trait_multiple_files() { - check_goto( - " - //- /lib.rs - trait T<|> {}; - struct Foo; - mod a; - mod b; - //- /a.rs - impl crate::T for crate::Foo {} - //- /b.rs - impl crate::T for crate::Foo {} - ", - &["impl IMPL_BLOCK FileId(2) [0; 31)", "impl IMPL_BLOCK FileId(3) [0; 31)"], - ); - } - - #[test] - fn goto_implementation_all_impls() { - check_goto( - " - //- /lib.rs - trait T {} - struct Foo<|>; - impl Foo {} - impl T for Foo {} - impl T for &Foo {} - ", - &[ - "impl IMPL_BLOCK FileId(1) [23; 34)", - "impl IMPL_BLOCK FileId(1) [35; 52)", - "impl IMPL_BLOCK FileId(1) [53; 71)", - ], - ); - } -} diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs deleted file mode 100644 index 45149bf0c..000000000 --- a/crates/ra_ide_api/src/inlay_hints.rs +++ /dev/null @@ -1,543 +0,0 @@ -//! FIXME: write short doc here - -use crate::{db::RootDatabase, FileId}; -use hir::{HirDisplay, SourceAnalyzer}; -use ra_syntax::{ - ast::{self, AstNode, TypeAscriptionOwner}, - match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, -}; - -#[derive(Debug, PartialEq, Eq)] -pub enum InlayKind { - TypeHint, -} - -#[derive(Debug)] -pub struct InlayHint { - pub range: TextRange, - pub kind: InlayKind, - pub label: SmolStr, -} - -pub(crate) fn inlay_hints( - db: &RootDatabase, - file_id: FileId, - file: &SourceFile, - max_inlay_hint_length: Option, -) -> Vec { - file.syntax() - .descendants() - .map(|node| get_inlay_hints(db, file_id, &node, max_inlay_hint_length).unwrap_or_default()) - .flatten() - .collect() -} - -fn get_inlay_hints( - db: &RootDatabase, - file_id: FileId, - node: &SyntaxNode, - max_inlay_hint_length: Option, -) -> Option> { - let analyzer = SourceAnalyzer::new(db, hir::Source::new(file_id.into(), node), None); - match_ast! { - match node { - ast::LetStmt(it) => { - if it.ascribed_type().is_some() { - return None; - } - let pat = it.pat()?; - Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length)) - }, - ast::LambdaExpr(it) => { - it.param_list().map(|param_list| { - param_list - .params() - .filter(|closure_param| closure_param.ascribed_type().is_none()) - .filter_map(|closure_param| closure_param.pat()) - .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false, max_inlay_hint_length)) - .flatten() - .collect() - }) - }, - ast::ForExpr(it) => { - let pat = it.pat()?; - Some(get_pat_type_hints(db, &analyzer, pat, false, max_inlay_hint_length)) - }, - ast::IfExpr(it) => { - let pat = it.condition()?.pat()?; - Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length)) - }, - ast::WhileExpr(it) => { - let pat = it.condition()?.pat()?; - Some(get_pat_type_hints(db, &analyzer, pat, true, max_inlay_hint_length)) - }, - ast::MatchArmList(it) => { - Some( - it - .arms() - .map(|match_arm| match_arm.pats()) - .flatten() - .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true, max_inlay_hint_length)) - .flatten() - .collect(), - ) - }, - _ => None, - } - } -} - -fn get_pat_type_hints( - db: &RootDatabase, - analyzer: &SourceAnalyzer, - root_pat: ast::Pat, - skip_root_pat_hint: bool, - max_inlay_hint_length: Option, -) -> Vec { - let original_pat = &root_pat.clone(); - - get_leaf_pats(root_pat) - .into_iter() - .filter(|pat| !skip_root_pat_hint || pat != original_pat) - .filter_map(|pat| { - let ty = analyzer.type_of_pat(db, &pat)?; - if ty.is_unknown() { - return None; - } - Some((pat.syntax().text_range(), ty)) - }) - .map(|(range, pat_type)| InlayHint { - range, - kind: InlayKind::TypeHint, - label: pat_type.display_truncated(db, max_inlay_hint_length).to_string().into(), - }) - .collect() -} - -fn get_leaf_pats(root_pat: ast::Pat) -> Vec { - let mut pats_to_process = std::collections::VecDeque::::new(); - pats_to_process.push_back(root_pat); - - let mut leaf_pats = Vec::new(); - - while let Some(maybe_leaf_pat) = pats_to_process.pop_front() { - match &maybe_leaf_pat { - ast::Pat::BindPat(bind_pat) => { - if let Some(pat) = bind_pat.pat() { - pats_to_process.push_back(pat); - } else { - leaf_pats.push(maybe_leaf_pat); - } - } - ast::Pat::TuplePat(tuple_pat) => { - for arg_pat in tuple_pat.args() { - pats_to_process.push_back(arg_pat); - } - } - ast::Pat::RecordPat(record_pat) => { - if let Some(pat_list) = record_pat.record_field_pat_list() { - pats_to_process.extend( - pat_list - .record_field_pats() - .filter_map(|record_field_pat| { - record_field_pat - .pat() - .filter(|pat| pat.syntax().kind() != SyntaxKind::BIND_PAT) - }) - .chain(pat_list.bind_pats().map(|bind_pat| { - bind_pat.pat().unwrap_or_else(|| ast::Pat::from(bind_pat)) - })), - ); - } - } - ast::Pat::TupleStructPat(tuple_struct_pat) => { - for arg_pat in tuple_struct_pat.args() { - pats_to_process.push_back(arg_pat); - } - } - _ => (), - } - } - leaf_pats -} - -#[cfg(test)] -mod tests { - use crate::mock_analysis::single_file; - use insta::assert_debug_snapshot; - - #[test] - fn let_statement() { - let (analysis, file_id) = single_file( - r#" -#[derive(PartialEq)] -enum CustomOption { - None, - Some(T), -} - -#[derive(PartialEq)] -struct Test { - a: CustomOption, - b: u8, -} - -fn main() { - struct InnerStruct {} - - let test = 54; - let test: i32 = 33; - let mut test = 33; - let _ = 22; - let test = "test"; - let test = InnerStruct {}; - - let test = vec![222]; - let test: Vec<_> = (0..3).collect(); - let test = (0..3).collect::>(); - let test = (0..3).collect::>(); - - let mut test = Vec::new(); - test.push(333); - - let test = (42, 'a'); - let (a, (b, c, (d, e), f)) = (2, (3, 4, (6.6, 7.7), 5)); -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" - [ - InlayHint { - range: [193; 197), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [236; 244), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [275; 279), - kind: TypeHint, - label: "&str", - }, - InlayHint { - range: [539; 543), - kind: TypeHint, - label: "(i32, char)", - }, - InlayHint { - range: [566; 567), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [570; 571), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [573; 574), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [584; 585), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [577; 578), - kind: TypeHint, - label: "f64", - }, - InlayHint { - range: [580; 581), - kind: TypeHint, - label: "f64", - }, - ] - "### - ); - } - - #[test] - fn closure_parameter() { - let (analysis, file_id) = single_file( - r#" -fn main() { - let mut start = 0; - (0..2).for_each(|increment| { - start += increment; - }) -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" - [ - InlayHint { - range: [21; 30), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [57; 66), - kind: TypeHint, - label: "i32", - }, - ] - "### - ); - } - - #[test] - fn for_expression() { - let (analysis, file_id) = single_file( - r#" -fn main() { - let mut start = 0; - for increment in 0..2 { - start += increment; - } -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" - [ - InlayHint { - range: [21; 30), - kind: TypeHint, - label: "i32", - }, - InlayHint { - range: [44; 53), - kind: TypeHint, - label: "i32", - }, - ] - "### - ); - } - - #[test] - fn if_expr() { - let (analysis, file_id) = single_file( - r#" -#[derive(PartialEq)] -enum CustomOption { - None, - Some(T), -} - -#[derive(PartialEq)] -struct Test { - a: CustomOption, - b: u8, -} - -fn main() { - let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }); - if let CustomOption::None = &test {}; - if let test = &test {}; - if let CustomOption::Some(test) = &test {}; - if let CustomOption::Some(Test { a, b }) = &test {}; - if let CustomOption::Some(Test { a: x, b: y }) = &test {}; - if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {}; - if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {}; - if let CustomOption::Some(Test { b: y, .. }) = &test {}; - - if test == CustomOption::None {} -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" - [ - InlayHint { - range: [166; 170), - kind: TypeHint, - label: "CustomOption", - }, - InlayHint { - range: [334; 338), - kind: TypeHint, - label: "&Test", - }, - InlayHint { - range: [389; 390), - kind: TypeHint, - label: "&CustomOption", - }, - InlayHint { - range: [392; 393), - kind: TypeHint, - label: "&u8", - }, - InlayHint { - range: [531; 532), - kind: TypeHint, - label: "&u32", - }, - ] - "### - ); - } - - #[test] - fn while_expr() { - let (analysis, file_id) = single_file( - r#" -#[derive(PartialEq)] -enum CustomOption { - None, - Some(T), -} - -#[derive(PartialEq)] -struct Test { - a: CustomOption, - b: u8, -} - -fn main() { - let test = CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }); - while let CustomOption::None = &test {}; - while let test = &test {}; - while let CustomOption::Some(test) = &test {}; - while let CustomOption::Some(Test { a, b }) = &test {}; - while let CustomOption::Some(Test { a: x, b: y }) = &test {}; - while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {}; - while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {}; - while let CustomOption::Some(Test { b: y, .. }) = &test {}; - - while test == CustomOption::None {} -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" - [ - InlayHint { - range: [166; 170), - kind: TypeHint, - label: "CustomOption", - }, - InlayHint { - range: [343; 347), - kind: TypeHint, - label: "&Test", - }, - InlayHint { - range: [401; 402), - kind: TypeHint, - label: "&CustomOption", - }, - InlayHint { - range: [404; 405), - kind: TypeHint, - label: "&u8", - }, - InlayHint { - range: [549; 550), - kind: TypeHint, - label: "&u32", - }, - ] - "### - ); - } - - #[test] - fn match_arm_list() { - let (analysis, file_id) = single_file( - r#" -#[derive(PartialEq)] -enum CustomOption { - None, - Some(T), -} - -#[derive(PartialEq)] -struct Test { - a: CustomOption, - b: u8, -} - -fn main() { - match CustomOption::Some(Test { a: CustomOption::Some(3), b: 1 }) { - CustomOption::None => (), - test => (), - CustomOption::Some(test) => (), - CustomOption::Some(Test { a, b }) => (), - CustomOption::Some(Test { a: x, b: y }) => (), - CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) => (), - CustomOption::Some(Test { a: CustomOption::None, b: y }) => (), - CustomOption::Some(Test { b: y, .. }) => (), - _ => {} - } -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###" - [ - InlayHint { - range: [311; 315), - kind: TypeHint, - label: "Test", - }, - InlayHint { - range: [358; 359), - kind: TypeHint, - label: "CustomOption", - }, - InlayHint { - range: [361; 362), - kind: TypeHint, - label: "u8", - }, - InlayHint { - range: [484; 485), - kind: TypeHint, - label: "u32", - }, - ] - "### - ); - } - - #[test] - fn hint_truncation() { - let (analysis, file_id) = single_file( - r#" -struct Smol(T); - -struct VeryLongOuterName(T); - -fn main() { - let a = Smol(0u32); - let b = VeryLongOuterName(0usize); - let c = Smol(Smol(0u32)) -}"#, - ); - - assert_debug_snapshot!(analysis.inlay_hints(file_id, Some(8)).unwrap(), @r###" - [ - InlayHint { - range: [74; 75), - kind: TypeHint, - label: "Smol", - }, - InlayHint { - range: [98; 99), - kind: TypeHint, - label: "VeryLongOuterName<…>", - }, - InlayHint { - range: [137; 138), - kind: TypeHint, - label: "Smol>", - }, - ] - "### - ); - } -} diff --git a/crates/ra_ide_api/src/join_lines.rs b/crates/ra_ide_api/src/join_lines.rs deleted file mode 100644 index 7deeb3494..000000000 --- a/crates/ra_ide_api/src/join_lines.rs +++ /dev/null @@ -1,611 +0,0 @@ -//! FIXME: write short doc here - -use itertools::Itertools; -use ra_fmt::{compute_ws, extract_trivial_expression}; -use ra_syntax::{ - algo::{find_covering_element, non_trivia_sibling}, - ast::{self, AstNode, AstToken}, - Direction, NodeOrToken, SourceFile, - SyntaxKind::{self, WHITESPACE}, - SyntaxNode, SyntaxToken, TextRange, TextUnit, T, -}; -use ra_text_edit::{TextEdit, TextEditBuilder}; - -pub fn join_lines(file: &SourceFile, range: TextRange) -> TextEdit { - let range = if range.is_empty() { - let syntax = file.syntax(); - let text = syntax.text().slice(range.start()..); - let pos = match text.find_char('\n') { - None => return TextEditBuilder::default().finish(), - Some(pos) => pos, - }; - TextRange::offset_len(range.start() + pos, TextUnit::of_char('\n')) - } else { - range - }; - - let node = match find_covering_element(file.syntax(), range) { - NodeOrToken::Node(node) => node, - NodeOrToken::Token(token) => token.parent(), - }; - let mut edit = TextEditBuilder::default(); - for token in node.descendants_with_tokens().filter_map(|it| it.into_token()) { - let range = match range.intersection(&token.text_range()) { - Some(range) => range, - None => continue, - } - token.text_range().start(); - let text = token.text(); - for (pos, _) in text[range].bytes().enumerate().filter(|&(_, b)| b == b'\n') { - let pos: TextUnit = (pos as u32).into(); - let off = token.text_range().start() + range.start() + pos; - if !edit.invalidates_offset(off) { - remove_newline(&mut edit, &token, off); - } - } - } - - edit.finish() -} - -fn remove_newline(edit: &mut TextEditBuilder, token: &SyntaxToken, offset: TextUnit) { - if token.kind() != WHITESPACE || token.text().bytes().filter(|&b| b == b'\n').count() != 1 { - // The node is either the first or the last in the file - let suff = &token.text()[TextRange::from_to( - offset - token.text_range().start() + TextUnit::of_char('\n'), - TextUnit::of_str(token.text()), - )]; - let spaces = suff.bytes().take_while(|&b| b == b' ').count(); - - edit.replace(TextRange::offset_len(offset, ((spaces + 1) as u32).into()), " ".to_string()); - return; - } - - // Special case that turns something like: - // - // ``` - // my_function({<|> - // - // }) - // ``` - // - // into `my_function()` - if join_single_expr_block(edit, token).is_some() { - return; - } - // ditto for - // - // ``` - // use foo::{<|> - // bar - // }; - // ``` - if join_single_use_tree(edit, token).is_some() { - return; - } - - // The node is between two other nodes - let prev = token.prev_sibling_or_token().unwrap(); - let next = token.next_sibling_or_token().unwrap(); - if is_trailing_comma(prev.kind(), next.kind()) { - // Removes: trailing comma, newline (incl. surrounding whitespace) - edit.delete(TextRange::from_to(prev.text_range().start(), token.text_range().end())); - } else if prev.kind() == T![,] && next.kind() == T!['}'] { - // Removes: comma, newline (incl. surrounding whitespace) - let space = if let Some(left) = prev.prev_sibling_or_token() { - compute_ws(left.kind(), next.kind()) - } else { - " " - }; - edit.replace( - TextRange::from_to(prev.text_range().start(), token.text_range().end()), - space.to_string(), - ); - } else if let (Some(_), Some(next)) = ( - prev.as_token().cloned().and_then(ast::Comment::cast), - next.as_token().cloned().and_then(ast::Comment::cast), - ) { - // Removes: newline (incl. surrounding whitespace), start of the next comment - edit.delete(TextRange::from_to( - token.text_range().start(), - next.syntax().text_range().start() + TextUnit::of_str(next.prefix()), - )); - } else { - // Remove newline but add a computed amount of whitespace characters - edit.replace(token.text_range(), compute_ws(prev.kind(), next.kind()).to_string()); - } -} - -fn has_comma_after(node: &SyntaxNode) -> bool { - match non_trivia_sibling(node.clone().into(), Direction::Next) { - Some(n) => n.kind() == T![,], - _ => false, - } -} - -fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { - let block = ast::Block::cast(token.parent())?; - let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?; - let expr = extract_trivial_expression(&block_expr)?; - - let block_range = block_expr.syntax().text_range(); - let mut buf = expr.syntax().text().to_string(); - - // Match block needs to have a comma after the block - if let Some(match_arm) = block_expr.syntax().parent().and_then(ast::MatchArm::cast) { - if !has_comma_after(match_arm.syntax()) { - buf.push(','); - } - } - - edit.replace(block_range, buf); - - Some(()) -} - -fn join_single_use_tree(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { - let use_tree_list = ast::UseTreeList::cast(token.parent())?; - let (tree,) = use_tree_list.use_trees().collect_tuple()?; - edit.replace(use_tree_list.syntax().text_range(), tree.syntax().text().to_string()); - Some(()) -} - -fn is_trailing_comma(left: SyntaxKind, right: SyntaxKind) -> bool { - match (left, right) { - (T![,], T![')']) | (T![,], T![']']) => true, - _ => false, - } -} - -#[cfg(test)] -mod tests { - use crate::test_utils::{assert_eq_text, check_action, extract_range}; - - use super::*; - - fn check_join_lines(before: &str, after: &str) { - check_action(before, after, |file, offset| { - let range = TextRange::offset_len(offset, 0.into()); - let res = join_lines(file, range); - Some(res) - }) - } - - #[test] - fn test_join_lines_comma() { - check_join_lines( - r" -fn foo() { - <|>foo(1, - ) -} -", - r" -fn foo() { - <|>foo(1) -} -", - ); - } - - #[test] - fn test_join_lines_lambda_block() { - check_join_lines( - r" -pub fn reparse(&self, edit: &AtomTextEdit) -> File { - <|>self.incremental_reparse(edit).unwrap_or_else(|| { - self.full_reparse(edit) - }) -} -", - r" -pub fn reparse(&self, edit: &AtomTextEdit) -> File { - <|>self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit)) -} -", - ); - } - - #[test] - fn test_join_lines_block() { - check_join_lines( - r" -fn foo() { - foo(<|>{ - 92 - }) -}", - r" -fn foo() { - foo(<|>92) -}", - ); - } - - #[test] - fn join_lines_adds_comma_for_block_in_match_arm() { - check_join_lines( - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>{ - u.foo() - } - Err(v) => v, - } -}", - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>u.foo(), - Err(v) => v, - } -}", - ); - } - - #[test] - fn join_lines_multiline_in_block() { - check_join_lines( - r" -fn foo() { - match ty { - <|> Some(ty) => { - match ty { - _ => false, - } - } - _ => true, - } -} -", - r" -fn foo() { - match ty { - <|> Some(ty) => match ty { - _ => false, - }, - _ => true, - } -} -", - ); - } - - #[test] - fn join_lines_keeps_comma_for_block_in_match_arm() { - // We already have a comma - check_join_lines( - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>{ - u.foo() - }, - Err(v) => v, - } -}", - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>u.foo(), - Err(v) => v, - } -}", - ); - - // comma with whitespace between brace and , - check_join_lines( - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>{ - u.foo() - } , - Err(v) => v, - } -}", - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>u.foo() , - Err(v) => v, - } -}", - ); - - // comma with newline between brace and , - check_join_lines( - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>{ - u.foo() - } - , - Err(v) => v, - } -}", - r" -fn foo(e: Result) { - match e { - Ok(u) => <|>u.foo() - , - Err(v) => v, - } -}", - ); - } - - #[test] - fn join_lines_keeps_comma_with_single_arg_tuple() { - // A single arg tuple - check_join_lines( - r" -fn foo() { - let x = (<|>{ - 4 - },); -}", - r" -fn foo() { - let x = (<|>4,); -}", - ); - - // single arg tuple with whitespace between brace and comma - check_join_lines( - r" -fn foo() { - let x = (<|>{ - 4 - } ,); -}", - r" -fn foo() { - let x = (<|>4 ,); -}", - ); - - // single arg tuple with newline between brace and comma - check_join_lines( - r" -fn foo() { - let x = (<|>{ - 4 - } - ,); -}", - r" -fn foo() { - let x = (<|>4 - ,); -}", - ); - } - - #[test] - fn test_join_lines_use_items_left() { - // No space after the '{' - check_join_lines( - r" -<|>use ra_syntax::{ - TextUnit, TextRange, -};", - r" -<|>use ra_syntax::{TextUnit, TextRange, -};", - ); - } - - #[test] - fn test_join_lines_use_items_right() { - // No space after the '}' - check_join_lines( - r" -use ra_syntax::{ -<|> TextUnit, TextRange -};", - r" -use ra_syntax::{ -<|> TextUnit, TextRange};", - ); - } - - #[test] - fn test_join_lines_use_items_right_comma() { - // No space after the '}' - check_join_lines( - r" -use ra_syntax::{ -<|> TextUnit, TextRange, -};", - r" -use ra_syntax::{ -<|> TextUnit, TextRange};", - ); - } - - #[test] - fn test_join_lines_use_tree() { - check_join_lines( - r" -use ra_syntax::{ - algo::<|>{ - find_token_at_offset, - }, - ast, -};", - r" -use ra_syntax::{ - algo::<|>find_token_at_offset, - ast, -};", - ); - } - - #[test] - fn test_join_lines_normal_comments() { - check_join_lines( - r" -fn foo() { - // Hello<|> - // world! -} -", - r" -fn foo() { - // Hello<|> world! -} -", - ); - } - - #[test] - fn test_join_lines_doc_comments() { - check_join_lines( - r" -fn foo() { - /// Hello<|> - /// world! -} -", - r" -fn foo() { - /// Hello<|> world! -} -", - ); - } - - #[test] - fn test_join_lines_mod_comments() { - check_join_lines( - r" -fn foo() { - //! Hello<|> - //! world! -} -", - r" -fn foo() { - //! Hello<|> world! -} -", - ); - } - - #[test] - fn test_join_lines_multiline_comments_1() { - check_join_lines( - r" -fn foo() { - // Hello<|> - /* world! */ -} -", - r" -fn foo() { - // Hello<|> world! */ -} -", - ); - } - - #[test] - fn test_join_lines_multiline_comments_2() { - check_join_lines( - r" -fn foo() { - // The<|> - /* quick - brown - fox! */ -} -", - r" -fn foo() { - // The<|> quick - brown - fox! */ -} -", - ); - } - - fn check_join_lines_sel(before: &str, after: &str) { - let (sel, before) = extract_range(before); - let parse = SourceFile::parse(&before); - let result = join_lines(&parse.tree(), sel); - let actual = result.apply(&before); - assert_eq_text!(after, &actual); - } - - #[test] - fn test_join_lines_selection_fn_args() { - check_join_lines_sel( - r" -fn foo() { - <|>foo(1, - 2, - 3, - <|>) -} - ", - r" -fn foo() { - foo(1, 2, 3) -} - ", - ); - } - - #[test] - fn test_join_lines_selection_struct() { - check_join_lines_sel( - r" -struct Foo <|>{ - f: u32, -}<|> - ", - r" -struct Foo { f: u32 } - ", - ); - } - - #[test] - fn test_join_lines_selection_dot_chain() { - check_join_lines_sel( - r" -fn foo() { - join(<|>type_params.type_params() - .filter_map(|it| it.name()) - .map(|it| it.text())<|>) -}", - r" -fn foo() { - join(type_params.type_params().filter_map(|it| it.name()).map(|it| it.text())) -}", - ); - } - - #[test] - fn test_join_lines_selection_lambda_block_body() { - check_join_lines_sel( - r" -pub fn handle_find_matching_brace() { - params.offsets - .map(|offset| <|>{ - world.analysis().matching_brace(&file, offset).unwrap_or(offset) - }<|>) - .collect(); -}", - r" -pub fn handle_find_matching_brace() { - params.offsets - .map(|offset| world.analysis().matching_brace(&file, offset).unwrap_or(offset)) - .collect(); -}", - ); - } -} diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs deleted file mode 100644 index cb6c24eaa..000000000 --- a/crates/ra_ide_api/src/lib.rs +++ /dev/null @@ -1,489 +0,0 @@ -//! ra_ide_api crate provides "ide-centric" APIs for the rust-analyzer. That is, -//! it generally operates with files and text ranges, and returns results as -//! Strings, suitable for displaying to the human. -//! -//! What powers this API are the `RootDatabase` struct, which defines a `salsa` -//! database, and the `ra_hir` crate, where majority of the analysis happens. -//! However, IDE specific bits of the analysis (most notably completion) happen -//! in this crate. - -// For proving that RootDatabase is RefUnwindSafe. -#![recursion_limit = "128"] - -mod db; -pub mod mock_analysis; -mod symbol_index; -mod change; -mod source_change; -mod feature_flags; - -mod status; -mod completion; -mod runnables; -mod goto_definition; -mod goto_type_definition; -mod extend_selection; -mod hover; -mod call_info; -mod syntax_highlighting; -mod parent_module; -mod references; -mod impls; -mod assists; -mod diagnostics; -mod syntax_tree; -mod folding_ranges; -mod line_index; -mod line_index_utils; -mod join_lines; -mod typing; -mod matching_brace; -mod display; -mod inlay_hints; -mod wasm_shims; -mod expand; -mod expand_macro; - -#[cfg(test)] -mod marks; -#[cfg(test)] -mod test_utils; - -use std::sync::Arc; - -use ra_cfg::CfgOptions; -use ra_db::{ - salsa::{self, ParallelDatabase}, - CheckCanceled, Env, FileLoader, SourceDatabase, -}; -use ra_syntax::{SourceFile, TextRange, TextUnit}; - -use crate::{db::LineIndexDatabase, display::ToNav, symbol_index::FileSymbol}; - -pub use crate::{ - assists::{Assist, AssistId}, - change::{AnalysisChange, LibraryData}, - completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, - diagnostics::Severity, - display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, - expand_macro::ExpandedMacro, - feature_flags::FeatureFlags, - folding_ranges::{Fold, FoldKind}, - hover::HoverResult, - inlay_hints::{InlayHint, InlayKind}, - line_index::{LineCol, LineIndex}, - line_index_utils::translate_offset_with_edit, - references::{ReferenceSearchResult, SearchScope}, - runnables::{Runnable, RunnableKind}, - source_change::{FileSystemEdit, SourceChange, SourceFileEdit}, - syntax_highlighting::HighlightedRange, -}; - -pub use hir::Documentation; -pub use ra_db::{ - Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, -}; - -pub type Cancelable = Result; - -#[derive(Debug)] -pub struct Diagnostic { - pub message: String, - pub range: TextRange, - pub fix: Option, - pub severity: Severity, -} - -#[derive(Debug)] -pub struct Query { - query: String, - lowercased: String, - only_types: bool, - libs: bool, - exact: bool, - limit: usize, -} - -impl Query { - pub fn new(query: String) -> Query { - let lowercased = query.to_lowercase(); - Query { - query, - lowercased, - only_types: false, - libs: false, - exact: false, - limit: usize::max_value(), - } - } - - pub fn only_types(&mut self) { - self.only_types = true; - } - - pub fn libs(&mut self) { - self.libs = true; - } - - pub fn exact(&mut self) { - self.exact = true; - } - - pub fn limit(&mut self, limit: usize) { - self.limit = limit - } -} - -/// Info associated with a text range. -#[derive(Debug)] -pub struct RangeInfo { - pub range: TextRange, - pub info: T, -} - -impl RangeInfo { - pub fn new(range: TextRange, info: T) -> RangeInfo { - RangeInfo { range, info } - } -} - -/// Contains information about a call site. Specifically the -/// `FunctionSignature`and current parameter. -#[derive(Debug)] -pub struct CallInfo { - pub signature: FunctionSignature, - pub active_parameter: Option, -} - -/// `AnalysisHost` stores the current state of the world. -#[derive(Debug)] -pub struct AnalysisHost { - db: db::RootDatabase, -} - -impl Default for AnalysisHost { - fn default() -> AnalysisHost { - AnalysisHost::new(None, FeatureFlags::default()) - } -} - -impl AnalysisHost { - pub fn new(lru_capcity: Option, feature_flags: FeatureFlags) -> AnalysisHost { - AnalysisHost { db: db::RootDatabase::new(lru_capcity, feature_flags) } - } - /// Returns a snapshot of the current state, which you can query for - /// semantic information. - pub fn analysis(&self) -> Analysis { - Analysis { db: self.db.snapshot() } - } - - pub fn feature_flags(&self) -> &FeatureFlags { - &self.db.feature_flags - } - - /// Applies changes to the current state of the world. If there are - /// outstanding snapshots, they will be canceled. - pub fn apply_change(&mut self, change: AnalysisChange) { - self.db.apply_change(change) - } - - pub fn maybe_collect_garbage(&mut self) { - self.db.maybe_collect_garbage(); - } - - pub fn collect_garbage(&mut self) { - self.db.collect_garbage(); - } - /// NB: this clears the database - pub fn per_query_memory_usage(&mut self) -> Vec<(String, ra_prof::Bytes)> { - self.db.per_query_memory_usage() - } - pub fn raw_database( - &self, - ) -> &(impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { - &self.db - } - pub fn raw_database_mut( - &mut self, - ) -> &mut (impl hir::db::HirDatabase + salsa::Database + ra_db::SourceDatabaseExt) { - &mut self.db - } -} - -/// Analysis is a snapshot of a world state at a moment in time. It is the main -/// entry point for asking semantic information about the world. When the world -/// state is advanced using `AnalysisHost::apply_change` method, all existing -/// `Analysis` are canceled (most method return `Err(Canceled)`). -#[derive(Debug)] -pub struct Analysis { - db: salsa::Snapshot, -} - -// As a general design guideline, `Analysis` API are intended to be independent -// from the language server protocol. That is, when exposing some functionality -// we should think in terms of "what API makes most sense" and not in terms of -// "what types LSP uses". Although currently LSP is the only consumer of the -// API, the API should in theory be usable as a library, or via a different -// protocol. -impl Analysis { - // Creates an analysis instance for a single file, without any extenal - // dependencies, stdlib support or ability to apply changes. See - // `AnalysisHost` for creating a fully-featured analysis. - pub fn from_single_file(text: String) -> (Analysis, FileId) { - let mut host = AnalysisHost::default(); - let source_root = SourceRootId(0); - let mut change = AnalysisChange::new(); - change.add_root(source_root, true); - let mut crate_graph = CrateGraph::default(); - let file_id = FileId(0); - // FIXME: cfg options - // Default to enable test for single file. - let mut cfg_options = CfgOptions::default(); - cfg_options.insert_atom("test".into()); - crate_graph.add_crate_root(file_id, Edition::Edition2018, cfg_options, Env::default()); - change.add_file(source_root, file_id, "main.rs".into(), Arc::new(text)); - change.set_crate_graph(crate_graph); - host.apply_change(change); - (host.analysis(), file_id) - } - - /// Features for Analysis. - pub fn feature_flags(&self) -> &FeatureFlags { - &self.db.feature_flags - } - - /// Debug info about the current state of the analysis. - pub fn status(&self) -> Cancelable { - self.with_db(|db| status::status(&*db)) - } - - /// Gets the text of the source file. - pub fn file_text(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| db.file_text(file_id)) - } - - /// Gets the syntax tree of the file. - pub fn parse(&self, file_id: FileId) -> Cancelable { - self.with_db(|db| db.parse(file_id).tree()) - } - - /// Gets the file's `LineIndex`: data structure to convert between absolute - /// offsets and line/column representation. - pub fn file_line_index(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| db.line_index(file_id)) - } - - /// Selects the next syntactic nodes encompassing the range. - pub fn extend_selection(&self, frange: FileRange) -> Cancelable { - self.with_db(|db| extend_selection::extend_selection(db, frange)) - } - - /// Returns position of the matching brace (all types of braces are - /// supported). - pub fn matching_brace(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| { - let parse = db.parse(position.file_id); - let file = parse.tree(); - matching_brace::matching_brace(&file, position.offset) - }) - } - - /// Returns a syntax tree represented as `String`, for debug purposes. - // FIXME: use a better name here. - pub fn syntax_tree( - &self, - file_id: FileId, - text_range: Option, - ) -> Cancelable { - self.with_db(|db| syntax_tree::syntax_tree(&db, file_id, text_range)) - } - - pub fn expand_macro(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| expand_macro::expand_macro(db, position)) - } - - /// Returns an edit to remove all newlines in the range, cleaning up minor - /// stuff like trailing commas. - pub fn join_lines(&self, frange: FileRange) -> Cancelable { - self.with_db(|db| { - let parse = db.parse(frange.file_id); - let file_edit = SourceFileEdit { - file_id: frange.file_id, - edit: join_lines::join_lines(&parse.tree(), frange.range), - }; - SourceChange::source_file_edit("join lines", file_edit) - }) - } - - /// Returns an edit which should be applied when opening a new line, fixing - /// up minor stuff like continuing the comment. - pub fn on_enter(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| typing::on_enter(&db, position)) - } - - /// Returns an edit which should be applied after a character was typed. - /// - /// This is useful for some on-the-fly fixups, like adding `;` to `let =` - /// automatically. - pub fn on_char_typed( - &self, - position: FilePosition, - char_typed: char, - ) -> Cancelable> { - // Fast path to not even parse the file. - if !typing::TRIGGER_CHARS.contains(char_typed) { - return Ok(None); - } - self.with_db(|db| typing::on_char_typed(&db, position, char_typed)) - } - - /// Returns a tree representation of symbols in the file. Useful to draw a - /// file outline. - pub fn file_structure(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| file_structure(&db.parse(file_id).tree())) - } - - /// Returns a list of the places in the file where type hints can be displayed. - pub fn inlay_hints( - &self, - file_id: FileId, - max_inlay_hint_length: Option, - ) -> Cancelable> { - self.with_db(|db| { - inlay_hints::inlay_hints(db, file_id, &db.parse(file_id).tree(), max_inlay_hint_length) - }) - } - - /// Returns the set of folding ranges. - pub fn folding_ranges(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| folding_ranges::folding_ranges(&db.parse(file_id).tree())) - } - - /// Fuzzy searches for a symbol. - pub fn symbol_search(&self, query: Query) -> Cancelable> { - self.with_db(|db| { - symbol_index::world_symbols(db, query) - .into_iter() - .map(|s| s.to_nav(db)) - .collect::>() - }) - } - - /// Returns the definitions from the symbol at `position`. - pub fn goto_definition( - &self, - position: FilePosition, - ) -> Cancelable>>> { - self.with_db(|db| goto_definition::goto_definition(db, position)) - } - - /// Returns the impls from the symbol at `position`. - pub fn goto_implementation( - &self, - position: FilePosition, - ) -> Cancelable>>> { - self.with_db(|db| impls::goto_implementation(db, position)) - } - - /// Returns the type definitions for the symbol at `position`. - pub fn goto_type_definition( - &self, - position: FilePosition, - ) -> Cancelable>>> { - self.with_db(|db| goto_type_definition::goto_type_definition(db, position)) - } - - /// Finds all usages of the reference at point. - pub fn find_all_refs( - &self, - position: FilePosition, - search_scope: Option, - ) -> Cancelable> { - self.with_db(|db| references::find_all_refs(db, position, search_scope).map(|it| it.info)) - } - - /// Returns a short text describing element at position. - pub fn hover(&self, position: FilePosition) -> Cancelable>> { - self.with_db(|db| hover::hover(db, position)) - } - - /// Computes parameter information for the given call expression. - pub fn call_info(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| call_info::call_info(db, position)) - } - - /// Returns a `mod name;` declaration which created the current module. - pub fn parent_module(&self, position: FilePosition) -> Cancelable> { - self.with_db(|db| parent_module::parent_module(db, position)) - } - - /// Returns crates this file belongs too. - pub fn crate_for(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| parent_module::crate_for(db, file_id)) - } - - /// Returns the root file of the given crate. - pub fn crate_root(&self, crate_id: CrateId) -> Cancelable { - self.with_db(|db| db.crate_graph().crate_root(crate_id)) - } - - /// Returns the set of possible targets to run for the current file. - pub fn runnables(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| runnables::runnables(db, file_id)) - } - - /// Computes syntax highlighting for the given file. - pub fn highlight(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| syntax_highlighting::highlight(db, file_id)) - } - - /// Computes syntax highlighting for the given file. - pub fn highlight_as_html(&self, file_id: FileId, rainbow: bool) -> Cancelable { - self.with_db(|db| syntax_highlighting::highlight_as_html(db, file_id, rainbow)) - } - - /// Computes completions at the given position. - pub fn completions(&self, position: FilePosition) -> Cancelable>> { - self.with_db(|db| completion::completions(db, position).map(Into::into)) - } - - /// Computes assists (aka code actions aka intentions) for the given - /// position. - pub fn assists(&self, frange: FileRange) -> Cancelable> { - self.with_db(|db| assists::assists(db, frange)) - } - - /// Computes the set of diagnostics for the given file. - pub fn diagnostics(&self, file_id: FileId) -> Cancelable> { - self.with_db(|db| diagnostics::diagnostics(db, file_id)) - } - - /// Computes the type of the expression at the given position. - pub fn type_of(&self, frange: FileRange) -> Cancelable> { - self.with_db(|db| hover::type_of(db, frange)) - } - - /// Returns the edit required to rename reference at the position to the new - /// name. - pub fn rename( - &self, - position: FilePosition, - new_name: &str, - ) -> Cancelable>> { - self.with_db(|db| references::rename(db, position, new_name)) - } - - /// Performs an operation on that may be Canceled. - fn with_db T + std::panic::UnwindSafe, T>( - &self, - f: F, - ) -> Cancelable { - self.db.catch_canceled(f) - } -} - -#[test] -fn analysis_is_send() { - fn is_send() {} - is_send::(); -} diff --git a/crates/ra_ide_api/src/line_index.rs b/crates/ra_ide_api/src/line_index.rs deleted file mode 100644 index 710890d27..000000000 --- a/crates/ra_ide_api/src/line_index.rs +++ /dev/null @@ -1,283 +0,0 @@ -//! FIXME: write short doc here - -use crate::TextUnit; -use rustc_hash::FxHashMap; -use superslice::Ext; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct LineIndex { - pub(crate) newlines: Vec, - pub(crate) utf16_lines: FxHashMap>, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub struct LineCol { - /// Zero-based - pub line: u32, - /// Zero-based - pub col_utf16: u32, -} - -#[derive(Clone, Debug, Hash, PartialEq, Eq)] -pub(crate) struct Utf16Char { - pub(crate) start: TextUnit, - pub(crate) end: TextUnit, -} - -impl Utf16Char { - fn len(&self) -> TextUnit { - self.end - self.start - } -} - -impl LineIndex { - pub fn new(text: &str) -> LineIndex { - let mut utf16_lines = FxHashMap::default(); - let mut utf16_chars = Vec::new(); - - let mut newlines = vec![0.into()]; - let mut curr_row = 0.into(); - let mut curr_col = 0.into(); - let mut line = 0; - for c in text.chars() { - curr_row += TextUnit::of_char(c); - if c == '\n' { - newlines.push(curr_row); - - // Save any utf-16 characters seen in the previous line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, utf16_chars); - utf16_chars = Vec::new(); - } - - // Prepare for processing the next line - curr_col = 0.into(); - line += 1; - continue; - } - - let char_len = TextUnit::of_char(c); - if char_len.to_usize() > 1 { - utf16_chars.push(Utf16Char { start: curr_col, end: curr_col + char_len }); - } - - curr_col += char_len; - } - - // Save any utf-16 characters seen in the last line - if !utf16_chars.is_empty() { - utf16_lines.insert(line, utf16_chars); - } - - LineIndex { newlines, utf16_lines } - } - - pub fn line_col(&self, offset: TextUnit) -> LineCol { - let line = self.newlines.upper_bound(&offset) - 1; - let line_start_offset = self.newlines[line]; - let col = offset - line_start_offset; - - LineCol { line: line as u32, col_utf16: self.utf8_to_utf16_col(line as u32, col) as u32 } - } - - pub fn offset(&self, line_col: LineCol) -> TextUnit { - //FIXME: return Result - let col = self.utf16_to_utf8_col(line_col.line, line_col.col_utf16); - self.newlines[line_col.line as usize] + col - } - - fn utf8_to_utf16_col(&self, line: u32, mut col: TextUnit) -> usize { - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - let mut correction = TextUnit::from_usize(0); - for c in utf16_chars { - if col >= c.end { - correction += c.len() - TextUnit::from_usize(1); - } else { - // From here on, all utf16 characters come *after* the character we are mapping, - // so we don't need to take them into account - break; - } - } - - col -= correction; - } - - col.to_usize() - } - - fn utf16_to_utf8_col(&self, line: u32, col: u32) -> TextUnit { - let mut col: TextUnit = col.into(); - if let Some(utf16_chars) = self.utf16_lines.get(&line) { - for c in utf16_chars { - if col >= c.start { - col += c.len() - TextUnit::from_usize(1); - } else { - // From here on, all utf16 characters come *after* the character we are mapping, - // so we don't need to take them into account - break; - } - } - } - - col - } -} - -#[cfg(test)] -/// Simple reference implementation to use in proptests -pub fn to_line_col(text: &str, offset: TextUnit) -> LineCol { - let mut res = LineCol { line: 0, col_utf16: 0 }; - for (i, c) in text.char_indices() { - if i + c.len_utf8() > offset.to_usize() { - // if it's an invalid offset, inside a multibyte char - // return as if it was at the start of the char - break; - } - if c == '\n' { - res.line += 1; - res.col_utf16 = 0; - } else { - res.col_utf16 += 1; - } - } - res -} - -#[cfg(test)] -mod test_line_index { - use super::*; - use proptest::{prelude::*, proptest}; - use ra_text_edit::test_utils::{arb_offset, arb_text}; - - #[test] - fn test_line_index() { - let text = "hello\nworld"; - let index = LineIndex::new(text); - assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 }); - assert_eq!(index.line_col(1.into()), LineCol { line: 0, col_utf16: 1 }); - assert_eq!(index.line_col(5.into()), LineCol { line: 0, col_utf16: 5 }); - assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 0 }); - assert_eq!(index.line_col(7.into()), LineCol { line: 1, col_utf16: 1 }); - assert_eq!(index.line_col(8.into()), LineCol { line: 1, col_utf16: 2 }); - assert_eq!(index.line_col(10.into()), LineCol { line: 1, col_utf16: 4 }); - assert_eq!(index.line_col(11.into()), LineCol { line: 1, col_utf16: 5 }); - assert_eq!(index.line_col(12.into()), LineCol { line: 1, col_utf16: 6 }); - - let text = "\nhello\nworld"; - let index = LineIndex::new(text); - assert_eq!(index.line_col(0.into()), LineCol { line: 0, col_utf16: 0 }); - assert_eq!(index.line_col(1.into()), LineCol { line: 1, col_utf16: 0 }); - assert_eq!(index.line_col(2.into()), LineCol { line: 1, col_utf16: 1 }); - assert_eq!(index.line_col(6.into()), LineCol { line: 1, col_utf16: 5 }); - assert_eq!(index.line_col(7.into()), LineCol { line: 2, col_utf16: 0 }); - } - - fn arb_text_with_offset() -> BoxedStrategy<(TextUnit, String)> { - arb_text().prop_flat_map(|text| (arb_offset(&text), Just(text))).boxed() - } - - fn to_line_col(text: &str, offset: TextUnit) -> LineCol { - let mut res = LineCol { line: 0, col_utf16: 0 }; - for (i, c) in text.char_indices() { - if i + c.len_utf8() > offset.to_usize() { - // if it's an invalid offset, inside a multibyte char - // return as if it was at the start of the char - break; - } - if c == '\n' { - res.line += 1; - res.col_utf16 = 0; - } else { - res.col_utf16 += 1; - } - } - res - } - - proptest! { - #[test] - fn test_line_index_proptest((offset, text) in arb_text_with_offset()) { - let expected = to_line_col(&text, offset); - let line_index = LineIndex::new(&text); - let actual = line_index.line_col(offset); - - assert_eq!(actual, expected); - } - } -} - -#[cfg(test)] -mod test_utf8_utf16_conv { - use super::*; - - #[test] - fn test_char_len() { - assert_eq!('メ'.len_utf8(), 3); - assert_eq!('メ'.len_utf16(), 1); - } - - #[test] - fn test_empty_index() { - let col_index = LineIndex::new( - " -const C: char = 'x'; -", - ); - assert_eq!(col_index.utf16_lines.len(), 0); - } - - #[test] - fn test_single_char() { - let col_index = LineIndex::new( - " -const C: char = 'メ'; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 1); - assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); - - // UTF-8 to UTF-16, no changes - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 22.into()), 20); - - // UTF-16 to UTF-8, no changes - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from(15)); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from(21)); - } - - #[test] - fn test_string() { - let col_index = LineIndex::new( - " -const C: char = \"メ メ\"; -", - ); - - assert_eq!(col_index.utf16_lines.len(), 1); - assert_eq!(col_index.utf16_lines[&1].len(), 2); - assert_eq!(col_index.utf16_lines[&1][0], Utf16Char { start: 17.into(), end: 20.into() }); - assert_eq!(col_index.utf16_lines[&1][1], Utf16Char { start: 21.into(), end: 24.into() }); - - // UTF-8 to UTF-16 - assert_eq!(col_index.utf8_to_utf16_col(1, 15.into()), 15); - - assert_eq!(col_index.utf8_to_utf16_col(1, 21.into()), 19); - assert_eq!(col_index.utf8_to_utf16_col(1, 25.into()), 21); - - assert!(col_index.utf8_to_utf16_col(2, 15.into()) == 15); - - // UTF-16 to UTF-8 - assert_eq!(col_index.utf16_to_utf8_col(1, 15), TextUnit::from_usize(15)); - - assert_eq!(col_index.utf16_to_utf8_col(1, 18), TextUnit::from_usize(20)); - assert_eq!(col_index.utf16_to_utf8_col(1, 19), TextUnit::from_usize(23)); - - assert_eq!(col_index.utf16_to_utf8_col(2, 15), TextUnit::from_usize(15)); - } -} diff --git a/crates/ra_ide_api/src/line_index_utils.rs b/crates/ra_ide_api/src/line_index_utils.rs deleted file mode 100644 index bd1e08feb..000000000 --- a/crates/ra_ide_api/src/line_index_utils.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! FIXME: write short doc here - -use crate::{line_index::Utf16Char, LineCol, LineIndex}; -use ra_syntax::{TextRange, TextUnit}; -use ra_text_edit::{AtomTextEdit, TextEdit}; - -#[derive(Debug, Clone)] -enum Step { - Newline(TextUnit), - Utf16Char(TextRange), -} - -#[derive(Debug)] -struct LineIndexStepIter<'a> { - line_index: &'a LineIndex, - next_newline_idx: usize, - utf16_chars: Option<(TextUnit, std::slice::Iter<'a, Utf16Char>)>, -} - -impl<'a> LineIndexStepIter<'a> { - fn from(line_index: &LineIndex) -> LineIndexStepIter { - let mut x = LineIndexStepIter { line_index, next_newline_idx: 0, utf16_chars: None }; - // skip first newline since it's not real - x.next(); - x - } -} - -impl<'a> Iterator for LineIndexStepIter<'a> { - type Item = Step; - fn next(&mut self) -> Option { - self.utf16_chars - .as_mut() - .and_then(|(newline, x)| { - let x = x.next()?; - Some(Step::Utf16Char(TextRange::from_to(*newline + x.start, *newline + x.end))) - }) - .or_else(|| { - let next_newline = *self.line_index.newlines.get(self.next_newline_idx)?; - self.utf16_chars = self - .line_index - .utf16_lines - .get(&(self.next_newline_idx as u32)) - .map(|x| (next_newline, x.iter())); - self.next_newline_idx += 1; - Some(Step::Newline(next_newline)) - }) - } -} - -#[derive(Debug)] -struct OffsetStepIter<'a> { - text: &'a str, - offset: TextUnit, -} - -impl<'a> Iterator for OffsetStepIter<'a> { - type Item = Step; - fn next(&mut self) -> Option { - let (next, next_offset) = self - .text - .char_indices() - .filter_map(|(i, c)| { - if c == '\n' { - let next_offset = self.offset + TextUnit::from_usize(i + 1); - let next = Step::Newline(next_offset); - Some((next, next_offset)) - } else { - let char_len = TextUnit::of_char(c); - if char_len.to_usize() > 1 { - let start = self.offset + TextUnit::from_usize(i); - let end = start + char_len; - let next = Step::Utf16Char(TextRange::from_to(start, end)); - let next_offset = end; - Some((next, next_offset)) - } else { - None - } - } - }) - .next()?; - let next_idx = (next_offset - self.offset).to_usize(); - self.text = &self.text[next_idx..]; - self.offset = next_offset; - Some(next) - } -} - -#[derive(Debug)] -enum NextSteps<'a> { - Use, - ReplaceMany(OffsetStepIter<'a>), - AddMany(OffsetStepIter<'a>), -} - -#[derive(Debug)] -struct TranslatedEdit<'a> { - delete: TextRange, - insert: &'a str, - diff: i64, -} - -struct Edits<'a> { - edits: &'a [AtomTextEdit], - current: Option>, - acc_diff: i64, -} - -impl<'a> Edits<'a> { - fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> { - let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 }; - x.advance_edit(); - x - } - fn advance_edit(&mut self) { - self.acc_diff += self.current.as_ref().map_or(0, |x| x.diff); - match self.edits.split_first() { - Some((next, rest)) => { - let delete = self.translate_range(next.delete); - let diff = next.insert.len() as i64 - next.delete.len().to_usize() as i64; - self.current = Some(TranslatedEdit { delete, insert: &next.insert, diff }); - self.edits = rest; - } - None => { - self.current = None; - } - } - } - - fn next_inserted_steps(&mut self) -> Option> { - let cur = self.current.as_ref()?; - let res = Some(OffsetStepIter { offset: cur.delete.start(), text: &cur.insert }); - self.advance_edit(); - res - } - - fn next_steps(&mut self, step: &Step) -> NextSteps { - let step_pos = match *step { - Step::Newline(n) => n, - Step::Utf16Char(r) => r.end(), - }; - match &mut self.current { - Some(edit) => { - if step_pos <= edit.delete.start() { - NextSteps::Use - } else if step_pos <= edit.delete.end() { - let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert }; - // empty slice to avoid returning steps again - edit.insert = &edit.insert[edit.insert.len()..]; - NextSteps::ReplaceMany(iter) - } else { - let iter = OffsetStepIter { offset: edit.delete.start(), text: &edit.insert }; - // empty slice to avoid returning steps again - edit.insert = &edit.insert[edit.insert.len()..]; - self.advance_edit(); - NextSteps::AddMany(iter) - } - } - None => NextSteps::Use, - } - } - - fn translate_range(&self, range: TextRange) -> TextRange { - if self.acc_diff == 0 { - range - } else { - let start = self.translate(range.start()); - let end = self.translate(range.end()); - TextRange::from_to(start, end) - } - } - - fn translate(&self, x: TextUnit) -> TextUnit { - if self.acc_diff == 0 { - x - } else { - TextUnit::from((x.to_usize() as i64 + self.acc_diff) as u32) - } - } - - fn translate_step(&self, x: &Step) -> Step { - if self.acc_diff == 0 { - x.clone() - } else { - match *x { - Step::Newline(n) => Step::Newline(self.translate(n)), - Step::Utf16Char(r) => Step::Utf16Char(self.translate_range(r)), - } - } - } -} - -#[derive(Debug)] -struct RunningLineCol { - line: u32, - last_newline: TextUnit, - col_adjust: TextUnit, -} - -impl RunningLineCol { - fn new() -> RunningLineCol { - RunningLineCol { line: 0, last_newline: TextUnit::from(0), col_adjust: TextUnit::from(0) } - } - - fn to_line_col(&self, offset: TextUnit) -> LineCol { - LineCol { - line: self.line, - col_utf16: ((offset - self.last_newline) - self.col_adjust).into(), - } - } - - fn add_line(&mut self, newline: TextUnit) { - self.line += 1; - self.last_newline = newline; - self.col_adjust = TextUnit::from(0); - } - - fn adjust_col(&mut self, range: TextRange) { - self.col_adjust += range.len() - TextUnit::from(1); - } -} - -pub fn translate_offset_with_edit( - line_index: &LineIndex, - offset: TextUnit, - text_edit: &TextEdit, -) -> LineCol { - let mut state = Edits::from_text_edit(&text_edit); - - let mut res = RunningLineCol::new(); - - macro_rules! test_step { - ($x:ident) => { - match &$x { - Step::Newline(n) => { - if offset < *n { - return res.to_line_col(offset); - } else { - res.add_line(*n); - } - } - Step::Utf16Char(x) => { - if offset < x.end() { - // if the offset is inside a multibyte char it's invalid - // clamp it to the start of the char - let clamp = offset.min(x.start()); - return res.to_line_col(clamp); - } else { - res.adjust_col(*x); - } - } - } - }; - } - - for orig_step in LineIndexStepIter::from(line_index) { - loop { - let translated_step = state.translate_step(&orig_step); - match state.next_steps(&translated_step) { - NextSteps::Use => { - test_step!(translated_step); - break; - } - NextSteps::ReplaceMany(ns) => { - for n in ns { - test_step!(n); - } - break; - } - NextSteps::AddMany(ns) => { - for n in ns { - test_step!(n); - } - } - } - } - } - - loop { - match state.next_inserted_steps() { - None => break, - Some(ns) => { - for n in ns { - test_step!(n); - } - } - } - } - - res.to_line_col(offset) -} - -#[cfg(test)] -mod test { - use super::*; - use crate::line_index; - use proptest::{prelude::*, proptest}; - use ra_text_edit::test_utils::{arb_offset, arb_text_with_edit}; - use ra_text_edit::TextEdit; - - #[derive(Debug)] - struct ArbTextWithEditAndOffset { - text: String, - edit: TextEdit, - edited_text: String, - offset: TextUnit, - } - - fn arb_text_with_edit_and_offset() -> BoxedStrategy { - arb_text_with_edit() - .prop_flat_map(|x| { - let edited_text = x.edit.apply(&x.text); - let arb_offset = arb_offset(&edited_text); - (Just(x), Just(edited_text), arb_offset).prop_map(|(x, edited_text, offset)| { - ArbTextWithEditAndOffset { text: x.text, edit: x.edit, edited_text, offset } - }) - }) - .boxed() - } - - proptest! { - #[test] - fn test_translate_offset_with_edit(x in arb_text_with_edit_and_offset()) { - let expected = line_index::to_line_col(&x.edited_text, x.offset); - let line_index = LineIndex::new(&x.text); - let actual = translate_offset_with_edit(&line_index, x.offset, &x.edit); - - assert_eq!(actual, expected); - } - } -} diff --git a/crates/ra_ide_api/src/marks.rs b/crates/ra_ide_api/src/marks.rs deleted file mode 100644 index 848ae4dc7..000000000 --- a/crates/ra_ide_api/src/marks.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! See test_utils/src/marks.rs - -test_utils::marks!( - inserts_angle_brackets_for_generics - inserts_parens_for_function_calls - goto_definition_works_for_macros - goto_definition_works_for_methods - goto_definition_works_for_fields - goto_definition_works_for_record_fields - call_info_bad_offset - dont_complete_current_use - dont_complete_primitive_in_use -); diff --git a/crates/ra_ide_api/src/matching_brace.rs b/crates/ra_ide_api/src/matching_brace.rs deleted file mode 100644 index d1204fac0..000000000 --- a/crates/ra_ide_api/src/matching_brace.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! FIXME: write short doc here - -use ra_syntax::{ast::AstNode, SourceFile, SyntaxKind, TextUnit, T}; - -pub fn matching_brace(file: &SourceFile, offset: TextUnit) -> Option { - const BRACES: &[SyntaxKind] = - &[T!['{'], T!['}'], T!['['], T![']'], T!['('], T![')'], T![<], T![>]]; - let (brace_node, brace_idx) = file - .syntax() - .token_at_offset(offset) - .filter_map(|node| { - let idx = BRACES.iter().position(|&brace| brace == node.kind())?; - Some((node, idx)) - }) - .next()?; - let parent = brace_node.parent(); - let matching_kind = BRACES[brace_idx ^ 1]; - let matching_node = parent.children_with_tokens().find(|node| node.kind() == matching_kind)?; - Some(matching_node.text_range().start()) -} - -#[cfg(test)] -mod tests { - use test_utils::{add_cursor, assert_eq_text, extract_offset}; - - use super::*; - - #[test] - fn test_matching_brace() { - fn do_check(before: &str, after: &str) { - let (pos, before) = extract_offset(before); - let parse = SourceFile::parse(&before); - let new_pos = match matching_brace(&parse.tree(), pos) { - None => pos, - Some(pos) => pos, - }; - let actual = add_cursor(&before, new_pos); - assert_eq_text!(after, &actual); - } - - do_check("struct Foo { a: i32, }<|>", "struct Foo <|>{ a: i32, }"); - } -} diff --git a/crates/ra_ide_api/src/mock_analysis.rs b/crates/ra_ide_api/src/mock_analysis.rs deleted file mode 100644 index bf8a54932..000000000 --- a/crates/ra_ide_api/src/mock_analysis.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! FIXME: write short doc here - -use std::sync::Arc; - -use ra_cfg::CfgOptions; -use ra_db::{Env, RelativePathBuf}; -use test_utils::{extract_offset, extract_range, parse_fixture, CURSOR_MARKER}; - -use crate::{ - Analysis, AnalysisChange, AnalysisHost, CrateGraph, Edition::Edition2018, FileId, FilePosition, - FileRange, SourceRootId, -}; - -/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis -/// from a set of in-memory files. -#[derive(Debug, Default)] -pub struct MockAnalysis { - files: Vec<(String, String)>, -} - -impl MockAnalysis { - pub fn new() -> MockAnalysis { - MockAnalysis::default() - } - /// Creates `MockAnalysis` using a fixture data in the following format: - /// - /// ```not_rust - /// //- /main.rs - /// mod foo; - /// fn main() {} - /// - /// //- /foo.rs - /// struct Baz; - /// ``` - pub fn with_files(fixture: &str) -> MockAnalysis { - let mut res = MockAnalysis::new(); - for entry in parse_fixture(fixture) { - res.add_file(&entry.meta, &entry.text); - } - res - } - - /// Same as `with_files`, but requires that a single file contains a `<|>` marker, - /// whose position is also returned. - pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) { - let mut position = None; - let mut res = MockAnalysis::new(); - for entry in parse_fixture(fixture) { - if entry.text.contains(CURSOR_MARKER) { - assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); - position = Some(res.add_file_with_position(&entry.meta, &entry.text)); - } else { - res.add_file(&entry.meta, &entry.text); - } - } - let position = position.expect("expected a marker (<|>)"); - (res, position) - } - - pub fn add_file(&mut self, path: &str, text: &str) -> FileId { - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text.to_string())); - file_id - } - pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { - let (offset, text) = extract_offset(text); - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text)); - FilePosition { file_id, offset } - } - pub fn add_file_with_range(&mut self, path: &str, text: &str) -> FileRange { - let (range, text) = extract_range(text); - let file_id = FileId((self.files.len() + 1) as u32); - self.files.push((path.to_string(), text)); - FileRange { file_id, range } - } - pub fn id_of(&self, path: &str) -> FileId { - let (idx, _) = self - .files - .iter() - .enumerate() - .find(|(_, (p, _text))| path == p) - .expect("no file in this mock"); - FileId(idx as u32 + 1) - } - pub fn analysis_host(self) -> AnalysisHost { - let mut host = AnalysisHost::default(); - let source_root = SourceRootId(0); - let mut change = AnalysisChange::new(); - change.add_root(source_root, true); - let mut crate_graph = CrateGraph::default(); - let mut root_crate = None; - for (i, (path, contents)) in self.files.into_iter().enumerate() { - assert!(path.starts_with('/')); - let path = RelativePathBuf::from_path(&path[1..]).unwrap(); - let file_id = FileId(i as u32 + 1); - let cfg_options = CfgOptions::default(); - if path == "/lib.rs" || path == "/main.rs" { - root_crate = Some(crate_graph.add_crate_root( - file_id, - Edition2018, - cfg_options, - Env::default(), - )); - } else if path.ends_with("/lib.rs") { - let other_crate = - crate_graph.add_crate_root(file_id, Edition2018, cfg_options, Env::default()); - let crate_name = path.parent().unwrap().file_name().unwrap(); - if let Some(root_crate) = root_crate { - crate_graph.add_dep(root_crate, crate_name.into(), other_crate).unwrap(); - } - } - change.add_file(source_root, file_id, path, Arc::new(contents)); - } - change.set_crate_graph(crate_graph); - host.apply_change(change); - host - } - pub fn analysis(self) -> Analysis { - self.analysis_host().analysis() - } -} - -/// Creates analysis from a multi-file fixture, returns positions marked with <|>. -pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) { - let (mock, position) = MockAnalysis::with_files_and_position(fixture); - (mock.analysis(), position) -} - -/// Creates analysis for a single file. -pub fn single_file(code: &str) -> (Analysis, FileId) { - let mut mock = MockAnalysis::new(); - let file_id = mock.add_file("/main.rs", code); - (mock.analysis(), file_id) -} - -/// Creates analysis for a single file, returns position marked with <|>. -pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) { - let mut mock = MockAnalysis::new(); - let pos = mock.add_file_with_position("/main.rs", code); - (mock.analysis(), pos) -} - -/// Creates analysis for a single file, returns range marked with a pair of <|>. -pub fn single_file_with_range(code: &str) -> (Analysis, FileRange) { - let mut mock = MockAnalysis::new(); - let pos = mock.add_file_with_range("/main.rs", code); - (mock.analysis(), pos) -} diff --git a/crates/ra_ide_api/src/parent_module.rs b/crates/ra_ide_api/src/parent_module.rs deleted file mode 100644 index 6027e7d54..000000000 --- a/crates/ra_ide_api/src/parent_module.rs +++ /dev/null @@ -1,104 +0,0 @@ -//! FIXME: write short doc here - -use ra_db::{CrateId, FileId, FilePosition}; - -use crate::{db::RootDatabase, NavigationTarget}; - -/// This returns `Vec` because a module may be included from several places. We -/// don't handle this case yet though, so the Vec has length at most one. -pub(crate) fn parent_module(db: &RootDatabase, position: FilePosition) -> Vec { - let src = hir::ModuleSource::from_position(db, position); - let module = match hir::Module::from_definition( - db, - hir::Source { file_id: position.file_id.into(), value: src }, - ) { - None => return Vec::new(), - Some(it) => it, - }; - let nav = NavigationTarget::from_module_to_decl(db, module); - vec![nav] -} - -/// Returns `Vec` for the same reason as `parent_module` -pub(crate) fn crate_for(db: &RootDatabase, file_id: FileId) -> Vec { - let src = hir::ModuleSource::from_file_id(db, file_id); - let module = - match hir::Module::from_definition(db, hir::Source { file_id: file_id.into(), value: src }) - { - Some(it) => it, - None => return Vec::new(), - }; - let krate = module.krate(); - vec![krate.crate_id()] -} - -#[cfg(test)] -mod tests { - use ra_cfg::CfgOptions; - use ra_db::Env; - - use crate::{ - mock_analysis::{analysis_and_position, MockAnalysis}, - AnalysisChange, CrateGraph, - Edition::Edition2018, - }; - - #[test] - fn test_resolve_parent_module() { - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - mod foo; - //- /foo.rs - <|>// empty - ", - ); - let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); - nav.assert_match("foo MODULE FileId(1) [0; 8)"); - } - - #[test] - fn test_resolve_parent_module_for_inline() { - let (analysis, pos) = analysis_and_position( - " - //- /lib.rs - mod foo { - mod bar { - mod baz { <|> } - } - } - ", - ); - let nav = analysis.parent_module(pos).unwrap().pop().unwrap(); - nav.assert_match("baz MODULE FileId(1) [32; 44)"); - } - - #[test] - fn test_resolve_crate_root() { - let mock = MockAnalysis::with_files( - " - //- /bar.rs - mod foo; - //- /foo.rs - // empty <|> - ", - ); - let root_file = mock.id_of("/bar.rs"); - let mod_file = mock.id_of("/foo.rs"); - let mut host = mock.analysis_host(); - assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); - - let mut crate_graph = CrateGraph::default(); - let crate_id = crate_graph.add_crate_root( - root_file, - Edition2018, - CfgOptions::default(), - Env::default(), - ); - let mut change = AnalysisChange::new(); - change.set_crate_graph(crate_graph); - host.apply_change(change); - - assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]); - } -} diff --git a/crates/ra_ide_api/src/references.rs b/crates/ra_ide_api/src/references.rs deleted file mode 100644 index 21a1ea69e..000000000 --- a/crates/ra_ide_api/src/references.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! This module implements a reference search. -//! First, the element at the cursor position must be either an `ast::Name` -//! or `ast::NameRef`. If it's a `ast::NameRef`, at the classification step we -//! try to resolve the direct tree parent of this element, otherwise we -//! already have a definition and just need to get its HIR together with -//! some information that is needed for futher steps of searching. -//! After that, we collect files that might contain references and look -//! for text occurrences of the identifier. If there's an `ast::NameRef` -//! at the index that the match starts at and its tree parent is -//! resolved to the search element definition, we get a reference. - -mod classify; -mod name_definition; -mod rename; -mod search_scope; - -use hir::Source; -use once_cell::unsync::Lazy; -use ra_db::{SourceDatabase, SourceDatabaseExt}; -use ra_prof::profile; -use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SourceFile, SyntaxNode, TextUnit}; - -use crate::{ - db::RootDatabase, display::ToNav, FilePosition, FileRange, NavigationTarget, RangeInfo, -}; - -pub(crate) use self::{ - classify::{classify_name, classify_name_ref}, - name_definition::{NameDefinition, NameKind}, - rename::rename, -}; - -pub use self::search_scope::SearchScope; - -#[derive(Debug, Clone)] -pub struct ReferenceSearchResult { - declaration: NavigationTarget, - references: Vec, -} - -impl ReferenceSearchResult { - pub fn declaration(&self) -> &NavigationTarget { - &self.declaration - } - - pub fn references(&self) -> &[FileRange] { - &self.references - } - - /// Total number of references - /// At least 1 since all valid references should - /// Have a declaration - pub fn len(&self) -> usize { - self.references.len() + 1 - } -} - -// allow turning ReferenceSearchResult into an iterator -// over FileRanges -impl IntoIterator for ReferenceSearchResult { - type Item = FileRange; - type IntoIter = std::vec::IntoIter; - - fn into_iter(mut self) -> Self::IntoIter { - let mut v = Vec::with_capacity(self.len()); - v.push(FileRange { file_id: self.declaration.file_id(), range: self.declaration.range() }); - v.append(&mut self.references); - v.into_iter() - } -} - -pub(crate) fn find_all_refs( - db: &RootDatabase, - position: FilePosition, - search_scope: Option, -) -> Option> { - let parse = db.parse(position.file_id); - let syntax = parse.tree().syntax().clone(); - let RangeInfo { range, info: (name, def) } = find_name(db, &syntax, position)?; - - let declaration = match def.kind { - NameKind::Macro(mac) => mac.to_nav(db), - NameKind::Field(field) => field.to_nav(db), - NameKind::AssocItem(assoc) => assoc.to_nav(db), - NameKind::Def(def) => NavigationTarget::from_def(db, def)?, - NameKind::SelfType(imp) => imp.to_nav(db), - NameKind::Local(local) => local.to_nav(db), - NameKind::GenericParam(_) => return None, - }; - - let search_scope = { - let base = def.search_scope(db); - match search_scope { - None => base, - Some(scope) => base.intersection(&scope), - } - }; - - let references = process_definition(db, def, name, search_scope); - - Some(RangeInfo::new(range, ReferenceSearchResult { declaration, references })) -} - -fn find_name<'a>( - db: &RootDatabase, - syntax: &SyntaxNode, - position: FilePosition, -) -> Option> { - if let Some(name) = find_node_at_offset::(&syntax, position.offset) { - let def = classify_name(db, Source::new(position.file_id.into(), &name))?; - let range = name.syntax().text_range(); - return Some(RangeInfo::new(range, (name.text().to_string(), def))); - } - let name_ref = find_node_at_offset::(&syntax, position.offset)?; - let def = classify_name_ref(db, Source::new(position.file_id.into(), &name_ref))?; - let range = name_ref.syntax().text_range(); - Some(RangeInfo::new(range, (name_ref.text().to_string(), def))) -} - -fn process_definition( - db: &RootDatabase, - def: NameDefinition, - name: String, - scope: SearchScope, -) -> Vec { - let _p = profile("process_definition"); - - let pat = name.as_str(); - let mut refs = vec![]; - - for (file_id, search_range) in scope { - let text = db.file_text(file_id); - let parse = Lazy::new(|| SourceFile::parse(&text)); - - for (idx, _) in text.match_indices(pat) { - let offset = TextUnit::from_usize(idx); - - if let Some(name_ref) = - find_node_at_offset::(parse.tree().syntax(), offset) - { - let range = name_ref.syntax().text_range(); - if let Some(search_range) = search_range { - if !range.is_subrange(&search_range) { - continue; - } - } - if let Some(d) = classify_name_ref(db, Source::new(file_id.into(), &name_ref)) { - if d == def { - refs.push(FileRange { file_id, range }); - } - } - } - } - } - refs -} - -#[cfg(test)] -mod tests { - use crate::{ - mock_analysis::{analysis_and_position, single_file_with_position, MockAnalysis}, - ReferenceSearchResult, SearchScope, - }; - - #[test] - fn test_find_all_refs_for_local() { - let code = r#" - fn main() { - let mut i = 1; - let j = 1; - i = i<|> + j; - - { - i = 0; - } - - i = 5; - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 5); - } - - #[test] - fn test_find_all_refs_for_param_inside() { - let code = r#" - fn foo(i : u32) -> u32 { - i<|> - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 2); - } - - #[test] - fn test_find_all_refs_for_fn_param() { - let code = r#" - fn foo(i<|> : u32) -> u32 { - i - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 2); - } - - #[test] - fn test_find_all_refs_field_name() { - let code = r#" - //- /lib.rs - struct Foo { - pub spam<|>: u32, - } - - fn main(s: Foo) { - let f = s.spam; - } - "#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 2); - } - - #[test] - fn test_find_all_refs_impl_item_name() { - let code = r#" - //- /lib.rs - struct Foo; - impl Foo { - fn f<|>(&self) { } - } - "#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 1); - } - - #[test] - fn test_find_all_refs_enum_var_name() { - let code = r#" - //- /lib.rs - enum Foo { - A, - B<|>, - C, - } - "#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 1); - } - - #[test] - fn test_find_all_refs_two_modules() { - let code = r#" - //- /lib.rs - pub mod foo; - pub mod bar; - - fn f() { - let i = foo::Foo { n: 5 }; - } - - //- /foo.rs - use crate::bar; - - pub struct Foo { - pub n: u32, - } - - fn f() { - let i = bar::Bar { n: 5 }; - } - - //- /bar.rs - use crate::foo; - - pub struct Bar { - pub n: u32, - } - - fn f() { - let i = foo::Foo<|> { n: 5 }; - } - "#; - - let (analysis, pos) = analysis_and_position(code); - let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); - assert_eq!(refs.len(), 3); - } - - // `mod foo;` is not in the results because `foo` is an `ast::Name`. - // So, there are two references: the first one is a definition of the `foo` module, - // which is the whole `foo.rs`, and the second one is in `use foo::Foo`. - #[test] - fn test_find_all_refs_decl_module() { - let code = r#" - //- /lib.rs - mod foo<|>; - - use foo::Foo; - - fn f() { - let i = Foo { n: 5 }; - } - - //- /foo.rs - pub struct Foo { - pub n: u32, - } - "#; - - let (analysis, pos) = analysis_and_position(code); - let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); - assert_eq!(refs.len(), 2); - } - - #[test] - fn test_find_all_refs_super_mod_vis() { - let code = r#" - //- /lib.rs - mod foo; - - //- /foo.rs - mod some; - use some::Foo; - - fn f() { - let i = Foo { n: 5 }; - } - - //- /foo/some.rs - pub(super) struct Foo<|> { - pub n: u32, - } - "#; - - let (analysis, pos) = analysis_and_position(code); - let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); - assert_eq!(refs.len(), 3); - } - - #[test] - fn test_find_all_refs_with_scope() { - let code = r#" - //- /lib.rs - mod foo; - mod bar; - - pub fn quux<|>() {} - - //- /foo.rs - fn f() { super::quux(); } - - //- /bar.rs - fn f() { super::quux(); } - "#; - - let (mock, pos) = MockAnalysis::with_files_and_position(code); - let bar = mock.id_of("/bar.rs"); - let analysis = mock.analysis(); - - let refs = analysis.find_all_refs(pos, None).unwrap().unwrap(); - assert_eq!(refs.len(), 3); - - let refs = - analysis.find_all_refs(pos, Some(SearchScope::single_file(bar))).unwrap().unwrap(); - assert_eq!(refs.len(), 2); - } - - #[test] - fn test_find_all_refs_macro_def() { - let code = r#" - #[macro_export] - macro_rules! m1<|> { () => (()) } - - fn foo() { - m1(); - m1(); - }"#; - - let refs = get_all_refs(code); - assert_eq!(refs.len(), 3); - } - - fn get_all_refs(text: &str) -> ReferenceSearchResult { - let (analysis, position) = single_file_with_position(text); - analysis.find_all_refs(position, None).unwrap().unwrap() - } -} diff --git a/crates/ra_ide_api/src/references/classify.rs b/crates/ra_ide_api/src/references/classify.rs deleted file mode 100644 index 5cea805ec..000000000 --- a/crates/ra_ide_api/src/references/classify.rs +++ /dev/null @@ -1,186 +0,0 @@ -//! Functions that are used to classify an element from its definition or reference. - -use hir::{FromSource, Module, ModuleSource, PathResolution, Source, SourceAnalyzer}; -use ra_prof::profile; -use ra_syntax::{ast, match_ast, AstNode}; -use test_utils::tested_by; - -use super::{ - name_definition::{from_assoc_item, from_module_def, from_struct_field}, - NameDefinition, NameKind, -}; -use crate::db::RootDatabase; - -pub(crate) fn classify_name(db: &RootDatabase, name: Source<&ast::Name>) -> Option { - let _p = profile("classify_name"); - let parent = name.value.syntax().parent()?; - - match_ast! { - match parent { - ast::BindPat(it) => { - let src = name.with_value(it); - let local = hir::Local::from_source(db, src)?; - Some(NameDefinition { - visibility: None, - container: local.module(db), - kind: NameKind::Local(local), - }) - }, - ast::RecordFieldDef(it) => { - let ast = hir::FieldSource::Named(it); - let src = name.with_value(ast); - let field = hir::StructField::from_source(db, src)?; - Some(from_struct_field(db, field)) - }, - ast::Module(it) => { - let def = { - if !it.has_semi() { - let ast = hir::ModuleSource::Module(it); - let src = name.with_value(ast); - hir::Module::from_definition(db, src) - } else { - let src = name.with_value(it); - hir::Module::from_declaration(db, src) - } - }?; - Some(from_module_def(db, def.into(), None)) - }, - ast::StructDef(it) => { - let src = name.with_value(it); - let def = hir::Struct::from_source(db, src)?; - Some(from_module_def(db, def.into(), None)) - }, - ast::EnumDef(it) => { - let src = name.with_value(it); - let def = hir::Enum::from_source(db, src)?; - Some(from_module_def(db, def.into(), None)) - }, - ast::TraitDef(it) => { - let src = name.with_value(it); - let def = hir::Trait::from_source(db, src)?; - Some(from_module_def(db, def.into(), None)) - }, - ast::StaticDef(it) => { - let src = name.with_value(it); - let def = hir::Static::from_source(db, src)?; - Some(from_module_def(db, def.into(), None)) - }, - ast::EnumVariant(it) => { - let src = name.with_value(it); - let def = hir::EnumVariant::from_source(db, src)?; - Some(from_module_def(db, def.into(), None)) - }, - ast::FnDef(it) => { - let src = name.with_value(it); - let def = hir::Function::from_source(db, src)?; - if parent.parent().and_then(ast::ItemList::cast).is_some() { - Some(from_assoc_item(db, def.into())) - } else { - Some(from_module_def(db, def.into(), None)) - } - }, - ast::ConstDef(it) => { - let src = name.with_value(it); - let def = hir::Const::from_source(db, src)?; - if parent.parent().and_then(ast::ItemList::cast).is_some() { - Some(from_assoc_item(db, def.into())) - } else { - Some(from_module_def(db, def.into(), None)) - } - }, - ast::TypeAliasDef(it) => { - let src = name.with_value(it); - let def = hir::TypeAlias::from_source(db, src)?; - if parent.parent().and_then(ast::ItemList::cast).is_some() { - Some(from_assoc_item(db, def.into())) - } else { - Some(from_module_def(db, def.into(), None)) - } - }, - ast::MacroCall(it) => { - let src = name.with_value(it); - let def = hir::MacroDef::from_source(db, src.clone())?; - - let module_src = ModuleSource::from_child_node(db, src.as_ref().map(|it| it.syntax())); - let module = Module::from_definition(db, src.with_value(module_src))?; - - Some(NameDefinition { - visibility: None, - container: module, - kind: NameKind::Macro(def), - }) - }, - _ => None, - } - } -} - -pub(crate) fn classify_name_ref( - db: &RootDatabase, - name_ref: Source<&ast::NameRef>, -) -> Option { - let _p = profile("classify_name_ref"); - - let parent = name_ref.value.syntax().parent()?; - let analyzer = SourceAnalyzer::new(db, name_ref.map(|it| it.syntax()), None); - - if let Some(method_call) = ast::MethodCallExpr::cast(parent.clone()) { - tested_by!(goto_definition_works_for_methods); - if let Some(func) = analyzer.resolve_method_call(&method_call) { - return Some(from_assoc_item(db, func.into())); - } - } - - if let Some(field_expr) = ast::FieldExpr::cast(parent.clone()) { - tested_by!(goto_definition_works_for_fields); - if let Some(field) = analyzer.resolve_field(&field_expr) { - return Some(from_struct_field(db, field)); - } - } - - if let Some(record_field) = ast::RecordField::cast(parent.clone()) { - tested_by!(goto_definition_works_for_record_fields); - if let Some(field_def) = analyzer.resolve_record_field(&record_field) { - return Some(from_struct_field(db, field_def)); - } - } - - let ast = ModuleSource::from_child_node(db, name_ref.with_value(&parent)); - // FIXME: find correct container and visibility for each case - let container = Module::from_definition(db, name_ref.with_value(ast))?; - let visibility = None; - - if let Some(macro_call) = parent.ancestors().find_map(ast::MacroCall::cast) { - tested_by!(goto_definition_works_for_macros); - if let Some(macro_def) = analyzer.resolve_macro_call(db, name_ref.with_value(¯o_call)) { - let kind = NameKind::Macro(macro_def); - return Some(NameDefinition { kind, container, visibility }); - } - } - - let path = name_ref.value.syntax().ancestors().find_map(ast::Path::cast)?; - let resolved = analyzer.resolve_path(db, &path)?; - match resolved { - PathResolution::Def(def) => Some(from_module_def(db, def, Some(container))), - PathResolution::AssocItem(item) => Some(from_assoc_item(db, item)), - PathResolution::Local(local) => { - let container = local.module(db); - let kind = NameKind::Local(local); - Some(NameDefinition { kind, container, visibility: None }) - } - PathResolution::GenericParam(par) => { - // FIXME: get generic param def - let kind = NameKind::GenericParam(par); - Some(NameDefinition { kind, container, visibility }) - } - PathResolution::Macro(def) => { - let kind = NameKind::Macro(def); - Some(NameDefinition { kind, container, visibility }) - } - PathResolution::SelfType(impl_block) => { - let kind = NameKind::SelfType(impl_block); - let container = impl_block.module(db); - Some(NameDefinition { kind, container, visibility }) - } - } -} diff --git a/crates/ra_ide_api/src/references/name_definition.rs b/crates/ra_ide_api/src/references/name_definition.rs deleted file mode 100644 index 10d3a2364..000000000 --- a/crates/ra_ide_api/src/references/name_definition.rs +++ /dev/null @@ -1,83 +0,0 @@ -//! `NameDefinition` keeps information about the element we want to search references for. -//! The element is represented by `NameKind`. It's located inside some `container` and -//! has a `visibility`, which defines a search scope. -//! Note that the reference search is possible for not all of the classified items. - -use hir::{ - Adt, AssocItem, GenericParam, HasSource, ImplBlock, Local, MacroDef, Module, ModuleDef, - StructField, VariantDef, -}; -use ra_syntax::{ast, ast::VisibilityOwner}; - -use crate::db::RootDatabase; - -#[derive(Debug, PartialEq, Eq)] -pub enum NameKind { - Macro(MacroDef), - Field(StructField), - AssocItem(AssocItem), - Def(ModuleDef), - SelfType(ImplBlock), - Local(Local), - GenericParam(GenericParam), -} - -#[derive(PartialEq, Eq)] -pub(crate) struct NameDefinition { - pub visibility: Option, - pub container: Module, - pub kind: NameKind, -} - -pub(super) fn from_assoc_item(db: &RootDatabase, item: AssocItem) -> NameDefinition { - let container = item.module(db); - let visibility = match item { - AssocItem::Function(f) => f.source(db).value.visibility(), - AssocItem::Const(c) => c.source(db).value.visibility(), - AssocItem::TypeAlias(a) => a.source(db).value.visibility(), - }; - let kind = NameKind::AssocItem(item); - NameDefinition { kind, container, visibility } -} - -pub(super) fn from_struct_field(db: &RootDatabase, field: StructField) -> NameDefinition { - let kind = NameKind::Field(field); - let parent = field.parent_def(db); - let container = parent.module(db); - let visibility = match parent { - VariantDef::Struct(s) => s.source(db).value.visibility(), - VariantDef::Union(e) => e.source(db).value.visibility(), - VariantDef::EnumVariant(e) => e.source(db).value.parent_enum().visibility(), - }; - NameDefinition { kind, container, visibility } -} - -pub(super) fn from_module_def( - db: &RootDatabase, - def: ModuleDef, - module: Option, -) -> NameDefinition { - let kind = NameKind::Def(def); - let (container, visibility) = match def { - ModuleDef::Module(it) => { - let container = it.parent(db).or_else(|| Some(it)).unwrap(); - let visibility = it.declaration_source(db).and_then(|s| s.value.visibility()); - (container, visibility) - } - ModuleDef::EnumVariant(it) => { - let container = it.module(db); - let visibility = it.source(db).value.parent_enum().visibility(); - (container, visibility) - } - ModuleDef::Function(it) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::Const(it) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::Static(it) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::Trait(it) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::TypeAlias(it) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::Adt(Adt::Struct(it)) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::Adt(Adt::Union(it)) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::Adt(Adt::Enum(it)) => (it.module(db), it.source(db).value.visibility()), - ModuleDef::BuiltinType(..) => (module.unwrap(), None), - }; - NameDefinition { kind, container, visibility } -} diff --git a/crates/ra_ide_api/src/references/rename.rs b/crates/ra_ide_api/src/references/rename.rs deleted file mode 100644 index d58496049..000000000 --- a/crates/ra_ide_api/src/references/rename.rs +++ /dev/null @@ -1,328 +0,0 @@ -//! FIXME: write short doc here - -use hir::ModuleSource; -use ra_db::{RelativePath, RelativePathBuf, SourceDatabase, SourceDatabaseExt}; -use ra_syntax::{algo::find_node_at_offset, ast, AstNode, SyntaxNode}; -use ra_text_edit::TextEdit; - -use crate::{ - db::RootDatabase, FileId, FilePosition, FileSystemEdit, RangeInfo, SourceChange, - SourceFileEdit, TextRange, -}; - -use super::find_all_refs; - -pub(crate) fn rename( - db: &RootDatabase, - position: FilePosition, - new_name: &str, -) -> Option> { - let parse = db.parse(position.file_id); - if let Some((ast_name, ast_module)) = - find_name_and_module_at_offset(parse.tree().syntax(), position) - { - let range = ast_name.syntax().text_range(); - rename_mod(db, &ast_name, &ast_module, position, new_name) - .map(|info| RangeInfo::new(range, info)) - } else { - rename_reference(db, position, new_name) - } -} - -fn find_name_and_module_at_offset( - syntax: &SyntaxNode, - position: FilePosition, -) -> Option<(ast::Name, ast::Module)> { - let ast_name = find_node_at_offset::(syntax, position.offset)?; - let ast_module = ast::Module::cast(ast_name.syntax().parent()?)?; - Some((ast_name, ast_module)) -} - -fn source_edit_from_file_id_range( - file_id: FileId, - range: TextRange, - new_name: &str, -) -> SourceFileEdit { - SourceFileEdit { file_id, edit: TextEdit::replace(range, new_name.into()) } -} - -fn rename_mod( - db: &RootDatabase, - ast_name: &ast::Name, - ast_module: &ast::Module, - position: FilePosition, - new_name: &str, -) -> Option { - let mut source_file_edits = Vec::new(); - let mut file_system_edits = Vec::new(); - let module_src = hir::Source { file_id: position.file_id.into(), value: ast_module.clone() }; - if let Some(module) = hir::Module::from_declaration(db, module_src) { - let src = module.definition_source(db); - let file_id = src.file_id.original_file(db); - match src.value { - ModuleSource::SourceFile(..) => { - let mod_path: RelativePathBuf = db.file_relative_path(file_id); - // mod is defined in path/to/dir/mod.rs - let dst_path = if mod_path.file_stem() == Some("mod") { - mod_path - .parent() - .and_then(|p| p.parent()) - .or_else(|| Some(RelativePath::new(""))) - .map(|p| p.join(new_name).join("mod.rs")) - } else { - Some(mod_path.with_file_name(new_name).with_extension("rs")) - }; - if let Some(path) = dst_path { - let move_file = FileSystemEdit::MoveFile { - src: file_id, - dst_source_root: db.file_source_root(position.file_id), - dst_path: path, - }; - file_system_edits.push(move_file); - } - } - ModuleSource::Module(..) => {} - } - } - - let edit = SourceFileEdit { - file_id: position.file_id, - edit: TextEdit::replace(ast_name.syntax().text_range(), new_name.into()), - }; - source_file_edits.push(edit); - - Some(SourceChange::from_edits("rename", source_file_edits, file_system_edits)) -} - -fn rename_reference( - db: &RootDatabase, - position: FilePosition, - new_name: &str, -) -> Option> { - let RangeInfo { range, info: refs } = find_all_refs(db, position, None)?; - - let edit = refs - .into_iter() - .map(|range| source_edit_from_file_id_range(range.file_id, range.range, new_name)) - .collect::>(); - - if edit.is_empty() { - return None; - } - - Some(RangeInfo::new(range, SourceChange::source_file_edits("rename", edit))) -} - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - use ra_text_edit::TextEditBuilder; - use test_utils::assert_eq_text; - - use crate::{ - mock_analysis::analysis_and_position, mock_analysis::single_file_with_position, FileId, - }; - - #[test] - fn test_rename_for_local() { - test_rename( - r#" - fn main() { - let mut i = 1; - let j = 1; - i = i<|> + j; - - { - i = 0; - } - - i = 5; - }"#, - "k", - r#" - fn main() { - let mut k = 1; - let j = 1; - k = k + j; - - { - k = 0; - } - - k = 5; - }"#, - ); - } - - #[test] - fn test_rename_for_param_inside() { - test_rename( - r#" - fn foo(i : u32) -> u32 { - i<|> - }"#, - "j", - r#" - fn foo(j : u32) -> u32 { - j - }"#, - ); - } - - #[test] - fn test_rename_refs_for_fn_param() { - test_rename( - r#" - fn foo(i<|> : u32) -> u32 { - i - }"#, - "new_name", - r#" - fn foo(new_name : u32) -> u32 { - new_name - }"#, - ); - } - - #[test] - fn test_rename_for_mut_param() { - test_rename( - r#" - fn foo(mut i<|> : u32) -> u32 { - i - }"#, - "new_name", - r#" - fn foo(mut new_name : u32) -> u32 { - new_name - }"#, - ); - } - - #[test] - fn test_rename_mod() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod bar; - - //- /bar.rs - mod foo<|>; - - //- /bar/foo.rs - // emtpy - ", - ); - let new_name = "foo2"; - let source_change = analysis.rename(position, new_name).unwrap(); - assert_debug_snapshot!(&source_change, -@r###" - Some( - RangeInfo { - range: [4; 7), - info: SourceChange { - label: "rename", - source_file_edits: [ - SourceFileEdit { - file_id: FileId( - 2, - ), - edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: [4; 7), - insert: "foo2", - }, - ], - }, - }, - ], - file_system_edits: [ - MoveFile { - src: FileId( - 3, - ), - dst_source_root: SourceRootId( - 0, - ), - dst_path: "bar/foo2.rs", - }, - ], - cursor_position: None, - }, - }, - ) - "###); - } - - #[test] - fn test_rename_mod_in_dir() { - let (analysis, position) = analysis_and_position( - " - //- /lib.rs - mod fo<|>o; - //- /foo/mod.rs - // emtpy - ", - ); - let new_name = "foo2"; - let source_change = analysis.rename(position, new_name).unwrap(); - assert_debug_snapshot!(&source_change, - @r###" - Some( - RangeInfo { - range: [4; 7), - info: SourceChange { - label: "rename", - source_file_edits: [ - SourceFileEdit { - file_id: FileId( - 1, - ), - edit: TextEdit { - atoms: [ - AtomTextEdit { - delete: [4; 7), - insert: "foo2", - }, - ], - }, - }, - ], - file_system_edits: [ - MoveFile { - src: FileId( - 2, - ), - dst_source_root: SourceRootId( - 0, - ), - dst_path: "foo2/mod.rs", - }, - ], - cursor_position: None, - }, - }, - ) - "### - ); - } - - fn test_rename(text: &str, new_name: &str, expected: &str) { - let (analysis, position) = single_file_with_position(text); - let source_change = analysis.rename(position, new_name).unwrap(); - let mut text_edit_builder = TextEditBuilder::default(); - let mut file_id: Option = None; - if let Some(change) = source_change { - for edit in change.info.source_file_edits { - file_id = Some(edit.file_id); - for atom in edit.edit.as_atoms() { - text_edit_builder.replace(atom.delete, atom.insert.clone()); - } - } - } - let result = - text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap()); - assert_eq_text!(expected, &*result); - } -} diff --git a/crates/ra_ide_api/src/references/search_scope.rs b/crates/ra_ide_api/src/references/search_scope.rs deleted file mode 100644 index f5c9589f4..000000000 --- a/crates/ra_ide_api/src/references/search_scope.rs +++ /dev/null @@ -1,145 +0,0 @@ -//! Generally, `search_scope` returns files that might contain references for the element. -//! For `pub(crate)` things it's a crate, for `pub` things it's a crate and dependant crates. -//! In some cases, the location of the references is known to within a `TextRange`, -//! e.g. for things like local variables. -use std::mem; - -use hir::{DefWithBody, HasSource, ModuleSource}; -use ra_db::{FileId, SourceDatabase, SourceDatabaseExt}; -use ra_prof::profile; -use ra_syntax::{AstNode, TextRange}; -use rustc_hash::FxHashMap; - -use crate::db::RootDatabase; - -use super::{NameDefinition, NameKind}; - -pub struct SearchScope { - entries: FxHashMap>, -} - -impl SearchScope { - fn new(entries: FxHashMap>) -> SearchScope { - SearchScope { entries } - } - pub fn single_file(file: FileId) -> SearchScope { - SearchScope::new(std::iter::once((file, None)).collect()) - } - pub(crate) fn intersection(&self, other: &SearchScope) -> SearchScope { - let (mut small, mut large) = (&self.entries, &other.entries); - if small.len() > large.len() { - mem::swap(&mut small, &mut large) - } - - let res = small - .iter() - .filter_map(|(file_id, r1)| { - let r2 = large.get(file_id)?; - let r = intersect_ranges(*r1, *r2)?; - Some((*file_id, r)) - }) - .collect(); - return SearchScope::new(res); - - fn intersect_ranges( - r1: Option, - r2: Option, - ) -> Option> { - match (r1, r2) { - (None, r) | (r, None) => Some(r), - (Some(r1), Some(r2)) => { - let r = r1.intersection(&r2)?; - Some(Some(r)) - } - } - } - } -} - -impl IntoIterator for SearchScope { - type Item = (FileId, Option); - type IntoIter = std::collections::hash_map::IntoIter>; - fn into_iter(self) -> Self::IntoIter { - self.entries.into_iter() - } -} - -impl NameDefinition { - pub(crate) fn search_scope(&self, db: &RootDatabase) -> SearchScope { - let _p = profile("search_scope"); - - let module_src = self.container.definition_source(db); - let file_id = module_src.file_id.original_file(db); - - if let NameKind::Local(var) = self.kind { - let range = match var.parent(db) { - DefWithBody::Function(f) => f.source(db).value.syntax().text_range(), - DefWithBody::Const(c) => c.source(db).value.syntax().text_range(), - DefWithBody::Static(s) => s.source(db).value.syntax().text_range(), - }; - let mut res = FxHashMap::default(); - res.insert(file_id, Some(range)); - return SearchScope::new(res); - } - - let vis = - self.visibility.as_ref().map(|v| v.syntax().to_string()).unwrap_or("".to_string()); - - if vis.as_str() == "pub(super)" { - if let Some(parent_module) = self.container.parent(db) { - let mut res = FxHashMap::default(); - let parent_src = parent_module.definition_source(db); - let file_id = parent_src.file_id.original_file(db); - - match parent_src.value { - ModuleSource::Module(m) => { - let range = Some(m.syntax().text_range()); - res.insert(file_id, range); - } - ModuleSource::SourceFile(_) => { - res.insert(file_id, None); - res.extend(parent_module.children(db).map(|m| { - let src = m.definition_source(db); - (src.file_id.original_file(db), None) - })); - } - } - return SearchScope::new(res); - } - } - - if vis.as_str() != "" { - let source_root_id = db.file_source_root(file_id); - let source_root = db.source_root(source_root_id); - let mut res = source_root.walk().map(|id| (id, None)).collect::>(); - - // FIXME: add "pub(in path)" - - if vis.as_str() == "pub(crate)" { - return SearchScope::new(res); - } - if vis.as_str() == "pub" { - let krate = self.container.krate(); - let crate_graph = db.crate_graph(); - for crate_id in crate_graph.iter() { - let mut crate_deps = crate_graph.dependencies(crate_id); - if crate_deps.any(|dep| dep.crate_id() == krate.crate_id()) { - let root_file = crate_graph.crate_root(crate_id); - let source_root_id = db.file_source_root(root_file); - let source_root = db.source_root(source_root_id); - res.extend(source_root.walk().map(|id| (id, None))); - } - } - return SearchScope::new(res); - } - } - - let mut res = FxHashMap::default(); - let range = match module_src.value { - ModuleSource::Module(m) => Some(m.syntax().text_range()), - ModuleSource::SourceFile(_) => None, - }; - res.insert(file_id, range); - SearchScope::new(res) - } -} diff --git a/crates/ra_ide_api/src/runnables.rs b/crates/ra_ide_api/src/runnables.rs deleted file mode 100644 index 8039a5164..000000000 --- a/crates/ra_ide_api/src/runnables.rs +++ /dev/null @@ -1,242 +0,0 @@ -//! FIXME: write short doc here - -use hir::Source; -use itertools::Itertools; -use ra_db::SourceDatabase; -use ra_syntax::{ - ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner}, - match_ast, SyntaxNode, TextRange, -}; - -use crate::{db::RootDatabase, FileId}; - -#[derive(Debug)] -pub struct Runnable { - pub range: TextRange, - pub kind: RunnableKind, -} - -#[derive(Debug)] -pub enum RunnableKind { - Test { name: String }, - TestMod { path: String }, - Bench { name: String }, - Bin, -} - -pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { - let parse = db.parse(file_id); - parse.tree().syntax().descendants().filter_map(|i| runnable(db, file_id, i)).collect() -} - -fn runnable(db: &RootDatabase, file_id: FileId, item: SyntaxNode) -> Option { - match_ast! { - match item { - ast::FnDef(it) => { runnable_fn(it) }, - ast::Module(it) => { runnable_mod(db, file_id, it) }, - _ => { None }, - } - } -} - -fn runnable_fn(fn_def: ast::FnDef) -> Option { - let name = fn_def.name()?.text().clone(); - let kind = if name == "main" { - RunnableKind::Bin - } else if fn_def.has_atom_attr("test") { - RunnableKind::Test { name: name.to_string() } - } else if fn_def.has_atom_attr("bench") { - RunnableKind::Bench { name: name.to_string() } - } else { - return None; - }; - Some(Runnable { range: fn_def.syntax().text_range(), kind }) -} - -fn runnable_mod(db: &RootDatabase, file_id: FileId, module: ast::Module) -> Option { - let has_test_function = module - .item_list()? - .items() - .filter_map(|it| match it { - ast::ModuleItem::FnDef(it) => Some(it), - _ => None, - }) - .any(|f| f.has_atom_attr("test")); - if !has_test_function { - return None; - } - let range = module.syntax().text_range(); - let src = hir::ModuleSource::from_child_node(db, Source::new(file_id.into(), &module.syntax())); - let module = hir::Module::from_definition(db, Source::new(file_id.into(), src))?; - - let path = module.path_to_root(db).into_iter().rev().filter_map(|it| it.name(db)).join("::"); - Some(Runnable { range, kind: RunnableKind::TestMod { path } }) -} - -#[cfg(test)] -mod tests { - use insta::assert_debug_snapshot; - - use crate::mock_analysis::analysis_and_position; - - #[test] - fn test_runnables() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - fn main() {} - - #[test] - fn test_foo() {} - - #[test] - #[ignore] - fn test_foo() {} - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_debug_snapshot!(&runnables, - @r###" - [ - Runnable { - range: [1; 21), - kind: Bin, - }, - Runnable { - range: [22; 46), - kind: Test { - name: "test_foo", - }, - }, - Runnable { - range: [47; 81), - kind: Test { - name: "test_foo", - }, - }, - ] - "### - ); - } - - #[test] - fn test_runnables_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod test_mod { - #[test] - fn test_foo1() {} - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_debug_snapshot!(&runnables, - @r###" - [ - Runnable { - range: [1; 59), - kind: TestMod { - path: "test_mod", - }, - }, - Runnable { - range: [28; 57), - kind: Test { - name: "test_foo1", - }, - }, - ] - "### - ); - } - - #[test] - fn test_runnables_one_depth_layer_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod foo { - mod test_mod { - #[test] - fn test_foo1() {} - } - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_debug_snapshot!(&runnables, - @r###" - [ - Runnable { - range: [23; 85), - kind: TestMod { - path: "foo::test_mod", - }, - }, - Runnable { - range: [46; 79), - kind: Test { - name: "test_foo1", - }, - }, - ] - "### - ); - } - - #[test] - fn test_runnables_multiple_depth_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod foo { - mod bar { - mod test_mod { - #[test] - fn test_foo1() {} - } - } - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert_debug_snapshot!(&runnables, - @r###" - [ - Runnable { - range: [41; 115), - kind: TestMod { - path: "foo::bar::test_mod", - }, - }, - Runnable { - range: [68; 105), - kind: Test { - name: "test_foo1", - }, - }, - ] - "### - ); - } - - #[test] - fn test_runnables_no_test_function_in_module() { - let (analysis, pos) = analysis_and_position( - r#" - //- /lib.rs - <|> //empty - mod test_mod { - fn foo1() {} - } - "#, - ); - let runnables = analysis.runnables(pos.file_id).unwrap(); - assert!(runnables.is_empty()) - } -} diff --git a/crates/ra_ide_api/src/snapshots/highlighting.html b/crates/ra_ide_api/src/snapshots/highlighting.html deleted file mode 100644 index b39c4d371..000000000 --- a/crates/ra_ide_api/src/snapshots/highlighting.html +++ /dev/null @@ -1,48 +0,0 @@ - - -
#[derive(Clone, Debug)]
-struct Foo {
-    pub x: i32,
-    pub y: i32,
-}
-
-fn foo<T>() -> T {
-    unimplemented!();
-    foo::<i32>();
-}
-
-// comment
-fn main() {
-    println!("Hello, {}!", 92);
-
-    let mut vec = Vec::new();
-    if true {
-        vec.push(Foo { x: 0, y: 1 });
-    }
-    unsafe { vec.set_len(0); }
-
-    let mut x = 42;
-    let y = &mut x;
-    let z = &y;
-
-    y;
-}
\ No newline at end of file diff --git a/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html b/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html deleted file mode 100644 index 79f11ea80..000000000 --- a/crates/ra_ide_api/src/snapshots/rainbow_highlighting.html +++ /dev/null @@ -1,33 +0,0 @@ - - -
fn main() {
-    let hello = "hello";
-    let x = hello.to_string();
-    let y = hello.to_string();
-
-    let x = "other color please!";
-    let y = x.to_string();
-}
-
-fn bar() {
-    let mut hello = "hello";
-}
\ No newline at end of file diff --git a/crates/ra_ide_api/src/source_change.rs b/crates/ra_ide_api/src/source_change.rs deleted file mode 100644 index f5f7f8807..000000000 --- a/crates/ra_ide_api/src/source_change.rs +++ /dev/null @@ -1,119 +0,0 @@ -//! This modules defines type to represent changes to the source code, that flow -//! from the server to the client. -//! -//! It can be viewed as a dual for `AnalysisChange`. - -use ra_db::RelativePathBuf; -use ra_text_edit::TextEdit; - -use crate::{FileId, FilePosition, SourceRootId, TextUnit}; - -#[derive(Debug)] -pub struct SourceChange { - pub label: String, - pub source_file_edits: Vec, - pub file_system_edits: Vec, - pub cursor_position: Option, -} - -impl SourceChange { - /// Creates a new SourceChange with the given label - /// from the edits. - pub(crate) fn from_edits>( - label: L, - source_file_edits: Vec, - file_system_edits: Vec, - ) -> Self { - SourceChange { - label: label.into(), - source_file_edits, - file_system_edits, - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only the given `SourceFileEdits`. - pub(crate) fn source_file_edits>(label: L, edits: Vec) -> Self { - SourceChange { - label: label.into(), - source_file_edits: edits, - file_system_edits: vec![], - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only the given `FileSystemEdits`. - pub(crate) fn file_system_edits>(label: L, edits: Vec) -> Self { - SourceChange { - label: label.into(), - source_file_edits: vec![], - file_system_edits: edits, - cursor_position: None, - } - } - - /// Creates a new SourceChange with the given label, - /// containing only a single `SourceFileEdit`. - pub(crate) fn source_file_edit>(label: L, edit: SourceFileEdit) -> Self { - SourceChange::source_file_edits(label, vec![edit]) - } - - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub(crate) fn source_file_edit_from>( - label: L, - file_id: FileId, - edit: TextEdit, - ) -> Self { - SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit }) - } - - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub(crate) fn file_system_edit>(label: L, edit: FileSystemEdit) -> Self { - SourceChange::file_system_edits(label, vec![edit]) - } - - /// Sets the cursor position to the given `FilePosition` - pub(crate) fn with_cursor(mut self, cursor_position: FilePosition) -> Self { - self.cursor_position = Some(cursor_position); - self - } - - /// Sets the cursor position to the given `FilePosition` - pub(crate) fn with_cursor_opt(mut self, cursor_position: Option) -> Self { - self.cursor_position = cursor_position; - self - } -} - -#[derive(Debug)] -pub struct SourceFileEdit { - pub file_id: FileId, - pub edit: TextEdit, -} - -#[derive(Debug)] -pub enum FileSystemEdit { - CreateFile { source_root: SourceRootId, path: RelativePathBuf }, - MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf }, -} - -pub(crate) struct SingleFileChange { - pub label: String, - pub edit: TextEdit, - pub cursor_position: Option, -} - -impl SingleFileChange { - pub(crate) fn into_source_change(self, file_id: FileId) -> SourceChange { - SourceChange { - label: self.label, - source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }], - file_system_edits: Vec::new(), - cursor_position: self.cursor_position.map(|offset| FilePosition { file_id, offset }), - } - } -} diff --git a/crates/ra_ide_api/src/status.rs b/crates/ra_ide_api/src/status.rs deleted file mode 100644 index 1bb27eb85..000000000 --- a/crates/ra_ide_api/src/status.rs +++ /dev/null @@ -1,136 +0,0 @@ -//! FIXME: write short doc here - -use std::{fmt, iter::FromIterator, sync::Arc}; - -use hir::MacroFile; -use ra_db::{ - salsa::{ - debug::{DebugQueryTable, TableEntry}, - Database, - }, - FileTextQuery, SourceRootId, -}; -use ra_prof::{memory_usage, Bytes}; -use ra_syntax::{ast, Parse, SyntaxNode}; - -use crate::{ - db::RootDatabase, - symbol_index::{LibrarySymbolsQuery, SymbolIndex}, - FileId, -}; - -fn syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { - db.query(ra_db::ParseQuery).entries::() -} -fn macro_syntax_tree_stats(db: &RootDatabase) -> SyntaxTreeStats { - db.query(hir::db::ParseMacroQuery).entries::() -} - -pub(crate) fn status(db: &RootDatabase) -> String { - let files_stats = db.query(FileTextQuery).entries::(); - let syntax_tree_stats = syntax_tree_stats(db); - let macro_syntax_tree_stats = macro_syntax_tree_stats(db); - let symbols_stats = db.query(LibrarySymbolsQuery).entries::(); - format!( - "{}\n{}\n{}\n{} (macros)\n\n\nmemory:\n{}\ngc {:?} seconds ago", - files_stats, - symbols_stats, - syntax_tree_stats, - macro_syntax_tree_stats, - memory_usage(), - db.last_gc.elapsed().as_secs(), - ) -} - -#[derive(Default)] -struct FilesStats { - total: usize, - size: Bytes, -} - -impl fmt::Display for FilesStats { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{} ({}) files", self.total, self.size) - } -} - -impl FromIterator>> for FilesStats { - fn from_iter(iter: T) -> FilesStats - where - T: IntoIterator>>, - { - let mut res = FilesStats::default(); - for entry in iter { - res.total += 1; - res.size += entry.value.unwrap().len(); - } - res - } -} - -#[derive(Default)] -pub(crate) struct SyntaxTreeStats { - total: usize, - pub(crate) retained: usize, -} - -impl fmt::Display for SyntaxTreeStats { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{} trees, {} retained", self.total, self.retained) - } -} - -impl FromIterator>> for SyntaxTreeStats { - fn from_iter(iter: T) -> SyntaxTreeStats - where - T: IntoIterator>>, - { - let mut res = SyntaxTreeStats::default(); - for entry in iter { - res.total += 1; - res.retained += entry.value.is_some() as usize; - } - res - } -} - -impl FromIterator, M)>>> for SyntaxTreeStats { - fn from_iter(iter: T) -> SyntaxTreeStats - where - T: IntoIterator, M)>>>, - { - let mut res = SyntaxTreeStats::default(); - for entry in iter { - res.total += 1; - res.retained += entry.value.is_some() as usize; - } - res - } -} - -#[derive(Default)] -struct LibrarySymbolsStats { - total: usize, - size: Bytes, -} - -impl fmt::Display for LibrarySymbolsStats { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - write!(fmt, "{} ({}) symbols", self.total, self.size,) - } -} - -impl FromIterator>> for LibrarySymbolsStats { - fn from_iter(iter: T) -> LibrarySymbolsStats - where - T: IntoIterator>>, - { - let mut res = LibrarySymbolsStats::default(); - for entry in iter { - let value = entry.value.unwrap(); - res.total += value.len(); - res.size += value.memory_size(); - } - res - } -} diff --git a/crates/ra_ide_api/src/symbol_index.rs b/crates/ra_ide_api/src/symbol_index.rs deleted file mode 100644 index 5729eb5b3..000000000 --- a/crates/ra_ide_api/src/symbol_index.rs +++ /dev/null @@ -1,405 +0,0 @@ -//! This module handles fuzzy-searching of functions, structs and other symbols -//! by name across the whole workspace and dependencies. -//! -//! It works by building an incrementally-updated text-search index of all -//! symbols. The backbone of the index is the **awesome** `fst` crate by -//! @BurntSushi. -//! -//! In a nutshell, you give a set of strings to `fst`, and it builds a -//! finite state machine describing this set of strings. The strings which -//! could fuzzy-match a pattern can also be described by a finite state machine. -//! What is freaking cool is that you can now traverse both state machines in -//! lock-step to enumerate the strings which are both in the input set and -//! fuzz-match the query. Or, more formally, given two languages described by -//! FSTs, one can build a product FST which describes the intersection of the -//! languages. -//! -//! `fst` does not support cheap updating of the index, but it supports unioning -//! of state machines. So, to account for changing source code, we build an FST -//! for each library (which is assumed to never change) and an FST for each Rust -//! file in the current workspace, and run a query against the union of all -//! those FSTs. -use std::{ - fmt, - hash::{Hash, Hasher}, - mem, - sync::Arc, -}; - -use fst::{self, Streamer}; -use ra_db::{ - salsa::{self, ParallelDatabase}, - SourceDatabaseExt, SourceRootId, -}; -use ra_syntax::{ - ast::{self, NameOwner}, - match_ast, AstNode, Parse, SmolStr, SourceFile, - SyntaxKind::{self, *}, - SyntaxNode, SyntaxNodePtr, TextRange, WalkEvent, -}; -#[cfg(not(feature = "wasm"))] -use rayon::prelude::*; - -use crate::{db::RootDatabase, FileId, Query}; - -#[salsa::query_group(SymbolsDatabaseStorage)] -pub(crate) trait SymbolsDatabase: hir::db::HirDatabase { - fn file_symbols(&self, file_id: FileId) -> Arc; - #[salsa::input] - fn library_symbols(&self, id: SourceRootId) -> Arc; - /// The set of "local" (that is, from the current workspace) roots. - /// Files in local roots are assumed to change frequently. - #[salsa::input] - fn local_roots(&self) -> Arc>; - /// The set of roots for crates.io libraries. - /// Files in libraries are assumed to never change. - #[salsa::input] - fn library_roots(&self) -> Arc>; -} - -fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc { - db.check_canceled(); - let parse = db.parse(file_id); - - let symbols = source_file_to_file_symbols(&parse.tree(), file_id); - - // FIXME: add macros here - - Arc::new(SymbolIndex::new(symbols)) -} - -pub(crate) fn world_symbols(db: &RootDatabase, query: Query) -> Vec { - /// Need to wrap Snapshot to provide `Clone` impl for `map_with` - struct Snap(salsa::Snapshot); - impl Clone for Snap { - fn clone(&self) -> Snap { - Snap(self.0.snapshot()) - } - } - - let buf: Vec> = if query.libs { - let snap = Snap(db.snapshot()); - #[cfg(not(feature = "wasm"))] - let buf = db - .library_roots() - .par_iter() - .map_with(snap, |db, &lib_id| db.0.library_symbols(lib_id)) - .collect(); - - #[cfg(feature = "wasm")] - let buf = db.library_roots().iter().map(|&lib_id| snap.0.library_symbols(lib_id)).collect(); - - buf - } else { - let mut files = Vec::new(); - for &root in db.local_roots().iter() { - let sr = db.source_root(root); - files.extend(sr.walk()) - } - - let snap = Snap(db.snapshot()); - #[cfg(not(feature = "wasm"))] - let buf = - files.par_iter().map_with(snap, |db, &file_id| db.0.file_symbols(file_id)).collect(); - - #[cfg(feature = "wasm")] - let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect(); - - buf - }; - query.search(&buf) -} - -pub(crate) fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec { - let name = name_ref.text(); - let mut query = Query::new(name.to_string()); - query.exact(); - query.limit(4); - crate::symbol_index::world_symbols(db, query) -} - -#[derive(Default)] -pub(crate) struct SymbolIndex { - symbols: Vec, - map: fst::Map, -} - -impl fmt::Debug for SymbolIndex { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish() - } -} - -impl PartialEq for SymbolIndex { - fn eq(&self, other: &SymbolIndex) -> bool { - self.symbols == other.symbols - } -} - -impl Eq for SymbolIndex {} - -impl Hash for SymbolIndex { - fn hash(&self, hasher: &mut H) { - self.symbols.hash(hasher) - } -} - -impl SymbolIndex { - fn new(mut symbols: Vec) -> SymbolIndex { - fn cmp_key<'a>(s1: &'a FileSymbol) -> impl Ord + 'a { - unicase::Ascii::new(s1.name.as_str()) - } - #[cfg(not(feature = "wasm"))] - symbols.par_sort_by(|s1, s2| cmp_key(s1).cmp(&cmp_key(s2))); - - #[cfg(feature = "wasm")] - symbols.sort_by(|s1, s2| cmp_key(s1).cmp(&cmp_key(s2))); - - let mut builder = fst::MapBuilder::memory(); - - let mut last_batch_start = 0; - - for idx in 0..symbols.len() { - if symbols.get(last_batch_start).map(cmp_key) == symbols.get(idx + 1).map(cmp_key) { - continue; - } - - let start = last_batch_start; - let end = idx + 1; - last_batch_start = end; - - let key = symbols[start].name.as_str().to_lowercase(); - let value = SymbolIndex::range_to_map_value(start, end); - - builder.insert(key, value).unwrap(); - } - - let map = fst::Map::from_bytes(builder.into_inner().unwrap()).unwrap(); - SymbolIndex { symbols, map } - } - - pub(crate) fn len(&self) -> usize { - self.symbols.len() - } - - pub(crate) fn memory_size(&self) -> usize { - self.map.as_fst().size() + self.symbols.len() * mem::size_of::() - } - - #[cfg(not(feature = "wasm"))] - pub(crate) fn for_files( - files: impl ParallelIterator)>, - ) -> SymbolIndex { - let symbols = files - .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id)) - .collect::>(); - SymbolIndex::new(symbols) - } - - #[cfg(feature = "wasm")] - pub(crate) fn for_files( - files: impl Iterator)>, - ) -> SymbolIndex { - let symbols = files - .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id)) - .collect::>(); - SymbolIndex::new(symbols) - } - - fn range_to_map_value(start: usize, end: usize) -> u64 { - debug_assert![start <= (std::u32::MAX as usize)]; - debug_assert![end <= (std::u32::MAX as usize)]; - - ((start as u64) << 32) | end as u64 - } - - fn map_value_to_range(value: u64) -> (usize, usize) { - let end = value as u32 as usize; - let start = (value >> 32) as usize; - (start, end) - } -} - -impl Query { - pub(crate) fn search(self, indices: &[Arc]) -> Vec { - let mut op = fst::map::OpBuilder::new(); - for file_symbols in indices.iter() { - let automaton = fst::automaton::Subsequence::new(&self.lowercased); - op = op.add(file_symbols.map.search(automaton)) - } - let mut stream = op.union(); - let mut res = Vec::new(); - while let Some((_, indexed_values)) = stream.next() { - if res.len() >= self.limit { - break; - } - for indexed_value in indexed_values { - let symbol_index = &indices[indexed_value.index]; - let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value); - - for symbol in &symbol_index.symbols[start..end] { - if self.only_types && !is_type(symbol.ptr.kind()) { - continue; - } - if self.exact && symbol.name != self.query { - continue; - } - res.push(symbol.clone()); - } - } - } - res - } -} - -fn is_type(kind: SyntaxKind) -> bool { - match kind { - STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => true, - _ => false, - } -} - -/// The actual data that is stored in the index. It should be as compact as -/// possible. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct FileSymbol { - pub(crate) file_id: FileId, - pub(crate) name: SmolStr, - pub(crate) ptr: SyntaxNodePtr, - pub(crate) name_range: Option, - pub(crate) container_name: Option, -} - -fn source_file_to_file_symbols(source_file: &SourceFile, file_id: FileId) -> Vec { - let mut symbols = Vec::new(); - let mut stack = Vec::new(); - - for event in source_file.syntax().preorder() { - match event { - WalkEvent::Enter(node) => { - if let Some(mut symbol) = to_file_symbol(&node, file_id) { - symbol.container_name = stack.last().cloned(); - - stack.push(symbol.name.clone()); - symbols.push(symbol); - } - } - - WalkEvent::Leave(node) => { - if to_symbol(&node).is_some() { - stack.pop(); - } - } - } - } - - symbols -} - -fn to_symbol(node: &SyntaxNode) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> { - fn decl(node: N) -> Option<(SmolStr, SyntaxNodePtr, TextRange)> { - let name = node.name()?; - let name_range = name.syntax().text_range(); - let name = name.text().clone(); - let ptr = SyntaxNodePtr::new(node.syntax()); - - Some((name, ptr, name_range)) - } - match_ast! { - match node { - ast::FnDef(it) => { decl(it) }, - ast::StructDef(it) => { decl(it) }, - ast::EnumDef(it) => { decl(it) }, - ast::TraitDef(it) => { decl(it) }, - ast::Module(it) => { decl(it) }, - ast::TypeAliasDef(it) => { decl(it) }, - ast::ConstDef(it) => { decl(it) }, - ast::StaticDef(it) => { decl(it) }, - _ => None, - } - } -} - -fn to_file_symbol(node: &SyntaxNode, file_id: FileId) -> Option { - to_symbol(node).map(move |(name, ptr, name_range)| FileSymbol { - name, - ptr, - file_id, - name_range: Some(name_range), - container_name: None, - }) -} - -#[cfg(test)] -mod tests { - use crate::{display::NavigationTarget, mock_analysis::single_file, Query}; - use ra_syntax::{ - SmolStr, - SyntaxKind::{FN_DEF, STRUCT_DEF}, - }; - - #[test] - fn test_world_symbols_with_no_container() { - let code = r#" - enum FooInner { } - "#; - - let mut symbols = get_symbols_matching(code, "FooInner"); - - let s = symbols.pop().unwrap(); - - assert_eq!(s.name(), "FooInner"); - assert!(s.container_name().is_none()); - } - - #[test] - fn test_world_symbols_include_container_name() { - let code = r#" -fn foo() { - enum FooInner { } -} - "#; - - let mut symbols = get_symbols_matching(code, "FooInner"); - - let s = symbols.pop().unwrap(); - - assert_eq!(s.name(), "FooInner"); - assert_eq!(s.container_name(), Some(&SmolStr::new("foo"))); - - let code = r#" -mod foo { - struct FooInner; -} - "#; - - let mut symbols = get_symbols_matching(code, "FooInner"); - - let s = symbols.pop().unwrap(); - - assert_eq!(s.name(), "FooInner"); - assert_eq!(s.container_name(), Some(&SmolStr::new("foo"))); - } - - #[test] - fn test_world_symbols_are_case_sensitive() { - let code = r#" -fn foo() {} - -struct Foo; - "#; - - let symbols = get_symbols_matching(code, "Foo"); - - let fn_match = symbols.iter().find(|s| s.name() == "foo").map(|s| s.kind()); - let struct_match = symbols.iter().find(|s| s.name() == "Foo").map(|s| s.kind()); - - assert_eq!(fn_match, Some(FN_DEF)); - assert_eq!(struct_match, Some(STRUCT_DEF)); - } - - fn get_symbols_matching(text: &str, query: &str) -> Vec { - let (analysis, _) = single_file(text); - analysis.symbol_search(Query::new(query.into())).unwrap() - } -} diff --git a/crates/ra_ide_api/src/syntax_highlighting.rs b/crates/ra_ide_api/src/syntax_highlighting.rs deleted file mode 100644 index 10165a9bb..000000000 --- a/crates/ra_ide_api/src/syntax_highlighting.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! FIXME: write short doc here - -use rustc_hash::{FxHashMap, FxHashSet}; - -use hir::{Name, Source}; -use ra_db::SourceDatabase; -use ra_prof::profile; -use ra_syntax::{ast, AstNode, Direction, SyntaxElement, SyntaxKind, SyntaxKind::*, TextRange, T}; - -use crate::{ - db::RootDatabase, - references::{ - classify_name, classify_name_ref, - NameKind::{self, *}, - }, - FileId, -}; - -#[derive(Debug)] -pub struct HighlightedRange { - pub range: TextRange, - pub tag: &'static str, - pub binding_hash: Option, -} - -fn is_control_keyword(kind: SyntaxKind) -> bool { - match kind { - T![for] - | T![loop] - | T![while] - | T![continue] - | T![break] - | T![if] - | T![else] - | T![match] - | T![return] => true, - _ => false, - } -} - -pub(crate) fn highlight(db: &RootDatabase, file_id: FileId) -> Vec { - let _p = profile("highlight"); - let parse = db.parse(file_id); - let root = parse.tree().syntax().clone(); - - fn calc_binding_hash(file_id: FileId, name: &Name, shadow_count: u32) -> u64 { - fn hash(x: T) -> u64 { - use std::{collections::hash_map::DefaultHasher, hash::Hasher}; - - let mut hasher = DefaultHasher::new(); - x.hash(&mut hasher); - hasher.finish() - } - - hash((file_id, name, shadow_count)) - } - - // Visited nodes to handle highlighting priorities - // FIXME: retain only ranges here - let mut highlighted: FxHashSet = FxHashSet::default(); - let mut bindings_shadow_count: FxHashMap = FxHashMap::default(); - - let mut res = Vec::new(); - for node in root.descendants_with_tokens() { - if highlighted.contains(&node) { - continue; - } - let mut binding_hash = None; - let tag = match node.kind() { - FN_DEF => { - bindings_shadow_count.clear(); - continue; - } - COMMENT => "comment", - STRING | RAW_STRING | RAW_BYTE_STRING | BYTE_STRING => "string", - ATTR => "attribute", - NAME_REF => { - if node.ancestors().any(|it| it.kind() == ATTR) { - continue; - } - - let name_ref = node.as_node().cloned().and_then(ast::NameRef::cast).unwrap(); - let name_kind = - classify_name_ref(db, Source::new(file_id.into(), &name_ref)).map(|d| d.kind); - - if let Some(Local(local)) = &name_kind { - if let Some(name) = local.name(db) { - let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); - binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count)) - } - }; - - name_kind.map_or("text", |it| highlight_name(db, it)) - } - NAME => { - let name = node.as_node().cloned().and_then(ast::Name::cast).unwrap(); - let name_kind = - classify_name(db, Source::new(file_id.into(), &name)).map(|d| d.kind); - - if let Some(Local(local)) = &name_kind { - if let Some(name) = local.name(db) { - let shadow_count = bindings_shadow_count.entry(name.clone()).or_default(); - *shadow_count += 1; - binding_hash = Some(calc_binding_hash(file_id, &name, *shadow_count)) - } - }; - - match name_kind { - Some(name_kind) => highlight_name(db, name_kind), - None => name.syntax().parent().map_or("function", |x| match x.kind() { - TYPE_PARAM | STRUCT_DEF | ENUM_DEF | TRAIT_DEF | TYPE_ALIAS_DEF => "type", - RECORD_FIELD_DEF => "field", - _ => "function", - }), - } - } - INT_NUMBER | FLOAT_NUMBER | CHAR | BYTE => "literal", - LIFETIME => "parameter", - T![unsafe] => "keyword.unsafe", - k if is_control_keyword(k) => "keyword.control", - k if k.is_keyword() => "keyword", - _ => { - if let Some(macro_call) = node.as_node().cloned().and_then(ast::MacroCall::cast) { - if let Some(path) = macro_call.path() { - if let Some(segment) = path.segment() { - if let Some(name_ref) = segment.name_ref() { - highlighted.insert(name_ref.syntax().clone().into()); - let range_start = name_ref.syntax().text_range().start(); - let mut range_end = name_ref.syntax().text_range().end(); - for sibling in path.syntax().siblings_with_tokens(Direction::Next) { - match sibling.kind() { - T![!] | IDENT => range_end = sibling.text_range().end(), - _ => (), - } - } - res.push(HighlightedRange { - range: TextRange::from_to(range_start, range_end), - tag: "macro", - binding_hash: None, - }) - } - } - } - } - continue; - } - }; - res.push(HighlightedRange { range: node.text_range(), tag, binding_hash }) - } - res -} - -pub(crate) fn highlight_as_html(db: &RootDatabase, file_id: FileId, rainbow: bool) -> String { - let parse = db.parse(file_id); - - fn rainbowify(seed: u64) -> String { - use rand::prelude::*; - let mut rng = SmallRng::seed_from_u64(seed); - format!( - "hsl({h},{s}%,{l}%)", - h = rng.gen_range::(0, 361), - s = rng.gen_range::(42, 99), - l = rng.gen_range::(40, 91), - ) - } - - let mut ranges = highlight(db, file_id); - ranges.sort_by_key(|it| it.range.start()); - // quick non-optimal heuristic to intersect token ranges and highlighted ranges - let mut frontier = 0; - let mut could_intersect: Vec<&HighlightedRange> = Vec::new(); - - let mut buf = String::new(); - buf.push_str(&STYLE); - buf.push_str("
");
-    let tokens = parse.tree().syntax().descendants_with_tokens().filter_map(|it| it.into_token());
-    for token in tokens {
-        could_intersect.retain(|it| token.text_range().start() <= it.range.end());
-        while let Some(r) = ranges.get(frontier) {
-            if r.range.start() <= token.text_range().end() {
-                could_intersect.push(r);
-                frontier += 1;
-            } else {
-                break;
-            }
-        }
-        let text = html_escape(&token.text());
-        let ranges = could_intersect
-            .iter()
-            .filter(|it| token.text_range().is_subrange(&it.range))
-            .collect::>();
-        if ranges.is_empty() {
-            buf.push_str(&text);
-        } else {
-            let classes = ranges.iter().map(|x| x.tag).collect::>().join(" ");
-            let binding_hash = ranges.first().and_then(|x| x.binding_hash);
-            let color = match (rainbow, binding_hash) {
-                (true, Some(hash)) => format!(
-                    " data-binding-hash=\"{}\" style=\"color: {};\"",
-                    hash,
-                    rainbowify(hash)
-                ),
-                _ => "".into(),
-            };
-            buf.push_str(&format!("{}", classes, color, text));
-        }
-    }
-    buf.push_str("
"); - buf -} - -fn highlight_name(db: &RootDatabase, name_kind: NameKind) -> &'static str { - match name_kind { - Macro(_) => "macro", - Field(_) => "field", - AssocItem(hir::AssocItem::Function(_)) => "function", - AssocItem(hir::AssocItem::Const(_)) => "constant", - AssocItem(hir::AssocItem::TypeAlias(_)) => "type", - Def(hir::ModuleDef::Module(_)) => "module", - Def(hir::ModuleDef::Function(_)) => "function", - Def(hir::ModuleDef::Adt(_)) => "type", - Def(hir::ModuleDef::EnumVariant(_)) => "constant", - Def(hir::ModuleDef::Const(_)) => "constant", - Def(hir::ModuleDef::Static(_)) => "constant", - Def(hir::ModuleDef::Trait(_)) => "type", - Def(hir::ModuleDef::TypeAlias(_)) => "type", - Def(hir::ModuleDef::BuiltinType(_)) => "type", - SelfType(_) => "type", - GenericParam(_) => "type", - Local(local) => { - if local.is_mut(db) { - "variable.mut" - } else if local.ty(db).is_mutable_reference() { - "variable.mut" - } else { - "variable" - } - } - } -} - -//FIXME: like, real html escaping -fn html_escape(text: &str) -> String { - text.replace("<", "<").replace(">", ">") -} - -const STYLE: &str = " - -"; - -#[cfg(test)] -mod tests { - use crate::mock_analysis::single_file; - use test_utils::{assert_eq_text, project_dir, read_text}; - - #[test] - fn test_highlighting() { - let (analysis, file_id) = single_file( - r#" -#[derive(Clone, Debug)] -struct Foo { - pub x: i32, - pub y: i32, -} - -fn foo() -> T { - unimplemented!(); - foo::(); -} - -// comment -fn main() { - println!("Hello, {}!", 92); - - let mut vec = Vec::new(); - if true { - vec.push(Foo { x: 0, y: 1 }); - } - unsafe { vec.set_len(0); } - - let mut x = 42; - let y = &mut x; - let z = &y; - - y; -} -"# - .trim(), - ); - let dst_file = project_dir().join("crates/ra_ide_api/src/snapshots/highlighting.html"); - let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); - let expected_html = &read_text(&dst_file); - std::fs::write(dst_file, &actual_html).unwrap(); - assert_eq_text!(expected_html, actual_html); - } - - #[test] - fn test_rainbow_highlighting() { - let (analysis, file_id) = single_file( - r#" -fn main() { - let hello = "hello"; - let x = hello.to_string(); - let y = hello.to_string(); - - let x = "other color please!"; - let y = x.to_string(); -} - -fn bar() { - let mut hello = "hello"; -} -"# - .trim(), - ); - let dst_file = - project_dir().join("crates/ra_ide_api/src/snapshots/rainbow_highlighting.html"); - let actual_html = &analysis.highlight_as_html(file_id, true).unwrap(); - let expected_html = &read_text(&dst_file); - std::fs::write(dst_file, &actual_html).unwrap(); - assert_eq_text!(expected_html, actual_html); - } -} diff --git a/crates/ra_ide_api/src/syntax_tree.rs b/crates/ra_ide_api/src/syntax_tree.rs deleted file mode 100644 index 4d0f0fc47..000000000 --- a/crates/ra_ide_api/src/syntax_tree.rs +++ /dev/null @@ -1,359 +0,0 @@ -//! FIXME: write short doc here - -use crate::db::RootDatabase; -use ra_db::SourceDatabase; -use ra_syntax::{ - algo, AstNode, NodeOrToken, SourceFile, - SyntaxKind::{RAW_STRING, STRING}, - SyntaxToken, TextRange, -}; - -pub use ra_db::FileId; - -pub(crate) fn syntax_tree( - db: &RootDatabase, - file_id: FileId, - text_range: Option, -) -> String { - let parse = db.parse(file_id); - if let Some(text_range) = text_range { - let node = match algo::find_covering_element(parse.tree().syntax(), text_range) { - NodeOrToken::Node(node) => node, - NodeOrToken::Token(token) => { - if let Some(tree) = syntax_tree_for_string(&token, text_range) { - return tree; - } - token.parent() - } - }; - - format!("{:#?}", node) - } else { - format!("{:#?}", parse.tree().syntax()) - } -} - -/// Attempts parsing the selected contents of a string literal -/// as rust syntax and returns its syntax tree -fn syntax_tree_for_string(token: &SyntaxToken, text_range: TextRange) -> Option { - // When the range is inside a string - // we'll attempt parsing it as rust syntax - // to provide the syntax tree of the contents of the string - match token.kind() { - STRING | RAW_STRING => syntax_tree_for_token(token, text_range), - _ => None, - } -} - -fn syntax_tree_for_token(node: &SyntaxToken, text_range: TextRange) -> Option { - // Range of the full node - let node_range = node.text_range(); - let text = node.text().to_string(); - - // We start at some point inside the node - // Either we have selected the whole string - // or our selection is inside it - let start = text_range.start() - node_range.start(); - - // how many characters we have selected - let len = text_range.len().to_usize(); - - let node_len = node_range.len().to_usize(); - - let start = start.to_usize(); - - // We want to cap our length - let len = len.min(node_len); - - // Ensure our slice is inside the actual string - let end = if start + len < text.len() { start + len } else { text.len() - start }; - - let text = &text[start..end]; - - // Remove possible extra string quotes from the start - // and the end of the string - let text = text - .trim_start_matches('r') - .trim_start_matches('#') - .trim_start_matches('"') - .trim_end_matches('#') - .trim_end_matches('"') - .trim() - // Remove custom markers - .replace("<|>", ""); - - let parsed = SourceFile::parse(&text); - - // If the "file" parsed without errors, - // return its syntax - if parsed.errors().is_empty() { - return Some(format!("{:#?}", parsed.tree().syntax())); - } - - None -} - -#[cfg(test)] -mod tests { - use test_utils::assert_eq_text; - - use crate::mock_analysis::{single_file, single_file_with_range}; - - #[test] - fn test_syntax_tree_without_range() { - // Basic syntax - let (analysis, file_id) = single_file(r#"fn foo() {}"#); - let syn = analysis.syntax_tree(file_id, None).unwrap(); - - assert_eq_text!( - syn.trim(), - r#" -SOURCE_FILE@[0; 11) - FN_DEF@[0; 11) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 6) - IDENT@[3; 6) "foo" - PARAM_LIST@[6; 8) - L_PAREN@[6; 7) "(" - R_PAREN@[7; 8) ")" - WHITESPACE@[8; 9) " " - BLOCK_EXPR@[9; 11) - BLOCK@[9; 11) - L_CURLY@[9; 10) "{" - R_CURLY@[10; 11) "}" -"# - .trim() - ); - - let (analysis, file_id) = single_file( - r#" -fn test() { - assert!(" - fn foo() { - } - ", ""); -}"# - .trim(), - ); - let syn = analysis.syntax_tree(file_id, None).unwrap(); - - assert_eq_text!( - syn.trim(), - r#" -SOURCE_FILE@[0; 60) - FN_DEF@[0; 60) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 7) - IDENT@[3; 7) "test" - PARAM_LIST@[7; 9) - L_PAREN@[7; 8) "(" - R_PAREN@[8; 9) ")" - WHITESPACE@[9; 10) " " - BLOCK_EXPR@[10; 60) - BLOCK@[10; 60) - L_CURLY@[10; 11) "{" - WHITESPACE@[11; 16) "\n " - EXPR_STMT@[16; 58) - MACRO_CALL@[16; 57) - PATH@[16; 22) - PATH_SEGMENT@[16; 22) - NAME_REF@[16; 22) - IDENT@[16; 22) "assert" - EXCL@[22; 23) "!" - TOKEN_TREE@[23; 57) - L_PAREN@[23; 24) "(" - STRING@[24; 52) "\"\n fn foo() {\n ..." - COMMA@[52; 53) "," - WHITESPACE@[53; 54) " " - STRING@[54; 56) "\"\"" - R_PAREN@[56; 57) ")" - SEMI@[57; 58) ";" - WHITESPACE@[58; 59) "\n" - R_CURLY@[59; 60) "}" -"# - .trim() - ); - } - - #[test] - fn test_syntax_tree_with_range() { - let (analysis, range) = single_file_with_range(r#"<|>fn foo() {}<|>"#.trim()); - let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); - - assert_eq_text!( - syn.trim(), - r#" -FN_DEF@[0; 11) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 6) - IDENT@[3; 6) "foo" - PARAM_LIST@[6; 8) - L_PAREN@[6; 7) "(" - R_PAREN@[7; 8) ")" - WHITESPACE@[8; 9) " " - BLOCK_EXPR@[9; 11) - BLOCK@[9; 11) - L_CURLY@[9; 10) "{" - R_CURLY@[10; 11) "}" -"# - .trim() - ); - - let (analysis, range) = single_file_with_range( - r#"fn test() { - <|>assert!(" - fn foo() { - } - ", "");<|> -}"# - .trim(), - ); - let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); - - assert_eq_text!( - syn.trim(), - r#" -EXPR_STMT@[16; 58) - MACRO_CALL@[16; 57) - PATH@[16; 22) - PATH_SEGMENT@[16; 22) - NAME_REF@[16; 22) - IDENT@[16; 22) "assert" - EXCL@[22; 23) "!" - TOKEN_TREE@[23; 57) - L_PAREN@[23; 24) "(" - STRING@[24; 52) "\"\n fn foo() {\n ..." - COMMA@[52; 53) "," - WHITESPACE@[53; 54) " " - STRING@[54; 56) "\"\"" - R_PAREN@[56; 57) ")" - SEMI@[57; 58) ";" -"# - .trim() - ); - } - - #[test] - fn test_syntax_tree_inside_string() { - let (analysis, range) = single_file_with_range( - r#"fn test() { - assert!(" -<|>fn foo() { -}<|> -fn bar() { -} - ", ""); -}"# - .trim(), - ); - let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); - assert_eq_text!( - syn.trim(), - r#" -SOURCE_FILE@[0; 12) - FN_DEF@[0; 12) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 6) - IDENT@[3; 6) "foo" - PARAM_LIST@[6; 8) - L_PAREN@[6; 7) "(" - R_PAREN@[7; 8) ")" - WHITESPACE@[8; 9) " " - BLOCK_EXPR@[9; 12) - BLOCK@[9; 12) - L_CURLY@[9; 10) "{" - WHITESPACE@[10; 11) "\n" - R_CURLY@[11; 12) "}" -"# - .trim() - ); - - // With a raw string - let (analysis, range) = single_file_with_range( - r###"fn test() { - assert!(r#" -<|>fn foo() { -}<|> -fn bar() { -} - "#, ""); -}"### - .trim(), - ); - let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); - assert_eq_text!( - syn.trim(), - r#" -SOURCE_FILE@[0; 12) - FN_DEF@[0; 12) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 6) - IDENT@[3; 6) "foo" - PARAM_LIST@[6; 8) - L_PAREN@[6; 7) "(" - R_PAREN@[7; 8) ")" - WHITESPACE@[8; 9) " " - BLOCK_EXPR@[9; 12) - BLOCK@[9; 12) - L_CURLY@[9; 10) "{" - WHITESPACE@[10; 11) "\n" - R_CURLY@[11; 12) "}" -"# - .trim() - ); - - // With a raw string - let (analysis, range) = single_file_with_range( - r###"fn test() { - assert!(r<|>#" -fn foo() { -} -fn bar() { -}"<|>#, ""); -}"### - .trim(), - ); - let syn = analysis.syntax_tree(range.file_id, Some(range.range)).unwrap(); - assert_eq_text!( - syn.trim(), - r#" -SOURCE_FILE@[0; 25) - FN_DEF@[0; 12) - FN_KW@[0; 2) "fn" - WHITESPACE@[2; 3) " " - NAME@[3; 6) - IDENT@[3; 6) "foo" - PARAM_LIST@[6; 8) - L_PAREN@[6; 7) "(" - R_PAREN@[7; 8) ")" - WHITESPACE@[8; 9) " " - BLOCK_EXPR@[9; 12) - BLOCK@[9; 12) - L_CURLY@[9; 10) "{" - WHITESPACE@[10; 11) "\n" - R_CURLY@[11; 12) "}" - WHITESPACE@[12; 13) "\n" - FN_DEF@[13; 25) - FN_KW@[13; 15) "fn" - WHITESPACE@[15; 16) " " - NAME@[16; 19) - IDENT@[16; 19) "bar" - PARAM_LIST@[19; 21) - L_PAREN@[19; 20) "(" - R_PAREN@[20; 21) ")" - WHITESPACE@[21; 22) " " - BLOCK_EXPR@[22; 25) - BLOCK@[22; 25) - L_CURLY@[22; 23) "{" - WHITESPACE@[23; 24) "\n" - R_CURLY@[24; 25) "}" -"# - .trim() - ); - } -} diff --git a/crates/ra_ide_api/src/test_utils.rs b/crates/ra_ide_api/src/test_utils.rs deleted file mode 100644 index 8adb214d4..000000000 --- a/crates/ra_ide_api/src/test_utils.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! FIXME: write short doc here - -use ra_syntax::{SourceFile, TextUnit}; -use ra_text_edit::TextEdit; - -pub use test_utils::*; - -pub fn check_action Option>( - before: &str, - after: &str, - f: F, -) { - let (before_cursor_pos, before) = extract_offset(before); - let file = SourceFile::parse(&before).ok().unwrap(); - let result = f(&file, before_cursor_pos).expect("code action is not applicable"); - let actual = result.apply(&before); - let actual_cursor_pos = - result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit"); - let actual = add_cursor(&actual, actual_cursor_pos); - assert_eq_text!(after, &actual); -} diff --git a/crates/ra_ide_api/src/typing.rs b/crates/ra_ide_api/src/typing.rs deleted file mode 100644 index 21e5be9b3..000000000 --- a/crates/ra_ide_api/src/typing.rs +++ /dev/null @@ -1,490 +0,0 @@ -//! This module handles auto-magic editing actions applied together with users -//! edits. For example, if the user typed -//! -//! ```text -//! foo -//! .bar() -//! .baz() -//! | // <- cursor is here -//! ``` -//! -//! and types `.` next, we want to indent the dot. -//! -//! Language server executes such typing assists synchronously. That is, they -//! block user's typing and should be pretty fast for this reason! - -use ra_db::{FilePosition, SourceDatabase}; -use ra_fmt::leading_indent; -use ra_syntax::{ - algo::find_node_at_offset, - ast::{self, AstToken}, - AstNode, SmolStr, SourceFile, - SyntaxKind::*, - SyntaxToken, TextRange, TextUnit, TokenAtOffset, -}; -use ra_text_edit::TextEdit; - -use crate::{db::RootDatabase, source_change::SingleFileChange, SourceChange, SourceFileEdit}; - -pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option { - let parse = db.parse(position.file_id); - let file = parse.tree(); - let comment = file - .syntax() - .token_at_offset(position.offset) - .left_biased() - .and_then(ast::Comment::cast)?; - - if comment.kind().shape.is_block() { - return None; - } - - let prefix = comment.prefix(); - let comment_range = comment.syntax().text_range(); - if position.offset < comment_range.start() + TextUnit::of_str(prefix) + TextUnit::from(1) { - return None; - } - - // Continuing non-doc line comments (like this one :) ) is annoying - if prefix == "//" && comment_range.end() == position.offset { - return None; - } - - let indent = node_indent(&file, comment.syntax())?; - let inserted = format!("\n{}{} ", indent, prefix); - let cursor_position = position.offset + TextUnit::of_str(&inserted); - let edit = TextEdit::insert(position.offset, inserted); - - Some( - SourceChange::source_file_edit( - "on enter", - SourceFileEdit { edit, file_id: position.file_id }, - ) - .with_cursor(FilePosition { offset: cursor_position, file_id: position.file_id }), - ) -} - -fn node_indent(file: &SourceFile, token: &SyntaxToken) -> Option { - let ws = match file.syntax().token_at_offset(token.text_range().start()) { - TokenAtOffset::Between(l, r) => { - assert!(r == *token); - l - } - TokenAtOffset::Single(n) => { - assert!(n == *token); - return Some("".into()); - } - TokenAtOffset::None => unreachable!(), - }; - if ws.kind() != WHITESPACE { - return None; - } - let text = ws.text(); - let pos = text.rfind('\n').map(|it| it + 1).unwrap_or(0); - Some(text[pos..].into()) -} - -pub(crate) const TRIGGER_CHARS: &str = ".=>"; - -pub(crate) fn on_char_typed( - db: &RootDatabase, - position: FilePosition, - char_typed: char, -) -> Option { - assert!(TRIGGER_CHARS.contains(char_typed)); - let file = &db.parse(position.file_id).tree(); - assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); - let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?; - Some(single_file_change.into_source_change(position.file_id)) -} - -fn on_char_typed_inner( - file: &SourceFile, - offset: TextUnit, - char_typed: char, -) -> Option { - assert!(TRIGGER_CHARS.contains(char_typed)); - match char_typed { - '.' => on_dot_typed(file, offset), - '=' => on_eq_typed(file, offset), - '>' => on_arrow_typed(file, offset), - _ => unreachable!(), - } -} - -/// Returns an edit which should be applied after `=` was typed. Primarily, -/// this works when adding `let =`. -// FIXME: use a snippet completion instead of this hack here. -fn on_eq_typed(file: &SourceFile, offset: TextUnit) -> Option { - assert_eq!(file.syntax().text().char_at(offset), Some('=')); - let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?; - if let_stmt.has_semi() { - return None; - } - if let Some(expr) = let_stmt.initializer() { - let expr_range = expr.syntax().text_range(); - if expr_range.contains(offset) && offset != expr_range.start() { - return None; - } - if file.syntax().text().slice(offset..expr_range.start()).contains_char('\n') { - return None; - } - } else { - return None; - } - let offset = let_stmt.syntax().text_range().end(); - Some(SingleFileChange { - label: "add semicolon".to_string(), - edit: TextEdit::insert(offset, ";".to_string()), - cursor_position: None, - }) -} - -/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately. -fn on_dot_typed(file: &SourceFile, offset: TextUnit) -> Option { - assert_eq!(file.syntax().text().char_at(offset), Some('.')); - let whitespace = - file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?; - - let current_indent = { - let text = whitespace.text(); - let newline = text.rfind('\n')?; - &text[newline + 1..] - }; - let current_indent_len = TextUnit::of_str(current_indent); - - // Make sure dot is a part of call chain - let field_expr = ast::FieldExpr::cast(whitespace.syntax().parent())?; - let prev_indent = leading_indent(field_expr.syntax())?; - let target_indent = format!(" {}", prev_indent); - let target_indent_len = TextUnit::of_str(&target_indent); - if current_indent_len == target_indent_len { - return None; - } - - Some(SingleFileChange { - label: "reindent dot".to_string(), - edit: TextEdit::replace( - TextRange::from_to(offset - current_indent_len, offset), - target_indent, - ), - cursor_position: Some( - offset + target_indent_len - current_indent_len + TextUnit::of_char('.'), - ), - }) -} - -/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }` -fn on_arrow_typed(file: &SourceFile, offset: TextUnit) -> Option { - let file_text = file.syntax().text(); - assert_eq!(file_text.char_at(offset), Some('>')); - let after_arrow = offset + TextUnit::of_char('>'); - if file_text.char_at(after_arrow) != Some('{') { - return None; - } - if find_node_at_offset::(file.syntax(), offset).is_none() { - return None; - } - - Some(SingleFileChange { - label: "add space after return type".to_string(), - edit: TextEdit::insert(after_arrow, " ".to_string()), - cursor_position: Some(after_arrow), - }) -} - -#[cfg(test)] -mod tests { - use test_utils::{add_cursor, assert_eq_text, extract_offset}; - - use crate::mock_analysis::single_file; - - use super::*; - - #[test] - fn test_on_enter() { - fn apply_on_enter(before: &str) -> Option { - let (offset, before) = extract_offset(before); - let (analysis, file_id) = single_file(&before); - let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?; - - assert_eq!(result.source_file_edits.len(), 1); - let actual = result.source_file_edits[0].edit.apply(&before); - let actual = add_cursor(&actual, result.cursor_position.unwrap().offset); - Some(actual) - } - - fn do_check(before: &str, after: &str) { - let actual = apply_on_enter(before).unwrap(); - assert_eq_text!(after, &actual); - } - - fn do_check_noop(text: &str) { - assert!(apply_on_enter(text).is_none()) - } - - do_check( - r" -/// Some docs<|> -fn foo() { -} -", - r" -/// Some docs -/// <|> -fn foo() { -} -", - ); - do_check( - r" -impl S { - /// Some<|> docs. - fn foo() {} -} -", - r" -impl S { - /// Some - /// <|> docs. - fn foo() {} -} -", - ); - do_check( - r" -fn main() { - // Fix<|> me - let x = 1 + 1; -} -", - r" -fn main() { - // Fix - // <|> me - let x = 1 + 1; -} -", - ); - do_check_noop( - r" -fn main() { - // Fix me<|> - let x = 1 + 1; -} -", - ); - - do_check_noop(r"<|>//! docz"); - } - - fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> { - let (offset, before) = extract_offset(before); - let edit = TextEdit::insert(offset, char_typed.to_string()); - let before = edit.apply(&before); - let parse = SourceFile::parse(&before); - on_char_typed_inner(&parse.tree(), offset, char_typed) - .map(|it| (it.edit.apply(&before), it)) - } - - fn type_char(char_typed: char, before: &str, after: &str) { - let (actual, file_change) = do_type_char(char_typed, before) - .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); - - if after.contains("<|>") { - let (offset, after) = extract_offset(after); - assert_eq_text!(&after, &actual); - assert_eq!(file_change.cursor_position, Some(offset)) - } else { - assert_eq_text!(after, &actual); - } - } - - fn type_char_noop(char_typed: char, before: &str) { - let file_change = do_type_char(char_typed, before); - assert!(file_change.is_none()) - } - - #[test] - fn test_on_eq_typed() { - // do_check(r" - // fn foo() { - // let foo =<|> - // } - // ", r" - // fn foo() { - // let foo =; - // } - // "); - type_char( - '=', - r" -fn foo() { - let foo <|> 1 + 1 -} -", - r" -fn foo() { - let foo = 1 + 1; -} -", - ); - // do_check(r" - // fn foo() { - // let foo =<|> - // let bar = 1; - // } - // ", r" - // fn foo() { - // let foo =; - // let bar = 1; - // } - // "); - } - - #[test] - fn indents_new_chain_call() { - type_char( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - . - } - ", - ); - type_char_noop( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|> - } - ", - ) - } - - #[test] - fn indents_new_chain_call_with_semi() { - type_char( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|>; - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .; - } - ", - ); - type_char_noop( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - <|>; - } - ", - ) - } - - #[test] - fn indents_continued_chain_call() { - type_char( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - <|> - } - ", - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - . - } - ", - ); - type_char_noop( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - self.child_impl(db, name) - .first() - <|> - } - ", - ); - } - - #[test] - fn indents_middle_of_chain_call() { - type_char( - '.', - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - <|> - .nth(92) - .unwrap(); - } - ", - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - . - .nth(92) - .unwrap(); - } - ", - ); - type_char_noop( - '.', - r" - fn source_impl() { - let var = enum_defvariant_list().unwrap() - <|> - .nth(92) - .unwrap(); - } - ", - ); - } - - #[test] - fn dont_indent_freestanding_dot() { - type_char_noop( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - <|> - } - ", - ); - type_char_noop( - '.', - r" - pub fn child(&self, db: &impl HirDatabase, name: &Name) -> Cancelable> { - <|> - } - ", - ); - } - - #[test] - fn adds_space_after_return_type() { - type_char('>', "fn foo() -<|>{ 92 }", "fn foo() -><|> { 92 }") - } -} diff --git a/crates/ra_ide_api/src/wasm_shims.rs b/crates/ra_ide_api/src/wasm_shims.rs deleted file mode 100644 index 088cc9be4..000000000 --- a/crates/ra_ide_api/src/wasm_shims.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! FIXME: write short doc here - -#[cfg(not(feature = "wasm"))] -pub use std::time::Instant; - -#[cfg(feature = "wasm")] -#[derive(Clone, Copy, Debug)] -pub struct Instant; - -#[cfg(feature = "wasm")] -impl Instant { - pub fn now() -> Self { - Self - } - - pub fn elapsed(&self) -> std::time::Duration { - std::time::Duration::new(0, 0) - } -} diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 58b9cfaa0..21aef842c 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -22,7 +22,7 @@ jod-thread = "0.1.0" ra_vfs = "0.5.0" ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } -ra_ide_api = { path = "../ra_ide_api" } +ra_ide = { path = "../ra_ide" } lsp-server = "0.3.0" ra_project_model = { path = "../ra_project_model" } ra_prof = { path = "../ra_prof" } diff --git a/crates/ra_lsp_server/src/cargo_target_spec.rs b/crates/ra_lsp_server/src/cargo_target_spec.rs index d996b53de..c4a9e7101 100644 --- a/crates/ra_lsp_server/src/cargo_target_spec.rs +++ b/crates/ra_lsp_server/src/cargo_target_spec.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use ra_ide_api::{FileId, RunnableKind}; +use ra_ide::{FileId, RunnableKind}; use ra_project_model::{self, ProjectWorkspace, TargetKind}; use crate::{world::WorldSnapshot, Result}; diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 94ed619fa..b13093cfe 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs @@ -6,7 +6,7 @@ use lsp_types::{ SymbolKind, TextDocumentEdit, TextDocumentIdentifier, TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit, }; -use ra_ide_api::{ +use ra_ide::{ translate_offset_with_edit, CompletionItem, CompletionItemKind, FileId, FilePosition, FileRange, FileSystemEdit, Fold, FoldKind, InsertTextFormat, LineCol, LineIndex, NavigationTarget, RangeInfo, Severity, SourceChange, SourceFileEdit, @@ -173,7 +173,7 @@ impl ConvWith<&LineIndex> for Range { } } -impl Conv for ra_ide_api::Documentation { +impl Conv for ra_ide::Documentation { type Output = lsp_types::Documentation; fn conv(self) -> Documentation { Documentation::MarkupContent(MarkupContent { @@ -183,7 +183,7 @@ impl Conv for ra_ide_api::Documentation { } } -impl Conv for ra_ide_api::FunctionSignature { +impl Conv for ra_ide::FunctionSignature { type Output = lsp_types::SignatureInformation; fn conv(self) -> Self::Output { use lsp_types::{ParameterInformation, ParameterLabel, SignatureInformation}; diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 9c36402b0..2ca149fd5 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -1,6 +1,6 @@ //! Implementation of the LSP for rust-analyzer. //! -//! This crate takes Rust-specific analysis results from ra_ide_api and +//! This crate takes Rust-specific analysis results from ra_ide and //! translates into LSP types. //! //! It also is the root of all state. `world` module defines the bulk of the diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 0dc0aeee8..83845f1e0 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -9,7 +9,7 @@ use std::{error::Error, fmt, panic, path::PathBuf, sync::Arc, time::Instant}; use crossbeam_channel::{select, unbounded, RecvError, Sender}; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ClientCapabilities, NumberOrString}; -use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; +use ra_ide::{Canceled, FeatureFlags, FileId, LibraryData, SourceRootId}; use ra_prof::profile; use ra_vfs::{VfsTask, Watch}; use relative_path::RelativePathBuf; diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index e552f2106..c81fa7f67 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -9,7 +9,7 @@ use lsp_types::{ Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, }; -use ra_ide_api::{ +use ra_ide::{ AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope, }; use ra_prof::profile; @@ -162,7 +162,7 @@ pub fn handle_on_type_formatting( let line_index = world.analysis().file_line_index(position.file_id)?; let line_endings = world.file_line_endings(position.file_id); - // in `ra_ide_api`, the `on_type` invariant is that + // in `ra_ide`, the `on_type` invariant is that // `text.char_at(position) == typed_char`. position.offset = position.offset - TextUnit::of_char('.'); let char_typed = params.ch.chars().next().unwrap_or('\0'); @@ -894,7 +894,7 @@ pub fn handle_inlay_hints( label: api_type.label.to_string(), range: api_type.range.conv_with(&line_index), kind: match api_type.kind { - ra_ide_api::InlayKind::TypeHint => InlayKind::TypeHint, + ra_ide::InlayKind::TypeHint => InlayKind::TypeHint, }, }) .collect()) diff --git a/crates/ra_lsp_server/src/main_loop/subscriptions.rs b/crates/ra_lsp_server/src/main_loop/subscriptions.rs index 3856263b0..609b2adcc 100644 --- a/crates/ra_lsp_server/src/main_loop/subscriptions.rs +++ b/crates/ra_lsp_server/src/main_loop/subscriptions.rs @@ -1,6 +1,6 @@ //! FIXME: write short doc here -use ra_ide_api::FileId; +use ra_ide::FileId; use rustc_hash::FxHashSet; #[derive(Default, Debug)] diff --git a/crates/ra_lsp_server/src/world.rs b/crates/ra_lsp_server/src/world.rs index 9bdea70c7..927449b45 100644 --- a/crates/ra_lsp_server/src/world.rs +++ b/crates/ra_lsp_server/src/world.rs @@ -9,7 +9,7 @@ use crossbeam_channel::{unbounded, Receiver}; use lsp_server::ErrorCode; use lsp_types::Url; use parking_lot::RwLock; -use ra_ide_api::{ +use ra_ide::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FeatureFlags, FileId, LibraryData, SourceRootId, }; -- cgit v1.2.3 From 27b362b05910c81fd5b28f6cd5d2c075311032f9 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 21:44:38 +0300 Subject: Reformat --- crates/ra_hir/src/from_source.rs | 11 +++++------ crates/ra_hir_def/src/nameres/collector.rs | 3 +-- crates/ra_ide/src/syntax_highlighting.rs | 3 +-- 3 files changed, 7 insertions(+), 10 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/from_source.rs b/crates/ra_hir/src/from_source.rs index 7756ca80e..9f7c22b21 100644 --- a/crates/ra_hir/src/from_source.rs +++ b/crates/ra_hir/src/from_source.rs @@ -262,12 +262,11 @@ impl Module { let original_file = src.file_id.original_file(db); - let (krate, local_id) = - db.relevant_crates(original_file).iter().find_map(|&crate_id| { - let crate_def_map = db.crate_def_map(crate_id); - let local_id = crate_def_map.modules_for_file(original_file).next()?; - Some((crate_id, local_id)) - })?; + let (krate, local_id) = db.relevant_crates(original_file).iter().find_map(|&crate_id| { + let crate_def_map = db.crate_def_map(crate_id); + let local_id = crate_def_map.modules_for_file(original_file).next()?; + Some((crate_id, local_id)) + })?; Some(Module { id: ModuleId { krate, local_id } }) } } diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 6cd14026b..603b49738 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -682,8 +682,7 @@ where } fn define_def(&mut self, def: &raw::DefData) { - let module = - ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; + let module = ModuleId { krate: self.def_collector.def_map.krate, local_id: self.module_id }; let ctx = LocationCtx::new(self.def_collector.db, module, self.file_id); let name = def.name.clone(); diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 2c568a747..9a3e4c82f 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -332,8 +332,7 @@ fn bar() { "# .trim(), ); - let dst_file = - project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html"); + let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html"); let actual_html = &analysis.highlight_as_html(file_id, true).unwrap(); let expected_html = &read_text(&dst_file); std::fs::write(dst_file, &actual_html).unwrap(); -- cgit v1.2.3 From 8d3469682681d5b206d5ae31fc63fb97d9cedb3a Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 22:12:09 +0300 Subject: Memoize impl resolutions --- crates/ra_hir_ty/src/db.rs | 5 ++++- crates/ra_hir_ty/src/infer/coerce.rs | 22 ++++++------------- crates/ra_hir_ty/src/infer/path.rs | 10 ++++----- crates/ra_hir_ty/src/lib.rs | 15 +++++++++++++ crates/ra_hir_ty/src/lower.rs | 35 ++++++++++++++++++++----------- crates/ra_hir_ty/src/method_resolution.rs | 30 +++++++++----------------- crates/ra_hir_ty/src/traits/chalk.rs | 34 +++++++++++++----------------- 7 files changed, 76 insertions(+), 75 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir_ty/src/db.rs b/crates/ra_hir_ty/src/db.rs index aa2659c4b..9ce154593 100644 --- a/crates/ra_hir_ty/src/db.rs +++ b/crates/ra_hir_ty/src/db.rs @@ -11,7 +11,7 @@ use ra_db::{salsa, CrateId}; use crate::{ method_resolution::CrateImplBlocks, traits::{AssocTyValue, Impl}, - CallableDef, FnSig, GenericPredicate, InferenceResult, Substs, Ty, TyDefId, TypeCtor, + CallableDef, FnSig, GenericPredicate, ImplTy, InferenceResult, Substs, Ty, TyDefId, TypeCtor, ValueTyDefId, }; @@ -27,6 +27,9 @@ pub trait HirDatabase: DefDatabase { #[salsa::invoke(crate::lower::value_ty_query)] fn value_ty(&self, def: ValueTyDefId) -> Ty; + #[salsa::invoke(crate::lower::impl_ty_query)] + fn impl_ty(&self, def: ImplId) -> ImplTy; + #[salsa::invoke(crate::lower::field_types_query)] fn field_types(&self, var: VariantId) -> Arc>; diff --git a/crates/ra_hir_ty/src/infer/coerce.rs b/crates/ra_hir_ty/src/infer/coerce.rs index d66a21932..719a0f395 100644 --- a/crates/ra_hir_ty/src/infer/coerce.rs +++ b/crates/ra_hir_ty/src/infer/coerce.rs @@ -4,16 +4,11 @@ //! //! See: https://doc.rust-lang.org/nomicon/coercions.html -use hir_def::{ - lang_item::LangItemTarget, - resolver::{HasResolver, Resolver}, - type_ref::Mutability, - AdtId, -}; +use hir_def::{lang_item::LangItemTarget, resolver::Resolver, type_ref::Mutability, AdtId}; use rustc_hash::FxHashMap; use test_utils::tested_by; -use crate::{autoderef, db::HirDatabase, Substs, TraitRef, Ty, TypeCtor, TypeWalk}; +use crate::{autoderef, db::HirDatabase, ImplTy, Substs, Ty, TypeCtor, TypeWalk}; use super::{InEnvironment, InferTy, InferenceContext, TypeVarValue}; @@ -59,17 +54,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { impls .iter() .filter_map(|&impl_id| { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + let trait_ref = match db.impl_ty(impl_id) { + ImplTy::TraitRef(it) => it, + ImplTy::Inherent(_) => return None, + }; // `CoerseUnsized` has one generic parameter for the target type. - let trait_ref = TraitRef::from_hir( - db, - &resolver, - impl_data.target_trait.as_ref()?, - Some(target_ty), - )?; let cur_from_ty = trait_ref.substs.0.get(0)?; let cur_to_ty = trait_ref.substs.0.get(1)?; diff --git a/crates/ra_hir_ty/src/infer/path.rs b/crates/ra_hir_ty/src/infer/path.rs index e6676e1aa..14be66836 100644 --- a/crates/ra_hir_ty/src/infer/path.rs +++ b/crates/ra_hir_ty/src/infer/path.rs @@ -2,7 +2,7 @@ use hir_def::{ path::{Path, PathKind, PathSegment}, - resolver::{HasResolver, ResolveValueResult, Resolver, TypeNs, ValueNs}, + resolver::{ResolveValueResult, Resolver, TypeNs, ValueNs}, AssocItemId, ContainerId, Lookup, }; use hir_expand::name::Name; @@ -244,17 +244,15 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> { ContainerId::ImplId(it) => it, _ => return None, }; - let resolver = impl_id.resolver(self.db); - let impl_data = self.db.impl_data(impl_id); - let impl_block = Ty::from_hir(self.db, &resolver, &impl_data.target_type); - let impl_block_substs = impl_block.substs()?; + let self_ty = self.db.impl_ty(impl_id).self_type().clone(); + let self_ty_substs = self_ty.substs()?; let actual_substs = actual_def_ty.substs()?; let mut new_substs = vec![Ty::Unknown; gen.count_parent_params()]; // The following code *link up* the function actual parma type // and impl_block type param index - impl_block_substs.iter().zip(actual_substs.iter()).for_each(|(param, pty)| { + self_ty_substs.iter().zip(actual_substs.iter()).for_each(|(param, pty)| { if let Ty::Param { idx, .. } = param { if let Some(s) = new_substs.get_mut(*idx as usize) { *s = pty.clone(); diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index f25846326..c9ee34008 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -486,6 +486,21 @@ impl TypeWalk for TraitRef { } } +#[derive(Clone, PartialEq, Eq, Debug)] +pub enum ImplTy { + Inherent(Ty), + TraitRef(TraitRef), +} + +impl ImplTy { + pub(crate) fn self_type(&self) -> &Ty { + match self { + ImplTy::Inherent(it) => it, + ImplTy::TraitRef(tr) => &tr.substs[0], + } + } +} + /// Like `generics::WherePredicate`, but with resolved types: A condition on the /// parameters of a generic item. #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index 53d955a12..f8331d257 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs @@ -14,21 +14,21 @@ use hir_def::{ path::{GenericArg, Path, PathKind, PathSegment}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, - AdtId, AstItemDef, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, + AdtId, AstItemDef, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, ImplId, LocalStructFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, UnionId, VariantId, }; use ra_arena::map::ArenaMap; use ra_db::CrateId; -use super::{ - FnSig, GenericPredicate, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, - Ty, TypeCtor, TypeWalk, -}; use crate::{ db::HirDatabase, primitive::{FloatTy, IntTy}, - utils::make_mut_slice, - utils::{all_super_traits, associated_type_by_name_including_super_traits, variant_data}, + utils::{ + all_super_traits, associated_type_by_name_including_super_traits, make_mut_slice, + variant_data, + }, + FnSig, GenericPredicate, ImplTy, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, + TraitRef, Ty, TypeCtor, TypeWalk, }; impl Ty { @@ -179,11 +179,7 @@ impl Ty { let name = resolved_segment.name.clone(); Ty::Param { idx, name } } - TypeNs::SelfType(impl_id) => { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - Ty::from_hir(db, &resolver, &impl_data.target_type) - } + TypeNs::SelfType(impl_id) => db.impl_ty(impl_id).self_type().clone(), TypeNs::AdtSelfType(adt) => db.ty(adt.into()), TypeNs::AdtId(it) => Ty::from_hir_path_inner(db, resolver, resolved_segment, it.into()), @@ -751,3 +747,18 @@ pub(crate) fn value_ty_query(db: &impl HirDatabase, def: ValueTyDefId) -> Ty { ValueTyDefId::StaticId(it) => type_for_static(db, it), } } + +pub(crate) fn impl_ty_query(db: &impl HirDatabase, impl_id: ImplId) -> ImplTy { + let impl_data = db.impl_data(impl_id); + let resolver = impl_id.resolver(db); + let self_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); + match impl_data.target_trait.as_ref() { + Some(trait_ref) => { + match TraitRef::from_hir(db, &resolver, trait_ref, Some(self_ty.clone())) { + Some(it) => ImplTy::TraitRef(it), + None => ImplTy::Inherent(self_ty), + } + } + None => ImplTy::Inherent(self_ty), + } +} diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs index 53c541eb8..ee1936b0e 100644 --- a/crates/ra_hir_ty/src/method_resolution.rs +++ b/crates/ra_hir_ty/src/method_resolution.rs @@ -6,8 +6,8 @@ use std::sync::Arc; use arrayvec::ArrayVec; use hir_def::{ - lang_item::LangItemTarget, resolver::HasResolver, resolver::Resolver, type_ref::Mutability, - AssocItemId, AstItemDef, FunctionId, HasModule, ImplId, TraitId, + lang_item::LangItemTarget, resolver::Resolver, type_ref::Mutability, AssocItemId, AstItemDef, + FunctionId, HasModule, ImplId, TraitId, }; use hir_expand::name::Name; use ra_db::CrateId; @@ -15,14 +15,13 @@ use ra_prof::profile; use rustc_hash::FxHashMap; use crate::{ + autoderef, db::HirDatabase, primitive::{FloatBitness, Uncertain}, utils::all_super_traits, - Ty, TypeCtor, + Canonical, ImplTy, InEnvironment, TraitEnvironment, TraitRef, Ty, TypeCtor, }; -use super::{autoderef, Canonical, InEnvironment, TraitEnvironment, TraitRef}; - /// This is used as a key for indexing impls. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TyFingerprint { @@ -59,22 +58,13 @@ impl CrateImplBlocks { let crate_def_map = db.crate_def_map(krate); for (_module_id, module_data) in crate_def_map.modules.iter() { for &impl_id in module_data.impls.iter() { - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - - match &impl_data.target_trait { - Some(trait_ref) => { - if let Some(tr) = - TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty)) - { - res.impls_by_trait.entry(tr.trait_).or_default().push(impl_id); - } + match db.impl_ty(impl_id) { + ImplTy::TraitRef(tr) => { + res.impls_by_trait.entry(tr.trait_).or_default().push(impl_id); } - None => { - if let Some(target_ty_fp) = TyFingerprint::for_impl(&target_ty) { - res.impls.entry(target_ty_fp).or_default().push(impl_id); + ImplTy::Inherent(self_ty) => { + if let Some(self_ty_fp) = TyFingerprint::for_impl(&self_ty) { + res.impls.entry(self_ty_fp).or_default().push(impl_id); } } } diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index 810e8c21a..35de37e6b 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -11,8 +11,8 @@ use chalk_rust_ir::{AssociatedTyDatum, AssociatedTyValue, ImplDatum, StructDatum use ra_db::CrateId; use hir_def::{ - expr::Expr, lang_item::LangItemTarget, resolver::HasResolver, AssocItemId, AstItemDef, - ContainerId, GenericDefId, ImplId, Lookup, TraitId, TypeAliasId, + expr::Expr, lang_item::LangItemTarget, AssocItemId, AstItemDef, ContainerId, GenericDefId, + ImplId, Lookup, TraitId, TypeAliasId, }; use hir_expand::name; @@ -20,9 +20,8 @@ use ra_db::salsa::{InternId, InternKey}; use super::{AssocTyValue, Canonical, ChalkContext, Impl, Obligation}; use crate::{ - db::HirDatabase, - display::HirDisplay, - {ApplicationTy, GenericPredicate, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, TypeWalk}, + db::HirDatabase, display::HirDisplay, ApplicationTy, GenericPredicate, ImplTy, ProjectionTy, + Substs, TraitRef, Ty, TypeCtor, TypeWalk, }; /// This represents a trait whose name we could not resolve. @@ -631,13 +630,11 @@ fn impl_block_datum( chalk_id: chalk_ir::ImplId, impl_id: ImplId, ) -> Option>> { + let trait_ref = match db.impl_ty(impl_id) { + ImplTy::TraitRef(it) => it, + ImplTy::Inherent(_) => return None, + }; let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - - // `CoerseUnsized` has one generic parameter for the target type. - let trait_ref = - TraitRef::from_hir(db, &resolver, impl_data.target_trait.as_ref()?, Some(target_ty))?; let generic_params = db.generic_params(impl_id.into()); let bound_vars = Substs::bound_vars(&generic_params); @@ -790,17 +787,14 @@ fn type_alias_associated_ty_value( _ => panic!("assoc ty value should be in impl"), }; - let impl_data = db.impl_data(impl_id); - let resolver = impl_id.resolver(db); - let target_ty = Ty::from_hir(db, &resolver, &impl_data.target_type); - let target_trait = impl_data - .target_trait - .as_ref() - .and_then(|trait_ref| TraitRef::from_hir(db, &resolver, &trait_ref, Some(target_ty))) - .expect("assoc ty value should not exist"); // we don't return any assoc ty values if the impl'd trait can't be resolved + let trait_ref = match db.impl_ty(impl_id) { + ImplTy::TraitRef(it) => it, + // we don't return any assoc ty values if the impl'd trait can't be resolved + ImplTy::Inherent(_) => panic!("assoc ty value should not exist"), + }; let assoc_ty = db - .trait_data(target_trait.trait_) + .trait_data(trait_ref.trait_) .associated_type_by_name(&type_alias_data.name) .expect("assoc ty value should not exist"); // validated when building the impl data as well let generic_params = db.generic_params(impl_id.into()); -- cgit v1.2.3 From 04735abfaec30461252aecde10bb1d0d344728f1 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 22:21:01 +0300 Subject: Minimize API --- crates/ra_hir/src/code_model.rs | 9 +-------- crates/ra_hir_ty/src/lower.rs | 11 +++-------- 2 files changed, 4 insertions(+), 16 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 5a3e24e9e..9488521a9 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -29,10 +29,7 @@ use ra_syntax::{ast, AstNode, SyntaxNode}; use crate::{ db::{DefDatabase, HirDatabase}, ty::display::HirFormatter, - ty::{ - self, InEnvironment, InferenceResult, TraitEnvironment, TraitRef, Ty, TyDefId, TypeCtor, - TypeWalk, - }, + ty::{self, InEnvironment, InferenceResult, TraitEnvironment, Ty, TyDefId, TypeCtor, TypeWalk}, CallableDef, Either, HirDisplay, Name, Source, }; @@ -731,10 +728,6 @@ impl Trait { db.trait_data(self.id).items.iter().map(|(_name, it)| (*it).into()).collect() } - pub fn trait_ref(self, db: &impl HirDatabase) -> TraitRef { - TraitRef::for_trait(db, self.id) - } - pub fn is_auto(self, db: &impl DefDatabase) -> bool { db.trait_data(self.id).auto } diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index f8331d257..091c60f4f 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs @@ -363,7 +363,7 @@ pub(super) fn substs_from_path_segment( } impl TraitRef { - pub(crate) fn from_path( + fn from_path( db: &impl HirDatabase, resolver: &Resolver, path: &Path, @@ -377,7 +377,7 @@ impl TraitRef { Some(TraitRef::from_resolved_path(db, resolver, resolved.into(), segment, explicit_self_ty)) } - pub(super) fn from_resolved_path( + pub(crate) fn from_resolved_path( db: &impl HirDatabase, resolver: &Resolver, resolved: TraitId, @@ -391,7 +391,7 @@ impl TraitRef { TraitRef { trait_: resolved, substs } } - pub(crate) fn from_hir( + fn from_hir( db: &impl HirDatabase, resolver: &Resolver, type_ref: &TypeRef, @@ -415,11 +415,6 @@ impl TraitRef { substs_from_path_segment(db, resolver, segment, Some(resolved.into()), !has_self_param) } - pub fn for_trait(db: &impl HirDatabase, trait_: TraitId) -> TraitRef { - let substs = Substs::identity(&db.generic_params(trait_.into())); - TraitRef { trait_, substs } - } - pub(crate) fn from_type_bound( db: &impl HirDatabase, resolver: &Resolver, -- cgit v1.2.3 From 1d14fd17372b42c3343daf6adc9a520fdf5e9810 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 27 Nov 2019 23:22:20 +0300 Subject: Use Name::missing consistently --- crates/ra_hir/src/code_model.rs | 17 +++++++---------- crates/ra_hir_def/src/adt.rs | 18 +++++++++--------- crates/ra_hir_def/src/data.rs | 11 ++++++----- crates/ra_hir_def/src/nameres/collector.rs | 2 +- crates/ra_hir_ty/src/lib.rs | 25 +++++++------------------ crates/ra_ide/src/completion/presentation.rs | 5 +---- crates/ra_ide/src/display/function_signature.rs | 7 ++----- 7 files changed, 33 insertions(+), 52 deletions(-) (limited to 'crates') diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index 9488521a9..38d66c2a7 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -330,7 +330,7 @@ impl Struct { Some(self.module(db).krate()) } - pub fn name(self, db: &impl DefDatabase) -> Option { + pub fn name(self, db: &impl DefDatabase) -> Name { db.struct_data(self.id.into()).name.clone() } @@ -371,7 +371,7 @@ pub struct Union { } impl Union { - pub fn name(self, db: &impl DefDatabase) -> Option { + pub fn name(self, db: &impl DefDatabase) -> Name { db.union_data(self.id).name.clone() } @@ -420,7 +420,7 @@ impl Enum { Some(self.module(db).krate()) } - pub fn name(self, db: &impl DefDatabase) -> Option { + pub fn name(self, db: &impl DefDatabase) -> Name { db.enum_data(self.id).name.clone() } @@ -433,11 +433,8 @@ impl Enum { } pub fn variant(self, db: &impl DefDatabase, name: &Name) -> Option { - db.enum_data(self.id) - .variants - .iter() - .find(|(_id, data)| data.name.as_ref() == Some(name)) - .map(|(id, _)| EnumVariant { parent: self, id }) + let id = db.enum_data(self.id).variant(name)?; + Some(EnumVariant { parent: self, id }) } pub fn ty(self, db: &impl HirDatabase) -> Type { @@ -459,7 +456,7 @@ impl EnumVariant { self.parent } - pub fn name(self, db: &impl DefDatabase) -> Option { + pub fn name(self, db: &impl DefDatabase) -> Name { db.enum_data(self.parent.id).variants[self.id].name.clone() } @@ -720,7 +717,7 @@ impl Trait { Module { id: self.id.module(db) } } - pub fn name(self, db: &impl DefDatabase) -> Option { + pub fn name(self, db: &impl DefDatabase) -> Name { db.trait_data(self.id).name.clone() } diff --git a/crates/ra_hir_def/src/adt.rs b/crates/ra_hir_def/src/adt.rs index 0cf418d30..3666529b0 100644 --- a/crates/ra_hir_def/src/adt.rs +++ b/crates/ra_hir_def/src/adt.rs @@ -18,19 +18,19 @@ use crate::{ /// Note that we use `StructData` for unions as well! #[derive(Debug, Clone, PartialEq, Eq)] pub struct StructData { - pub name: Option, + pub name: Name, pub variant_data: Arc, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct EnumData { - pub name: Option, + pub name: Name, pub variants: Arena, } #[derive(Debug, Clone, PartialEq, Eq)] pub struct EnumVariantData { - pub name: Option, + pub name: Name, pub variant_data: Arc, } @@ -51,14 +51,14 @@ pub struct StructFieldData { impl StructData { pub(crate) fn struct_data_query(db: &impl DefDatabase, id: StructId) -> Arc { let src = id.source(db); - let name = src.value.name().map(|n| n.as_name()); + let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); let variant_data = VariantData::new(src.value.kind()); let variant_data = Arc::new(variant_data); Arc::new(StructData { name, variant_data }) } pub(crate) fn union_data_query(db: &impl DefDatabase, id: UnionId) -> Arc { let src = id.source(db); - let name = src.value.name().map(|n| n.as_name()); + let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); let variant_data = VariantData::new( src.value .record_field_def_list() @@ -73,14 +73,14 @@ impl StructData { impl EnumData { pub(crate) fn enum_data_query(db: &impl DefDatabase, e: EnumId) -> Arc { let src = e.source(db); - let name = src.value.name().map(|n| n.as_name()); + let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); let mut trace = Trace::new_for_arena(); lower_enum(&mut trace, &src.value); Arc::new(EnumData { name, variants: trace.into_arena() }) } - pub(crate) fn variant(&self, name: &Name) -> Option { - let (id, _) = self.variants.iter().find(|(_id, data)| data.name.as_ref() == Some(name))?; + pub fn variant(&self, name: &Name) -> Option { + let (id, _) = self.variants.iter().find(|(_id, data)| &data.name == name)?; Some(id) } } @@ -104,7 +104,7 @@ fn lower_enum( trace.alloc( || var.clone(), || EnumVariantData { - name: var.name().map(|it| it.as_name()), + name: var.name().map_or_else(Name::missing, |it| it.as_name()), variant_data: Arc::new(VariantData::new(var.kind())), }, ); diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index 813099a05..fee10b237 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -86,7 +86,7 @@ impl TypeAliasData { #[derive(Debug, Clone, PartialEq, Eq)] pub struct TraitData { - pub name: Option, + pub name: Name, pub items: Vec<(Name, AssocItemId)>, pub auto: bool, } @@ -94,7 +94,7 @@ pub struct TraitData { impl TraitData { pub(crate) fn trait_data_query(db: &impl DefDatabase, tr: TraitId) -> Arc { let src = tr.source(db); - let name = src.value.name().map(|n| n.as_name()); + let name = src.value.name().map_or_else(Name::missing, |n| n.as_name()); let auto = src.value.is_auto(); let ast_id_map = db.ast_id_map(src.file_id); @@ -104,7 +104,7 @@ impl TraitData { .impl_items() .map(|item_node| match item_node { ast::ImplItem::FnDef(it) => { - let name = it.name().map(|it| it.as_name()).unwrap_or_else(Name::missing); + let name = it.name().map_or_else(Name::missing, |it| it.as_name()); let def = FunctionLoc { container, ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), @@ -114,7 +114,7 @@ impl TraitData { (name, def) } ast::ImplItem::ConstDef(it) => { - let name = it.name().map(|it| it.as_name()).unwrap_or_else(Name::missing); + let name = it.name().map_or_else(Name::missing, |it| it.as_name()); let def = ConstLoc { container, ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), @@ -124,7 +124,7 @@ impl TraitData { (name, def) } ast::ImplItem::TypeAliasDef(it) => { - let name = it.name().map(|it| it.as_name()).unwrap_or_else(Name::missing); + let name = it.name().map_or_else(Name::missing, |it| it.as_name()); let def = TypeAliasLoc { container, ast_id: AstId::new(src.file_id, ast_id_map.ast_id(&it)), @@ -214,6 +214,7 @@ impl ImplData { #[derive(Debug, Clone, PartialEq, Eq)] pub struct ConstData { + /// const _: () = (); pub name: Option, pub type_ref: TypeRef, } diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 603b49738..fd8245113 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -362,7 +362,7 @@ where .variants .iter() .filter_map(|(local_id, variant_data)| { - let name = variant_data.name.clone()?; + let name = variant_data.name.clone(); let variant = EnumVariantId { parent: e, local_id }; let res = Resolution { def: PerNs::both(variant.into(), variant.into()), diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index c9ee34008..b45c8f82f 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -901,12 +901,10 @@ impl HirDisplay for ApplicationTy { let sig = f.db.callable_item_signature(def); let name = match def { CallableDef::FunctionId(ff) => f.db.function_data(ff).name.clone(), - CallableDef::StructId(s) => { - f.db.struct_data(s).name.clone().unwrap_or_else(Name::missing) - } + CallableDef::StructId(s) => f.db.struct_data(s).name.clone(), CallableDef::EnumVariantId(e) => { let enum_data = f.db.enum_data(e.parent); - enum_data.variants[e.local_id].name.clone().unwrap_or_else(Name::missing) + enum_data.variants[e.local_id].name.clone() } }; match def { @@ -929,8 +927,7 @@ impl HirDisplay for ApplicationTy { AdtId::StructId(it) => f.db.struct_data(it).name.clone(), AdtId::UnionId(it) => f.db.union_data(it).name.clone(), AdtId::EnumId(it) => f.db.enum_data(it).name.clone(), - } - .unwrap_or_else(Name::missing); + }; write!(f, "{}", name)?; if self.parameters.len() > 0 { write!(f, "<")?; @@ -943,7 +940,7 @@ impl HirDisplay for ApplicationTy { ContainerId::TraitId(it) => it, _ => panic!("not an associated type"), }; - let trait_name = f.db.trait_data(trait_).name.clone().unwrap_or_else(Name::missing); + let trait_name = f.db.trait_data(trait_).name.clone(); let name = f.db.type_alias_data(type_alias).name.clone(); write!(f, "{}::{}", trait_name, name)?; if self.parameters.len() > 0 { @@ -971,8 +968,7 @@ impl HirDisplay for ProjectionTy { return write!(f, "…"); } - let trait_name = - f.db.trait_data(self.trait_(f.db)).name.clone().unwrap_or_else(Name::missing); + let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; if self.parameters.len() > 1 { write!(f, "<")?; @@ -1021,14 +1017,7 @@ impl HirDisplay for Ty { // We assume that the self type is $0 (i.e. the // existential) here, which is the only thing that's // possible in actual Rust, and hence don't print it - write!( - f, - "{}", - f.db.trait_data(trait_ref.trait_) - .name - .clone() - .unwrap_or_else(Name::missing) - )?; + write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?; if trait_ref.substs.len() > 1 { write!(f, "<")?; f.write_joined(&trait_ref.substs[1..], ", ")?; @@ -1088,7 +1077,7 @@ impl TraitRef { } else { write!(f, ": ")?; } - write!(f, "{}", f.db.trait_data(self.trait_).name.clone().unwrap_or_else(Name::missing))?; + write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?; if self.substs.len() > 1 { write!(f, "<")?; f.write_joined(&self.substs[1..], ", ")?; diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 5f056730a..97475fc0b 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs @@ -267,10 +267,7 @@ impl Completions { pub(crate) fn add_enum_variant(&mut self, ctx: &CompletionContext, variant: hir::EnumVariant) { let is_deprecated = is_deprecated(variant, ctx.db); - let name = match variant.name(ctx.db) { - Some(it) => it, - None => return, - }; + let name = variant.name(ctx.db); let detail_types = variant.fields(ctx.db).into_iter().map(|field| field.ty(ctx.db)); let detail = join(detail_types.map(|t| t.display(ctx.db).to_string())) .separator(", ") diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index d96de4e4c..324ad9552 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -93,12 +93,9 @@ impl FunctionSignature { _ => (), }; - let parent_name = match variant.parent_enum(db).name(db) { - Some(name) => name.to_string(), - None => "missing".into(), - }; + let parent_name = variant.parent_enum(db).name(db).to_string(); - let name = format!("{}::{}", parent_name, variant.name(db).unwrap()); + let name = format!("{}::{}", parent_name, variant.name(db)); let params = variant .fields(db) -- cgit v1.2.3