Skip to content

feat: goto definition on range operators #18362

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion crates/hir/src/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use hir_def::{
path::ModPath,
resolver::{self, HasResolver, Resolver, TypeNs},
type_ref::Mutability,
AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId,
AsMacroCall, DefWithBodyId, FunctionId, MacroId, StructId, TraitId, VariantId,
};
use hir_expand::{
attrs::collect_attrs,
Expand Down Expand Up @@ -203,6 +203,10 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast))
}

pub fn resolve_range_expr(&self, range_expr: &ast::RangeExpr) -> Option<Struct> {
self.imp.resolve_range_expr(range_expr).map(Struct::from)
}

pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<Function> {
self.imp.resolve_await_to_poll(await_expr).map(Function::from)
}
Expand Down Expand Up @@ -1357,6 +1361,10 @@ impl<'db> SemanticsImpl<'db> {
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
}

fn resolve_range_expr(&self, range_expr: &ast::RangeExpr) -> Option<StructId> {
self.analyze(range_expr.syntax())?.resolve_range_expr(self.db, range_expr)
}

fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<FunctionId> {
self.analyze(await_expr.syntax())?.resolve_await_to_poll(self.db, await_expr)
}
Expand Down
34 changes: 27 additions & 7 deletions crates/hir/src/source_analyzer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
//! purely for "IDE needs".
use std::iter::{self, once};

use crate::{
db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static,
Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant,
};
use either::Either;
use hir_def::{
body::{
Expand All @@ -21,7 +26,7 @@ use hir_def::{
resolver::{resolver_for_scope, Resolver, TypeNs, ValueNs},
type_ref::Mutability,
AsMacroCall, AssocItemId, ConstId, DefWithBodyId, FieldId, FunctionId, ItemContainerId,
LocalFieldId, Lookup, ModuleDefId, TraitId, VariantId,
LocalFieldId, Lookup, ModuleDefId, StructId, TraitId, VariantId,
};
use hir_expand::{
mod_path::path,
Expand All @@ -40,18 +45,13 @@ use hir_ty::{
use intern::sym;
use itertools::Itertools;
use smallvec::SmallVec;
use syntax::ast::{RangeItem, RangeOp};
use syntax::{
ast::{self, AstNode},
SyntaxKind, SyntaxNode, TextRange, TextSize,
};
use triomphe::Arc;

use crate::{
db::HirDatabase, semantics::PathResolution, Adt, AssocItem, BindingMode, BuiltinAttr,
BuiltinType, Callable, Const, DeriveHelper, Field, Function, Local, Macro, ModuleDef, Static,
Struct, ToolModule, Trait, TraitAlias, TupleField, Type, TypeAlias, Variant,
};

/// `SourceAnalyzer` is a convenience wrapper which exposes HIR API in terms of
/// original source files. It should not be used inside the HIR itself.
#[derive(Debug)]
Expand Down Expand Up @@ -348,6 +348,26 @@ impl SourceAnalyzer {
}
}

pub(crate) fn resolve_range_expr(
&self,
db: &dyn HirDatabase,
range_expr: &ast::RangeExpr,
) -> Option<StructId> {
let path: ModPath = match (range_expr.op_kind()?, range_expr.start(), range_expr.end()) {
(RangeOp::Exclusive, None, None) => path![core::ops::RangeFull],
(RangeOp::Exclusive, None, Some(_)) => path![core::ops::RangeTo],
(RangeOp::Exclusive, Some(_), None) => path![core::ops::RangeFrom],
(RangeOp::Exclusive, Some(_), Some(_)) => path![core::ops::Range],
(RangeOp::Inclusive, None, Some(_)) => path![core::ops::RangeToInclusive],
(RangeOp::Inclusive, Some(_), Some(_)) => path![core::ops::RangeInclusive],

// [E0586] inclusive ranges must be bounded at the end
(RangeOp::Inclusive, None, None) => return None,
(RangeOp::Inclusive, Some(_), None) => return None,
};
self.resolver.resolve_known_struct(db.upcast(), &path)
}

pub(crate) fn resolve_await_to_poll(
&self,
db: &dyn HirDatabase,
Expand Down
23 changes: 17 additions & 6 deletions crates/ide-db/src/defs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@

// FIXME: this badly needs rename/rewrite (matklad, 2020-02-06).

use crate::documentation::{Documentation, HasDocs};
use crate::famous_defs::FamousDefs;
use crate::RootDatabase;
use arrayvec::ArrayVec;
use either::Either;
use hir::{
Adt, AsAssocItem, AsExternAssocItem, AssocItem, AttributeTemplate, BuiltinAttr, BuiltinType,
Const, Crate, DefWithBody, DeriveHelper, DocLinkDef, ExternAssocItem, ExternCrateDecl, Field,
Function, GenericParam, HasVisibility, HirDisplay, Impl, InlineAsmOperand, Label, Local, Macro,
Module, ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, ToolModule, Trait,
TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility,
Module, ModuleDef, Name, PathResolution, Semantics, Static, StaticLifetime, Struct, ToolModule,
Trait, TraitAlias, TupleField, TypeAlias, Variant, VariantDef, Visibility,
};
use span::Edition;
use stdx::{format_to, impl_from};
Expand All @@ -21,10 +24,6 @@ use syntax::{
match_ast, SyntaxKind, SyntaxNode, SyntaxToken,
};

use crate::documentation::{Documentation, HasDocs};
use crate::famous_defs::FamousDefs;
use crate::RootDatabase;

// FIXME: a more precise name would probably be `Symbol`?
#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)]
pub enum Definition {
Expand Down Expand Up @@ -319,6 +318,7 @@ impl IdentClass {
.map(IdentClass::NameClass)
.or_else(|| NameRefClass::classify_lifetime(sema, &lifetime).map(IdentClass::NameRefClass))
},
ast::RangeExpr(range_expr) => OperatorClass::classify_range(sema, &range_expr).map(IdentClass::Operator),
ast::AwaitExpr(await_expr) => OperatorClass::classify_await(sema, &await_expr).map(IdentClass::Operator),
ast::BinExpr(bin_expr) => OperatorClass::classify_bin(sema, &bin_expr).map(IdentClass::Operator),
ast::IndexExpr(index_expr) => OperatorClass::classify_index(sema, &index_expr).map(IdentClass::Operator),
Expand Down Expand Up @@ -372,6 +372,9 @@ impl IdentClass {
| OperatorClass::Index(func)
| OperatorClass::Try(func),
) => res.push(Definition::Function(func)),
IdentClass::Operator(OperatorClass::Range(struct0)) => {
res.push(Definition::Adt(Adt::Struct(struct0)))
}
}
res
}
Expand Down Expand Up @@ -546,6 +549,7 @@ impl NameClass {

#[derive(Debug)]
pub enum OperatorClass {
Range(Struct),
Await(Function),
Prefix(Function),
Index(Function),
Expand All @@ -554,6 +558,13 @@ pub enum OperatorClass {
}

impl OperatorClass {
pub fn classify_range(
sema: &Semantics<'_, RootDatabase>,
range_expr: &ast::RangeExpr,
) -> Option<OperatorClass> {
sema.resolve_range_expr(range_expr).map(OperatorClass::Range)
}

pub fn classify_await(
sema: &Semantics<'_, RootDatabase>,
await_expr: &ast::AwaitExpr,
Expand Down
89 changes: 86 additions & 3 deletions crates/ide/src/goto_definition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ use ide_db::{
RootDatabase, SymbolKind,
};
use itertools::Itertools;

use span::{Edition, FileId};
use syntax::{
ast::{self, HasLoopBody},
Expand Down Expand Up @@ -418,10 +417,10 @@ fn expr_to_nav(

#[cfg(test)]
mod tests {
use crate::fixture;
use ide_db::FileRange;
use itertools::Itertools;

use crate::fixture;
use syntax::SmolStr;

#[track_caller]
fn check(ra_fixture: &str) {
Expand Down Expand Up @@ -450,6 +449,90 @@ mod tests {
assert!(navs.is_empty(), "didn't expect this to resolve anywhere: {navs:?}")
}

fn check_name(expected_name: &str, ra_fixture: &str) {
let (analysis, position, _) = fixture::annotations(ra_fixture);
let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
assert!(navs.len() < 2, "expected single navigation target but encountered {}", navs.len());
let Some(target) = navs.into_iter().next() else {
panic!("expected single navigation target but encountered none");
};
assert_eq!(target.name, SmolStr::new_inline(expected_name));
}

#[test]
fn goto_def_range() {
check_name(
"Range",
r#"
//- minicore: range
let x = 0.$0.1;
"#,
);
}

#[test]
fn goto_def_range_from() {
check_name(
"RangeFrom",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[0.$0.]
}
"#,
);
}

#[test]
fn goto_def_range_inclusive() {
check_name(
"RangeInclusive",
r#"
//- minicore: range
let x = 0.$0.=1;
"#,
);
}

#[test]
fn goto_def_range_full() {
check_name(
"RangeFull",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[.$0.]
}
"#,
);
}

#[test]
fn goto_def_range_to() {
check_name(
"RangeTo",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[.$0.10]
}
"#,
);
}

#[test]
fn goto_def_range_to_inclusive() {
check_name(
"RangeToInclusive",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[.$0.=10]
}
"#,
);
}

#[test]
fn goto_def_in_included_file() {
check(
Expand Down