mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 13:03:31 +00:00
Merge #4839
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:
commit
4575c38810
11 changed files with 1360 additions and 38 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
@ -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},
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
Loading…
Reference in a new issue