rust-analyzer/crates/ide/src/goto_definition.rs
Chayim Refael Friedman 8adcbdcc49 Implement semitransparent hygiene
Or macro_rules hygiene, or mixed site hygiene. In other words, hygiene for variables and labels but not items.

The realization that made me implement this was that while "full" hygiene (aka. def site hygiene) is really hard for us to implement, and will likely involve intrusive changes and performance losses, since every `Name` will have to carry hygiene, mixed site hygiene is very local: it applies only to bodies, and we very well can save it in a side map with minor losses.

This fixes one diagnostic in r-a that was about `izip!()` using hygiene (yay!) but it introduces a huge number of others, because of #18262. Up until now this issue wasn't a major problem because it only affected few cases, but with hygiene identifiers referred by macros like that are not resolved at all. The next commit will fix that.
2024-10-22 21:26:56 +03:00

3025 lines
51 KiB
Rust

use std::{iter, mem::discriminant};
use crate::{
doc_links::token_as_doc_comment,
navigation_target::{self, ToNav},
FilePosition, NavigationTarget, RangeInfo, TryToNav, UpmappingResult,
};
use hir::{AsAssocItem, AssocItem, FileRange, InFile, MacroFileIdExt, ModuleDef, Semantics};
use ide_db::{
base_db::{AnchoredPath, FileLoader, SourceDatabase},
defs::{Definition, IdentClass},
helpers::pick_best_token,
RootDatabase, SymbolKind,
};
use itertools::Itertools;
use span::{Edition, FileId};
use syntax::{
ast::{self, HasLoopBody},
match_ast, AstNode, AstToken,
SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, T,
};
// Feature: Go to Definition
//
// Navigates to the definition of an identifier.
//
// For outline modules, this will navigate to the source file of the module.
//
// |===
// | Editor | Shortcut
//
// | VS Code | kbd:[F12]
// |===
//
// image::https://user-images.githubusercontent.com/48062697/113065563-025fbe00-91b1-11eb-83e4-a5a703610b23.gif[]
pub(crate) fn goto_definition(
db: &RootDatabase,
FilePosition { file_id, offset }: FilePosition,
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
let sema = &Semantics::new(db);
let file = sema.parse_guess_edition(file_id).syntax().clone();
let edition =
sema.attach_first_edition(file_id).map(|it| it.edition()).unwrap_or(Edition::CURRENT);
let original_token = pick_best_token(file.token_at_offset(offset), |kind| match kind {
IDENT
| INT_NUMBER
| LIFETIME_IDENT
| T![self]
| T![super]
| T![crate]
| T![Self]
| COMMENT => 4,
// index and prefix ops
T!['['] | T![']'] | T![?] | T![*] | T![-] | T![!] => 3,
kind if kind.is_keyword(edition) => 2,
T!['('] | T![')'] => 2,
kind if kind.is_trivia() => 0,
_ => 1,
})?;
if let Some(doc_comment) = token_as_doc_comment(&original_token) {
return doc_comment.get_definition_with_descend_at(sema, offset, |def, _, link_range| {
let nav = def.try_to_nav(db)?;
Some(RangeInfo::new(link_range, nav.collect()))
});
}
if let Some((range, resolution)) =
sema.check_for_format_args_template(original_token.clone(), offset)
{
return Some(RangeInfo::new(
range,
match resolution {
Some(res) => def_to_nav(db, Definition::from(res)),
None => vec![],
},
));
}
if let Some(navs) = handle_control_flow_keywords(sema, &original_token) {
return Some(RangeInfo::new(original_token.text_range(), navs));
}
let navs = sema
.descend_into_macros_no_opaque(original_token.clone())
.into_iter()
.filter_map(|token| {
let parent = token.parent()?;
if let Some(token) = ast::String::cast(token.clone()) {
if let Some(x) = try_lookup_include_path(sema, token, file_id) {
return Some(vec![x]);
}
}
if ast::TokenTree::can_cast(parent.kind()) {
if let Some(x) = try_lookup_macro_def_in_macro_use(sema, token) {
return Some(vec![x]);
}
}
Some(
IdentClass::classify_node(sema, &parent)?
.definitions()
.into_iter()
.flat_map(|def| {
if let Definition::ExternCrateDecl(crate_def) = def {
return crate_def
.resolved_crate(db)
.map(|it| it.root_module().to_nav(sema.db))
.into_iter()
.flatten()
.collect();
}
try_filter_trait_item_definition(sema, &def)
.unwrap_or_else(|| def_to_nav(sema.db, def))
})
.collect(),
)
})
.flatten()
.unique()
.collect::<Vec<NavigationTarget>>();
Some(RangeInfo::new(original_token.text_range(), navs))
}
fn try_lookup_include_path(
sema: &Semantics<'_, RootDatabase>,
token: ast::String,
file_id: FileId,
) -> Option<NavigationTarget> {
let file = sema.hir_file_for(&token.syntax().parent()?).macro_file()?;
if !iter::successors(Some(file), |file| file.parent(sema.db).macro_file())
// Check that we are in the eager argument expansion of an include macro
.any(|file| file.is_include_like_macro(sema.db) && file.eager_arg(sema.db).is_none())
{
return None;
}
let path = token.value().ok()?;
let file_id = sema.db.resolve_path(AnchoredPath { anchor: file_id, path: &path })?;
let size = sema.db.file_text(file_id).len().try_into().ok()?;
Some(NavigationTarget {
file_id,
full_range: TextRange::new(0.into(), size),
name: path.into(),
alias: None,
focus_range: None,
kind: None,
container_name: None,
description: None,
docs: None,
})
}
fn try_lookup_macro_def_in_macro_use(
sema: &Semantics<'_, RootDatabase>,
token: SyntaxToken,
) -> Option<NavigationTarget> {
let extern_crate = token.parent()?.ancestors().find_map(ast::ExternCrate::cast)?;
let extern_crate = sema.to_def(&extern_crate)?;
let krate = extern_crate.resolved_crate(sema.db)?;
for mod_def in krate.root_module().declarations(sema.db) {
if let ModuleDef::Macro(mac) = mod_def {
if mac.name(sema.db).as_str() == token.text() {
if let Some(nav) = mac.try_to_nav(sema.db) {
return Some(nav.call_site);
}
}
}
}
None
}
/// finds the trait definition of an impl'd item, except function
/// e.g.
/// ```rust
/// trait A { type a; }
/// struct S;
/// impl A for S { type a = i32; } // <-- on this associate type, will get the location of a in the trait
/// ```
fn try_filter_trait_item_definition(
sema: &Semantics<'_, RootDatabase>,
def: &Definition,
) -> Option<Vec<NavigationTarget>> {
let db = sema.db;
let assoc = def.as_assoc_item(db)?;
match assoc {
AssocItem::Function(..) => None,
AssocItem::Const(..) | AssocItem::TypeAlias(..) => {
let trait_ = assoc.implemented_trait(db)?;
let name = def.name(db)?;
let discriminant_value = discriminant(&assoc);
trait_
.items(db)
.iter()
.filter(|itm| discriminant(*itm) == discriminant_value)
.find_map(|itm| (itm.name(db)? == name).then(|| itm.try_to_nav(db)).flatten())
.map(|it| it.collect())
}
}
}
fn handle_control_flow_keywords(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
) -> Option<Vec<NavigationTarget>> {
match token.kind() {
// For `fn` / `loop` / `while` / `for` / `async`, return the keyword it self,
// so that VSCode will find the references when using `ctrl + click`
T![fn] | T![async] | T![try] | T![return] => nav_for_exit_points(sema, token),
T![loop] | T![while] | T![break] | T![continue] => nav_for_break_points(sema, token),
T![for] if token.parent().and_then(ast::ForExpr::cast).is_some() => {
nav_for_break_points(sema, token)
}
_ => None,
}
}
pub(crate) fn find_fn_or_blocks(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
) -> Vec<SyntaxNode> {
let find_ancestors = |token: SyntaxToken| {
let token_kind = token.kind();
for anc in sema.token_ancestors_with_macros(token) {
let node = match_ast! {
match anc {
ast::Fn(fn_) => fn_.syntax().clone(),
ast::ClosureExpr(c) => c.syntax().clone(),
ast::BlockExpr(blk) => {
match blk.modifier() {
Some(ast::BlockModifier::Async(_)) => blk.syntax().clone(),
Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => blk.syntax().clone(),
_ => continue,
}
},
_ => continue,
}
};
return Some(node);
}
None
};
sema.descend_into_macros(token.clone()).into_iter().filter_map(find_ancestors).collect_vec()
}
fn nav_for_exit_points(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
) -> Option<Vec<NavigationTarget>> {
let db = sema.db;
let token_kind = token.kind();
let navs = find_fn_or_blocks(sema, token)
.into_iter()
.filter_map(|node| {
let file_id = sema.hir_file_for(&node);
match_ast! {
match node {
ast::Fn(fn_) => {
let mut nav = sema.to_def(&fn_)?.try_to_nav(db)?;
// For async token, we navigate to itself, which triggers
// VSCode to find the references
let focus_token = if matches!(token_kind, T![async]) {
fn_.async_token()?
} else {
fn_.fn_token()?
};
let focus_frange = InFile::new(file_id, focus_token.text_range())
.original_node_file_range_opt(db)
.map(|(frange, _)| frange);
if let Some(FileRange { file_id, range }) = focus_frange {
let contains_frange = |nav: &NavigationTarget| {
nav.file_id == file_id && nav.full_range.contains_range(range)
};
if let Some(def_site) = nav.def_site.as_mut() {
if contains_frange(def_site) {
def_site.focus_range = Some(range);
}
} else if contains_frange(&nav.call_site) {
nav.call_site.focus_range = Some(range);
}
}
Some(nav)
},
ast::ClosureExpr(c) => {
let pipe_tok = c.param_list().and_then(|it| it.pipe_token())?.text_range();
let closure_in_file = InFile::new(file_id, c.into());
Some(expr_to_nav(db, closure_in_file, Some(pipe_tok)))
},
ast::BlockExpr(blk) => {
match blk.modifier() {
Some(ast::BlockModifier::Async(_)) => {
let async_tok = blk.async_token()?.text_range();
let blk_in_file = InFile::new(file_id, blk.into());
Some(expr_to_nav(db, blk_in_file, Some(async_tok)))
},
Some(ast::BlockModifier::Try(_)) if token_kind != T![return] => {
let try_tok = blk.try_token()?.text_range();
let blk_in_file = InFile::new(file_id, blk.into());
Some(expr_to_nav(db, blk_in_file, Some(try_tok)))
},
_ => None,
}
},
_ => None,
}
}
})
.flatten()
.collect_vec();
Some(navs)
}
pub(crate) fn find_loops(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
) -> Option<Vec<ast::Expr>> {
let parent = token.parent()?;
let lbl = match_ast! {
match parent {
ast::BreakExpr(break_) => break_.lifetime(),
ast::ContinueExpr(continue_) => continue_.lifetime(),
_ => None,
}
};
let label_matches =
|it: Option<ast::Label>| match (lbl.as_ref(), it.and_then(|it| it.lifetime())) {
(Some(lbl), Some(it)) => lbl.text() == it.text(),
(None, _) => true,
(Some(_), None) => false,
};
let find_ancestors = |token: SyntaxToken| {
for anc in sema.token_ancestors_with_macros(token).filter_map(ast::Expr::cast) {
let node = match &anc {
ast::Expr::LoopExpr(loop_) if label_matches(loop_.label()) => anc,
ast::Expr::WhileExpr(while_) if label_matches(while_.label()) => anc,
ast::Expr::ForExpr(for_) if label_matches(for_.label()) => anc,
ast::Expr::BlockExpr(blk)
if blk.label().is_some() && label_matches(blk.label()) =>
{
anc
}
_ => continue,
};
return Some(node);
}
None
};
sema.descend_into_macros(token.clone())
.into_iter()
.filter_map(find_ancestors)
.collect_vec()
.into()
}
fn nav_for_break_points(
sema: &Semantics<'_, RootDatabase>,
token: &SyntaxToken,
) -> Option<Vec<NavigationTarget>> {
let db = sema.db;
let navs = find_loops(sema, token)?
.into_iter()
.filter_map(|expr| {
let file_id = sema.hir_file_for(expr.syntax());
let expr_in_file = InFile::new(file_id, expr.clone());
let focus_range = match expr {
ast::Expr::LoopExpr(loop_) => loop_.loop_token()?.text_range(),
ast::Expr::WhileExpr(while_) => while_.while_token()?.text_range(),
ast::Expr::ForExpr(for_) => for_.for_token()?.text_range(),
// We guarantee that the label exists
ast::Expr::BlockExpr(blk) => blk.label().unwrap().syntax().text_range(),
_ => return None,
};
let nav = expr_to_nav(db, expr_in_file, Some(focus_range));
Some(nav)
})
.flatten()
.collect_vec();
Some(navs)
}
fn def_to_nav(db: &RootDatabase, def: Definition) -> Vec<NavigationTarget> {
def.try_to_nav(db).map(|it| it.collect()).unwrap_or_default()
}
fn expr_to_nav(
db: &RootDatabase,
InFile { file_id, value }: InFile<ast::Expr>,
focus_range: Option<TextRange>,
) -> UpmappingResult<NavigationTarget> {
let kind = SymbolKind::Label;
let value_range = value.syntax().text_range();
let navs = navigation_target::orig_range_with_focus_r(db, file_id, value_range, focus_range);
navs.map(|(hir::FileRangeWrapper { file_id, range }, focus_range)| {
NavigationTarget::from_syntax(file_id, "<expr>".into(), focus_range, range, kind)
})
}
#[cfg(test)]
mod tests {
use crate::fixture;
use ide_db::FileRange;
use itertools::Itertools;
use syntax::SmolStr;
#[track_caller]
fn check(ra_fixture: &str) {
let (analysis, position, expected) = fixture::annotations(ra_fixture);
let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
let cmp = |&FileRange { file_id, range }: &_| (file_id, range.start());
let navs = navs
.into_iter()
.map(|nav| FileRange { file_id: nav.file_id, range: nav.focus_or_full_range() })
.sorted_by_key(cmp)
.collect::<Vec<_>>();
let expected = expected
.into_iter()
.map(|(FileRange { file_id, range }, _)| FileRange { file_id, range })
.sorted_by_key(cmp)
.collect::<Vec<_>>();
assert_eq!(expected, navs);
}
fn check_unresolved(ra_fixture: &str) {
let (analysis, position) = fixture::position(ra_fixture);
let navs = analysis.goto_definition(position).unwrap().expect("no definition found").info;
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_pat_range_to_inclusive() {
check_name(
"RangeToInclusive",
r#"
//- minicore: range
fn f(ch: char) -> bool {
match ch {
..$0='z' => true,
_ => false
}
}
"#,
);
}
#[test]
fn goto_def_pat_range_to() {
check_name(
"RangeTo",
r#"
//- minicore: range
fn f(ch: char) -> bool {
match ch {
.$0.'z' => true,
_ => false
}
}
"#,
);
}
#[test]
fn goto_def_pat_range() {
check_name(
"Range",
r#"
//- minicore: range
fn f(ch: char) -> bool {
match ch {
'a'.$0.'z' => true,
_ => false
}
}
"#,
);
}
#[test]
fn goto_def_pat_range_inclusive() {
check_name(
"RangeInclusive",
r#"
//- minicore: range
fn f(ch: char) -> bool {
match ch {
'a'..$0='z' => true,
_ => false
}
}
"#,
);
}
#[test]
fn goto_def_pat_range_from() {
check_name(
"RangeFrom",
r#"
//- minicore: range
fn f(ch: char) -> bool {
match ch {
'a'..$0 => true,
_ => false
}
}
"#,
);
}
#[test]
fn goto_def_expr_range() {
check_name(
"Range",
r#"
//- minicore: range
let x = 0.$0.1;
"#,
);
}
#[test]
fn goto_def_expr_range_from() {
check_name(
"RangeFrom",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[0.$0.]
}
"#,
);
}
#[test]
fn goto_def_expr_range_inclusive() {
check_name(
"RangeInclusive",
r#"
//- minicore: range
let x = 0.$0.=1;
"#,
);
}
#[test]
fn goto_def_expr_range_full() {
check_name(
"RangeFull",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[.$0.]
}
"#,
);
}
#[test]
fn goto_def_expr_range_to() {
check_name(
"RangeTo",
r#"
//- minicore: range
fn f(arr: &[i32]) -> &[i32] {
&arr[.$0.10]
}
"#,
);
}
#[test]
fn goto_def_expr_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(
r#"
//- minicore:include
//- /main.rs
include!("a.rs");
fn main() {
foo();
}
//- /a.rs
fn func_in_include() {
//^^^^^^^^^^^^^^^
}
fn foo() {
func_in_include$0();
}
"#,
);
}
#[test]
fn goto_def_in_included_file_nested() {
check(
r#"
//- minicore:include
//- /main.rs
macro_rules! passthrough {
($($tt:tt)*) => { $($tt)* }
}
passthrough!(include!("a.rs"));
fn main() {
foo();
}
//- /a.rs
fn func_in_include() {
//^^^^^^^^^^^^^^^
}
fn foo() {
func_in_include$0();
}
"#,
);
}
#[test]
fn goto_def_in_included_file_inside_mod() {
check(
r#"
//- minicore:include
//- /main.rs
mod a {
include!("b.rs");
}
//- /b.rs
fn func_in_include() {
//^^^^^^^^^^^^^^^
}
fn foo() {
func_in_include$0();
}
"#,
);
check(
r#"
//- minicore:include
//- /main.rs
mod a {
include!("a.rs");
}
//- /a.rs
fn func_in_include() {
//^^^^^^^^^^^^^^^
}
fn foo() {
func_in_include$0();
}
"#,
);
}
#[test]
fn goto_def_if_items_same_name() {
check(
r#"
trait Trait {
type A;
const A: i32;
//^
}
struct T;
impl Trait for T {
type A = i32;
const A$0: i32 = -9;
}"#,
);
}
#[test]
fn goto_def_in_mac_call_in_attr_invoc() {
check(
r#"
//- proc_macros: identity
pub struct Struct {
// ^^^^^^
field: i32,
}
macro_rules! identity {
($($tt:tt)*) => {$($tt)*};
}
#[proc_macros::identity]
fn function() {
identity!(Struct$0 { field: 0 });
}
"#,
)
}
#[test]
fn goto_def_for_extern_crate() {
check(
r#"
//- /main.rs crate:main deps:std
extern crate std$0;
//- /std/lib.rs crate:std
// empty
//^file
"#,
)
}
#[test]
fn goto_def_for_renamed_extern_crate() {
check(
r#"
//- /main.rs crate:main deps:std
extern crate std as abc$0;
//- /std/lib.rs crate:std
// empty
//^file
"#,
)
}
#[test]
fn goto_def_in_items() {
check(
r#"
struct Foo;
//^^^
enum E { X(Foo$0) }
"#,
);
}
#[test]
fn goto_def_at_start_of_item() {
check(
r#"
struct Foo;
//^^^
enum E { X($0Foo) }
"#,
);
}
#[test]
fn goto_definition_resolves_correct_name() {
check(
r#"
//- /lib.rs
use a::Foo;
mod a;
mod b;
enum E { X(Foo$0) }
//- /a.rs
pub struct Foo;
//^^^
//- /b.rs
pub struct Foo;
"#,
);
}
#[test]
fn goto_def_for_module_declaration() {
check(
r#"
//- /lib.rs
mod $0foo;
//- /foo.rs
// empty
//^file
"#,
);
check(
r#"
//- /lib.rs
mod $0foo;
//- /foo/mod.rs
// empty
//^file
"#,
);
}
#[test]
fn goto_def_for_macros() {
check(
r#"
macro_rules! foo { () => { () } }
//^^^
fn bar() {
$0foo!();
}
"#,
);
}
#[test]
fn goto_def_for_macros_from_other_crates() {
check(
r#"
//- /lib.rs crate:main deps:foo
use foo::foo;
fn bar() {
$0foo!();
}
//- /foo/lib.rs crate:foo
#[macro_export]
macro_rules! foo { () => { () } }
//^^^
"#,
);
}
#[test]
fn goto_def_for_macros_in_use_tree() {
check(
r#"
//- /lib.rs crate:main deps:foo
use foo::foo$0;
//- /foo/lib.rs crate:foo
#[macro_export]
macro_rules! foo { () => { () } }
//^^^
"#,
);
}
#[test]
fn goto_def_for_macro_defined_fn_with_arg() {
check(
r#"
//- /lib.rs
macro_rules! define_fn {
($name:ident) => (fn $name() {})
}
define_fn!(foo);
//^^^
fn bar() {
$0foo();
}
"#,
);
}
#[test]
fn goto_def_for_macro_defined_fn_no_arg() {
check(
r#"
//- /lib.rs
macro_rules! define_fn {
() => (fn foo() {})
//^^^
}
define_fn!();
//^^^^^^^^^^^^^
fn bar() {
$0foo();
}
"#,
);
}
#[test]
fn goto_definition_works_for_macro_inside_pattern() {
check(
r#"
//- /lib.rs
macro_rules! foo {() => {0}}
//^^^
fn bar() {
match (0,1) {
($0foo!(), _) => {}
}
}
"#,
);
}
#[test]
fn goto_definition_works_for_macro_inside_match_arm_lhs() {
check(
r#"
//- /lib.rs
macro_rules! foo {() => {0}}
//^^^
fn bar() {
match 0 {
$0foo!() => {}
}
}
"#,
);
}
#[test]
fn goto_definition_works_for_consts_inside_range_pattern() {
check(
r#"
//- /lib.rs
const A: u32 = 0;
//^
fn bar(v: u32) {
match v {
0..=$0A => {}
_ => {}
}
}
"#,
);
}
#[test]
fn goto_def_for_use_alias() {
check(
r#"
//- /lib.rs crate:main deps:foo
use foo as bar$0;
//- /foo/lib.rs crate:foo
// empty
//^file
"#,
);
}
#[test]
fn goto_def_for_use_alias_foo_macro() {
check(
r#"
//- /lib.rs crate:main deps:foo
use foo::foo as bar$0;
//- /foo/lib.rs crate:foo
#[macro_export]
macro_rules! foo { () => { () } }
//^^^
"#,
);
}
#[test]
fn goto_def_for_methods() {
check(
r#"
struct Foo;
impl Foo {
fn frobnicate(&self) { }
//^^^^^^^^^^
}
fn bar(foo: &Foo) {
foo.frobnicate$0();
}
"#,
);
}
#[test]
fn goto_def_for_fields() {
check(
r#"
struct Foo {
spam: u32,
} //^^^^
fn bar(foo: &Foo) {
foo.spam$0;
}
"#,
);
}
#[test]
fn goto_def_for_record_fields() {
check(
r#"
//- /lib.rs
struct Foo {
spam: u32,
} //^^^^
fn bar() -> Foo {
Foo {
spam$0: 0,
}
}
"#,
);
}
#[test]
fn goto_def_for_record_pat_fields() {
check(
r#"
//- /lib.rs
struct Foo {
spam: u32,
} //^^^^
fn bar(foo: Foo) -> Foo {
let Foo { spam$0: _, } = foo
}
"#,
);
}
#[test]
fn goto_def_for_record_fields_macros() {
check(
r"
macro_rules! m { () => { 92 };}
struct Foo { spam: u32 }
//^^^^
fn bar() -> Foo {
Foo { spam$0: m!() }
}
",
);
}
#[test]
fn goto_for_tuple_fields() {
check(
r#"
struct Foo(u32);
//^^^
fn bar() {
let foo = Foo(0);
foo.$00;
}
"#,
);
}
#[test]
fn goto_def_for_ufcs_inherent_methods() {
check(
r#"
struct Foo;
impl Foo {
fn frobnicate() { }
} //^^^^^^^^^^
fn bar(foo: &Foo) {
Foo::frobnicate$0();
}
"#,
);
}
#[test]
fn goto_def_for_ufcs_trait_methods_through_traits() {
check(
r#"
trait Foo {
fn frobnicate();
} //^^^^^^^^^^
fn bar() {
Foo::frobnicate$0();
}
"#,
);
}
#[test]
fn goto_def_for_ufcs_trait_methods_through_self() {
check(
r#"
struct Foo;
trait Trait {
fn frobnicate();
} //^^^^^^^^^^
impl Trait for Foo {}
fn bar() {
Foo::frobnicate$0();
}
"#,
);
}
#[test]
fn goto_definition_on_self() {
check(
r#"
struct Foo;
impl Foo {
//^^^
pub fn new() -> Self {
Self$0 {}
}
}
"#,
);
check(
r#"
struct Foo;
impl Foo {
//^^^
pub fn new() -> Self$0 {
Self {}
}
}
"#,
);
check(
r#"
enum Foo { A }
impl Foo {
//^^^
pub fn new() -> Self$0 {
Foo::A
}
}
"#,
);
check(
r#"
enum Foo { A }
impl Foo {
//^^^
pub fn thing(a: &Self$0) {
}
}
"#,
);
}
#[test]
fn goto_definition_on_self_in_trait_impl() {
check(
r#"
struct Foo;
trait Make {
fn new() -> Self;
}
impl Make for Foo {
//^^^
fn new() -> Self {
Self$0 {}
}
}
"#,
);
check(
r#"
struct Foo;
trait Make {
fn new() -> Self;
}
impl Make for Foo {
//^^^
fn new() -> Self$0 {
Self {}
}
}
"#,
);
}
#[test]
fn goto_def_when_used_on_definition_name_itself() {
check(
r#"
struct Foo$0 { value: u32 }
//^^^
"#,
);
check(
r#"
struct Foo {
field$0: string,
} //^^^^^
"#,
);
check(
r#"
fn foo_test$0() { }
//^^^^^^^^
"#,
);
check(
r#"
enum Foo$0 { Variant }
//^^^
"#,
);
check(
r#"
enum Foo {
Variant1,
Variant2$0,
//^^^^^^^^
Variant3,
}
"#,
);
check(
r#"
static INNER$0: &str = "";
//^^^^^
"#,
);
check(
r#"
const INNER$0: &str = "";
//^^^^^
"#,
);
check(
r#"
type Thing$0 = Option<()>;
//^^^^^
"#,
);
check(
r#"
trait Foo$0 { }
//^^^
"#,
);
check(
r#"
trait Foo$0 = ;
//^^^
"#,
);
check(
r#"
mod bar$0 { }
//^^^
"#,
);
}
#[test]
fn goto_from_macro() {
check(
r#"
macro_rules! id {
($($tt:tt)*) => { $($tt)* }
}
fn foo() {}
//^^^
id! {
fn bar() {
fo$0o();
}
}
mod confuse_index { fn foo(); }
"#,
);
}
#[test]
fn goto_through_format() {
check(
r#"
//- minicore: fmt
#[macro_export]
macro_rules! format {
($($arg:tt)*) => ($crate::fmt::format($crate::__export::format_args!($($arg)*)))
}
pub mod __export {
pub use core::format_args;
fn foo() {} // for index confusion
}
fn foo() -> i8 {}
//^^^
fn test() {
format!("{}", fo$0o())
}
"#,
);
}
#[test]
fn goto_through_included_file() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include {}
include!("foo.rs");
fn f() {
foo$0();
}
mod confuse_index {
pub fn foo() {}
}
//- /foo.rs
fn foo() {}
//^^^
"#,
);
}
#[test]
fn goto_through_included_file_struct_with_doc_comment() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include {}
include!("foo.rs");
fn f() {
let x = Foo$0;
}
mod confuse_index {
pub struct Foo;
}
//- /foo.rs
/// This is a doc comment
pub struct Foo;
//^^^
"#,
);
}
#[test]
fn goto_for_type_param() {
check(
r#"
struct Foo<T: Clone> { t: $0T }
//^
"#,
);
}
#[test]
fn goto_within_macro() {
check(
r#"
macro_rules! id {
($($tt:tt)*) => ($($tt)*)
}
fn foo() {
let x = 1;
//^
id!({
let y = $0x;
let z = y;
});
}
"#,
);
check(
r#"
macro_rules! id {
($($tt:tt)*) => ($($tt)*)
}
fn foo() {
let x = 1;
id!({
let y = x;
//^
let z = $0y;
});
}
"#,
);
}
#[test]
fn goto_def_in_local_fn() {
check(
r#"
fn main() {
fn foo() {
let x = 92;
//^
$0x;
}
}
"#,
);
}
#[test]
fn goto_def_in_local_macro() {
check(
r#"
fn bar() {
macro_rules! foo { () => { () } }
//^^^
$0foo!();
}
"#,
);
}
#[test]
fn goto_def_for_field_init_shorthand() {
check(
r#"
struct Foo { x: i32 }
//^
fn main() {
let x = 92;
//^
Foo { x$0 };
}
"#,
)
}
#[test]
fn goto_def_for_enum_variant_field() {
check(
r#"
enum Foo {
Bar { x: i32 }
//^
}
fn baz(foo: Foo) {
match foo {
Foo::Bar { x$0 } => x
//^
};
}
"#,
);
}
#[test]
fn goto_def_for_enum_variant_self_pattern_const() {
check(
r#"
enum Foo { Bar }
//^^^
impl Foo {
fn baz(self) {
match self { Self::Bar$0 => {} }
}
}
"#,
);
}
#[test]
fn goto_def_for_enum_variant_self_pattern_record() {
check(
r#"
enum Foo { Bar { val: i32 } }
//^^^
impl Foo {
fn baz(self) -> i32 {
match self { Self::Bar$0 { val } => {} }
}
}
"#,
);
}
#[test]
fn goto_def_for_enum_variant_self_expr_const() {
check(
r#"
enum Foo { Bar }
//^^^
impl Foo {
fn baz(self) { Self::Bar$0; }
}
"#,
);
}
#[test]
fn goto_def_for_enum_variant_self_expr_record() {
check(
r#"
enum Foo { Bar { val: i32 } }
//^^^
impl Foo {
fn baz(self) { Self::Bar$0 {val: 4}; }
}
"#,
);
}
#[test]
fn goto_def_for_type_alias_generic_parameter() {
check(
r#"
type Alias<T> = T$0;
//^
"#,
)
}
#[test]
fn goto_def_for_macro_container() {
check(
r#"
//- /lib.rs crate:main deps:foo
foo::module$0::mac!();
//- /foo/lib.rs crate:foo
pub mod module {
//^^^^^^
#[macro_export]
macro_rules! _mac { () => { () } }
pub use crate::_mac as mac;
}
"#,
);
}
#[test]
fn goto_def_for_assoc_ty_in_path() {
check(
r#"
trait Iterator {
type Item;
//^^^^
}
fn f() -> impl Iterator<Item$0 = u8> {}
"#,
);
}
#[test]
fn goto_def_for_super_assoc_ty_in_path() {
check(
r#"
trait Super {
type Item;
//^^^^
}
trait Sub: Super {}
fn f() -> impl Sub<Item$0 = u8> {}
"#,
);
}
#[test]
fn goto_def_for_module_declaration_in_path_if_types_and_values_same_name() {
check(
r#"
mod bar {
pub struct Foo {}
//^^^
pub fn Foo() {}
}
fn baz() {
let _foo_enum: bar::Foo$0 = bar::Foo {};
}
"#,
)
}
#[test]
fn unknown_assoc_ty() {
check_unresolved(
r#"
trait Iterator { type Item; }
fn f() -> impl Iterator<Invalid$0 = u8> {}
"#,
)
}
#[test]
fn goto_def_for_assoc_ty_in_path_multiple() {
check(
r#"
trait Iterator {
type A;
//^
type B;
}
fn f() -> impl Iterator<A$0 = u8, B = ()> {}
"#,
);
check(
r#"
trait Iterator {
type A;
type B;
//^
}
fn f() -> impl Iterator<A = u8, B$0 = ()> {}
"#,
);
}
#[test]
fn goto_def_for_assoc_ty_ufcs() {
check(
r#"
trait Iterator {
type Item;
//^^^^
}
fn g() -> <() as Iterator<Item$0 = ()>>::Item {}
"#,
);
}
#[test]
fn goto_def_for_assoc_ty_ufcs_multiple() {
check(
r#"
trait Iterator {
type A;
//^
type B;
}
fn g() -> <() as Iterator<A$0 = (), B = u8>>::B {}
"#,
);
check(
r#"
trait Iterator {
type A;
type B;
//^
}
fn g() -> <() as Iterator<A = (), B$0 = u8>>::A {}
"#,
);
}
#[test]
fn goto_self_param_ty_specified() {
check(
r#"
struct Foo {}
impl Foo {
fn bar(self: &Foo) {
//^^^^
let foo = sel$0f;
}
}"#,
)
}
#[test]
fn goto_self_param_on_decl() {
check(
r#"
struct Foo {}
impl Foo {
fn bar(&self$0) {
//^^^^
}
}"#,
)
}
#[test]
fn goto_lifetime_param_on_decl() {
check(
r#"
fn foo<'foobar$0>(_: &'foobar ()) {
//^^^^^^^
}"#,
)
}
#[test]
fn goto_lifetime_param_decl() {
check(
r#"
fn foo<'foobar>(_: &'foobar$0 ()) {
//^^^^^^^
}"#,
)
}
#[test]
fn goto_lifetime_param_decl_nested() {
check(
r#"
fn foo<'foobar>(_: &'foobar ()) {
fn foo<'foobar>(_: &'foobar$0 ()) {}
//^^^^^^^
}"#,
)
}
#[test]
fn goto_lifetime_hrtb() {
// FIXME: requires the HIR to somehow track these hrtb lifetimes
check_unresolved(
r#"
trait Foo<T> {}
fn foo<T>() where for<'a> T: Foo<&'a$0 (u8, u16)>, {}
//^^
"#,
);
check_unresolved(
r#"
trait Foo<T> {}
fn foo<T>() where for<'a$0> T: Foo<&'a (u8, u16)>, {}
//^^
"#,
);
}
#[test]
fn goto_lifetime_hrtb_for_type() {
// FIXME: requires ForTypes to be implemented
check_unresolved(
r#"trait Foo<T> {}
fn foo<T>() where T: for<'a> Foo<&'a$0 (u8, u16)>, {}
//^^
"#,
);
}
#[test]
fn goto_label() {
check(
r#"
fn foo<'foo>(_: &'foo ()) {
'foo: {
//^^^^
'bar: loop {
break 'foo$0;
}
}
}"#,
)
}
#[test]
fn goto_def_for_intra_doc_link_same_file() {
check(
r#"
/// Blah, [`bar`](bar) .. [`foo`](foo$0) has [`bar`](bar)
pub fn bar() { }
/// You might want to see [`std::fs::read()`] too.
pub fn foo() { }
//^^^
}"#,
)
}
#[test]
fn goto_def_for_intra_doc_link_inner() {
check(
r#"
//- /main.rs
mod m;
struct S;
//^
//- /m.rs
//! [`super::S$0`]
"#,
)
}
#[test]
fn goto_incomplete_field() {
check(
r#"
struct A { a: u32 }
//^
fn foo() { A { a$0: }; }
"#,
)
}
#[test]
fn goto_proc_macro() {
check(
r#"
//- /main.rs crate:main deps:mac
use mac::fn_macro;
fn_macro$0!();
//- /mac.rs crate:mac
#![crate_type="proc-macro"]
#[proc_macro]
fn fn_macro() {}
//^^^^^^^^
"#,
)
}
#[test]
fn goto_intra_doc_links() {
check(
r#"
pub mod theitem {
/// This is the item. Cool!
pub struct TheItem;
//^^^^^^^
}
/// Gives you a [`TheItem$0`].
///
/// [`TheItem`]: theitem::TheItem
pub fn gimme() -> theitem::TheItem {
theitem::TheItem
}
"#,
);
}
#[test]
fn goto_ident_from_pat_macro() {
check(
r#"
macro_rules! pat {
($name:ident) => { Enum::Variant1($name) }
}
enum Enum {
Variant1(u8),
Variant2,
}
fn f(e: Enum) {
match e {
pat!(bind) => {
//^^^^
bind$0
}
Enum::Variant2 => {}
}
}
"#,
);
}
#[test]
fn goto_include() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include_str {}
fn main() {
let str = include_str!("foo.txt$0");
}
//- /foo.txt
// empty
//^file
"#,
);
}
#[test]
fn goto_include_has_eager_input() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include_str {}
#[rustc_builtin_macro]
macro_rules! concat {}
fn main() {
let str = include_str!(concat!("foo", ".tx$0t"));
}
//- /foo.txt
// empty
//^file
"#,
);
}
#[test]
fn goto_doc_include_str() {
check(
r#"
//- /main.rs
#[rustc_builtin_macro]
macro_rules! include_str {}
#[doc = include_str!("docs.md$0")]
struct Item;
//- /docs.md
// docs
//^file
"#,
);
}
#[test]
fn goto_shadow_include() {
check(
r#"
//- /main.rs
macro_rules! include {
("included.rs") => {}
}
include!("included.rs$0");
//- /included.rs
// empty
"#,
);
}
mod goto_impl_of_trait_fn {
use super::check;
#[test]
fn cursor_on_impl() {
check(
r#"
trait Twait {
fn a();
}
struct Stwuct;
impl Twait for Stwuct {
fn a$0();
//^
}
"#,
);
}
#[test]
fn method_call() {
check(
r#"
trait Twait {
fn a(&self);
}
struct Stwuct;
impl Twait for Stwuct {
fn a(&self){};
//^
}
fn f() {
let s = Stwuct;
s.a$0();
}
"#,
);
}
#[test]
fn method_call_inside_block() {
check(
r#"
trait Twait {
fn a(&self);
}
fn outer() {
struct Stwuct;
impl Twait for Stwuct {
fn a(&self){}
//^
}
fn f() {
let s = Stwuct;
s.a$0();
}
}
"#,
);
}
#[test]
fn path_call() {
check(
r#"
trait Twait {
fn a(&self);
}
struct Stwuct;
impl Twait for Stwuct {
fn a(&self){};
//^
}
fn f() {
let s = Stwuct;
Stwuct::a$0(&s);
}
"#,
);
}
#[test]
fn where_clause_can_work() {
check(
r#"
trait G {
fn g(&self);
}
trait Bound{}
trait EA{}
struct Gen<T>(T);
impl <T:EA> G for Gen<T> {
fn g(&self) {
}
}
impl <T> G for Gen<T>
where T : Bound
{
fn g(&self){
//^
}
}
struct A;
impl Bound for A{}
fn f() {
let gen = Gen::<A>(A);
gen.g$0();
}
"#,
);
}
#[test]
fn wc_case_is_ok() {
check(
r#"
trait G {
fn g(&self);
}
trait BParent{}
trait Bound: BParent{}
struct Gen<T>(T);
impl <T> G for Gen<T>
where T : Bound
{
fn g(&self){
//^
}
}
struct A;
impl Bound for A{}
fn f() {
let gen = Gen::<A>(A);
gen.g$0();
}
"#,
);
}
#[test]
fn method_call_defaulted() {
check(
r#"
trait Twait {
fn a(&self) {}
//^
}
struct Stwuct;
impl Twait for Stwuct {
}
fn f() {
let s = Stwuct;
s.a$0();
}
"#,
);
}
#[test]
fn method_call_on_generic() {
check(
r#"
trait Twait {
fn a(&self) {}
//^
}
fn f<T: Twait>(s: T) {
s.a$0();
}
"#,
);
}
}
#[test]
fn goto_def_of_trait_impl_const() {
check(
r#"
trait Twait {
const NOMS: bool;
// ^^^^
}
struct Stwuct;
impl Twait for Stwuct {
const NOMS$0: bool = true;
}
"#,
);
}
#[test]
fn goto_def_of_trait_impl_type_alias() {
check(
r#"
trait Twait {
type IsBad;
// ^^^^^
}
struct Stwuct;
impl Twait for Stwuct {
type IsBad$0 = !;
}
"#,
);
}
#[test]
fn goto_def_derive_input() {
check(
r#"
//- minicore:derive
#[rustc_builtin_macro]
pub macro Copy {}
// ^^^^
#[derive(Copy$0)]
struct Foo;
"#,
);
check(
r#"
//- minicore:derive
#[rustc_builtin_macro]
pub macro Copy {}
// ^^^^
#[cfg_attr(feature = "false", derive)]
#[derive(Copy$0)]
struct Foo;
"#,
);
check(
r#"
//- minicore:derive
mod foo {
#[rustc_builtin_macro]
pub macro Copy {}
// ^^^^
}
#[derive(foo::Copy$0)]
struct Foo;
"#,
);
check(
r#"
//- minicore:derive
mod foo {
// ^^^
#[rustc_builtin_macro]
pub macro Copy {}
}
#[derive(foo$0::Copy)]
struct Foo;
"#,
);
}
#[test]
fn goto_def_in_macro_multi() {
check(
r#"
struct Foo {
foo: ()
//^^^
}
macro_rules! foo {
($ident:ident) => {
fn $ident(Foo { $ident }: Foo) {}
}
}
foo!(foo$0);
//^^^
//^^^
"#,
);
check(
r#"
fn bar() {}
//^^^
struct bar;
//^^^
macro_rules! foo {
($ident:ident) => {
fn foo() {
let _: $ident = $ident;
}
}
}
foo!(bar$0);
"#,
);
}
#[test]
fn goto_await_poll() {
check(
r#"
//- minicore: future
struct MyFut;
impl core::future::Future for MyFut {
type Output = ();
fn poll(
//^^^^
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>
) -> std::task::Poll<Self::Output>
{
()
}
}
fn f() {
MyFut.await$0;
}
"#,
);
}
#[test]
fn goto_await_into_future_poll() {
check(
r#"
//- minicore: future
struct Futurable;
impl core::future::IntoFuture for Futurable {
type IntoFuture = MyFut;
}
struct MyFut;
impl core::future::Future for MyFut {
type Output = ();
fn poll(
//^^^^
self: std::pin::Pin<&mut Self>,
cx: &mut std::task::Context<'_>
) -> std::task::Poll<Self::Output>
{
()
}
}
fn f() {
Futurable.await$0;
}
"#,
);
}
#[test]
fn goto_try_op() {
check(
r#"
//- minicore: try
struct Struct;
impl core::ops::Try for Struct {
fn branch(
//^^^^^^
self
) {}
}
fn f() {
Struct?$0;
}
"#,
);
}
#[test]
fn goto_index_op() {
check(
r#"
//- minicore: index
struct Struct;
impl core::ops::Index<usize> for Struct {
fn index(
//^^^^^
self
) {}
}
fn f() {
Struct[0]$0;
}
"#,
);
}
#[test]
fn goto_index_mut_op() {
check(
r#"
//- minicore: index
struct Foo;
struct Bar;
impl core::ops::Index<usize> for Foo {
type Output = Bar;
fn index(&self, index: usize) -> &Self::Output {}
}
impl core::ops::IndexMut<usize> for Foo {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {}
//^^^^^^^^^
}
fn f() {
let mut foo = Foo;
foo[0]$0 = Bar;
}
"#,
);
}
#[test]
fn goto_prefix_op() {
check(
r#"
//- minicore: deref
struct Struct;
impl core::ops::Deref for Struct {
fn deref(
//^^^^^
self
) {}
}
fn f() {
$0*Struct;
}
"#,
);
}
#[test]
fn goto_deref_mut() {
check(
r#"
//- minicore: deref, deref_mut
struct Foo;
struct Bar;
impl core::ops::Deref for Foo {
type Target = Bar;
fn deref(&self) -> &Self::Target {}
}
impl core::ops::DerefMut for Foo {
fn deref_mut(&mut self) -> &mut Self::Target {}
//^^^^^^^^^
}
fn f() {
let a = Foo;
$0*a = Bar;
}
"#,
);
}
#[test]
fn goto_bin_op() {
check(
r#"
//- minicore: add
struct Struct;
impl core::ops::Add for Struct {
fn add(
//^^^
self
) {}
}
fn f() {
Struct +$0 Struct;
}
"#,
);
}
#[test]
fn goto_bin_op_multiple_impl() {
check(
r#"
//- minicore: add
struct S;
impl core::ops::Add for S {
fn add(
//^^^
) {}
}
impl core::ops::Add<usize> for S {
fn add(
) {}
}
fn f() {
S +$0 S
}
"#,
);
check(
r#"
//- minicore: add
struct S;
impl core::ops::Add for S {
fn add(
) {}
}
impl core::ops::Add<usize> for S {
fn add(
//^^^
) {}
}
fn f() {
S +$0 0usize
}
"#,
);
}
#[test]
fn path_call_multiple_trait_impl() {
check(
r#"
trait Trait<T> {
fn f(_: T);
}
impl Trait<i32> for usize {
fn f(_: i32) {}
//^
}
impl Trait<i64> for usize {
fn f(_: i64) {}
}
fn main() {
usize::f$0(0i32);
}
"#,
);
check(
r#"
trait Trait<T> {
fn f(_: T);
}
impl Trait<i32> for usize {
fn f(_: i32) {}
}
impl Trait<i64> for usize {
fn f(_: i64) {}
//^
}
fn main() {
usize::f$0(0i64);
}
"#,
)
}
#[test]
fn query_impls_in_nearest_block() {
check(
r#"
struct S1;
impl S1 {
fn e() -> () {}
}
fn f1() {
struct S1;
impl S1 {
fn e() -> () {}
//^
}
fn f2() {
fn f3() {
S1::e$0();
}
}
}
"#,
);
check(
r#"
struct S1;
impl S1 {
fn e() -> () {}
}
fn f1() {
struct S1;
impl S1 {
fn e() -> () {}
//^
}
fn f2() {
struct S2;
S1::e$0();
}
}
fn f12() {
struct S1;
impl S1 {
fn e() -> () {}
}
}
"#,
);
check(
r#"
struct S1;
impl S1 {
fn e() -> () {}
//^
}
fn f2() {
struct S2;
S1::e$0();
}
"#,
);
}
#[test]
fn implicit_format_args() {
check(
r#"
//- minicore: fmt
fn test() {
let a = "world";
// ^
format_args!("hello {a$0}");
}
"#,
);
}
#[test]
fn goto_macro_def_from_macro_use() {
check(
r#"
//- /main.rs crate:main deps:mac
#[macro_use(foo$0)]
extern crate mac;
//- /mac.rs crate:mac
#[macro_export]
macro_rules! foo {
//^^^
() => {};
}
"#,
);
check(
r#"
//- /main.rs crate:main deps:mac
#[macro_use(foo, bar$0, baz)]
extern crate mac;
//- /mac.rs crate:mac
#[macro_export]
macro_rules! foo {
() => {};
}
#[macro_export]
macro_rules! bar {
//^^^
() => {};
}
#[macro_export]
macro_rules! baz {
() => {};
}
"#,
);
}
#[test]
fn goto_shadowed_preludes_in_block_module() {
check(
r#"
//- /main.rs crate:main edition:2021 deps:core
pub struct S;
//^
fn main() {
fn f() -> S$0 {
fn inner() {} // forces a block def map
return S;
}
}
//- /core.rs crate:core
pub mod prelude {
pub mod rust_2021 {
pub enum S;
}
}
"#,
);
}
#[test]
fn goto_def_on_return_kw() {
check(
r#"
macro_rules! N {
($i:ident, $x:expr, $blk:expr) => {
for $i in 0..$x {
$blk
}
};
}
fn main() {
fn f() {
// ^^
N!(i, 5, {
println!("{}", i);
return$0;
});
for i in 1..5 {
return;
}
(|| {
return;
})();
}
}
"#,
)
}
#[test]
fn goto_def_on_return_kw_in_closure() {
check(
r#"
macro_rules! N {
($i:ident, $x:expr, $blk:expr) => {
for $i in 0..$x {
$blk
}
};
}
fn main() {
fn f() {
N!(i, 5, {
println!("{}", i);
return;
});
for i in 1..5 {
return;
}
(|| {
// ^
return$0;
})();
}
}
"#,
)
}
#[test]
fn goto_def_on_break_kw() {
check(
r#"
fn main() {
for i in 1..5 {
// ^^^
break$0;
}
}
"#,
)
}
#[test]
fn goto_def_on_continue_kw() {
check(
r#"
fn main() {
for i in 1..5 {
// ^^^
continue$0;
}
}
"#,
)
}
#[test]
fn goto_def_on_break_kw_for_block() {
check(
r#"
fn main() {
'a:{
// ^^^
break$0 'a;
}
}
"#,
)
}
#[test]
fn goto_def_on_break_with_label() {
check(
r#"
fn foo() {
'outer: loop {
// ^^^^
'inner: loop {
'innermost: loop {
}
break$0 'outer;
}
}
}
"#,
);
}
#[test]
fn label_inside_macro() {
check(
r#"
macro_rules! m {
($s:stmt) => { $s };
}
fn foo() {
'label: loop {
// ^^^^^^
m!(continue 'label$0);
}
}
"#,
);
}
#[test]
fn goto_def_on_return_in_try() {
check(
r#"
fn main() {
fn f() {
// ^^
try {
return$0;
}
return;
}
}
"#,
)
}
#[test]
fn goto_def_on_break_in_try() {
check(
r#"
fn main() {
for i in 1..100 {
// ^^^
let x: Result<(), ()> = try {
break$0;
};
}
}
"#,
)
}
#[test]
fn goto_def_on_return_in_async_block() {
check(
r#"
fn main() {
async {
// ^^^^^
return$0;
}
}
"#,
)
}
#[test]
fn goto_def_on_for_kw() {
check(
r#"
fn main() {
for$0 i in 1..5 {}
// ^^^
}
"#,
)
}
#[test]
fn goto_def_on_fn_kw() {
check(
r#"
fn main() {
fn$0 foo() {}
// ^^
}
"#,
)
}
#[test]
fn shadow_builtin_macro() {
check(
r#"
//- minicore: column
//- /a.rs crate:a
#[macro_export]
macro_rules! column { () => {} }
// ^^^^^^
//- /b.rs crate:b deps:a
use a::column;
fn foo() {
$0column!();
}
"#,
);
}
#[test]
fn issue_18138() {
check(
r#"
mod foo {
macro_rules! x {
() => {
pub struct Foo;
// ^^^
};
}
pub(crate) use x as m;
}
mod bar {
use crate::m;
m!();
// ^^^^^
fn qux() {
Foo$0;
}
}
mod m {}
use foo::m;
"#,
);
}
#[test]
fn macro_label_hygiene() {
check(
r#"
macro_rules! m {
($x:stmt) => {
'bar: loop { $x }
};
}
fn foo() {
'bar: loop {
// ^^^^
m!(continue 'bar$0);
}
}
"#,
);
}
}