aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-03-13 13:50:35 +0000
committerGitHub <[email protected]>2021-03-13 13:50:35 +0000
commit7accf6bc37c059a83a58c82f463f02a02ed2226f (patch)
tree979c8ebe39bde9c989cb1a09f06cea4cf7cb04ac /crates
parent9674490b45a931399a9a05cd3a4c7e8fae9ec2a8 (diff)
parent49cdb2452a225dec3834e69fb011c3d7e68084f7 (diff)
Merge #7799
7799: Related tests r=matklad a=vsrs ![tests](https://user-images.githubusercontent.com/62505555/109397453-a9013680-7947-11eb-8b11-ac03079f7645.gif) This adds an ability to look for tests for the item under the cursor: function, constant, data type, etc The LSP part is bound to change. But the feature itself already works and I'm looking for a feedback :) Co-authored-by: vsrs <[email protected]>
Diffstat (limited to 'crates')
-rw-r--r--crates/cfg/src/cfg_expr.rs2
-rw-r--r--crates/ide/src/lib.rs9
-rw-r--r--crates/ide/src/runnables.rs344
-rw-r--r--crates/ide_db/src/search.rs4
-rw-r--r--crates/rust-analyzer/src/handlers.rs30
-rw-r--r--crates/rust-analyzer/src/lsp_ext.rs13
-rw-r--r--crates/rust-analyzer/src/main_loop.rs1
-rw-r--r--crates/rust-analyzer/src/to_proto.rs5
8 files changed, 391 insertions, 17 deletions
diff --git a/crates/cfg/src/cfg_expr.rs b/crates/cfg/src/cfg_expr.rs
index 42327f1e1..069fc01d0 100644
--- a/crates/cfg/src/cfg_expr.rs
+++ b/crates/cfg/src/cfg_expr.rs
@@ -49,7 +49,7 @@ impl fmt::Display for CfgAtom {
49 } 49 }
50} 50}
51 51
52#[derive(Debug, Clone, PartialEq, Eq)] 52#[derive(Debug, Clone, PartialEq, Eq, Hash)]
53pub enum CfgExpr { 53pub enum CfgExpr {
54 Invalid, 54 Invalid,
55 Atom(CfgAtom), 55 Atom(CfgAtom),
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 0a493d2f3..a8b169e87 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -447,6 +447,15 @@ impl Analysis {
447 self.with_db(|db| runnables::runnables(db, file_id)) 447 self.with_db(|db| runnables::runnables(db, file_id))
448 } 448 }
449 449
450 /// Returns the set of tests for the given file position.
451 pub fn related_tests(
452 &self,
453 position: FilePosition,
454 search_scope: Option<SearchScope>,
455 ) -> Cancelable<Vec<Runnable>> {
456 self.with_db(|db| runnables::related_tests(db, position, search_scope))
457 }
458
450 /// Computes syntax highlighting for the given file 459 /// Computes syntax highlighting for the given file
451 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> { 460 pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HlRange>> {
452 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false)) 461 self.with_db(|db| syntax_highlighting::highlight(db, file_id, None, false))
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 280565563..faa91541e 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -1,10 +1,17 @@
1use std::fmt; 1use std::fmt;
2 2
3use ast::NameOwner;
3use cfg::CfgExpr; 4use cfg::CfgExpr;
4use hir::{AsAssocItem, HasAttrs, HasSource, Semantics}; 5use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
5use ide_assists::utils::test_related_attribute; 6use ide_assists::utils::test_related_attribute;
6use ide_db::{defs::Definition, RootDatabase, SymbolKind}; 7use ide_db::{
8 base_db::{FilePosition, FileRange},
9 defs::Definition,
10 search::SearchScope,
11 RootDatabase, SymbolKind,
12};
7use itertools::Itertools; 13use itertools::Itertools;
14use rustc_hash::FxHashSet;
8use syntax::{ 15use syntax::{
9 ast::{self, AstNode, AttrsOwner}, 16 ast::{self, AstNode, AttrsOwner},
10 match_ast, SyntaxNode, 17 match_ast, SyntaxNode,
@@ -12,17 +19,17 @@ use syntax::{
12 19
13use crate::{ 20use crate::{
14 display::{ToNav, TryToNav}, 21 display::{ToNav, TryToNav},
15 FileId, NavigationTarget, 22 references, FileId, NavigationTarget,
16}; 23};
17 24
18#[derive(Debug, Clone)] 25#[derive(Debug, Clone, Hash, PartialEq, Eq)]
19pub struct Runnable { 26pub struct Runnable {
20 pub nav: NavigationTarget, 27 pub nav: NavigationTarget,
21 pub kind: RunnableKind, 28 pub kind: RunnableKind,
22 pub cfg: Option<CfgExpr>, 29 pub cfg: Option<CfgExpr>,
23} 30}
24 31
25#[derive(Debug, Clone)] 32#[derive(Debug, Clone, Hash, PartialEq, Eq)]
26pub enum TestId { 33pub enum TestId {
27 Name(String), 34 Name(String),
28 Path(String), 35 Path(String),
@@ -37,7 +44,7 @@ impl fmt::Display for TestId {
37 } 44 }
38} 45}
39 46
40#[derive(Debug, Clone)] 47#[derive(Debug, Clone, Hash, PartialEq, Eq)]
41pub enum RunnableKind { 48pub enum RunnableKind {
42 Test { test_id: TestId, attr: TestAttr }, 49 Test { test_id: TestId, attr: TestAttr },
43 TestMod { path: String }, 50 TestMod { path: String },
@@ -105,6 +112,105 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
105 res 112 res
106} 113}
107 114
115// Feature: Related Tests
116//
117// Provides a sneak peek of all tests where the current item is used.
118//
119// The simplest way to use this feature is via the context menu:
120// - Right-click on the selected item. The context menu opens.
121// - Select **Peek related tests**
122//
123// |===
124// | Editor | Action Name
125//
126// | VS Code | **Rust Analyzer: Peek related tests**
127// |===
128pub(crate) fn related_tests(
129 db: &RootDatabase,
130 position: FilePosition,
131 search_scope: Option<SearchScope>,
132) -> Vec<Runnable> {
133 let sema = Semantics::new(db);
134 let mut res: FxHashSet<Runnable> = FxHashSet::default();
135
136 find_related_tests(&sema, position, search_scope, &mut res);
137
138 res.into_iter().collect_vec()
139}
140
141fn find_related_tests(
142 sema: &Semantics<RootDatabase>,
143 position: FilePosition,
144 search_scope: Option<SearchScope>,
145 tests: &mut FxHashSet<Runnable>,
146) {
147 if let Some(refs) = references::find_all_refs(&sema, position, search_scope) {
148 for (file_id, refs) in refs.references {
149 let file = sema.parse(file_id);
150 let file = file.syntax();
151 let functions = refs.iter().filter_map(|(range, _)| {
152 let token = file.token_at_offset(range.start()).next()?;
153 let token = sema.descend_into_macros(token);
154 let syntax = token.parent();
155 syntax.ancestors().find_map(ast::Fn::cast)
156 });
157
158 for fn_def in functions {
159 if let Some(runnable) = as_test_runnable(&sema, &fn_def) {
160 // direct test
161 tests.insert(runnable);
162 } else if let Some(module) = parent_test_module(&sema, &fn_def) {
163 // indirect test
164 find_related_tests_in_module(sema, &fn_def, &module, tests);
165 }
166 }
167 }
168 }
169}
170
171fn find_related_tests_in_module(
172 sema: &Semantics<RootDatabase>,
173 fn_def: &ast::Fn,
174 parent_module: &hir::Module,
175 tests: &mut FxHashSet<Runnable>,
176) {
177 if let Some(fn_name) = fn_def.name() {
178 let mod_source = parent_module.definition_source(sema.db);
179 let range = match mod_source.value {
180 hir::ModuleSource::Module(m) => m.syntax().text_range(),
181 hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
182 hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
183 };
184
185 let file_id = mod_source.file_id.original_file(sema.db);
186 let mod_scope = SearchScope::file_range(FileRange { file_id, range });
187 let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
188 find_related_tests(sema, fn_pos, Some(mod_scope), tests)
189 }
190}
191
192fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
193 if test_related_attribute(&fn_def).is_some() {
194 let function = sema.to_def(fn_def)?;
195 runnable_fn(sema, function)
196 } else {
197 None
198 }
199}
200
201fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
202 fn_def.syntax().ancestors().find_map(|node| {
203 let module = ast::Module::cast(node)?;
204 let module = sema.to_def(&module)?;
205
206 if has_test_function_or_multiple_test_submodules(sema, &module) {
207 Some(module)
208 } else {
209 None
210 }
211 })
212}
213
108fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) { 214fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
109 acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| { 215 acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
110 let runnable = match def { 216 let runnable = match def {
@@ -256,7 +362,7 @@ fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Op
256 Some(res) 362 Some(res)
257} 363}
258 364
259#[derive(Debug, Copy, Clone)] 365#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
260pub struct TestAttr { 366pub struct TestAttr {
261 pub ignore: bool, 367 pub ignore: bool,
262} 368}
@@ -349,6 +455,12 @@ mod tests {
349 ); 455 );
350 } 456 }
351 457
458 fn check_tests(ra_fixture: &str, expect: Expect) {
459 let (analysis, position) = fixture::position(ra_fixture);
460 let tests = analysis.related_tests(position, None).unwrap();
461 expect.assert_debug_eq(&tests);
462 }
463
352 #[test] 464 #[test]
353 fn test_runnables() { 465 fn test_runnables() {
354 check( 466 check(
@@ -1074,4 +1186,224 @@ mod tests {
1074 "#]], 1186 "#]],
1075 ); 1187 );
1076 } 1188 }
1189
1190 #[test]
1191 fn find_no_tests() {
1192 check_tests(
1193 r#"
1194//- /lib.rs
1195fn foo$0() { };
1196"#,
1197 expect![[r#"
1198 []
1199 "#]],
1200 );
1201 }
1202
1203 #[test]
1204 fn find_direct_fn_test() {
1205 check_tests(
1206 r#"
1207//- /lib.rs
1208fn foo$0() { };
1209
1210mod tests {
1211 #[test]
1212 fn foo_test() {
1213 super::foo()
1214 }
1215}
1216"#,
1217 expect![[r#"
1218 [
1219 Runnable {
1220 nav: NavigationTarget {
1221 file_id: FileId(
1222 0,
1223 ),
1224 full_range: 31..85,
1225 focus_range: 46..54,
1226 name: "foo_test",
1227 kind: Function,
1228 },
1229 kind: Test {
1230 test_id: Path(
1231 "tests::foo_test",
1232 ),
1233 attr: TestAttr {
1234 ignore: false,
1235 },
1236 },
1237 cfg: None,
1238 },
1239 ]
1240 "#]],
1241 );
1242 }
1243
1244 #[test]
1245 fn find_direct_struct_test() {
1246 check_tests(
1247 r#"
1248//- /lib.rs
1249struct Fo$0o;
1250fn foo(arg: &Foo) { };
1251
1252mod tests {
1253 use super::*;
1254
1255 #[test]
1256 fn foo_test() {
1257 foo(Foo);
1258 }
1259}
1260"#,
1261 expect![[r#"
1262 [
1263 Runnable {
1264 nav: NavigationTarget {
1265 file_id: FileId(
1266 0,
1267 ),
1268 full_range: 71..122,
1269 focus_range: 86..94,
1270 name: "foo_test",
1271 kind: Function,
1272 },
1273 kind: Test {
1274 test_id: Path(
1275 "tests::foo_test",
1276 ),
1277 attr: TestAttr {
1278 ignore: false,
1279 },
1280 },
1281 cfg: None,
1282 },
1283 ]
1284 "#]],
1285 );
1286 }
1287
1288 #[test]
1289 fn find_indirect_fn_test() {
1290 check_tests(
1291 r#"
1292//- /lib.rs
1293fn foo$0() { };
1294
1295mod tests {
1296 use super::foo;
1297
1298 fn check1() {
1299 check2()
1300 }
1301
1302 fn check2() {
1303 foo()
1304 }
1305
1306 #[test]
1307 fn foo_test() {
1308 check1()
1309 }
1310}
1311"#,
1312 expect![[r#"
1313 [
1314 Runnable {
1315 nav: NavigationTarget {
1316 file_id: FileId(
1317 0,
1318 ),
1319 full_range: 133..183,
1320 focus_range: 148..156,
1321 name: "foo_test",
1322 kind: Function,
1323 },
1324 kind: Test {
1325 test_id: Path(
1326 "tests::foo_test",
1327 ),
1328 attr: TestAttr {
1329 ignore: false,
1330 },
1331 },
1332 cfg: None,
1333 },
1334 ]
1335 "#]],
1336 );
1337 }
1338
1339 #[test]
1340 fn tests_are_unique() {
1341 check_tests(
1342 r#"
1343//- /lib.rs
1344fn foo$0() { };
1345
1346mod tests {
1347 use super::foo;
1348
1349 #[test]
1350 fn foo_test() {
1351 foo();
1352 foo();
1353 }
1354
1355 #[test]
1356 fn foo2_test() {
1357 foo();
1358 foo();
1359 }
1360
1361}
1362"#,
1363 expect![[r#"
1364 [
1365 Runnable {
1366 nav: NavigationTarget {
1367 file_id: FileId(
1368 0,
1369 ),
1370 full_range: 52..115,
1371 focus_range: 67..75,
1372 name: "foo_test",
1373 kind: Function,
1374 },
1375 kind: Test {
1376 test_id: Path(
1377 "tests::foo_test",
1378 ),
1379 attr: TestAttr {
1380 ignore: false,
1381 },
1382 },
1383 cfg: None,
1384 },
1385 Runnable {
1386 nav: NavigationTarget {
1387 file_id: FileId(
1388 0,
1389 ),
1390 full_range: 121..185,
1391 focus_range: 136..145,
1392 name: "foo2_test",
1393 kind: Function,
1394 },
1395 kind: Test {
1396 test_id: Path(
1397 "tests::foo2_test",
1398 ),
1399 attr: TestAttr {
1400 ignore: false,
1401 },
1402 },
1403 cfg: None,
1404 },
1405 ]
1406 "#]],
1407 );
1408 }
1077} 1409}
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index ddcfbd3f3..fa18703e1 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -86,6 +86,10 @@ impl SearchScope {
86 SearchScope::new(std::iter::once((file, None)).collect()) 86 SearchScope::new(std::iter::once((file, None)).collect())
87 } 87 }
88 88
89 pub fn file_range(range: FileRange) -> SearchScope {
90 SearchScope::new(std::iter::once((range.file_id, Some(range.range))).collect())
91 }
92
89 pub fn files(files: &[FileId]) -> SearchScope { 93 pub fn files(files: &[FileId]) -> SearchScope {
90 SearchScope::new(files.iter().map(|f| (*f, None)).collect()) 94 SearchScope::new(files.iter().map(|f| (*f, None)).collect())
91 } 95 }
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs
index 6cc433cb8..706a39dab 100644
--- a/crates/rust-analyzer/src/handlers.rs
+++ b/crates/rust-analyzer/src/handlers.rs
@@ -555,7 +555,7 @@ pub(crate) fn handle_runnables(
555 if should_skip_target(&runnable, cargo_spec.as_ref()) { 555 if should_skip_target(&runnable, cargo_spec.as_ref()) {
556 continue; 556 continue;
557 } 557 }
558 let mut runnable = to_proto::runnable(&snap, file_id, runnable)?; 558 let mut runnable = to_proto::runnable(&snap, runnable)?;
559 if expect_test { 559 if expect_test {
560 runnable.label = format!("{} + expect", runnable.label); 560 runnable.label = format!("{} + expect", runnable.label);
561 runnable.args.expect_test = Some(true); 561 runnable.args.expect_test = Some(true);
@@ -607,6 +607,24 @@ pub(crate) fn handle_runnables(
607 Ok(res) 607 Ok(res)
608} 608}
609 609
610pub(crate) fn handle_related_tests(
611 snap: GlobalStateSnapshot,
612 params: lsp_types::TextDocumentPositionParams,
613) -> Result<Vec<lsp_ext::TestInfo>> {
614 let _p = profile::span("handle_related_tests");
615 let position = from_proto::file_position(&snap, params)?;
616
617 let tests = snap.analysis.related_tests(position, None)?;
618 let mut res = Vec::new();
619 for it in tests {
620 if let Ok(runnable) = to_proto::runnable(&snap, it) {
621 res.push(lsp_ext::TestInfo { runnable })
622 }
623 }
624
625 Ok(res)
626}
627
610pub(crate) fn handle_completion( 628pub(crate) fn handle_completion(
611 snap: GlobalStateSnapshot, 629 snap: GlobalStateSnapshot,
612 params: lsp_types::CompletionParams, 630 params: lsp_types::CompletionParams,
@@ -772,7 +790,7 @@ pub(crate) fn handle_hover(
772 contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)), 790 contents: HoverContents::Markup(to_proto::markup_content(info.info.markup)),
773 range: Some(range), 791 range: Some(range),
774 }, 792 },
775 actions: prepare_hover_actions(&snap, position.file_id, &info.info.actions), 793 actions: prepare_hover_actions(&snap, &info.info.actions),
776 }; 794 };
777 795
778 Ok(Some(hover)) 796 Ok(Some(hover))
@@ -1440,17 +1458,16 @@ fn show_impl_command_link(
1440 1458
1441fn runnable_action_links( 1459fn runnable_action_links(
1442 snap: &GlobalStateSnapshot, 1460 snap: &GlobalStateSnapshot,
1443 file_id: FileId,
1444 runnable: Runnable, 1461 runnable: Runnable,
1445) -> Option<lsp_ext::CommandLinkGroup> { 1462) -> Option<lsp_ext::CommandLinkGroup> {
1446 let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; 1463 let cargo_spec = CargoTargetSpec::for_file(&snap, runnable.nav.file_id).ok()?;
1447 let hover_config = snap.config.hover(); 1464 let hover_config = snap.config.hover();
1448 if !hover_config.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) { 1465 if !hover_config.runnable() || should_skip_target(&runnable, cargo_spec.as_ref()) {
1449 return None; 1466 return None;
1450 } 1467 }
1451 1468
1452 let action: &'static _ = runnable.action(); 1469 let action: &'static _ = runnable.action();
1453 to_proto::runnable(snap, file_id, runnable).ok().map(|r| { 1470 to_proto::runnable(snap, runnable).ok().map(|r| {
1454 let mut group = lsp_ext::CommandLinkGroup::default(); 1471 let mut group = lsp_ext::CommandLinkGroup::default();
1455 1472
1456 if hover_config.run { 1473 if hover_config.run {
@@ -1489,7 +1506,6 @@ fn goto_type_action_links(
1489 1506
1490fn prepare_hover_actions( 1507fn prepare_hover_actions(
1491 snap: &GlobalStateSnapshot, 1508 snap: &GlobalStateSnapshot,
1492 file_id: FileId,
1493 actions: &[HoverAction], 1509 actions: &[HoverAction],
1494) -> Vec<lsp_ext::CommandLinkGroup> { 1510) -> Vec<lsp_ext::CommandLinkGroup> {
1495 if snap.config.hover().none() || !snap.config.hover_actions() { 1511 if snap.config.hover().none() || !snap.config.hover_actions() {
@@ -1500,7 +1516,7 @@ fn prepare_hover_actions(
1500 .iter() 1516 .iter()
1501 .filter_map(|it| match it { 1517 .filter_map(|it| match it {
1502 HoverAction::Implementation(position) => show_impl_command_link(snap, position), 1518 HoverAction::Implementation(position) => show_impl_command_link(snap, position),
1503 HoverAction::Runnable(r) => runnable_action_links(snap, file_id, r.clone()), 1519 HoverAction::Runnable(r) => runnable_action_links(snap, r.clone()),
1504 HoverAction::GoToType(targets) => goto_type_action_links(snap, targets), 1520 HoverAction::GoToType(targets) => goto_type_action_links(snap, targets),
1505 }) 1521 })
1506 .collect() 1522 .collect()
diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs
index 0d2c8f7ff..efcdcd1d9 100644
--- a/crates/rust-analyzer/src/lsp_ext.rs
+++ b/crates/rust-analyzer/src/lsp_ext.rs
@@ -177,6 +177,19 @@ pub struct CargoRunnable {
177 pub expect_test: Option<bool>, 177 pub expect_test: Option<bool>,
178} 178}
179 179
180pub enum RelatedTests {}
181
182impl Request for RelatedTests {
183 type Params = lsp_types::TextDocumentPositionParams;
184 type Result = Vec<TestInfo>;
185 const METHOD: &'static str = "rust-analyzer/relatedTests";
186}
187
188#[derive(Debug, Deserialize, Serialize)]
189pub struct TestInfo {
190 pub runnable: Runnable,
191}
192
180pub enum InlayHints {} 193pub enum InlayHints {}
181 194
182impl Request for InlayHints { 195impl Request for InlayHints {
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs
index f0cb309e4..984790d35 100644
--- a/crates/rust-analyzer/src/main_loop.rs
+++ b/crates/rust-analyzer/src/main_loop.rs
@@ -500,6 +500,7 @@ impl GlobalState {
500 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro) 500 .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)
501 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module) 501 .on::<lsp_ext::ParentModule>(handlers::handle_parent_module)
502 .on::<lsp_ext::Runnables>(handlers::handle_runnables) 502 .on::<lsp_ext::Runnables>(handlers::handle_runnables)
503 .on::<lsp_ext::RelatedTests>(handlers::handle_related_tests)
503 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints) 504 .on::<lsp_ext::InlayHints>(handlers::handle_inlay_hints)
504 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action) 505 .on::<lsp_ext::CodeActionRequest>(handlers::handle_code_action)
505 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve) 506 .on::<lsp_ext::CodeActionResolveRequest>(handlers::handle_code_action_resolve)
diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs
index 1a8cdadad..9ca0915b9 100644
--- a/crates/rust-analyzer/src/to_proto.rs
+++ b/crates/rust-analyzer/src/to_proto.rs
@@ -838,11 +838,10 @@ pub(crate) fn resolved_code_action(
838 838
839pub(crate) fn runnable( 839pub(crate) fn runnable(
840 snap: &GlobalStateSnapshot, 840 snap: &GlobalStateSnapshot,
841 file_id: FileId,
842 runnable: Runnable, 841 runnable: Runnable,
843) -> Result<lsp_ext::Runnable> { 842) -> Result<lsp_ext::Runnable> {
844 let config = snap.config.runnables(); 843 let config = snap.config.runnables();
845 let spec = CargoTargetSpec::for_file(snap, file_id)?; 844 let spec = CargoTargetSpec::for_file(snap, runnable.nav.file_id)?;
846 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone()); 845 let workspace_root = spec.as_ref().map(|it| it.workspace_root.clone());
847 let target = spec.as_ref().map(|s| s.target.clone()); 846 let target = spec.as_ref().map(|s| s.target.clone());
848 let (cargo_args, executable_args) = 847 let (cargo_args, executable_args) =
@@ -875,7 +874,7 @@ pub(crate) fn code_lens(
875 let annotation_range = range(&line_index, annotation.range); 874 let annotation_range = range(&line_index, annotation.range);
876 875
877 let action = run.action(); 876 let action = run.action();
878 let r = runnable(&snap, run.nav.file_id, run)?; 877 let r = runnable(&snap, run)?;
879 878
880 let command = if debug { 879 let command = if debug {
881 command::debug_single(&r) 880 command::debug_single(&r)