4839: `Go to Type Definition` hover action. r=matklad a=vsrs

![hover_actions_goto](https://user-images.githubusercontent.com/62505555/83335671-0122e380-a2b7-11ea-9922-fbdcfb11a7f3.gif)

This implementation supports things like `dyn Trait<SomeType>`, `-> impl Trait`, etc.

Co-authored-by: vsrs <vit@conrlab.com>
This commit is contained in:
bors[bot] 2020-06-19 13:34:24 +00:00 committed by GitHub
commit 4575c38810
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 1360 additions and 38 deletions

View file

@ -26,8 +26,8 @@ use hir_ty::{
autoderef,
display::{HirDisplayError, HirFormatter},
expr::ExprValidator,
method_resolution, ApplicationTy, Canonical, InEnvironment, Substs, TraitEnvironment, Ty,
TyDefId, TypeCtor,
method_resolution, ApplicationTy, Canonical, GenericPredicate, InEnvironment, Substs,
TraitEnvironment, Ty, TyDefId, TypeCtor,
};
use ra_db::{CrateId, CrateName, Edition, FileId};
use ra_prof::profile;
@ -186,6 +186,22 @@ impl ModuleDef {
module.visibility_of(db, self)
}
pub fn name(self, db: &dyn HirDatabase) -> Option<Name> {
match self {
ModuleDef::Adt(it) => Some(it.name(db)),
ModuleDef::Trait(it) => Some(it.name(db)),
ModuleDef::Function(it) => Some(it.name(db)),
ModuleDef::EnumVariant(it) => Some(it.name(db)),
ModuleDef::TypeAlias(it) => Some(it.name(db)),
ModuleDef::Module(it) => it.name(db),
ModuleDef::Const(it) => it.name(db),
ModuleDef::Static(it) => it.name(db),
ModuleDef::BuiltinType(it) => Some(it.as_name()),
}
}
}
pub use hir_def::{
@ -1359,6 +1375,27 @@ impl Type {
Some(adt.into())
}
pub fn as_dyn_trait(&self) -> Option<Trait> {
self.ty.value.dyn_trait().map(Into::into)
}
pub fn as_impl_traits(&self, db: &dyn HirDatabase) -> Option<Vec<Trait>> {
self.ty.value.impl_trait_bounds(db).map(|it| {
it.into_iter()
.filter_map(|pred| match pred {
hir_ty::GenericPredicate::Implemented(trait_ref) => {
Some(Trait::from(trait_ref.trait_))
}
_ => None,
})
.collect()
})
}
pub fn as_associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<Trait> {
self.ty.value.associated_type_parent_trait(db).map(Into::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) {
@ -1380,6 +1417,80 @@ impl Type {
ty: InEnvironment { value: ty, environment: self.ty.environment.clone() },
}
}
pub fn walk(&self, db: &dyn HirDatabase, mut cb: impl FnMut(Type)) {
// TypeWalk::walk for a Ty at first visits parameters and only after that the Ty itself.
// We need a different order here.
fn walk_substs(
db: &dyn HirDatabase,
type_: &Type,
substs: &Substs,
cb: &mut impl FnMut(Type),
) {
for ty in substs.iter() {
walk_type(db, &type_.derived(ty.clone()), cb);
}
}
fn walk_bounds(
db: &dyn HirDatabase,
type_: &Type,
bounds: &[GenericPredicate],
cb: &mut impl FnMut(Type),
) {
for pred in bounds {
match pred {
GenericPredicate::Implemented(trait_ref) => {
cb(type_.clone());
walk_substs(db, type_, &trait_ref.substs, cb);
}
_ => (),
}
}
}
fn walk_type(db: &dyn HirDatabase, type_: &Type, cb: &mut impl FnMut(Type)) {
let ty = type_.ty.value.strip_references();
match ty {
Ty::Apply(ApplicationTy { ctor, parameters }) => {
match ctor {
TypeCtor::Adt(_) => {
cb(type_.derived(ty.clone()));
}
TypeCtor::AssociatedType(_) => {
if let Some(_) = ty.associated_type_parent_trait(db) {
cb(type_.derived(ty.clone()));
}
}
_ => (),
}
// adt params, tuples, etc...
walk_substs(db, type_, parameters, cb);
}
Ty::Opaque(opaque_ty) => {
if let Some(bounds) = ty.impl_trait_bounds(db) {
walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
}
walk_substs(db, type_, &opaque_ty.parameters, cb);
}
Ty::Placeholder(_) => {
if let Some(bounds) = ty.impl_trait_bounds(db) {
walk_bounds(db, &type_.derived(ty.clone()), &bounds, cb);
}
}
Ty::Dyn(bounds) => {
walk_bounds(db, &type_.derived(ty.clone()), bounds.as_ref(), cb);
}
_ => (),
}
}
walk_type(db, self, &mut cb);
}
}
impl HirDisplay for Type {

View file

@ -73,6 +73,7 @@ pub use lower::{
pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment};
pub use chalk_ir::{BoundVar, DebruijnIndex};
use itertools::Itertools;
/// 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
@ -815,6 +816,11 @@ impl Ty {
}
}
/// If this is a `dyn Trait`, returns that trait.
pub fn dyn_trait(&self) -> Option<TraitId> {
self.dyn_trait_ref().map(|it| it.trait_)
}
fn builtin_deref(&self) -> Option<Ty> {
match self {
Ty::Apply(a_ty) => match a_ty.ctor {
@ -867,13 +873,56 @@ impl Ty {
}
}
/// If this is a `dyn Trait`, returns that trait.
pub fn dyn_trait(&self) -> Option<TraitId> {
pub fn impl_trait_bounds(&self, db: &dyn HirDatabase) -> Option<Vec<GenericPredicate>> {
match self {
Ty::Dyn(predicates) => predicates.iter().find_map(|pred| match pred {
GenericPredicate::Implemented(tr) => Some(tr.trait_),
_ => None,
}),
Ty::Opaque(opaque_ty) => {
let predicates = match opaque_ty.opaque_ty_id {
OpaqueTyId::ReturnTypeImplTrait(func, idx) => {
db.return_type_impl_traits(func).map(|it| {
let data = (*it)
.as_ref()
.map(|rpit| rpit.impl_traits[idx as usize].bounds.clone());
data.clone().subst(&opaque_ty.parameters)
})
}
};
predicates.map(|it| it.value)
}
Ty::Placeholder(id) => {
let generic_params = db.generic_params(id.parent);
let param_data = &generic_params.types[id.local_id];
match param_data.provenance {
hir_def::generics::TypeParamProvenance::ArgumentImplTrait => {
let predicates = db
.generic_predicates_for_param(*id)
.into_iter()
.map(|pred| pred.value.clone())
.collect_vec();
Some(predicates)
}
_ => None,
}
}
_ => None,
}
}
pub fn associated_type_parent_trait(&self, db: &dyn HirDatabase) -> Option<TraitId> {
match self {
Ty::Apply(ApplicationTy { ctor: TypeCtor::AssociatedType(type_alias_id), .. }) => {
match type_alias_id.lookup(db.upcast()).container {
AssocContainerId::TraitId(trait_id) => Some(trait_id),
_ => None,
}
}
Ty::Projection(projection_ty) => {
match projection_ty.associated_ty.lookup(db.upcast()).container {
AssocContainerId::TraitId(trait_id) => Some(trait_id),
_ => None,
}
}
_ => None,
}
}
@ -1057,5 +1106,5 @@ pub struct ReturnTypeImplTraits {
#[derive(Clone, PartialEq, Eq, Debug, Hash)]
pub(crate) struct ReturnTypeImplTrait {
pub(crate) bounds: Binders<Vec<GenericPredicate>>,
pub bounds: Binders<Vec<GenericPredicate>>,
}

File diff suppressed because it is too large Load diff

View file

@ -66,7 +66,7 @@ pub use crate::{
display::{file_structure, FunctionSignature, NavigationTarget, StructureNode},
expand_macro::ExpandedMacro,
folding_ranges::{Fold, FoldKind},
hover::{HoverAction, HoverConfig, HoverResult},
hover::{HoverAction, HoverConfig, HoverGotoTypeData, HoverResult},
inlay_hints::{InlayHint, InlayHintsConfig, InlayKind},
references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult},
runnables::{Runnable, RunnableKind, TestId},

View file

@ -296,6 +296,7 @@ impl Config {
set(value, "/hoverActions/implementations", &mut self.hover.implementations);
set(value, "/hoverActions/run", &mut self.hover.run);
set(value, "/hoverActions/debug", &mut self.hover.debug);
set(value, "/hoverActions/gotoTypeDef", &mut self.hover.goto_type_def);
} else {
self.hover = HoverConfig::NO_ACTIONS;
}

View file

@ -18,8 +18,8 @@ use lsp_types::{
TextDocumentIdentifier, Url, WorkspaceEdit,
};
use ra_ide::{
FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind,
SearchScope, TextEdit,
FileId, FilePosition, FileRange, HoverAction, HoverGotoTypeData, NavigationTarget, Query,
RangeInfo, Runnable, RunnableKind, SearchScope, TextEdit,
};
use ra_prof::profile;
use ra_project_model::TargetKind;
@ -1150,6 +1150,23 @@ fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command {
}
}
fn goto_location_command(snap: &GlobalStateSnapshot, nav: &NavigationTarget) -> Option<Command> {
let value = if snap.config.client_caps.location_link {
let link = to_proto::location_link(snap, None, nav.clone()).ok()?;
to_value(link).ok()?
} else {
let range = FileRange { file_id: nav.file_id(), range: nav.range() };
let location = to_proto::location(snap, range).ok()?;
to_value(location).ok()?
};
Some(Command {
title: nav.name().to_string(),
command: "rust-analyzer.gotoLocation".into(),
arguments: Some(vec![value]),
})
}
fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink {
lsp_ext::CommandLink { tooltip: Some(tooltip), command }
}
@ -1180,13 +1197,13 @@ fn show_impl_command_link(
None
}
fn to_runnable_action(
fn runnable_action_links(
snap: &GlobalStateSnapshot,
file_id: FileId,
runnable: Runnable,
) -> Option<lsp_ext::CommandLinkGroup> {
let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?;
if should_skip_target(&runnable, cargo_spec.as_ref()) {
if !snap.config.hover.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) {
return None;
}
@ -1208,6 +1225,26 @@ fn to_runnable_action(
})
}
fn goto_type_action_links(
snap: &GlobalStateSnapshot,
nav_targets: &[HoverGotoTypeData],
) -> Option<lsp_ext::CommandLinkGroup> {
if !snap.config.hover.goto_type_def || nav_targets.is_empty() {
return None;
}
Some(lsp_ext::CommandLinkGroup {
title: Some("Go to ".into()),
commands: nav_targets
.iter()
.filter_map(|it| {
goto_location_command(snap, &it.nav)
.map(|cmd| to_command_link(cmd, it.mod_path.clone()))
})
.collect(),
})
}
fn prepare_hover_actions(
snap: &GlobalStateSnapshot,
file_id: FileId,
@ -1221,7 +1258,8 @@ fn prepare_hover_actions(
.iter()
.filter_map(|it| match it {
HoverAction::Implementaion(position) => show_impl_command_link(snap, position),
HoverAction::Runnable(r) => to_runnable_action(snap, file_id, r.clone()),
HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()),
HoverAction::GoToType(targets) => goto_type_action_links(snap, targets),
})
.collect()
}

View file

@ -510,6 +510,11 @@
"type": "boolean",
"default": true
},
"rust-analyzer.hoverActions.gotoTypeDef": {
"markdownDescription": "Whether to show `Go to Type Definition` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.",
"type": "boolean",
"default": true
},
"rust-analyzer.linkedProjects": {
"markdownDescription": "Disable project auto-discovery in favor of explicitly specified set of projects. \nElements must be paths pointing to Cargo.toml, rust-project.json, or JSON objects in rust-project.json format",
"type": "array",

View file

@ -353,6 +353,20 @@ export function applyActionGroup(_ctx: Ctx): Cmd {
};
}
export function gotoLocation(ctx: Ctx): Cmd {
return async (locationLink: lc.LocationLink) => {
const client = ctx.client;
if (client) {
const uri = client.protocol2CodeConverter.asUri(locationLink.targetUri);
let range = client.protocol2CodeConverter.asRange(locationLink.targetSelectionRange);
// collapse the range to a cursor position
range = range.with({ end: range.start });
await vscode.window.showTextDocument(uri, { selection: range });
}
};
}
export function resolveCodeAction(ctx: Ctx): Cmd {
const client = ctx.client;
return async (params: ra.ResolveCodeActionParams) => {

View file

@ -117,7 +117,7 @@ export class Config {
return {
engine: this.get<string>("debug.engine"),
engineSettings: this.get<object>("debug.engineSettings"),
openUpDebugPane: this.get<boolean>("debug.openUpDebugPane"),
openDebugPane: this.get<boolean>("debug.openDebugPane"),
sourceFileMap: sourceFileMap
};
}
@ -135,6 +135,9 @@ export class Config {
return {
enable: this.get<boolean>("hoverActions.enable"),
implementations: this.get<boolean>("hoverActions.implementations"),
run: this.get<boolean>("hoverActions.run"),
debug: this.get<boolean>("hoverActions.debug"),
gotoTypeDef: this.get<boolean>("hoverActions.gotoTypeDef"),
};
}
}

View file

@ -82,7 +82,7 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v
}
debugOutput.clear();
if (ctx.config.debug.openUpDebugPane) {
if (ctx.config.debug.openDebugPane) {
debugOutput.show(true);
}

View file

@ -100,6 +100,7 @@ export async function activate(context: vscode.ExtensionContext) {
ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand);
ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction);
ctx.registerCommand('applyActionGroup', commands.applyActionGroup);
ctx.registerCommand('gotoLocation', commands.gotoLocation);
ctx.pushCleanup(activateTaskProvider(workspaceFolder));