diff --git a/.gitignore b/.gitignore index dab51647db70..aef0fac3397f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ crates/*/target *.iml .vscode/settings.json *.html +generated_assists.adoc +generated_features.adoc diff --git a/Cargo.lock b/Cargo.lock index 9981a2e331a3..308e36836fa8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,8 +113,9 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "chalk-derive" -version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9bd01eab87277d973183a1d2e56bace1c11f8242c52c20636fb7dddf343ac9" dependencies = [ "proc-macro2", "quote", @@ -124,8 +125,9 @@ dependencies = [ [[package]] name = "chalk-engine" -version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c7a637c3d17ed555aef16e16952a5d1e127bd55178cc30be22afeb92da90c7d" dependencies = [ "chalk-derive", "chalk-ir", @@ -134,8 +136,9 @@ dependencies = [ [[package]] name = "chalk-ir" -version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595e5735ded16c3f3dc348f7b15bbb2521a0080b1863cac38ad5271589944670" dependencies = [ "chalk-derive", "lazy_static", @@ -143,8 +146,9 @@ dependencies = [ [[package]] name = "chalk-solve" -version = "0.10.1-dev" -source = "git+https://github.com/rust-lang/chalk.git?rev=329b7f3fdd2431ed6f6778cde53f22374c7d094c#329b7f3fdd2431ed6f6778cde53f22374c7d094c" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d9d938139db425867a30cc0cfec0269406d8238d0571d829041eaa7a8455d11" dependencies = [ "chalk-derive", "chalk-engine", @@ -243,12 +247,13 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab6bffe714b6bb07e42f201352c34f51fefd355ace793f9e638ebd52d23f98d2" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" dependencies = [ "cfg-if", "crossbeam-utils", + "maybe-uninit", ] [[package]] @@ -462,18 +467,18 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.3.2" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" +checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe" dependencies = [ "autocfg", ] [[package]] name = "inotify" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24e40d6fd5d64e2082e0c796495c8ef5ad667a96d03e5aaa0becfd9d47bcbfb8" +checksum = "4816c66d2c8ae673df83366c18341538f234a26d65a9ecea5c348b453ac1d02f" dependencies = [ "bitflags", "inotify-sys", @@ -561,9 +566,9 @@ dependencies = [ [[package]] name = "jod-thread" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4022656272c3e564a7cdebcaaba6518d844b0d0c1836597196efb5bfeb98bb49" +checksum = "8b23360e99b8717f20aaa4598f5a6541efbe30630039fbc7706cf954a87947ae" [[package]] name = "kernel32-sys" @@ -809,9 +814,9 @@ dependencies = [ [[package]] name = "paste" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53181dcd37421c08d3b69f887784956674d09c3f9a47a04fece2b130a5b346b" +checksum = "d508492eeb1e5c38ee696371bf7b9fc33c83d46a7d451606b96458fbbbdc2dec" dependencies = [ "paste-impl", "proc-macro-hack", @@ -819,9 +824,9 @@ dependencies = [ [[package]] name = "paste-impl" -version = "0.1.15" +version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ca490fa1c034a71412b4d1edcb904ec5a0981a4426c9eb2128c0fda7a68d17" +checksum = "84f328a6a63192b333fce5fbb4be79db6758a4d518dfac6d54412f1492f72d32" dependencies = [ "proc-macro-hack", "proc-macro2", @@ -871,18 +876,18 @@ checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4" [[package]] name = "proc-macro2" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1502d12e458c49a4c9cbff560d0fe0060c252bc29799ed94ca2ed4bb665a0101" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" dependencies = [ "unicode-xid", ] [[package]] name = "quote" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" dependencies = [ "proc-macro2", ] @@ -977,7 +982,10 @@ dependencies = [ "anymap", "drop_bomb", "either", + "fst", + "indexmap", "insta", + "itertools", "log", "once_cell", "ra_arena", @@ -1006,6 +1014,7 @@ dependencies = [ "ra_prof", "ra_syntax", "ra_tt", + "rustc-hash", "test_utils", ] @@ -1119,6 +1128,7 @@ dependencies = [ "memmap", "ra_mbe", "ra_proc_macro", + "ra_toolchain", "ra_tt", "serde_derive", "test_utils", @@ -1400,9 +1410,9 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "ryu" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d612bc64430efeb3f7ee6ef26d590dce0c43249217bddc62112540c7941e1" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "salsa" @@ -1511,9 +1521,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.53" +version = "1.0.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226" dependencies = [ "itoa", "ryu", @@ -1533,9 +1543,9 @@ dependencies = [ [[package]] name = "serde_yaml" -version = "0.8.12" +version = "0.8.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16c7a592a1ec97c9c1c68d75b6e537dcbf60c7618e038e7841e00af1d9ccf0c4" +checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" dependencies = [ "dtoa", "linked-hash-map", @@ -1576,9 +1586,9 @@ checksum = "ab16ced94dbd8a46c82fd81e3ed9a8727dac2977ea869d217bcc4ea1f122e81f" [[package]] name = "syn" -version = "1.0.29" +version = "1.0.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb37da98a55b1d08529362d9cbb863be17556873df2585904ab9d2bc951291d0" +checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6" dependencies = [ "proc-macro2", "quote", @@ -1587,9 +1597,9 @@ dependencies = [ [[package]] name = "synstructure" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" dependencies = [ "proc-macro2", "quote", @@ -1639,6 +1649,7 @@ dependencies = [ "relative-path", "rustc-hash", "serde_json", + "stdx", "text-size", ] @@ -1797,9 +1808,9 @@ dependencies = [ [[package]] name = "yaml-rust" -version = "0.4.3" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65923dd1784f44da1d2c3dbbc5e822045628c590ba72123e1c73d3c230c4434d" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" dependencies = [ "linked-hash-map", ] diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index 5b1a4680b439..edd8255f44a1 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs @@ -1,5 +1,7 @@ //! See `AssistContext` +use std::mem; + use algo::find_covering_element; use hir::Semantics; use ra_db::{FileId, FileRange}; @@ -170,13 +172,32 @@ impl Assists { pub(crate) struct AssistBuilder { edit: TextEditBuilder, - file: FileId, + file_id: FileId, is_snippet: bool, + edits: Vec, } impl AssistBuilder { - pub(crate) fn new(file: FileId) -> AssistBuilder { - AssistBuilder { edit: TextEditBuilder::default(), file, is_snippet: false } + pub(crate) fn new(file_id: FileId) -> AssistBuilder { + AssistBuilder { + edit: TextEditBuilder::default(), + file_id, + is_snippet: false, + edits: Vec::new(), + } + } + + pub(crate) fn edit_file(&mut self, file_id: FileId) { + self.file_id = file_id; + } + + fn commit(&mut self) { + let edit = mem::take(&mut self.edit).finish(); + if !edit.is_empty() { + let new_edit = SourceFileEdit { file_id: self.file_id, edit }; + assert!(!self.edits.iter().any(|it| it.file_id == new_edit.file_id)); + self.edits.push(new_edit); + } } /// Remove specified `range` of text. @@ -234,21 +255,15 @@ impl AssistBuilder { algo::diff(&node, &new).into_text_edit(&mut self.edit) } - // FIXME: better API - pub(crate) fn set_file(&mut self, assist_file: FileId) { - self.file = assist_file; - } - // FIXME: kill this API /// Get access to the raw `TextEditBuilder`. pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder { &mut self.edit } - fn finish(self) -> SourceChange { - let edit = self.edit.finish(); - let source_file_edit = SourceFileEdit { file_id: self.file, edit }; - let mut res: SourceChange = source_file_edit.into(); + fn finish(mut self) -> SourceChange { + self.commit(); + let mut res: SourceChange = mem::take(&mut self.edits).into(); if self.is_snippet { res.is_snippet = true; } diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs index ab20c66493c1..90b06a62595c 100644 --- a/crates/ra_assists/src/handlers/add_explicit_type.rs +++ b/crates/ra_assists/src/handlers/add_explicit_type.rs @@ -195,7 +195,7 @@ struct Test { } fn main() { - let test<|> = Test { t: 23, k: 33 }; + let test<|> = Test { t: 23u8, k: 33 }; }"#, r#" struct Test { @@ -204,7 +204,7 @@ struct Test { } fn main() { - let test: Test = Test { t: 23, k: 33 }; + let test: Test = Test { t: 23u8, k: 33 }; }"#, ); } diff --git a/crates/ra_assists/src/handlers/add_function.rs b/crates/ra_assists/src/handlers/add_function.rs index 24f931a85e19..1cfbd75aa11c 100644 --- a/crates/ra_assists/src/handlers/add_function.rs +++ b/crates/ra_assists/src/handlers/add_function.rs @@ -64,7 +64,7 @@ pub(crate) fn add_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> let target = call.syntax().text_range(); acc.add(AssistId("add_function"), "Add function", target, |builder| { let function_template = function_builder.render(); - builder.set_file(function_template.file); + builder.edit_file(function_template.file); let new_fn = function_template.to_string(ctx.config.snippet_cap); match ctx.config.snippet_cap { Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn), diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs index edf96d50ec14..5092bf336671 100644 --- a/crates/ra_assists/src/handlers/auto_import.rs +++ b/crates/ra_assists/src/handlers/auto_import.rs @@ -130,7 +130,7 @@ impl AutoImportAssets { fn search_for_imports(&self, db: &RootDatabase) -> BTreeSet { let _p = profile("auto_import::search_for_imports"); let current_crate = self.module_with_name_to_import.krate(); - ImportsLocator::new(db) + ImportsLocator::new(db, current_crate) .find_imports(&self.get_search_query()) .into_iter() .filter_map(|candidate| match &self.import_candidate { @@ -841,4 +841,105 @@ fn main() { ", ) } + + #[test] + fn dep_import() { + check_assist( + auto_import, + r" + //- /lib.rs crate:dep + pub struct Struct; + + //- /main.rs crate:main deps:dep + fn main() { + Struct<|> + }", + r"use dep::Struct; + +fn main() { + Struct +} +", + ); + } + + #[test] + fn whole_segment() { + // Tests that only imports whose last segment matches the identifier get suggested. + check_assist( + auto_import, + r" + //- /lib.rs crate:dep + pub mod fmt { + pub trait Display {} + } + + pub fn panic_fmt() {} + + //- /main.rs crate:main deps:dep + struct S; + + impl f<|>mt::Display for S {}", + r"use dep::fmt; + +struct S; +impl fmt::Display for S {} +", + ); + } + + #[test] + fn macro_generated() { + // Tests that macro-generated items are suggested from external crates. + check_assist( + auto_import, + r" + //- /lib.rs crate:dep + + macro_rules! mac { + () => { + pub struct Cheese; + }; + } + + mac!(); + + //- /main.rs crate:main deps:dep + + fn main() { + Cheese<|>; + }", + r"use dep::Cheese; + +fn main() { + Cheese; +} +", + ); + } + + #[test] + fn casing() { + // Tests that differently cased names don't interfere and we only suggest the matching one. + check_assist( + auto_import, + r" + //- /lib.rs crate:dep + + pub struct FMT; + pub struct fmt; + + //- /main.rs crate:main deps:dep + + fn main() { + FMT<|>; + }", + r"use dep::FMT; + +fn main() { + FMT; +} +", + ); + } } diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs index 4cc75a7ce2dc..dfade743215c 100644 --- a/crates/ra_assists/src/handlers/early_return.rs +++ b/crates/ra_assists/src/handlers/early_return.rs @@ -154,7 +154,7 @@ pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) parent_block: &ast::BlockExpr, if_expr: &ast::IfExpr, ) -> SyntaxNode { - let then_block_items = then_block.dedent(IndentLevel::from(1)); + let then_block_items = then_block.dedent(IndentLevel(1)); let end_of_then = then_block_items.syntax().last_child_or_token().unwrap(); let end_of_then = if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) { diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs new file mode 100644 index 000000000000..44db7917a60e --- /dev/null +++ b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs @@ -0,0 +1,320 @@ +use hir::{EnumVariant, Module, ModuleDef, Name}; +use ra_db::FileId; +use ra_fmt::leading_indent; +use ra_ide_db::{defs::Definition, search::Reference, RootDatabase}; +use ra_syntax::{ + algo::find_node_at_offset, + ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner}, + SourceFile, SyntaxNode, TextRange, TextSize, +}; +use rustc_hash::FxHashSet; + +use crate::{ + assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId, Assists, +}; + +// Assist: extract_struct_from_enum_variant +// +// Extracts a struct from enum variant. +// +// ``` +// enum A { <|>One(u32, u32) } +// ``` +// -> +// ``` +// struct One(pub u32, pub u32); +// +// enum A { One(One) } +// ``` +pub(crate) fn extract_struct_from_enum_variant( + acc: &mut Assists, + ctx: &AssistContext, +) -> Option<()> { + let variant = ctx.find_node_at_offset::()?; + let field_list = match variant.kind() { + ast::StructKind::Tuple(field_list) => field_list, + _ => return None, + }; + let variant_name = variant.name()?.to_string(); + let variant_hir = ctx.sema.to_def(&variant)?; + if existing_struct_def(ctx.db, &variant_name, &variant_hir) { + return None; + } + let enum_ast = variant.parent_enum(); + let visibility = enum_ast.visibility(); + let enum_hir = ctx.sema.to_def(&enum_ast)?; + let variant_hir_name = variant_hir.name(ctx.db); + let enum_module_def = ModuleDef::from(enum_hir); + let current_module = enum_hir.module(ctx.db); + let target = variant.syntax().text_range(); + acc.add( + AssistId("extract_struct_from_enum_variant"), + "Extract struct from enum variant", + target, + |builder| { + let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir)); + let res = definition.find_usages(&ctx.db, None); + let start_offset = variant.parent_enum().syntax().text_range().start(); + let mut visited_modules_set = FxHashSet::default(); + visited_modules_set.insert(current_module); + for reference in res { + let source_file = ctx.sema.parse(reference.file_range.file_id); + update_reference( + ctx, + builder, + reference, + &source_file, + &enum_module_def, + &variant_hir_name, + &mut visited_modules_set, + ); + } + extract_struct_def( + builder, + enum_ast.syntax(), + &variant_name, + &field_list.to_string(), + start_offset, + ctx.frange.file_id, + &visibility, + ); + let list_range = field_list.syntax().text_range(); + update_variant(builder, &variant_name, ctx.frange.file_id, list_range); + }, + ) +} + +fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool { + variant + .parent_enum(db) + .module(db) + .scope(db, None) + .into_iter() + .any(|(name, _)| name.to_string() == variant_name.to_string()) +} + +fn insert_import( + ctx: &AssistContext, + builder: &mut AssistBuilder, + path: &ast::PathExpr, + module: &Module, + enum_module_def: &ModuleDef, + variant_hir_name: &Name, +) -> Option<()> { + let db = ctx.db; + let mod_path = module.find_use_path(db, enum_module_def.clone()); + if let Some(mut mod_path) = mod_path { + mod_path.segments.pop(); + mod_path.segments.push(variant_hir_name.clone()); + insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder()); + } + Some(()) +} + +fn extract_struct_def( + builder: &mut AssistBuilder, + enum_ast: &SyntaxNode, + variant_name: &str, + variant_list: &str, + start_offset: TextSize, + file_id: FileId, + visibility: &Option, +) -> Option<()> { + let visibility_string = if let Some(visibility) = visibility { + format!("{} ", visibility.to_string()) + } else { + "".to_string() + }; + let indent = if let Some(indent) = leading_indent(enum_ast) { + indent.to_string() + } else { + "".to_string() + }; + let struct_def = format!( + r#"{}struct {}{}; + +{}"#, + visibility_string, + variant_name, + list_with_visibility(variant_list), + indent + ); + builder.edit_file(file_id); + builder.insert(start_offset, struct_def); + Some(()) +} + +fn update_variant( + builder: &mut AssistBuilder, + variant_name: &str, + file_id: FileId, + list_range: TextRange, +) -> Option<()> { + let inside_variant_range = TextRange::new( + list_range.start().checked_add(TextSize::from(1))?, + list_range.end().checked_sub(TextSize::from(1))?, + ); + builder.edit_file(file_id); + builder.replace(inside_variant_range, variant_name); + Some(()) +} + +fn update_reference( + ctx: &AssistContext, + builder: &mut AssistBuilder, + reference: Reference, + source_file: &SourceFile, + enum_module_def: &ModuleDef, + variant_hir_name: &Name, + visited_modules_set: &mut FxHashSet, +) -> Option<()> { + let path_expr: ast::PathExpr = find_node_at_offset::( + source_file.syntax(), + reference.file_range.range.start(), + )?; + let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?; + let list = call.arg_list()?; + let segment = path_expr.path()?.segment()?; + let module = ctx.sema.scope(&path_expr.syntax()).module()?; + let list_range = list.syntax().text_range(); + let inside_list_range = TextRange::new( + list_range.start().checked_add(TextSize::from(1))?, + list_range.end().checked_sub(TextSize::from(1))?, + ); + builder.edit_file(reference.file_range.file_id); + if !visited_modules_set.contains(&module) { + if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name) + .is_some() + { + visited_modules_set.insert(module); + } + } + builder.replace(inside_list_range, format!("{}{}", segment, list)); + Some(()) +} + +fn list_with_visibility(list: &str) -> String { + list.split(',') + .map(|part| { + let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 }; + let mut mod_part = part.trim().to_string(); + mod_part.insert_str(index, "pub "); + mod_part + }) + .collect::>() + .join(", ") +} + +#[cfg(test)] +mod tests { + + use crate::{ + tests::{check_assist, check_assist_not_applicable}, + utils::FamousDefs, + }; + + use super::*; + + #[test] + fn test_extract_struct_several_fields() { + check_assist( + extract_struct_from_enum_variant, + "enum A { <|>One(u32, u32) }", + r#"struct One(pub u32, pub u32); + +enum A { One(One) }"#, + ); + } + + #[test] + fn test_extract_struct_one_field() { + check_assist( + extract_struct_from_enum_variant, + "enum A { <|>One(u32) }", + r#"struct One(pub u32); + +enum A { One(One) }"#, + ); + } + + #[test] + fn test_extract_struct_pub_visibility() { + check_assist( + extract_struct_from_enum_variant, + "pub enum A { <|>One(u32, u32) }", + r#"pub struct One(pub u32, pub u32); + +pub enum A { One(One) }"#, + ); + } + + #[test] + fn test_extract_struct_with_complex_imports() { + check_assist( + extract_struct_from_enum_variant, + r#"mod my_mod { + fn another_fn() { + let m = my_other_mod::MyEnum::MyField(1, 1); + } + + pub mod my_other_mod { + fn another_fn() { + let m = MyEnum::MyField(1, 1); + } + + pub enum MyEnum { + <|>MyField(u8, u8), + } + } +} + +fn another_fn() { + let m = my_mod::my_other_mod::MyEnum::MyField(1, 1); +}"#, + r#"use my_mod::my_other_mod::MyField; + +mod my_mod { + use my_other_mod::MyField; + + fn another_fn() { + let m = my_other_mod::MyEnum::MyField(MyField(1, 1)); + } + + pub mod my_other_mod { + fn another_fn() { + let m = MyEnum::MyField(MyField(1, 1)); + } + + pub struct MyField(pub u8, pub u8); + + pub enum MyEnum { + MyField(MyField), + } + } +} + +fn another_fn() { + let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1)); +}"#, + ); + } + + fn check_not_applicable(ra_fixture: &str) { + let fixture = + format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE); + check_assist_not_applicable(extract_struct_from_enum_variant, &fixture) + } + + #[test] + fn test_extract_enum_not_applicable_for_element_with_no_fields() { + check_not_applicable("enum A { <|>One }"); + } + + #[test] + fn test_extract_enum_not_applicable_if_struct_exists() { + check_not_applicable( + r#"struct One; + enum A { <|>One(u8) }"#, + ); + } +} diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs index 9ec42f568c59..531b3560f2e4 100644 --- a/crates/ra_assists/src/handlers/fix_visibility.rs +++ b/crates/ra_assists/src/handlers/fix_visibility.rs @@ -63,7 +63,7 @@ fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> O }; acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { - builder.set_file(target_file); + builder.edit_file(target_file); match ctx.config.snippet_cap { Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), None => builder.insert(offset, format!("{} ", missing_visibility)), @@ -106,7 +106,7 @@ fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility); acc.add(AssistId("fix_visibility"), assist_label, target, |builder| { - builder.set_file(target_file); + builder.edit_file(target_file); match ctx.config.snippet_cap { Some(cap) => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)), None => builder.insert(offset, format!("{} ", missing_visibility)), diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs index beb5b7366d90..28fcbc9ba62e 100644 --- a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs +++ b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs @@ -41,8 +41,6 @@ pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) - if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::FnDef::cast) { generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range()) } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::ImplDef::cast) { - // only allow naming the last anonymous lifetime - lifetime_token.next_token().filter(|tok| tok.kind() == SyntaxKind::R_ANGLE)?; generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range()) } else { None @@ -190,6 +188,23 @@ mod tests { ); } + #[test] + fn test_impl_with_other_type_param() { + check_assist( + introduce_named_lifetime, + "impl fmt::Display for SepByBuilder<'_<|>, I> + where + I: Iterator, + I::Item: fmt::Display, + {", + "impl fmt::Display for SepByBuilder<'a, I> + where + I: Iterator, + I::Item: fmt::Display, + {", + ) + } + #[test] fn test_example_case_cursor_before_tick() { check_assist( diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs index e016f51c3eb4..dfcd787de585 100644 --- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs @@ -51,6 +51,7 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) acc.add(AssistId("replace_if_let_with_match"), "Replace with match", target, move |edit| { let match_expr = { let then_arm = { + let then_block = then_block.reset_indent().indent(IndentLevel(1)); let then_expr = unwrap_trivial_block(then_block); make::match_arm(vec![pat.clone()], then_expr) }; @@ -64,8 +65,8 @@ pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) let else_expr = unwrap_trivial_block(else_block); make::match_arm(vec![pattern], else_expr) }; - make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) - .indent(IndentLevel::from_node(if_expr.syntax())) + let match_expr = make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])); + match_expr.indent(IndentLevel::from_node(if_expr.syntax())) }; edit.replace_ast::(if_expr.into(), match_expr); @@ -213,4 +214,36 @@ fn foo(x: Result) { "#, ); } + + #[test] + fn nested_indent() { + check_assist( + replace_if_let_with_match, + r#" +fn main() { + if true { + <|>if let Ok(rel_path) = path.strip_prefix(root_path) { + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) + } else { + None + } + } +} +"#, + r#" +fn main() { + if true { + match path.strip_prefix(root_path) { + Ok(rel_path) => { + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) + } + _ => None, + } + } +} +"#, + ) + } } diff --git a/crates/ra_assists/src/handlers/unwrap_block.rs b/crates/ra_assists/src/handlers/unwrap_block.rs index 8440c7d0f43e..1fb13f481419 100644 --- a/crates/ra_assists/src/handlers/unwrap_block.rs +++ b/crates/ra_assists/src/handlers/unwrap_block.rs @@ -1,7 +1,10 @@ use ra_fmt::unwrap_trivial_block; use ra_syntax::{ - ast::{self, ElseBranch, Expr, LoopBodyOwner}, - match_ast, AstNode, TextRange, T, + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + }, + AstNode, TextRange, T, }; use crate::{AssistContext, AssistId, Assists}; @@ -24,94 +27,73 @@ use crate::{AssistContext, AssistId, Assists}; // } // ``` pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { - let l_curly_token = ctx.find_token_at_offset(T!['{'])?; - let block = ast::BlockExpr::cast(l_curly_token.parent())?; - let parent = block.syntax().parent()?; let assist_id = AssistId("unwrap_block"); let assist_label = "Unwrap block"; - let (expr, expr_to_unwrap) = match_ast! { - match parent { - ast::ForExpr(for_expr) => { - let block_expr = for_expr.loop_body()?; - let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; - (ast::Expr::ForExpr(for_expr), expr_to_unwrap) - }, - ast::WhileExpr(while_expr) => { - let block_expr = while_expr.loop_body()?; - let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; - (ast::Expr::WhileExpr(while_expr), expr_to_unwrap) - }, - ast::LoopExpr(loop_expr) => { - let block_expr = loop_expr.loop_body()?; - let expr_to_unwrap = extract_expr(ctx.frange.range, block_expr)?; - (ast::Expr::LoopExpr(loop_expr), expr_to_unwrap) - }, - ast::IfExpr(if_expr) => { - let mut resp = None; + let l_curly_token = ctx.find_token_at_offset(T!['{'])?; + let mut block = ast::BlockExpr::cast(l_curly_token.parent())?; + let mut parent = block.syntax().parent()?; + if ast::MatchArm::can_cast(parent.kind()) { + parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? + } - let then_branch = if_expr.then_branch()?; - if then_branch.l_curly_token()?.text_range().contains_range(ctx.frange.range) { - if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { - // For `else if` blocks - let ancestor_then_branch = ancestor.then_branch()?; - let l_curly_token = then_branch.l_curly_token()?; + let parent = ast::Expr::cast(parent)?; - let target = then_branch.syntax().text_range(); - return acc.add(assist_id, assist_label, target, |edit| { - let range_to_del_else_if = TextRange::new(ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); - let range_to_del_rest = TextRange::new(then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end()); + match parent.clone() { + ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (), + ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)), + ast::Expr::IfExpr(if_expr) => { + let then_branch = if_expr.then_branch()?; + if then_branch == block { + if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { + // For `else if` blocks + let ancestor_then_branch = ancestor.then_branch()?; - edit.delete(range_to_del_rest); - edit.delete(range_to_del_else_if); - edit.replace(target, update_expr_string(then_branch.to_string(), &[' ', '{'])); - }); - } else { - resp = Some((ast::Expr::IfExpr(if_expr.clone()), Expr::BlockExpr(then_branch))); - } - } else if let Some(else_branch) = if_expr.else_branch() { - match else_branch { - ElseBranch::Block(else_block) => { - let l_curly_token = else_block.l_curly_token()?; - if l_curly_token.text_range().contains_range(ctx.frange.range) { - let target = else_block.syntax().text_range(); - return acc.add(assist_id, assist_label, target, |edit| { - let range_to_del = TextRange::new(then_branch.syntax().text_range().end(), l_curly_token.text_range().start()); + let target = then_branch.syntax().text_range(); + return acc.add(assist_id, assist_label, target, |edit| { + let range_to_del_else_if = TextRange::new( + ancestor_then_branch.syntax().text_range().end(), + l_curly_token.text_range().start(), + ); + let range_to_del_rest = TextRange::new( + then_branch.syntax().text_range().end(), + if_expr.syntax().text_range().end(), + ); - edit.delete(range_to_del); - edit.replace(target, update_expr_string(else_block.to_string(), &[' ', '{'])); - }); - } - }, - ElseBranch::IfExpr(_) => {}, - } + edit.delete(range_to_del_rest); + edit.delete(range_to_del_else_if); + edit.replace( + target, + update_expr_string(then_branch.to_string(), &[' ', '{']), + ); + }); } + } else { + let target = block.syntax().text_range(); + return acc.add(assist_id, assist_label, target, |edit| { + let range_to_del = TextRange::new( + then_branch.syntax().text_range().end(), + l_curly_token.text_range().start(), + ); - resp? - }, - _ => return None, + edit.delete(range_to_del); + edit.replace(target, update_expr_string(block.to_string(), &[' ', '{'])); + }); + } } + _ => return None, }; - let target = expr_to_unwrap.syntax().text_range(); - acc.add(assist_id, assist_label, target, |edit| { - edit.replace( - expr.syntax().text_range(), - update_expr_string(expr_to_unwrap.to_string(), &[' ', '{', '\n']), + let unwrapped = unwrap_trivial_block(block); + let target = unwrapped.syntax().text_range(); + acc.add(assist_id, assist_label, target, |builder| { + builder.replace( + parent.syntax().text_range(), + update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']), ); }) } -fn extract_expr(cursor_range: TextRange, block: ast::BlockExpr) -> Option { - let cursor_in_range = block.l_curly_token()?.text_range().contains_range(cursor_range); - - if cursor_in_range { - Some(unwrap_trivial_block(block)) - } else { - None - } -} - fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String { let expr_string = expr_str.trim_start_matches(trim_start_pat); let mut expr_string_lines: Vec<&str> = expr_string.lines().collect(); @@ -489,6 +471,30 @@ mod tests { ); } + #[test] + fn unwrap_match_arm() { + check_assist( + unwrap_block, + r#" +fn main() { + match rel_path { + Ok(rel_path) => {<|> + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) + } + Err(_) => None, + } +} +"#, + r#" +fn main() { + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) +} +"#, + ); + } + #[test] fn simple_if_in_while_bad_cursor_position() { check_assist_not_applicable( diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index fb5d59a87b75..185428bd5593 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -115,6 +115,7 @@ mod handlers { mod change_return_type_to_result; mod change_visibility; mod early_return; + mod extract_struct_from_enum_variant; mod fill_match_arms; mod fix_visibility; mod flip_binexpr; @@ -155,6 +156,7 @@ mod handlers { change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, early_return::convert_to_guarded_return, + extract_struct_from_enum_variant::extract_struct_from_enum_variant, fill_match_arms::fill_match_arms, fix_visibility::fix_visibility, flip_binexpr::flip_binexpr, diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index d17504529fd4..40a223727c1f 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -337,6 +337,21 @@ fn main() { ) } +#[test] +fn doctest_extract_struct_from_enum_variant() { + check_doc_test( + "extract_struct_from_enum_variant", + r#####" +enum A { <|>One(u32, u32) } +"#####, + r#####" +struct One(pub u32, pub u32); + +enum A { One(One) } +"#####, + ) +} + #[test] fn doctest_fill_match_arms() { check_doc_test( diff --git a/crates/ra_db/src/input.rs b/crates/ra_db/src/input.rs index 4d2d3b48a0c0..bf26048f2adb 100644 --- a/crates/ra_db/src/input.rs +++ b/crates/ra_db/src/input.rs @@ -15,12 +15,10 @@ use std::{ use ra_cfg::CfgOptions; use ra_syntax::SmolStr; -use rustc_hash::FxHashMap; -use rustc_hash::FxHashSet; +use ra_tt::TokenExpander; +use rustc_hash::{FxHashMap, FxHashSet}; use crate::{RelativePath, RelativePathBuf}; -use fmt::Display; -use ra_tt::TokenExpander; /// `FileId` is an integer which uniquely identifies a file. File paths are /// messy and system-dependent, so most of the code should work directly with @@ -111,7 +109,7 @@ impl CrateName { } } -impl Display for CrateName { +impl fmt::Display for CrateName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.0) } @@ -337,15 +335,11 @@ impl Env { } impl ExternSource { - pub fn extern_path(&self, path: impl AsRef) -> Option<(ExternSourceId, RelativePathBuf)> { - let path = path.as_ref(); + pub fn extern_path(&self, path: &Path) -> Option<(ExternSourceId, RelativePathBuf)> { self.extern_paths.iter().find_map(|(root_path, id)| { - if let Ok(rel_path) = path.strip_prefix(root_path) { - let rel_path = RelativePathBuf::from_path(rel_path).ok()?; - Some((*id, rel_path)) - } else { - None - } + let rel_path = path.strip_prefix(root_path).ok()?; + let rel_path = RelativePathBuf::from_path(rel_path).ok()?; + Some((*id, rel_path)) }) } diff --git a/crates/ra_db/src/lib.rs b/crates/ra_db/src/lib.rs index fd4280de2df4..80ddb6058afb 100644 --- a/crates/ra_db/src/lib.rs +++ b/crates/ra_db/src/lib.rs @@ -7,12 +7,13 @@ use std::{panic, sync::Arc}; use ra_prof::profile; use ra_syntax::{ast, Parse, SourceFile, TextRange, TextSize}; +use rustc_hash::FxHashSet; pub use crate::{ cancellation::Canceled, input::{ - CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, ExternSourceId, - FileId, ProcMacroId, SourceRoot, SourceRootId, + CrateData, CrateGraph, CrateId, CrateName, Dependency, Edition, Env, ExternSource, + ExternSourceId, FileId, ProcMacroId, SourceRoot, SourceRootId, }, }; pub use relative_path::{RelativePath, RelativePathBuf}; @@ -89,15 +90,13 @@ pub const DEFAULT_LRU_CAP: usize = 128; pub trait FileLoader { /// Text of the file. fn file_text(&self, file_id: FileId) -> Arc; - fn resolve_relative_path(&self, anchor: FileId, relative_path: &RelativePath) - -> Option; - fn relevant_crates(&self, file_id: FileId) -> Arc>; - - fn resolve_extern_path( - &self, - extern_id: ExternSourceId, - relative_path: &RelativePath, - ) -> Option; + /// Note that we intentionally accept a `&str` and not a `&Path` here. This + /// method exists to handle `#[path = "/some/path.rs"] mod foo;` and such, + /// so the input is guaranteed to be utf-8 string. We might introduce + /// `struct StrPath(str)` for clarity some day, but it's a bit messy, so we + /// get by with a `&str` for the time being. + fn resolve_path(&self, anchor: FileId, path: &str) -> Option; + fn relevant_crates(&self, file_id: FileId) -> Arc>; } /// Database which stores all significant input facts: source code and project @@ -135,16 +134,21 @@ pub trait SourceDatabaseExt: SourceDatabase { #[salsa::input] fn source_root(&self, id: SourceRootId) -> Arc; - fn source_root_crates(&self, id: SourceRootId) -> Arc>; + fn source_root_crates(&self, id: SourceRootId) -> Arc>; } fn source_root_crates( db: &(impl SourceDatabaseExt + SourceDatabase), id: SourceRootId, -) -> Arc> { - let root = db.source_root(id); +) -> Arc> { let graph = db.crate_graph(); - let res = root.walk().filter_map(|it| graph.crate_id_for_crate_root(it)).collect::>(); + let res = graph + .iter() + .filter(|&krate| { + let root_file = graph[krate].root_file_id; + db.file_source_root(root_file) == id + }) + .collect::>(); Arc::new(res) } @@ -155,33 +159,30 @@ impl FileLoader for FileLoaderDelegate<&'_ T> { fn file_text(&self, file_id: FileId) -> Arc { SourceDatabaseExt::file_text(self.0, file_id) } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - let path = { - let mut path = self.0.file_relative_path(anchor); - assert!(path.pop()); - path.push(relative_path); - path.normalize() - }; - let source_root = self.0.file_source_root(anchor); - let source_root = self.0.source_root(source_root); - source_root.file_by_relative_path(&path) + fn resolve_path(&self, anchor: FileId, path: &str) -> Option { + // FIXME: this *somehow* should be platform agnostic... + if std::path::Path::new(path).is_absolute() { + let krate = *self.relevant_crates(anchor).iter().next()?; + let (extern_source_id, relative_file) = + self.0.crate_graph()[krate].extern_source.extern_path(path.as_ref())?; + + let source_root = self.0.source_root(SourceRootId(extern_source_id.0)); + source_root.file_by_relative_path(&relative_file) + } else { + let rel_path = { + let mut rel_path = self.0.file_relative_path(anchor); + assert!(rel_path.pop()); + rel_path.push(path); + rel_path.normalize() + }; + let source_root = self.0.file_source_root(anchor); + let source_root = self.0.source_root(source_root); + source_root.file_by_relative_path(&rel_path) + } } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { let source_root = self.0.file_source_root(file_id); self.0.source_root_crates(source_root) } - - fn resolve_extern_path( - &self, - extern_id: ExternSourceId, - relative_path: &RelativePath, - ) -> Option { - let source_root = self.0.source_root(SourceRootId(extern_id.0)); - source_root.file_by_relative_path(&relative_path) - } } diff --git a/crates/ra_flycheck/src/lib.rs b/crates/ra_flycheck/src/lib.rs index 041e38a9ff4c..6c41705298bd 100644 --- a/crates/ra_flycheck/src/lib.rs +++ b/crates/ra_flycheck/src/lib.rs @@ -18,8 +18,17 @@ pub use cargo_metadata::diagnostic::{ #[derive(Clone, Debug, PartialEq, Eq)] pub enum FlycheckConfig { - CargoCommand { command: String, all_targets: bool, all_features: bool, extra_args: Vec }, - CustomCommand { command: String, args: Vec }, + CargoCommand { + command: String, + all_targets: bool, + all_features: bool, + features: Vec, + extra_args: Vec, + }, + CustomCommand { + command: String, + args: Vec, + }, } /// Flycheck wraps the shared state and communication machinery used for @@ -188,7 +197,13 @@ impl FlycheckThread { self.check_process = None; let mut cmd = match &self.config { - FlycheckConfig::CargoCommand { command, all_targets, all_features, extra_args } => { + FlycheckConfig::CargoCommand { + command, + all_targets, + all_features, + extra_args, + features, + } => { let mut cmd = Command::new(ra_toolchain::cargo()); cmd.arg(command); cmd.args(&["--workspace", "--message-format=json", "--manifest-path"]) @@ -198,6 +213,9 @@ impl FlycheckThread { } if *all_features { cmd.arg("--all-features"); + } else if !features.is_empty() { + cmd.arg("--features"); + cmd.arg(features.join(" ")); } cmd.args(extra_args); cmd diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index e40aeffbcd4f..1a9f6cc768b6 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -9,6 +9,7 @@ use hir_def::{ builtin_type::BuiltinType, docs::Documentation, expr::{BindingAnnotation, Pat, PatId}, + import_map, per_ns::PerNs, resolver::{HasResolver, Resolver}, type_ref::{Mutability, TypeRef}, @@ -98,6 +99,23 @@ impl Crate { db.crate_graph()[self.id].display_name.as_ref().cloned() } + pub fn query_external_importables( + self, + db: &dyn DefDatabase, + query: &str, + ) -> impl Iterator> { + import_map::search_dependencies( + db, + self.into(), + import_map::Query::new(query).anchor_end().case_sensitive().limit(40), + ) + .into_iter() + .map(|item| match item { + ItemInNs::Types(mod_id) | ItemInNs::Values(mod_id) => Either::Left(mod_id.into()), + ItemInNs::Macros(mac_id) => Either::Right(mac_id.into()), + }) + } + pub fn all(db: &dyn HirDatabase) -> Vec { db.crate_graph().iter().map(|id| Crate { id }).collect() } @@ -637,6 +655,10 @@ impl Function { db.function_data(self.id).params.clone() } + pub fn is_unsafe(self, db: &dyn HirDatabase) -> bool { + db.function_data(self.id).is_unsafe + } + pub fn diagnostics(self, db: &dyn HirDatabase, sink: &mut DiagnosticSink) { let _p = profile("Function::diagnostics"); let infer = db.infer(self.id.into()); @@ -1190,6 +1212,10 @@ impl Type { ) } + pub fn is_raw_ptr(&self) -> bool { + matches!(&self.ty.value, Ty::Apply(ApplicationTy { ctor: TypeCtor::RawPtr(..), .. })) + } + pub fn contains_unknown(&self) -> bool { return go(&self.ty.value); diff --git a/crates/ra_hir/src/db.rs b/crates/ra_hir/src/db.rs index ec931b34fe87..b6b665de1f29 100644 --- a/crates/ra_hir/src/db.rs +++ b/crates/ra_hir/src/db.rs @@ -3,11 +3,11 @@ pub use hir_def::db::{ AttrsQuery, BodyQuery, BodyWithSourceMapQuery, ConstDataQuery, CrateDefMapQueryQuery, CrateLangItemsQuery, DefDatabase, DefDatabaseStorage, DocumentationQuery, EnumDataQuery, - ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, InternConstQuery, - InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, InternImplQuery, - InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, InternUnionQuery, - LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery, StructDataQuery, - TraitDataQuery, TypeAliasDataQuery, UnionDataQuery, + ExprScopesQuery, FunctionDataQuery, GenericParamsQuery, ImplDataQuery, ImportMapQuery, + InternConstQuery, InternDatabase, InternDatabaseStorage, InternEnumQuery, InternFunctionQuery, + InternImplQuery, InternStaticQuery, InternStructQuery, InternTraitQuery, InternTypeAliasQuery, + InternUnionQuery, LangItemQuery, ModuleLangItemsQuery, RawItemsQuery, StaticDataQuery, + StructDataQuery, TraitDataQuery, TypeAliasDataQuery, UnionDataQuery, }; pub use hir_expand::db::{ AstDatabase, AstDatabaseStorage, AstIdMapQuery, InternEagerExpansionQuery, InternMacroQuery, @@ -18,8 +18,8 @@ pub use hir_ty::db::{ GenericDefaultsQuery, GenericPredicatesForParamQuery, GenericPredicatesQuery, HirDatabase, HirDatabaseStorage, ImplDatumQuery, ImplSelfTyQuery, ImplTraitQuery, ImplsForTraitQuery, ImplsInCrateQuery, InferQueryQuery, InternAssocTyValueQuery, InternChalkImplQuery, - InternTypeCtorQuery, InternTypeParamIdQuery, StructDatumQuery, TraitDatumQuery, - TraitSolveQuery, TyQuery, ValueTyQuery, + InternTypeCtorQuery, InternTypeParamIdQuery, ReturnTypeImplTraitsQuery, StructDatumQuery, + TraitDatumQuery, TraitSolveQuery, TyQuery, ValueTyQuery, }; #[test] diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 7c1f79f279b6..a232a58567ce 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs @@ -122,8 +122,9 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> { let macro_call = self.find_file(actual_macro_call.syntax().clone()).with_value(actual_macro_call); let sa = self.analyze2(macro_call.map(|it| it.syntax()), None); + let krate = sa.resolver.krate()?; let macro_call_id = macro_call - .as_call_id(self.db, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?; + .as_call_id(self.db, krate, |path| sa.resolver.resolve_path_as_macro(self.db, &path))?; hir_expand::db::expand_hypothetical(self.db, macro_call_id, hypothetical_args, token_to_map) } diff --git a/crates/ra_hir/src/source_analyzer.rs b/crates/ra_hir/src/source_analyzer.rs index 4b509f07c6b7..7c6bbea13c29 100644 --- a/crates/ra_hir/src/source_analyzer.rs +++ b/crates/ra_hir/src/source_analyzer.rs @@ -307,7 +307,8 @@ impl SourceAnalyzer { db: &dyn HirDatabase, macro_call: InFile<&ast::MacroCall>, ) -> Option { - let macro_call_id = macro_call.as_call_id(db.upcast(), |path| { + let krate = self.resolver.krate()?; + let macro_call_id = macro_call.as_call_id(db.upcast(), krate, |path| { self.resolver.resolve_path_as_macro(db.upcast(), &path) })?; Some(macro_call_id.as_file()) diff --git a/crates/ra_hir_def/Cargo.toml b/crates/ra_hir_def/Cargo.toml index b853583088cf..ef1f65ee0655 100644 --- a/crates/ra_hir_def/Cargo.toml +++ b/crates/ra_hir_def/Cargo.toml @@ -14,6 +14,9 @@ rustc-hash = "1.1.0" either = "1.5.3" anymap = "0.12.1" drop_bomb = "0.1.4" +fst = { version = "0.4", default-features = false } +itertools = "0.9.0" +indexmap = "1.4.0" stdx = { path = "../stdx" } diff --git a/crates/ra_hir_def/src/attr.rs b/crates/ra_hir_def/src/attr.rs index 8b6c0bedee78..2eeba0572991 100644 --- a/crates/ra_hir_def/src/attr.rs +++ b/crates/ra_hir_def/src/attr.rs @@ -87,12 +87,18 @@ impl Attrs { } pub(crate) fn new(owner: &dyn AttrsOwner, hygiene: &Hygiene) -> Attrs { + let docs = ast::CommentIter::from_syntax_node(owner.syntax()).doc_comment_text().map( + |docs_text| Attr { + input: Some(AttrInput::Literal(SmolStr::new(docs_text))), + path: ModPath::from(hir_expand::name!(doc)), + }, + ); let mut attrs = owner.attrs().peekable(); let entries = if attrs.peek().is_none() { // Avoid heap allocation None } else { - Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).collect()) + Some(attrs.flat_map(|ast| Attr::from_src(ast, hygiene)).chain(docs).collect()) }; Attrs { entries } } diff --git a/crates/ra_hir_def/src/body.rs b/crates/ra_hir_def/src/body.rs index 273036cee1cf..4f2350915dcb 100644 --- a/crates/ra_hir_def/src/body.rs +++ b/crates/ra_hir_def/src/body.rs @@ -97,7 +97,7 @@ impl Expander { let macro_call = InFile::new(self.current_file_id, ¯o_call); - if let Some(call_id) = macro_call.as_call_id(db, |path| { + if let Some(call_id) = macro_call.as_call_id(db, self.crate_def_map.krate, |path| { if let Some(local_scope) = local_scope { if let Some(def) = path.as_ident().and_then(|n| local_scope.get_legacy_macro(n)) { return Some(def); diff --git a/crates/ra_hir_def/src/data.rs b/crates/ra_hir_def/src/data.rs index e2130d931fdb..53599e74a266 100644 --- a/crates/ra_hir_def/src/data.rs +++ b/crates/ra_hir_def/src/data.rs @@ -34,6 +34,7 @@ pub struct FunctionData { /// True if the first param is `self`. This is relevant to decide whether this /// can be called as a method. pub has_self_param: bool, + pub is_unsafe: bool, pub visibility: RawVisibility, } @@ -85,17 +86,20 @@ impl FunctionData { ret_type }; + let is_unsafe = src.value.unsafe_token().is_some(); + let vis_default = RawVisibility::default_for_container(loc.container); let visibility = RawVisibility::from_ast_with_default(db, vis_default, src.map(|s| s.visibility())); - let sig = FunctionData { name, params, ret_type, has_self_param, visibility, attrs }; + let sig = + FunctionData { name, params, ret_type, has_self_param, is_unsafe, visibility, attrs }; Arc::new(sig) } } fn desugar_future_path(orig: TypeRef) -> Path { - let path = path![std::future::Future]; + let path = path![core::future::Future]; let mut generic_args: Vec<_> = std::iter::repeat(None).take(path.segments.len() - 1).collect(); let mut last = GenericArgs::empty(); last.bindings.push(AssociatedTypeBinding { diff --git a/crates/ra_hir_def/src/db.rs b/crates/ra_hir_def/src/db.rs index 945a0025e504..10cc26480f36 100644 --- a/crates/ra_hir_def/src/db.rs +++ b/crates/ra_hir_def/src/db.rs @@ -1,7 +1,7 @@ //! Defines database & queries for name resolution. use std::sync::Arc; -use hir_expand::{db::AstDatabase, name::Name, HirFileId}; +use hir_expand::{db::AstDatabase, HirFileId}; use ra_db::{salsa, CrateId, SourceDatabase, Upcast}; use ra_prof::profile; use ra_syntax::SmolStr; @@ -12,13 +12,10 @@ use crate::{ body::{scope::ExprScopes, Body, BodySourceMap}, data::{ConstData, FunctionData, ImplData, StaticData, TraitData, TypeAliasData}, docs::Documentation, - find_path, generics::GenericParams, - item_scope::ItemInNs, + import_map::ImportMap, lang_item::{LangItemTarget, LangItems}, nameres::{raw::RawItems, CrateDefMap}, - path::ModPath, - visibility::Visibility, AttrDefId, ConstId, ConstLoc, DefWithBodyId, EnumId, EnumLoc, FunctionId, FunctionLoc, GenericDefId, ImplId, ImplLoc, ModuleId, StaticId, StaticLoc, StructId, StructLoc, TraitId, TraitLoc, TypeAliasId, TypeAliasLoc, UnionId, UnionLoc, @@ -113,15 +110,8 @@ pub trait DefDatabase: InternDatabase + AstDatabase + Upcast { #[salsa::invoke(Documentation::documentation_query)] fn documentation(&self, def: AttrDefId) -> Option; - #[salsa::invoke(find_path::importable_locations_of_query)] - fn importable_locations_of( - &self, - item: ItemInNs, - krate: CrateId, - ) -> Arc<[(ModuleId, Name, Visibility)]>; - - #[salsa::invoke(find_path::find_path_inner_query)] - fn find_path_inner(&self, item: ItemInNs, from: ModuleId, max_len: usize) -> Option; + #[salsa::invoke(ImportMap::import_map_query)] + fn import_map(&self, krate: CrateId) -> Arc; } fn crate_def_map_wait(db: &impl DefDatabase, krate: CrateId) -> Arc { diff --git a/crates/ra_hir_def/src/docs.rs b/crates/ra_hir_def/src/docs.rs index b221ae1cece3..2630b3d895e6 100644 --- a/crates/ra_hir_def/src/docs.rs +++ b/crates/ra_hir_def/src/docs.rs @@ -29,6 +29,13 @@ impl Documentation { Documentation(s.into()) } + pub fn from_ast(node: &N) -> Option + where + N: ast::DocCommentsOwner + ast::AttrsOwner, + { + docs_from_ast(node) + } + pub fn as_str(&self) -> &str { &*self.0 } @@ -70,6 +77,45 @@ impl Documentation { } } -pub(crate) fn docs_from_ast(node: &impl ast::DocCommentsOwner) -> Option { - node.doc_comment_text().map(|it| Documentation::new(&it)) +pub(crate) fn docs_from_ast(node: &N) -> Option +where + N: ast::DocCommentsOwner + ast::AttrsOwner, +{ + let doc_comment_text = node.doc_comment_text(); + let doc_attr_text = expand_doc_attrs(node); + let docs = merge_doc_comments_and_attrs(doc_comment_text, doc_attr_text); + docs.map(|it| Documentation::new(&it)) +} + +fn merge_doc_comments_and_attrs( + doc_comment_text: Option, + doc_attr_text: Option, +) -> Option { + match (doc_comment_text, doc_attr_text) { + (Some(mut comment_text), Some(attr_text)) => { + comment_text.push_str("\n\n"); + comment_text.push_str(&attr_text); + Some(comment_text) + } + (Some(comment_text), None) => Some(comment_text), + (None, Some(attr_text)) => Some(attr_text), + (None, None) => None, + } +} + +fn expand_doc_attrs(owner: &dyn ast::AttrsOwner) -> Option { + let mut docs = String::new(); + for attr in owner.attrs() { + if let Some(("doc", value)) = + attr.as_simple_key_value().as_ref().map(|(k, v)| (k.as_str(), v.as_str())) + { + docs.push_str(value); + docs.push_str("\n\n"); + } + } + if docs.is_empty() { + None + } else { + Some(docs.trim_end_matches("\n\n").to_owned()) + } } diff --git a/crates/ra_hir_def/src/find_path.rs b/crates/ra_hir_def/src/find_path.rs index 4db7984730aa..06701a8309e7 100644 --- a/crates/ra_hir_def/src/find_path.rs +++ b/crates/ra_hir_def/src/find_path.rs @@ -1,9 +1,8 @@ //! An algorithm to find a path to refer to a certain item. -use std::sync::Arc; - use hir_expand::name::{known, AsName, Name}; use ra_prof::profile; +use rustc_hash::FxHashSet; use test_utils::mark; use crate::{ @@ -11,7 +10,7 @@ use crate::{ item_scope::ItemInNs, path::{ModPath, PathKind}, visibility::Visibility, - CrateId, ModuleDefId, ModuleId, + ModuleDefId, ModuleId, }; // FIXME: handle local items @@ -20,7 +19,7 @@ use crate::{ /// *from where* you're referring to the item, hence the `from` parameter. pub fn find_path(db: &dyn DefDatabase, item: ItemInNs, from: ModuleId) -> Option { let _p = profile("find_path"); - db.find_path_inner(item, from, MAX_PATH_LEN) + find_path_inner(db, item, from, MAX_PATH_LEN) } const MAX_PATH_LEN: usize = 15; @@ -36,20 +35,9 @@ impl ModPath { let first_segment = self.segments.first(); first_segment == Some(&known::alloc) || first_segment == Some(&known::core) } - - fn len(&self) -> usize { - self.segments.len() - + match self.kind { - PathKind::Plain => 0, - PathKind::Super(i) => i as usize, - PathKind::Crate => 1, - PathKind::Abs => 0, - PathKind::DollarCrate(_) => 1, - } - } } -pub(crate) fn find_path_inner_query( +fn find_path_inner( db: &dyn DefDatabase, item: ItemInNs, from: ModuleId, @@ -133,31 +121,67 @@ pub(crate) fn find_path_inner_query( } // - otherwise, look for modules containing (reexporting) it and import it from one of those + let crate_root = ModuleId { local_id: def_map.root, krate: from.krate }; let crate_attrs = db.attrs(crate_root.into()); let prefer_no_std = crate_attrs.by_key("no_std").exists(); - let importable_locations = find_importable_locations(db, item, from); let mut best_path = None; let mut best_path_len = max_len; - for (module_id, name) in importable_locations { - let mut path = match db.find_path_inner( - ItemInNs::Types(ModuleDefId::ModuleId(module_id)), - from, - best_path_len - 1, - ) { - None => continue, - Some(path) => path, - }; - path.segments.push(name); - let new_path = if let Some(best_path) = best_path { - select_best_path(best_path, path, prefer_no_std) - } else { - path - }; - best_path_len = new_path.len(); - best_path = Some(new_path); + if item.krate(db) == Some(from.krate) { + // Item was defined in the same crate that wants to import it. It cannot be found in any + // dependency in this case. + + let local_imports = find_local_import_locations(db, item, from); + for (module_id, name) in local_imports { + if let Some(mut path) = find_path_inner( + db, + ItemInNs::Types(ModuleDefId::ModuleId(module_id)), + from, + best_path_len - 1, + ) { + path.segments.push(name); + + let new_path = if let Some(best_path) = best_path { + select_best_path(best_path, path, prefer_no_std) + } else { + path + }; + best_path_len = new_path.len(); + best_path = Some(new_path); + } + } + } else { + // Item was defined in some upstream crate. This means that it must be exported from one, + // too (unless we can't name it at all). It could *also* be (re)exported by the same crate + // that wants to import it here, but we always prefer to use the external path here. + + let crate_graph = db.crate_graph(); + let extern_paths = crate_graph[from.krate].dependencies.iter().filter_map(|dep| { + let import_map = db.import_map(dep.crate_id); + import_map.import_info_for(item).and_then(|info| { + // Determine best path for containing module and append last segment from `info`. + let mut path = find_path_inner( + db, + ItemInNs::Types(ModuleDefId::ModuleId(info.container)), + from, + best_path_len - 1, + )?; + path.segments.push(info.path.segments.last().unwrap().clone()); + Some(path) + }) + }); + + for path in extern_paths { + let new_path = if let Some(best_path) = best_path { + select_best_path(best_path, path, prefer_no_std) + } else { + path + }; + best_path = Some(new_path); + } } + best_path } @@ -185,69 +209,86 @@ fn select_best_path(old_path: ModPath, new_path: ModPath, prefer_no_std: bool) - } } -fn find_importable_locations( +/// Finds locations in `from.krate` from which `item` can be imported by `from`. +fn find_local_import_locations( db: &dyn DefDatabase, item: ItemInNs, from: ModuleId, ) -> Vec<(ModuleId, Name)> { - let crate_graph = db.crate_graph(); - let mut result = Vec::new(); - // We only look in the crate from which we are importing, and the direct - // dependencies. We cannot refer to names from transitive dependencies - // directly (only through reexports in direct dependencies). - for krate in Some(from.krate) - .into_iter() - .chain(crate_graph[from.krate].dependencies.iter().map(|dep| dep.crate_id)) - { - result.extend( - db.importable_locations_of(item, krate) - .iter() - .filter(|(_, _, vis)| vis.is_visible_from(db, from)) - .map(|(m, n, _)| (*m, n.clone())), - ); - } - result -} + let _p = profile("find_local_import_locations"); + + // `from` can import anything below `from` with visibility of at least `from`, and anything + // above `from` with any visibility. That means we do not need to descend into private siblings + // of `from` (and similar). + + let def_map = db.crate_def_map(from.krate); + + // Compute the initial worklist. We start with all direct child modules of `from` as well as all + // of its (recursive) parent modules. + let data = &def_map.modules[from.local_id]; + let mut worklist = data + .children + .values() + .map(|child| ModuleId { krate: from.krate, local_id: *child }) + .collect::>(); + let mut parent = data.parent; + while let Some(p) = parent { + worklist.push(ModuleId { krate: from.krate, local_id: p }); + parent = def_map.modules[p].parent; + } + + let mut seen: FxHashSet<_> = FxHashSet::default(); + + let mut locations = Vec::new(); + while let Some(module) = worklist.pop() { + if !seen.insert(module) { + continue; // already processed this module + } + + let ext_def_map; + let data = if module.krate == from.krate { + &def_map[module.local_id] + } else { + // The crate might reexport a module defined in another crate. + ext_def_map = db.crate_def_map(module.krate); + &ext_def_map[module.local_id] + }; -/// Collects all locations from which we might import the item in a particular -/// crate. These include the original definition of the item, and any -/// non-private `use`s. -/// -/// Note that the crate doesn't need to be the one in which the item is defined; -/// it might be re-exported in other crates. -pub(crate) fn importable_locations_of_query( - db: &dyn DefDatabase, - item: ItemInNs, - krate: CrateId, -) -> Arc<[(ModuleId, Name, Visibility)]> { - let _p = profile("importable_locations_of_query"); - let def_map = db.crate_def_map(krate); - let mut result = Vec::new(); - for (local_id, data) in def_map.modules.iter() { if let Some((name, vis)) = data.scope.name_of(item) { - let is_private = if let Visibility::Module(private_to) = vis { - private_to.local_id == local_id - } else { - false - }; - let is_original_def = if let Some(module_def_id) = item.as_module_def_id() { - data.scope.declarations().any(|it| it == module_def_id) - } else { - false - }; - if is_private && !is_original_def { + if vis.is_visible_from(db, from) { + let is_private = if let Visibility::Module(private_to) = vis { + private_to.local_id == module.local_id + } else { + false + }; + let is_original_def = if let Some(module_def_id) = item.as_module_def_id() { + data.scope.declarations().any(|it| it == module_def_id) + } else { + false + }; + // Ignore private imports. these could be used if we are // in a submodule of this module, but that's usually not // what the user wants; and if this module can import // the item and we're a submodule of it, so can we. // Also this keeps the cached data smaller. - continue; + if !is_private || is_original_def { + locations.push((module, name.clone())); + } + } + } + + // Descend into all modules visible from `from`. + for (_, per_ns) in data.scope.entries() { + if let Some((ModuleDefId::ModuleId(module), vis)) = per_ns.take_types_vis() { + if vis.is_visible_from(db, from) { + worklist.push(module); + } } - result.push((ModuleId { krate, local_id }, name.clone(), vis)); } } - Arc::from(result) + locations } #[cfg(test)] @@ -264,8 +305,8 @@ mod tests { /// `code` needs to contain a cursor marker; checks that `find_path` for the /// item the `path` refers to returns that same path when called from the /// module the cursor is in. - fn check_found_path(code: &str, path: &str) { - let (db, pos) = TestDB::with_position(code); + fn check_found_path(ra_fixture: &str, path: &str) { + let (db, pos) = TestDB::with_position(ra_fixture); let module = db.module_for_file(pos.file_id); let parsed_path_file = ra_syntax::SourceFile::parse(&format!("use {};", path)); let ast_path = parsed_path_file @@ -395,6 +436,44 @@ mod tests { check_found_path(code, "std_renamed::S"); } + #[test] + fn partially_imported() { + // Tests that short paths are used even for external items, when parts of the path are + // already in scope. + check_found_path( + r#" + //- /main.rs crate:main deps:ra_syntax + + use ra_syntax::ast; + <|> + + //- /lib.rs crate:ra_syntax + pub mod ast { + pub enum ModuleItem { + A, B, C, + } + } + "#, + "ast::ModuleItem", + ); + + check_found_path( + r#" + //- /main.rs crate:main deps:ra_syntax + + <|> + + //- /lib.rs crate:ra_syntax + pub mod ast { + pub enum ModuleItem { + A, B, C, + } + } + "#, + "ra_syntax::ast::ModuleItem", + ); + } + #[test] fn same_crate_reexport() { let code = r#" diff --git a/crates/ra_hir_def/src/import_map.rs b/crates/ra_hir_def/src/import_map.rs new file mode 100644 index 000000000000..68e20d06b8b1 --- /dev/null +++ b/crates/ra_hir_def/src/import_map.rs @@ -0,0 +1,679 @@ +//! A map of all publicly exported items in a crate. + +use std::{cmp::Ordering, fmt, hash::BuildHasherDefault, sync::Arc}; + +use fst::{self, Streamer}; +use indexmap::{map::Entry, IndexMap}; +use ra_db::CrateId; +use rustc_hash::FxHasher; + +use crate::{ + db::DefDatabase, + item_scope::ItemInNs, + path::{ModPath, PathKind}, + visibility::Visibility, + ModuleDefId, ModuleId, +}; + +type FxIndexMap = IndexMap>; + +/// Item import details stored in the `ImportMap`. +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct ImportInfo { + /// A path that can be used to import the item, relative to the crate's root. + pub path: ModPath, + /// The module containing this item. + pub container: ModuleId, +} + +/// A map from publicly exported items to the path needed to import/name them from a downstream +/// crate. +/// +/// Reexports of items are taken into account, ie. if something is exported under multiple +/// names, the one with the shortest import path will be used. +/// +/// Note that all paths are relative to the containing crate's root, so the crate name still needs +/// to be prepended to the `ModPath` before the path is valid. +pub struct ImportMap { + map: FxIndexMap, + + /// List of keys stored in `map`, sorted lexicographically by their `ModPath`. Indexed by the + /// values returned by running `fst`. + /// + /// Since a path can refer to multiple items due to namespacing, we store all items with the + /// same path right after each other. This allows us to find all items after the FST gives us + /// the index of the first one. + importables: Vec, + fst: fst::Map>, +} + +impl ImportMap { + pub fn import_map_query(db: &dyn DefDatabase, krate: CrateId) -> Arc { + let _p = ra_prof::profile("import_map_query"); + let def_map = db.crate_def_map(krate); + let mut import_map = FxIndexMap::with_capacity_and_hasher(64, Default::default()); + + // We look only into modules that are public(ly reexported), starting with the crate root. + let empty = ModPath { kind: PathKind::Plain, segments: vec![] }; + let root = ModuleId { krate, local_id: def_map.root }; + let mut worklist = vec![(root, empty)]; + while let Some((module, mod_path)) = worklist.pop() { + let ext_def_map; + let mod_data = if module.krate == krate { + &def_map[module.local_id] + } else { + // The crate might reexport a module defined in another crate. + ext_def_map = db.crate_def_map(module.krate); + &ext_def_map[module.local_id] + }; + + let visible_items = mod_data.scope.entries().filter_map(|(name, per_ns)| { + let per_ns = per_ns.filter_visibility(|vis| vis == Visibility::Public); + if per_ns.is_none() { + None + } else { + Some((name, per_ns)) + } + }); + + for (name, per_ns) in visible_items { + let mk_path = || { + let mut path = mod_path.clone(); + path.segments.push(name.clone()); + path + }; + + for item in per_ns.iter_items() { + let path = mk_path(); + match import_map.entry(item) { + Entry::Vacant(entry) => { + entry.insert(ImportInfo { path, container: module }); + } + Entry::Occupied(mut entry) => { + // If the new path is shorter, prefer that one. + if path.len() < entry.get().path.len() { + *entry.get_mut() = ImportInfo { path, container: module }; + } else { + continue; + } + } + } + + // If we've just added a path to a module, descend into it. We might traverse + // modules multiple times, but only if the new path to it is shorter than the + // first (else we `continue` above). + if let Some(ModuleDefId::ModuleId(mod_id)) = item.as_module_def_id() { + worklist.push((mod_id, mk_path())); + } + } + } + } + + let mut importables = import_map.iter().collect::>(); + + importables.sort_by(cmp); + + // Build the FST, taking care not to insert duplicate values. + + let mut builder = fst::MapBuilder::memory(); + let mut last_batch_start = 0; + + for idx in 0..importables.len() { + if let Some(next_item) = importables.get(idx + 1) { + if cmp(&importables[last_batch_start], next_item) == Ordering::Equal { + continue; + } + } + + let start = last_batch_start; + last_batch_start = idx + 1; + + let key = fst_path(&importables[start].1.path); + + builder.insert(key, start as u64).unwrap(); + } + + let fst = fst::Map::new(builder.into_inner().unwrap()).unwrap(); + let importables = importables.iter().map(|(item, _)| **item).collect(); + + Arc::new(Self { map: import_map, fst, importables }) + } + + /// Returns the `ModPath` needed to import/mention `item`, relative to this crate's root. + pub fn path_of(&self, item: ItemInNs) -> Option<&ModPath> { + Some(&self.map.get(&item)?.path) + } + + pub fn import_info_for(&self, item: ItemInNs) -> Option<&ImportInfo> { + self.map.get(&item) + } +} + +impl PartialEq for ImportMap { + fn eq(&self, other: &Self) -> bool { + // `fst` and `importables` are built from `map`, so we don't need to compare them. + self.map == other.map + } +} + +impl Eq for ImportMap {} + +impl fmt::Debug for ImportMap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut importable_paths: Vec<_> = self + .map + .iter() + .map(|(item, info)| { + let ns = match item { + ItemInNs::Types(_) => "t", + ItemInNs::Values(_) => "v", + ItemInNs::Macros(_) => "m", + }; + format!("- {} ({})", info.path, ns) + }) + .collect(); + + importable_paths.sort(); + f.write_str(&importable_paths.join("\n")) + } +} + +fn fst_path(path: &ModPath) -> String { + let mut s = path.to_string(); + s.make_ascii_lowercase(); + s +} + +fn cmp((_, lhs): &(&ItemInNs, &ImportInfo), (_, rhs): &(&ItemInNs, &ImportInfo)) -> Ordering { + let lhs_str = fst_path(&lhs.path); + let rhs_str = fst_path(&rhs.path); + lhs_str.cmp(&rhs_str) +} + +#[derive(Debug)] +pub struct Query { + query: String, + lowercased: String, + anchor_end: bool, + case_sensitive: bool, + limit: usize, +} + +impl Query { + pub fn new(query: &str) -> Self { + Self { + lowercased: query.to_lowercase(), + query: query.to_string(), + anchor_end: false, + case_sensitive: false, + limit: usize::max_value(), + } + } + + /// Only returns items whose paths end with the (case-insensitive) query string as their last + /// segment. + pub fn anchor_end(self) -> Self { + Self { anchor_end: true, ..self } + } + + /// Limits the returned number of items to `limit`. + pub fn limit(self, limit: usize) -> Self { + Self { limit, ..self } + } + + /// Respect casing of the query string when matching. + pub fn case_sensitive(self) -> Self { + Self { case_sensitive: true, ..self } + } +} + +/// Searches dependencies of `krate` for an importable path matching `query`. +/// +/// This returns a list of items that could be imported from dependencies of `krate`. +pub fn search_dependencies<'a>( + db: &'a dyn DefDatabase, + krate: CrateId, + query: Query, +) -> Vec { + let _p = ra_prof::profile("search_dependencies").detail(|| format!("{:?}", query)); + + let graph = db.crate_graph(); + let import_maps: Vec<_> = + graph[krate].dependencies.iter().map(|dep| db.import_map(dep.crate_id)).collect(); + + let automaton = fst::automaton::Subsequence::new(&query.lowercased); + + let mut op = fst::map::OpBuilder::new(); + for map in &import_maps { + op = op.add(map.fst.search(&automaton)); + } + + let mut stream = op.union(); + let mut res = Vec::new(); + while let Some((_, indexed_values)) = stream.next() { + for indexed_value in indexed_values { + let import_map = &import_maps[indexed_value.index]; + let importables = &import_map.importables[indexed_value.value as usize..]; + + // Path shared by the importable items in this group. + let path = &import_map.map[&importables[0]].path; + + if query.anchor_end { + // Last segment must match query. + let last = path.segments.last().unwrap().to_string(); + if last.to_lowercase() != query.lowercased { + continue; + } + } + + // Add the items from this `ModPath` group. Those are all subsequent items in + // `importables` whose paths match `path`. + let iter = importables.iter().copied().take_while(|item| { + let item_path = &import_map.map[item].path; + fst_path(item_path) == fst_path(path) + }); + + if query.case_sensitive { + // FIXME: This does not do a subsequence match. + res.extend(iter.filter(|item| { + let item_path = &import_map.map[item].path; + item_path.to_string().contains(&query.query) + })); + } else { + res.extend(iter); + } + + if res.len() >= query.limit { + res.truncate(query.limit); + return res; + } + } + } + + res +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_db::TestDB; + use insta::assert_snapshot; + use itertools::Itertools; + use ra_db::fixture::WithFixture; + use ra_db::{SourceDatabase, Upcast}; + + fn import_map(ra_fixture: &str) -> String { + let db = TestDB::with_files(ra_fixture); + let crate_graph = db.crate_graph(); + + let s = crate_graph + .iter() + .filter_map(|krate| { + let cdata = &crate_graph[krate]; + let name = cdata.display_name.as_ref()?; + + let map = db.import_map(krate); + + Some(format!("{}:\n{:?}", name, map)) + }) + .join("\n"); + s + } + + fn search_dependencies_of(ra_fixture: &str, krate_name: &str, query: Query) -> String { + let db = TestDB::with_files(ra_fixture); + let crate_graph = db.crate_graph(); + let krate = crate_graph + .iter() + .find(|krate| { + crate_graph[*krate].display_name.as_ref().map(|n| n.to_string()) + == Some(krate_name.to_string()) + }) + .unwrap(); + + search_dependencies(db.upcast(), krate, query) + .into_iter() + .filter_map(|item| { + let mark = match item { + ItemInNs::Types(_) => "t", + ItemInNs::Values(_) => "v", + ItemInNs::Macros(_) => "m", + }; + item.krate(db.upcast()).map(|krate| { + let map = db.import_map(krate); + let path = map.path_of(item).unwrap(); + format!( + "{}::{} ({})", + crate_graph[krate].display_name.as_ref().unwrap(), + path, + mark + ) + }) + }) + .join("\n") + } + + #[test] + fn smoke() { + let map = import_map( + r" + //- /main.rs crate:main deps:lib + + mod private { + pub use lib::Pub; + pub struct InPrivateModule; + } + + pub mod publ1 { + use lib::Pub; + } + + pub mod real_pub { + pub use lib::Pub; + } + pub mod real_pu2 { // same path length as above + pub use lib::Pub; + } + + //- /lib.rs crate:lib + pub struct Pub {} + pub struct Pub2; // t + v + struct Priv; + ", + ); + + assert_snapshot!(map, @r###" + main: + - publ1 (t) + - real_pu2 (t) + - real_pub (t) + - real_pub::Pub (t) + lib: + - Pub (t) + - Pub2 (t) + - Pub2 (v) + "###); + } + + #[test] + fn prefers_shortest_path() { + let map = import_map( + r" + //- /main.rs crate:main + + pub mod sub { + pub mod subsub { + pub struct Def {} + } + + pub use super::sub::subsub::Def; + } + ", + ); + + assert_snapshot!(map, @r###" + main: + - sub (t) + - sub::Def (t) + - sub::subsub (t) + "###); + } + + #[test] + fn type_reexport_cross_crate() { + // Reexports need to be visible from a crate, even if the original crate exports the item + // at a shorter path. + let map = import_map( + r" + //- /main.rs crate:main deps:lib + pub mod m { + pub use lib::S; + } + //- /lib.rs crate:lib + pub struct S; + ", + ); + + assert_snapshot!(map, @r###" + main: + - m (t) + - m::S (t) + - m::S (v) + lib: + - S (t) + - S (v) + "###); + } + + #[test] + fn macro_reexport() { + let map = import_map( + r" + //- /main.rs crate:main deps:lib + pub mod m { + pub use lib::pub_macro; + } + //- /lib.rs crate:lib + #[macro_export] + macro_rules! pub_macro { + () => {}; + } + ", + ); + + assert_snapshot!(map, @r###" + main: + - m (t) + - m::pub_macro (m) + lib: + - pub_macro (m) + "###); + } + + #[test] + fn module_reexport() { + // Reexporting modules from a dependency adds all contents to the import map. + let map = import_map( + r" + //- /main.rs crate:main deps:lib + pub use lib::module as reexported_module; + //- /lib.rs crate:lib + pub mod module { + pub struct S; + } + ", + ); + + assert_snapshot!(map, @r###" + main: + - reexported_module (t) + - reexported_module::S (t) + - reexported_module::S (v) + lib: + - module (t) + - module::S (t) + - module::S (v) + "###); + } + + #[test] + fn cyclic_module_reexport() { + // A cyclic reexport does not hang. + let map = import_map( + r" + //- /lib.rs crate:lib + pub mod module { + pub struct S; + pub use super::sub::*; + } + + pub mod sub { + pub use super::module; + } + ", + ); + + assert_snapshot!(map, @r###" + lib: + - module (t) + - module::S (t) + - module::S (v) + - sub (t) + "###); + } + + #[test] + fn private_macro() { + let map = import_map( + r" + //- /lib.rs crate:lib + macro_rules! private_macro { + () => {}; + } + ", + ); + + assert_snapshot!(map, @r###" + lib: + "###); + } + + #[test] + fn namespacing() { + let map = import_map( + r" + //- /lib.rs crate:lib + pub struct Thing; // t + v + #[macro_export] + macro_rules! Thing { // m + () => {}; + } + ", + ); + + assert_snapshot!(map, @r###" + lib: + - Thing (m) + - Thing (t) + - Thing (v) + "###); + + let map = import_map( + r" + //- /lib.rs crate:lib + pub mod Thing {} // t + #[macro_export] + macro_rules! Thing { // m + () => {}; + } + ", + ); + + assert_snapshot!(map, @r###" + lib: + - Thing (m) + - Thing (t) + "###); + } + + #[test] + fn search() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep deps:tdep + use tdep::fmt as fmt_dep; + pub mod fmt { + pub trait Display { + fn fmt(); + } + } + #[macro_export] + macro_rules! Fmt { + () => {}; + } + pub struct Fmt; + + pub fn format() {} + pub fn no() {} + + //- /tdep.rs crate:tdep + pub mod fmt { + pub struct NotImportableFromMain; + } + "#; + + let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt")); + assert_snapshot!(res, @r###" + dep::fmt (t) + dep::Fmt (t) + dep::Fmt (v) + dep::Fmt (m) + dep::fmt::Display (t) + dep::format (v) + "###); + + let res = search_dependencies_of(ra_fixture, "main", Query::new("fmt").anchor_end()); + assert_snapshot!(res, @r###" + dep::fmt (t) + dep::Fmt (t) + dep::Fmt (v) + dep::Fmt (m) + "###); + } + + #[test] + fn search_casing() { + let ra_fixture = r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + + pub struct fmt; + pub struct FMT; + "#; + + let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT")); + + assert_snapshot!(res, @r###" + dep::fmt (t) + dep::fmt (v) + dep::FMT (t) + dep::FMT (v) + "###); + + let res = search_dependencies_of(ra_fixture, "main", Query::new("FMT").case_sensitive()); + + assert_snapshot!(res, @r###" + dep::FMT (t) + dep::FMT (v) + "###); + } + + #[test] + fn search_limit() { + let res = search_dependencies_of( + r#" + //- /main.rs crate:main deps:dep + //- /dep.rs crate:dep + pub mod fmt { + pub trait Display { + fn fmt(); + } + } + #[macro_export] + macro_rules! Fmt { + () => {}; + } + pub struct Fmt; + + pub fn format() {} + pub fn no() {} + "#, + "main", + Query::new("").limit(2), + ); + assert_snapshot!(res, @r###" + dep::fmt (t) + dep::Fmt (t) + "###); + } +} diff --git a/crates/ra_hir_def/src/item_scope.rs b/crates/ra_hir_def/src/item_scope.rs index fc15948adf4d..b03ba939a5a5 100644 --- a/crates/ra_hir_def/src/item_scope.rs +++ b/crates/ra_hir_def/src/item_scope.rs @@ -3,11 +3,12 @@ use hir_expand::name::Name; use once_cell::sync::Lazy; +use ra_db::CrateId; use rustc_hash::FxHashMap; use crate::{ - per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, ImplId, MacroDefId, ModuleDefId, - TraitId, + db::DefDatabase, per_ns::PerNs, visibility::Visibility, AdtId, BuiltinType, HasModule, ImplId, + Lookup, MacroDefId, ModuleDefId, TraitId, }; #[derive(Debug, Default, PartialEq, Eq)] @@ -203,4 +204,22 @@ impl ItemInNs { ItemInNs::Macros(_) => None, } } + + /// Returns the crate defining this item (or `None` if `self` is built-in). + pub fn krate(&self, db: &dyn DefDatabase) -> Option { + Some(match self { + ItemInNs::Types(did) | ItemInNs::Values(did) => match did { + ModuleDefId::ModuleId(id) => id.krate, + ModuleDefId::FunctionId(id) => id.lookup(db).module(db).krate, + ModuleDefId::AdtId(id) => id.module(db).krate, + ModuleDefId::EnumVariantId(id) => id.parent.lookup(db).container.module(db).krate, + ModuleDefId::ConstId(id) => id.lookup(db).container.module(db).krate, + ModuleDefId::StaticId(id) => id.lookup(db).container.module(db).krate, + ModuleDefId::TraitId(id) => id.lookup(db).container.module(db).krate, + ModuleDefId::TypeAliasId(id) => id.lookup(db).module(db).krate, + ModuleDefId::BuiltinType(_) => return None, + }, + ItemInNs::Macros(id) => return id.krate, + }) + } } diff --git a/crates/ra_hir_def/src/lib.rs b/crates/ra_hir_def/src/lib.rs index 5325a27608ea..edc59e5a80f4 100644 --- a/crates/ra_hir_def/src/lib.rs +++ b/crates/ra_hir_def/src/lib.rs @@ -43,6 +43,7 @@ pub mod child_by_source; pub mod visibility; pub mod find_path; +pub mod import_map; #[cfg(test)] mod test_db; @@ -416,6 +417,7 @@ pub trait AsMacroCall { fn as_call_id( &self, db: &dyn db::DefDatabase, + krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, ) -> Option; } @@ -424,13 +426,14 @@ impl AsMacroCall for InFile<&ast::MacroCall> { fn as_call_id( &self, db: &dyn db::DefDatabase, + krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, ) -> Option { let ast_id = AstId::new(self.file_id, db.ast_id_map(self.file_id).ast_id(self.value)); let h = Hygiene::new(db.upcast(), self.file_id); let path = path::ModPath::from_src(self.value.path()?, &h)?; - AstIdWithPath::new(ast_id.file_id, ast_id.value, path).as_call_id(db, resolver) + AstIdWithPath::new(ast_id.file_id, ast_id.value, path).as_call_id(db, krate, resolver) } } @@ -451,6 +454,7 @@ impl AsMacroCall for AstIdWithPath { fn as_call_id( &self, db: &dyn db::DefDatabase, + krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, ) -> Option { let def: MacroDefId = resolver(self.path.clone())?; @@ -460,13 +464,13 @@ impl AsMacroCall for AstIdWithPath { let hygiene = Hygiene::new(db.upcast(), self.ast_id.file_id); Some( - expand_eager_macro(db.upcast(), macro_call, def, &|path: ast::Path| { + expand_eager_macro(db.upcast(), krate, macro_call, def, &|path: ast::Path| { resolver(path::ModPath::from_src(path, &hygiene)?) })? .into(), ) } else { - Some(def.as_lazy_macro(db.upcast(), MacroCallKind::FnLike(self.ast_id)).into()) + Some(def.as_lazy_macro(db.upcast(), krate, MacroCallKind::FnLike(self.ast_id)).into()) } } } @@ -475,12 +479,14 @@ impl AsMacroCall for AstIdWithPath { fn as_call_id( &self, db: &dyn db::DefDatabase, + krate: CrateId, resolver: impl Fn(path::ModPath) -> Option, ) -> Option { let def = resolver(self.path.clone())?; Some( def.as_lazy_macro( db.upcast(), + krate, MacroCallKind::Attr(self.ast_id, self.path.segments.last()?.to_string()), ) .into(), diff --git a/crates/ra_hir_def/src/nameres/collector.rs b/crates/ra_hir_def/src/nameres/collector.rs index 353a31ad47a1..976e5e5850f1 100644 --- a/crates/ra_hir_def/src/nameres/collector.rs +++ b/crates/ra_hir_def/src/nameres/collector.rs @@ -571,16 +571,18 @@ impl DefCollector<'_> { return false; } - if let Some(call_id) = directive.ast_id.as_call_id(self.db, |path| { - let resolved_res = self.def_map.resolve_path_fp_with_macro( - self.db, - ResolveMode::Other, - directive.module_id, - &path, - BuiltinShadowMode::Module, - ); - resolved_res.resolved_def.take_macros() - }) { + if let Some(call_id) = + directive.ast_id.as_call_id(self.db, self.def_map.krate, |path| { + let resolved_res = self.def_map.resolve_path_fp_with_macro( + self.db, + ResolveMode::Other, + directive.module_id, + &path, + BuiltinShadowMode::Module, + ); + resolved_res.resolved_def.take_macros() + }) + { resolved.push((directive.module_id, call_id, directive.depth)); res = ReachedFixedPoint::No; return false; @@ -589,9 +591,10 @@ impl DefCollector<'_> { true }); attribute_macros.retain(|directive| { - if let Some(call_id) = directive - .ast_id - .as_call_id(self.db, |path| self.resolve_attribute_macro(&directive, &path)) + if let Some(call_id) = + directive.ast_id.as_call_id(self.db, self.def_map.krate, |path| { + self.resolve_attribute_macro(&directive, &path) + }) { resolved.push((directive.module_id, call_id, 0)); res = ReachedFixedPoint::No; @@ -957,11 +960,13 @@ impl ModCollector<'_, '_> { } // Case 2: try to resolve in legacy scope and expand macro_rules - if let Some(macro_call_id) = ast_id.as_call_id(self.def_collector.db, |path| { - path.as_ident().and_then(|name| { - self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) + if let Some(macro_call_id) = + ast_id.as_call_id(self.def_collector.db, self.def_collector.def_map.krate, |path| { + path.as_ident().and_then(|name| { + self.def_collector.def_map[self.module_id].scope.get_legacy_macro(&name) + }) }) - }) { + { self.def_collector.unexpanded_macros.push(MacroDirective { module_id: self.module_id, ast_id, diff --git a/crates/ra_hir_def/src/nameres/mod_resolution.rs b/crates/ra_hir_def/src/nameres/mod_resolution.rs index 386c5cade62d..cede4a6fc9e3 100644 --- a/crates/ra_hir_def/src/nameres/mod_resolution.rs +++ b/crates/ra_hir_def/src/nameres/mod_resolution.rs @@ -61,7 +61,7 @@ impl ModDir { }; for candidate in candidate_files.iter() { - if let Some(file_id) = db.resolve_relative_path(file_id, candidate) { + if let Some(file_id) = db.resolve_path(file_id, candidate.as_str()) { let mut root_non_dir_owner = false; let mut mod_path = RelativePathBuf::new(); if !(candidate.ends_with("mod.rs") || attr_path.is_some()) { diff --git a/crates/ra_hir_def/src/path.rs b/crates/ra_hir_def/src/path.rs index e84efe2abd04..ba16442bd517 100644 --- a/crates/ra_hir_def/src/path.rs +++ b/crates/ra_hir_def/src/path.rs @@ -76,6 +76,19 @@ impl ModPath { } } + /// Returns the number of segments in the path (counting special segments like `$crate` and + /// `super`). + pub fn len(&self) -> usize { + self.segments.len() + + match self.kind { + PathKind::Plain => 0, + PathKind::Super(i) => i as usize, + PathKind::Crate => 1, + PathKind::Abs => 0, + PathKind::DollarCrate(_) => 1, + } + } + pub fn is_ident(&self) -> bool { self.kind == PathKind::Plain && self.segments.len() == 1 } @@ -273,7 +286,7 @@ impl From for ModPath { impl Display for ModPath { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut first_segment = true; - let mut add_segment = |s| { + let mut add_segment = |s| -> fmt::Result { if !first_segment { f.write_str("::")?; } @@ -310,16 +323,16 @@ pub use hir_expand::name as __name; #[macro_export] macro_rules! __known_path { - (std::iter::IntoIterator) => {}; - (std::result::Result) => {}; - (std::ops::Range) => {}; - (std::ops::RangeFrom) => {}; - (std::ops::RangeFull) => {}; - (std::ops::RangeTo) => {}; - (std::ops::RangeToInclusive) => {}; - (std::ops::RangeInclusive) => {}; - (std::future::Future) => {}; - (std::ops::Try) => {}; + (core::iter::IntoIterator) => {}; + (core::result::Result) => {}; + (core::ops::Range) => {}; + (core::ops::RangeFrom) => {}; + (core::ops::RangeFull) => {}; + (core::ops::RangeTo) => {}; + (core::ops::RangeToInclusive) => {}; + (core::ops::RangeInclusive) => {}; + (core::future::Future) => {}; + (core::ops::Try) => {}; ($path:path) => { compile_error!("Please register your known path in the path module") }; diff --git a/crates/ra_hir_def/src/per_ns.rs b/crates/ra_hir_def/src/per_ns.rs index 6e435c8c12a7..74665c58851c 100644 --- a/crates/ra_hir_def/src/per_ns.rs +++ b/crates/ra_hir_def/src/per_ns.rs @@ -5,7 +5,7 @@ use hir_expand::MacroDefId; -use crate::{visibility::Visibility, ModuleDefId}; +use crate::{item_scope::ItemInNs, visibility::Visibility, ModuleDefId}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct PerNs { @@ -84,4 +84,12 @@ impl PerNs { macros: self.macros.or(other.macros), } } + + pub fn iter_items(self) -> impl Iterator { + self.types + .map(|it| ItemInNs::Types(it.0)) + .into_iter() + .chain(self.values.map(|it| ItemInNs::Values(it.0)).into_iter()) + .chain(self.macros.map(|it| ItemInNs::Macros(it.0)).into_iter()) + } } diff --git a/crates/ra_hir_def/src/test_db.rs b/crates/ra_hir_def/src/test_db.rs index eb83dee799eb..4581d87453b2 100644 --- a/crates/ra_hir_def/src/test_db.rs +++ b/crates/ra_hir_def/src/test_db.rs @@ -6,9 +6,8 @@ use std::{ }; use hir_expand::db::AstDatabase; -use ra_db::{ - salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath, Upcast, -}; +use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, Upcast}; +use rustc_hash::FxHashSet; use crate::db::DefDatabase; @@ -58,24 +57,12 @@ impl FileLoader for TestDB { fn file_text(&self, file_id: FileId) -> Arc { FileLoaderDelegate(self).file_text(file_id) } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + fn resolve_path(&self, anchor: FileId, path: &str) -> Option { + FileLoaderDelegate(self).resolve_path(anchor, path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } - - fn resolve_extern_path( - &self, - extern_id: ExternSourceId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path) - } } impl TestDB { diff --git a/crates/ra_hir_expand/Cargo.toml b/crates/ra_hir_expand/Cargo.toml index 2cd522766066..e5c9f3e997a7 100644 --- a/crates/ra_hir_expand/Cargo.toml +++ b/crates/ra_hir_expand/Cargo.toml @@ -10,6 +10,7 @@ doctest = false [dependencies] log = "0.4.8" either = "1.5.3" +rustc-hash = "1.0.0" ra_arena = { path = "../ra_arena" } ra_db = { path = "../ra_db" } diff --git a/crates/ra_hir_expand/src/builtin_derive.rs b/crates/ra_hir_expand/src/builtin_derive.rs index 1dc9cac6651e..26b667b55715 100644 --- a/crates/ra_hir_expand/src/builtin_derive.rs +++ b/crates/ra_hir_expand/src/builtin_derive.rs @@ -8,8 +8,7 @@ use ra_syntax::{ match_ast, }; -use crate::db::AstDatabase; -use crate::{name, quote, LazyMacroId, MacroCallId, MacroDefId, MacroDefKind}; +use crate::{db::AstDatabase, name, quote, LazyMacroId, MacroDefId, MacroDefKind}; macro_rules! register_builtin { ( $($trait:ident => $expand:ident),* ) => { @@ -156,23 +155,13 @@ fn expand_simple_derive( fn find_builtin_crate(db: &dyn AstDatabase, id: LazyMacroId) -> tt::TokenTree { // FIXME: make hygiene works for builtin derive macro // such that $crate can be used here. - - let m: MacroCallId = id.into(); - let file_id = m.as_file().original_file(db); let cg = db.crate_graph(); - let krates = db.relevant_crates(file_id); - let krate = match krates.get(0) { - Some(krate) => krate, - None => { - let tt = quote! { core }; - return tt.token_trees[0].clone(); - } - }; + let krate = db.lookup_intern_macro(id).krate; // XXX // All crates except core itself should have a dependency on core, // We detect `core` by seeing whether it doesn't have such a dependency. - let tt = if cg[*krate].dependencies.iter().any(|dep| dep.name == "core") { + let tt = if cg[krate].dependencies.iter().any(|dep| dep.name == "core") { quote! { core } } else { quote! { crate } @@ -264,10 +253,12 @@ fn partial_ord_expand( #[cfg(test)] mod tests { - use super::*; - use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc}; use name::{known, Name}; - use ra_db::{fixture::WithFixture, SourceDatabase}; + use ra_db::{fixture::WithFixture, CrateId, SourceDatabase}; + + use crate::{test_db::TestDB, AstId, MacroCallId, MacroCallKind, MacroCallLoc}; + + use super::*; fn expand_builtin_derive(s: &str, name: Name) -> String { let def = find_builtin_derive(&name).unwrap(); @@ -291,7 +282,11 @@ mod tests { let attr_id = AstId::new(file_id.into(), ast_id_map.ast_id(&items[0])); - let loc = MacroCallLoc { def, kind: MacroCallKind::Attr(attr_id, name.to_string()) }; + let loc = MacroCallLoc { + def, + krate: CrateId(0), + kind: MacroCallKind::Attr(attr_id, name.to_string()), + }; let id: MacroCallId = db.intern_macro(loc).into(); let parsed = db.parse_or_expand(id.as_file()).unwrap(); diff --git a/crates/ra_hir_expand/src/builtin_macro.rs b/crates/ra_hir_expand/src/builtin_macro.rs index 3bce8f673fd1..b50eb347c8e4 100644 --- a/crates/ra_hir_expand/src/builtin_macro.rs +++ b/crates/ra_hir_expand/src/builtin_macro.rs @@ -1,15 +1,14 @@ //! Builtin macro -use crate::db::AstDatabase; use crate::{ - ast::{self, AstToken, HasStringValue}, - name, AstId, CrateId, MacroDefId, MacroDefKind, TextSize, + db::AstDatabase, name, quote, AstId, CrateId, EagerMacroId, LazyMacroId, MacroCallId, + MacroDefId, MacroDefKind, TextSize, }; -use crate::{quote, EagerMacroId, LazyMacroId, MacroCallId}; use either::Either; use mbe::parse_to_token_tree; -use ra_db::{FileId, RelativePath}; +use ra_db::FileId; use ra_parser::FragmentKind; +use ra_syntax::ast::{self, AstToken, HasStringValue}; macro_rules! register_builtin { ( LAZY: $(($name:ident, $kind: ident) => $expand:ident),* , EAGER: $(($e_name:ident, $e_kind: ident) => $e_expand:ident),* ) => { @@ -295,19 +294,13 @@ fn concat_expand( fn relative_file(db: &dyn AstDatabase, call_id: MacroCallId, path: &str) -> Option { let call_site = call_id.as_file().original_file(db); - - // Handle trivial case - if let Some(res) = db.resolve_relative_path(call_site, &RelativePath::new(&path)) { - // Prevent include itself - return if res == call_site { None } else { Some(res) }; + let res = db.resolve_path(call_site, path)?; + // Prevent include itself + if res == call_site { + None + } else { + Some(res) } - - // Extern paths ? - let krate = *db.relevant_crates(call_site).get(0)?; - let (extern_source_id, relative_file) = - db.crate_graph()[krate].extern_source.extern_path(path)?; - - db.resolve_extern_path(extern_source_id, &relative_file) } fn parse_string(tt: &tt::Subtree) -> Result { @@ -339,10 +332,7 @@ fn include_expand( } fn get_env_inner(db: &dyn AstDatabase, arg_id: EagerMacroId, key: &str) -> Option { - let call_id: MacroCallId = arg_id.into(); - let original_file = call_id.as_file().original_file(db); - - let krate = *db.relevant_crates(original_file).get(0)?; + let krate = db.lookup_intern_eager_expansion(arg_id).krate; db.crate_graph()[krate].env.get(key) } @@ -401,6 +391,7 @@ mod tests { let expander = find_by_name(¯o_calls[0].name().unwrap().as_name()).unwrap(); + let krate = CrateId(0); let file_id = match expander { Either::Left(expander) => { // the first one should be a macro_rules @@ -413,6 +404,7 @@ mod tests { let loc = MacroCallLoc { def, + krate, kind: MacroCallKind::FnLike(AstId::new( file_id.into(), ast_id_map.ast_id(¯o_calls[1]), @@ -425,7 +417,7 @@ mod tests { Either::Right(expander) => { // the first one should be a macro_rules let def = MacroDefId { - krate: Some(CrateId(0)), + krate: Some(krate), ast_id: Some(AstId::new(file_id.into(), ast_id_map.ast_id(¯o_calls[0]))), kind: MacroDefKind::BuiltInEager(expander), local_inner: false, @@ -439,6 +431,7 @@ mod tests { def, fragment: FragmentKind::Expr, subtree: Arc::new(parsed_args.clone()), + krate, file_id: file_id.into(), } }); @@ -448,6 +441,7 @@ mod tests { def, fragment, subtree: Arc::new(subtree), + krate, file_id: file_id.into(), }; diff --git a/crates/ra_hir_expand/src/eager.rs b/crates/ra_hir_expand/src/eager.rs index 932f47c30989..302d2b3e099c 100644 --- a/crates/ra_hir_expand/src/eager.rs +++ b/crates/ra_hir_expand/src/eager.rs @@ -25,12 +25,14 @@ use crate::{ EagerCallLoc, EagerMacroId, InFile, MacroCallId, MacroCallKind, MacroDefId, MacroDefKind, }; +use ra_db::CrateId; use ra_parser::FragmentKind; use ra_syntax::{algo::SyntaxRewriter, SyntaxNode}; use std::sync::Arc; pub fn expand_eager_macro( db: &dyn AstDatabase, + krate: CrateId, macro_call: InFile, def: MacroDefId, resolver: &dyn Fn(ast::Path) -> Option, @@ -47,6 +49,7 @@ pub fn expand_eager_macro( def, fragment: FragmentKind::Expr, subtree: Arc::new(parsed_args.clone()), + krate, file_id: macro_call.file_id, } }); @@ -56,14 +59,20 @@ pub fn expand_eager_macro( let result = eager_macro_recur( db, InFile::new(arg_file_id.as_file(), parsed_args.syntax_node()), + krate, resolver, )?; let subtree = to_subtree(&result)?; if let MacroDefKind::BuiltInEager(eager) = def.kind { let (subtree, fragment) = eager.expand(db, arg_id, &subtree).ok()?; - let eager = - EagerCallLoc { def, fragment, subtree: Arc::new(subtree), file_id: macro_call.file_id }; + let eager = EagerCallLoc { + def, + fragment, + subtree: Arc::new(subtree), + krate, + file_id: macro_call.file_id, + }; Some(db.intern_eager_expansion(eager)) } else { @@ -81,11 +90,12 @@ fn lazy_expand( db: &dyn AstDatabase, def: &MacroDefId, macro_call: InFile, + krate: CrateId, ) -> Option> { let ast_id = db.ast_id_map(macro_call.file_id).ast_id(¯o_call.value); let id: MacroCallId = - def.as_lazy_macro(db, MacroCallKind::FnLike(macro_call.with_value(ast_id))).into(); + def.as_lazy_macro(db, krate, MacroCallKind::FnLike(macro_call.with_value(ast_id))).into(); db.parse_or_expand(id.as_file()).map(|node| InFile::new(id.as_file(), node)) } @@ -93,6 +103,7 @@ fn lazy_expand( fn eager_macro_recur( db: &dyn AstDatabase, curr: InFile, + krate: CrateId, macro_resolver: &dyn Fn(ast::Path) -> Option, ) -> Option { let original = curr.value.clone(); @@ -105,18 +116,23 @@ fn eager_macro_recur( let def: MacroDefId = macro_resolver(child.path()?)?; let insert = match def.kind { MacroDefKind::BuiltInEager(_) => { - let id: MacroCallId = - expand_eager_macro(db, curr.with_value(child.clone()), def, macro_resolver)? - .into(); + let id: MacroCallId = expand_eager_macro( + db, + krate, + curr.with_value(child.clone()), + def, + macro_resolver, + )? + .into(); db.parse_or_expand(id.as_file())? } MacroDefKind::Declarative | MacroDefKind::BuiltIn(_) | MacroDefKind::BuiltInDerive(_) | MacroDefKind::CustomDerive(_) => { - let expanded = lazy_expand(db, &def, curr.with_value(child.clone()))?; + let expanded = lazy_expand(db, &def, curr.with_value(child.clone()), krate)?; // replace macro inside - eager_macro_recur(db, expanded, macro_resolver)? + eager_macro_recur(db, expanded, krate, macro_resolver)? } }; diff --git a/crates/ra_hir_expand/src/lib.rs b/crates/ra_hir_expand/src/lib.rs index f440c073ba8a..5eac2605b9c1 100644 --- a/crates/ra_hir_expand/src/lib.rs +++ b/crates/ra_hir_expand/src/lib.rs @@ -209,8 +209,13 @@ pub struct MacroDefId { } impl MacroDefId { - pub fn as_lazy_macro(self, db: &dyn db::AstDatabase, kind: MacroCallKind) -> LazyMacroId { - db.intern_macro(MacroCallLoc { def: self, kind }) + pub fn as_lazy_macro( + self, + db: &dyn db::AstDatabase, + krate: CrateId, + kind: MacroCallKind, + ) -> LazyMacroId { + db.intern_macro(MacroCallLoc { def: self, krate, kind }) } } @@ -227,6 +232,7 @@ pub enum MacroDefKind { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct MacroCallLoc { pub(crate) def: MacroDefId, + pub(crate) krate: CrateId, pub(crate) kind: MacroCallKind, } @@ -274,6 +280,7 @@ pub struct EagerCallLoc { pub(crate) def: MacroDefId, pub(crate) fragment: FragmentKind, pub(crate) subtree: Arc, + pub(crate) krate: CrateId, pub(crate) file_id: HirFileId, } diff --git a/crates/ra_hir_expand/src/name.rs b/crates/ra_hir_expand/src/name.rs index ea495cb11a2b..660bdfe3365b 100644 --- a/crates/ra_hir_expand/src/name.rs +++ b/crates/ra_hir_expand/src/name.rs @@ -153,6 +153,7 @@ pub mod known { str, // Special names macro_rules, + doc, // Components of known path (value or mod name) std, core, diff --git a/crates/ra_hir_expand/src/test_db.rs b/crates/ra_hir_expand/src/test_db.rs index c1fb762debf6..09fc18c360f5 100644 --- a/crates/ra_hir_expand/src/test_db.rs +++ b/crates/ra_hir_expand/src/test_db.rs @@ -5,7 +5,8 @@ use std::{ sync::{Arc, Mutex}, }; -use ra_db::{salsa, CrateId, ExternSourceId, FileId, FileLoader, FileLoaderDelegate, RelativePath}; +use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate}; +use rustc_hash::FxHashSet; #[salsa::database( ra_db::SourceDatabaseExtStorage, @@ -41,21 +42,10 @@ impl FileLoader for TestDB { fn file_text(&self, file_id: FileId) -> Arc { FileLoaderDelegate(self).file_text(file_id) } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + fn resolve_path(&self, anchor: FileId, path: &str) -> Option { + FileLoaderDelegate(self).resolve_path(anchor, path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } - fn resolve_extern_path( - &self, - anchor: ExternSourceId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_extern_path(anchor, relative_path) - } } diff --git a/crates/ra_hir_ty/Cargo.toml b/crates/ra_hir_ty/Cargo.toml index 4b8dcdc07ff5..112fcd07e7c6 100644 --- a/crates/ra_hir_ty/Cargo.toml +++ b/crates/ra_hir_ty/Cargo.toml @@ -27,8 +27,8 @@ test_utils = { path = "../test_utils" } scoped-tls = "1" -chalk-solve = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" } -chalk-ir = { git = "https://github.com/rust-lang/chalk.git", rev = "329b7f3fdd2431ed6f6778cde53f22374c7d094c" } +chalk-solve = "0.11" +chalk-ir = "0.11" [dev-dependencies] insta = "0.16.0" diff --git a/crates/ra_hir_ty/src/db.rs b/crates/ra_hir_ty/src/db.rs index 0a8bb24ac239..bf71d38d6b23 100644 --- a/crates/ra_hir_ty/src/db.rs +++ b/crates/ra_hir_ty/src/db.rs @@ -3,8 +3,8 @@ use std::sync::Arc; use hir_def::{ - db::DefDatabase, DefWithBodyId, GenericDefId, ImplId, LocalFieldId, TraitId, TypeParamId, - VariantId, + db::DefDatabase, DefWithBodyId, FunctionId, GenericDefId, ImplId, LocalFieldId, TraitId, + TypeParamId, VariantId, }; use ra_arena::map::ArenaMap; use ra_db::{impl_intern_key, salsa, CrateId, Upcast}; @@ -13,8 +13,8 @@ use ra_prof::profile; use crate::{ method_resolution::{CrateImplDefs, TyFingerprint}, traits::{chalk, AssocTyValue, Impl}, - Binders, CallableDef, GenericPredicate, InferenceResult, PolyFnSig, Substs, TraitRef, Ty, - TyDefId, TypeCtor, ValueTyDefId, + Binders, CallableDef, GenericPredicate, InferenceResult, OpaqueTyId, PolyFnSig, + ReturnTypeImplTraits, Substs, TraitRef, Ty, TyDefId, TypeCtor, ValueTyDefId, }; use hir_expand::name::Name; @@ -48,6 +48,12 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::invoke(crate::callable_item_sig)] fn callable_item_signature(&self, def: CallableDef) -> PolyFnSig; + #[salsa::invoke(crate::lower::return_type_impl_traits)] + fn return_type_impl_traits( + &self, + def: FunctionId, + ) -> Option>>; + #[salsa::invoke(crate::lower::generic_predicates_for_param_query)] #[salsa::cycle(crate::lower::generic_predicates_for_param_recover)] fn generic_predicates_for_param( @@ -80,6 +86,8 @@ pub trait HirDatabase: DefDatabase + Upcast { #[salsa::interned] fn intern_type_param_id(&self, param_id: TypeParamId) -> GlobalTypeParamId; #[salsa::interned] + fn intern_impl_trait_id(&self, id: OpaqueTyId) -> InternedOpaqueTyId; + #[salsa::interned] fn intern_chalk_impl(&self, impl_: Impl) -> crate::traits::GlobalImplId; #[salsa::interned] fn intern_assoc_ty_value(&self, assoc_ty_value: AssocTyValue) -> crate::traits::AssocTyValueId; @@ -142,3 +150,7 @@ fn hir_database_is_object_safe() { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct GlobalTypeParamId(salsa::InternId); impl_intern_key!(GlobalTypeParamId); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct InternedOpaqueTyId(salsa::InternId); +impl_intern_key!(InternedOpaqueTyId); diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs index b9c4d2e89783..3c97e13545e3 100644 --- a/crates/ra_hir_ty/src/display.rs +++ b/crates/ra_hir_ty/src/display.rs @@ -4,7 +4,7 @@ use std::fmt; use crate::{ db::HirDatabase, utils::generics, ApplicationTy, CallableDef, FnSig, GenericPredicate, - Obligation, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, + Obligation, OpaqueTyId, ProjectionTy, Substs, TraitRef, Ty, TypeCtor, }; use hir_def::{ find_path, generics::TypeParamProvenance, item_scope::ItemInNs, AdtId, AssocContainerId, @@ -359,6 +359,21 @@ impl HirDisplay for ApplicationTy { write!(f, ">")?; } } + TypeCtor::OpaqueType(opaque_ty_id) => { + let bounds = match opaque_ty_id { + OpaqueTyId::ReturnTypeImplTrait(func, idx) => { + let datas = + f.db.return_type_impl_traits(func).expect("impl trait id without data"); + let data = (*datas) + .as_ref() + .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + data.clone().subst(&self.parameters) + } + }; + write!(f, "impl ")?; + write_bounds_like_dyn_trait(&bounds.value, f)?; + // FIXME: it would maybe be good to distinguish this from the alias type (when debug printing), and to show the substitution + } TypeCtor::Closure { .. } => { let sig = self.parameters[0].callable_sig(f.db); if let Some(sig) = sig { @@ -427,14 +442,24 @@ impl HirDisplay for Ty { } } Ty::Bound(idx) => write!(f, "?{}.{}", idx.debruijn.depth(), idx.index)?, - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { - match self { - Ty::Dyn(_) => write!(f, "dyn ")?, - Ty::Opaque(_) => write!(f, "impl ")?, - _ => unreachable!(), - }; + Ty::Dyn(predicates) => { + write!(f, "dyn ")?; write_bounds_like_dyn_trait(predicates, f)?; } + Ty::Opaque(opaque_ty) => { + let bounds = match opaque_ty.opaque_ty_id { + OpaqueTyId::ReturnTypeImplTrait(func, idx) => { + let datas = + f.db.return_type_impl_traits(func).expect("impl trait id without data"); + let data = (*datas) + .as_ref() + .map(|rpit| rpit.impl_traits[idx as usize].bounds.clone()); + data.clone().subst(&opaque_ty.parameters) + } + }; + write!(f, "impl ")?; + write_bounds_like_dyn_trait(&bounds.value, f)?; + } Ty::Unknown => write!(f, "{{unknown}}")?, Ty::Infer(..) => write!(f, "_")?, } diff --git a/crates/ra_hir_ty/src/expr.rs b/crates/ra_hir_ty/src/expr.rs index f04968e145bd..7db928dded28 100644 --- a/crates/ra_hir_ty/src/expr.rs +++ b/crates/ra_hir_ty/src/expr.rs @@ -226,17 +226,19 @@ impl<'a, 'b> ExprValidator<'a, 'b> { None => return, }; - let std_result_path = path![std::result::Result]; + let core_result_path = path![core::result::Result]; let resolver = self.func.resolver(db.upcast()); - let std_result_enum = match resolver.resolve_known_enum(db.upcast(), &std_result_path) { + let core_result_enum = match resolver.resolve_known_enum(db.upcast(), &core_result_path) { Some(it) => it, _ => return, }; - let std_result_ctor = TypeCtor::Adt(AdtId::EnumId(std_result_enum)); + let core_result_ctor = TypeCtor::Adt(AdtId::EnumId(core_result_enum)); let params = match &mismatch.expected { - Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &std_result_ctor => parameters, + Ty::Apply(ApplicationTy { ctor, parameters }) if ctor == &core_result_ctor => { + parameters + } _ => return, }; diff --git a/crates/ra_hir_ty/src/infer.rs b/crates/ra_hir_ty/src/infer.rs index dc77e88e50b2..3719f76a6e21 100644 --- a/crates/ra_hir_ty/src/infer.rs +++ b/crates/ra_hir_ty/src/infer.rs @@ -39,8 +39,7 @@ use ra_syntax::SmolStr; use super::{ primitive::{FloatTy, IntTy}, traits::{Guidance, Obligation, ProjectionPredicate, Solution}, - ApplicationTy, GenericPredicate, InEnvironment, ProjectionTy, Substs, TraitEnvironment, - TraitRef, Ty, TypeCtor, TypeWalk, Uncertain, + InEnvironment, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, }; use crate::{ db::HirDatabase, infer::diagnostics::InferenceDiagnostic, lower::ImplTraitLoweringMode, @@ -312,12 +311,6 @@ impl<'a> InferenceContext<'a> { fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty { match ty { Ty::Unknown => self.table.new_type_var(), - Ty::Apply(ApplicationTy { ctor: TypeCtor::Int(Uncertain::Unknown), .. }) => { - self.table.new_integer_var() - } - Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(Uncertain::Unknown), .. }) => { - self.table.new_float_var() - } _ => ty, } } @@ -383,25 +376,6 @@ impl<'a> InferenceContext<'a> { ) -> Ty { match assoc_ty { Some(res_assoc_ty) => { - // FIXME: - // Check if inner_ty is is `impl Trait` and contained input TypeAlias id - // this is a workaround while Chalk assoc type projection doesn't always work yet, - // but once that is fixed I don't think we should keep this - // (we'll probably change how associated types are resolved anyway) - if let Ty::Opaque(ref predicates) = inner_ty { - for p in predicates.iter() { - if let GenericPredicate::Projection(projection) = p { - if projection.projection_ty.associated_ty == res_assoc_ty { - if let ty_app!(_, params) = &projection.ty { - if params.len() == 0 { - return projection.ty.clone(); - } - } - } - } - } - } - let ty = self.table.new_type_var(); let builder = Substs::build_for_def(self.db, res_assoc_ty) .push(inner_ty) @@ -458,13 +432,13 @@ impl<'a> InferenceContext<'a> { }; return match resolution { TypeNs::AdtId(AdtId::StructId(strukt)) => { - let substs = Ty::substs_from_path(&ctx, path, strukt.into()); + let substs = Ty::substs_from_path(&ctx, path, strukt.into(), true); let ty = self.db.ty(strukt.into()); let ty = self.insert_type_vars(ty.subst(&substs)); forbid_unresolved_segments((ty, Some(strukt.into())), unresolved) } TypeNs::EnumVariantId(var) => { - let substs = Ty::substs_from_path(&ctx, path, var.into()); + let substs = Ty::substs_from_path(&ctx, path, var.into(), true); let ty = self.db.ty(var.parent.into()); let ty = self.insert_type_vars(ty.subst(&substs)); forbid_unresolved_segments((ty, Some(var.into())), unresolved) @@ -581,13 +555,13 @@ impl<'a> InferenceContext<'a> { } fn resolve_into_iter_item(&self) -> Option { - let path = path![std::iter::IntoIterator]; + let path = path![core::iter::IntoIterator]; let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; self.db.trait_data(trait_).associated_type_by_name(&name![Item]) } fn resolve_ops_try_ok(&self) -> Option { - let path = path![std::ops::Try]; + let path = path![core::ops::Try]; let trait_ = self.resolver.resolve_known_trait(self.db.upcast(), &path)?; self.db.trait_data(trait_).associated_type_by_name(&name![Ok]) } @@ -613,37 +587,37 @@ impl<'a> InferenceContext<'a> { } fn resolve_range_full(&self) -> Option { - let path = path![std::ops::RangeFull]; + let path = path![core::ops::RangeFull]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range(&self) -> Option { - let path = path![std::ops::Range]; + let path = path![core::ops::Range]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_inclusive(&self) -> Option { - let path = path![std::ops::RangeInclusive]; + let path = path![core::ops::RangeInclusive]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_from(&self) -> Option { - let path = path![std::ops::RangeFrom]; + let path = path![core::ops::RangeFrom]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_to(&self) -> Option { - let path = path![std::ops::RangeTo]; + let path = path![core::ops::RangeTo]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } fn resolve_range_to_inclusive(&self) -> Option { - let path = path![std::ops::RangeToInclusive]; + let path = path![core::ops::RangeToInclusive]; let struct_ = self.resolver.resolve_known_struct(self.db.upcast(), &path)?; Some(struct_.into()) } @@ -683,8 +657,8 @@ impl InferTy { fn fallback_value(self) -> Ty { match self { InferTy::TypeVar(..) => Ty::Unknown, - InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::i32()))), - InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(Uncertain::Known(FloatTy::f64()))), + InferTy::IntVar(..) => Ty::simple(TypeCtor::Int(IntTy::i32())), + InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(FloatTy::f64())), InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never), } } diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 4a98e2debff5..9fd310f69a79 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -18,7 +18,7 @@ use crate::{ traits::InEnvironment, utils::{generics, variant_data, Generics}, ApplicationTy, Binders, CallableDef, InferTy, IntTy, Mutability, Obligation, Rawness, Substs, - TraitRef, Ty, TypeCtor, Uncertain, + TraitRef, Ty, TypeCtor, }; use super::{ @@ -426,15 +426,7 @@ impl<'a> InferenceContext<'a> { match &inner_ty { // Fast path for builtins Ty::Apply(ApplicationTy { - ctor: - TypeCtor::Int(Uncertain::Known(IntTy { - signedness: Signedness::Signed, - .. - })), - .. - }) - | Ty::Apply(ApplicationTy { - ctor: TypeCtor::Int(Uncertain::Unknown), + ctor: TypeCtor::Int(IntTy { signedness: Signedness::Signed, .. }), .. }) | Ty::Apply(ApplicationTy { ctor: TypeCtor::Float(_), .. }) @@ -577,9 +569,7 @@ impl<'a> InferenceContext<'a> { ); self.infer_expr( *repeat, - &Expectation::has_type(Ty::simple(TypeCtor::Int(Uncertain::Known( - IntTy::usize(), - )))), + &Expectation::has_type(Ty::simple(TypeCtor::Int(IntTy::usize()))), ); } } @@ -592,13 +582,19 @@ impl<'a> InferenceContext<'a> { Ty::apply_one(TypeCtor::Ref(Mutability::Shared), Ty::simple(TypeCtor::Str)) } Literal::ByteString(..) => { - let byte_type = Ty::simple(TypeCtor::Int(Uncertain::Known(IntTy::u8()))); + let byte_type = Ty::simple(TypeCtor::Int(IntTy::u8())); let array_type = Ty::apply_one(TypeCtor::Array, byte_type); Ty::apply_one(TypeCtor::Ref(Mutability::Shared), array_type) } Literal::Char(..) => Ty::simple(TypeCtor::Char), - Literal::Int(_v, ty) => Ty::simple(TypeCtor::Int((*ty).into())), - Literal::Float(_v, ty) => Ty::simple(TypeCtor::Float((*ty).into())), + Literal::Int(_v, ty) => match ty { + Some(int_ty) => Ty::simple(TypeCtor::Int((*int_ty).into())), + None => self.table.new_integer_var(), + }, + Literal::Float(_v, ty) => match ty { + Some(float_ty) => Ty::simple(TypeCtor::Float((*float_ty).into())), + None => self.table.new_float_var(), + }, }, }; // use a new type variable if we got Ty::Unknown here diff --git a/crates/ra_hir_ty/src/infer/path.rs b/crates/ra_hir_ty/src/infer/path.rs index 1c2e56fb0d82..1ad0d8397166 100644 --- a/crates/ra_hir_ty/src/infer/path.rs +++ b/crates/ra_hir_ty/src/infer/path.rs @@ -95,7 +95,7 @@ impl<'a> InferenceContext<'a> { // self_subst is just for the parent let parent_substs = self_subst.unwrap_or_else(Substs::empty); let ctx = crate::lower::TyLoweringContext::new(self.db, &self.resolver); - let substs = Ty::substs_from_path(&ctx, path, typable); + let substs = Ty::substs_from_path(&ctx, path, typable, true); let full_substs = Substs::builder(substs.len()) .use_parent_substs(&parent_substs) .fill(substs.0[parent_substs.len()..].iter().cloned()) @@ -141,6 +141,7 @@ impl<'a> InferenceContext<'a> { def, resolved_segment, remaining_segments_for_ty, + true, ); if let Ty::Unknown = ty { return None; diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index 9fa8d3bdc3f4..2b9372b4b1bc 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -58,7 +58,7 @@ use ra_db::{impl_intern_key, salsa, CrateId}; use crate::{ db::HirDatabase, - primitive::{FloatTy, IntTy, Uncertain}, + primitive::{FloatTy, IntTy}, utils::{generics, make_mut_slice, Generics}, }; use display::HirDisplay; @@ -87,10 +87,10 @@ pub enum TypeCtor { Char, /// A primitive integer type. For example, `i32`. - Int(Uncertain), + Int(IntTy), /// A primitive floating-point type. For example, `f64`. - Float(Uncertain), + Float(FloatTy), /// Structures, enumerations and unions. Adt(AdtId), @@ -147,6 +147,12 @@ pub enum TypeCtor { /// an **application type** like `(Iterator::Item)`. AssociatedType(TypeAliasId), + /// This represents a placeholder for an opaque type in situations where we + /// don't know the hidden type (i.e. currently almost always). This is + /// analogous to the `AssociatedType` type constructor. As with that one, + /// these are only produced by Chalk. + OpaqueType(OpaqueTyId), + /// The type of a specific closure. /// /// The closure signature is stored in a `FnPtr` type in the first type @@ -194,6 +200,14 @@ impl TypeCtor { let generic_params = generics(db.upcast(), type_alias.into()); generic_params.len() } + TypeCtor::OpaqueType(opaque_ty_id) => { + match opaque_ty_id { + OpaqueTyId::ReturnTypeImplTrait(func, _) => { + let generic_params = generics(db.upcast(), func.into()); + generic_params.len() + } + } + } TypeCtor::FnPtr { num_args } => num_args as usize + 1, TypeCtor::Tuple { cardinality } => cardinality as usize, } @@ -220,6 +234,11 @@ impl TypeCtor { TypeCtor::AssociatedType(type_alias) => { Some(type_alias.lookup(db.upcast()).module(db.upcast()).krate) } + TypeCtor::OpaqueType(opaque_ty_id) => match opaque_ty_id { + OpaqueTyId::ReturnTypeImplTrait(func, _) => { + Some(func.lookup(db.upcast()).module(db.upcast()).krate) + } + }, } } @@ -241,6 +260,7 @@ impl TypeCtor { TypeCtor::Adt(adt) => Some(adt.into()), TypeCtor::FnDef(callable) => Some(callable.into()), TypeCtor::AssociatedType(type_alias) => Some(type_alias.into()), + TypeCtor::OpaqueType(_impl_trait_id) => None, } } } @@ -254,6 +274,12 @@ pub struct ApplicationTy { pub parameters: Substs, } +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct OpaqueTy { + pub opaque_ty_id: OpaqueTyId, + pub parameters: Substs, +} + /// A "projection" type corresponds to an (unnormalized) /// projection like `>::Foo`. Note that the /// trait and all its parameters are fully known. @@ -308,6 +334,12 @@ pub enum Ty { /// trait and all its parameters are fully known. Projection(ProjectionTy), + /// An opaque type (`impl Trait`). + /// + /// This is currently only used for return type impl trait; each instance of + /// `impl Trait` in a return type gets its own ID. + Opaque(OpaqueTy), + /// A placeholder for a type parameter; for example, `T` in `fn f(x: T) /// {}` when we're type-checking the body of that function. In this /// situation, we know this stands for *some* type, but don't know the exact @@ -332,12 +364,6 @@ pub enum Ty { /// didn't seem worth the overhead yet. Dyn(Arc<[GenericPredicate]>), - /// An opaque type (`impl Trait`). - /// - /// The predicates are quantified over the `Self` type; see `Ty::Dyn` for - /// more. - Opaque(Arc<[GenericPredicate]>), - /// A placeholder for a type which could not be computed; this is propagated /// to avoid useless error messages. Doubles as a placeholder where type /// variables are inserted before type checking, since we want to try to @@ -490,7 +516,7 @@ impl Deref for Substs { } } -#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] pub struct Binders { pub num_binders: usize, pub value: T, @@ -534,6 +560,20 @@ impl Binders { } } +impl TypeWalk for Binders { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + self.value.walk(f); + } + + fn walk_mut_binders( + &mut self, + f: &mut impl FnMut(&mut Ty, DebruijnIndex), + binders: DebruijnIndex, + ) { + self.value.walk_mut_binders(f, binders.shifted_in()) + } +} + /// A trait with type parameters. This includes the `Self`, so this represents a concrete type implementing the trait. /// Name to be bikeshedded: TraitBound? TraitImplements? #[derive(Clone, PartialEq, Eq, Debug, Hash)] @@ -947,11 +987,16 @@ impl TypeWalk for Ty { t.walk(f); } } - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { + Ty::Dyn(predicates) => { for p in predicates.iter() { p.walk(f); } } + Ty::Opaque(o_ty) => { + for t in o_ty.parameters.iter() { + t.walk(f); + } + } Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} } f(self); @@ -969,13 +1014,48 @@ impl TypeWalk for Ty { Ty::Projection(p_ty) => { p_ty.parameters.walk_mut_binders(f, binders); } - Ty::Dyn(predicates) | Ty::Opaque(predicates) => { + Ty::Dyn(predicates) => { for p in make_mut_slice(predicates) { p.walk_mut_binders(f, binders.shifted_in()); } } + Ty::Opaque(o_ty) => { + o_ty.parameters.walk_mut_binders(f, binders); + } Ty::Placeholder { .. } | Ty::Bound(_) | Ty::Infer(_) | Ty::Unknown => {} } f(self, binders); } } + +impl TypeWalk for Vec { + fn walk(&self, f: &mut impl FnMut(&Ty)) { + for t in self { + t.walk(f); + } + } + fn walk_mut_binders( + &mut self, + f: &mut impl FnMut(&mut Ty, DebruijnIndex), + binders: DebruijnIndex, + ) { + for t in self { + t.walk_mut_binders(f, binders); + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub enum OpaqueTyId { + ReturnTypeImplTrait(hir_def::FunctionId, u16), +} + +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub struct ReturnTypeImplTraits { + pub(crate) impl_traits: Vec, +} + +#[derive(Clone, PartialEq, Eq, Debug, Hash)] +pub(crate) struct ReturnTypeImplTrait { + pub(crate) bounds: Binders>, +} diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index 35ac86a461eb..42713928f451 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs @@ -21,8 +21,10 @@ use hir_def::{ HasModule, ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, VariantId, }; +use hir_expand::name::Name; use ra_arena::map::ArenaMap; use ra_db::CrateId; +use test_utils::mark; use crate::{ db::HirDatabase, @@ -31,10 +33,10 @@ use crate::{ all_super_trait_refs, associated_type_by_name_including_super_traits, generics, make_mut_slice, variant_data, }, - Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, PolyFnSig, ProjectionPredicate, - ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, + Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, OpaqueTy, OpaqueTyId, PolyFnSig, + ProjectionPredicate, ProjectionTy, ReturnTypeImplTrait, ReturnTypeImplTraits, Substs, + TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, }; -use hir_expand::name::Name; #[derive(Debug)] pub struct TyLoweringContext<'a> { @@ -47,7 +49,16 @@ pub struct TyLoweringContext<'a> { /// possible currently, so this should be fine for now. pub type_param_mode: TypeParamLoweringMode, pub impl_trait_mode: ImplTraitLoweringMode, - pub impl_trait_counter: std::cell::Cell, + impl_trait_counter: std::cell::Cell, + /// When turning `impl Trait` into opaque types, we have to collect the + /// bounds at the same time to get the IDs correct (without becoming too + /// complicated). I don't like using interior mutability (as for the + /// counter), but I've tried and failed to make the lifetimes work for + /// passing around a `&mut TyLoweringContext`. The core problem is that + /// we're grouping the mutable data (the counter and this field) together + /// with the immutable context (the references to the DB and resolver). + /// Splitting this up would be a possible fix. + opaque_type_data: std::cell::RefCell>, } impl<'a> TyLoweringContext<'a> { @@ -56,7 +67,34 @@ impl<'a> TyLoweringContext<'a> { let impl_trait_mode = ImplTraitLoweringMode::Disallowed; let type_param_mode = TypeParamLoweringMode::Placeholder; let in_binders = DebruijnIndex::INNERMOST; - Self { db, resolver, in_binders, impl_trait_mode, impl_trait_counter, type_param_mode } + let opaque_type_data = std::cell::RefCell::new(Vec::new()); + Self { + db, + resolver, + in_binders, + impl_trait_mode, + impl_trait_counter, + type_param_mode, + opaque_type_data, + } + } + + pub fn with_debruijn( + &self, + debruijn: DebruijnIndex, + f: impl FnOnce(&TyLoweringContext) -> T, + ) -> T { + let opaque_ty_data_vec = self.opaque_type_data.replace(Vec::new()); + let new_ctx = Self { + in_binders: debruijn, + impl_trait_counter: std::cell::Cell::new(self.impl_trait_counter.get()), + opaque_type_data: std::cell::RefCell::new(opaque_ty_data_vec), + ..*self + }; + let result = f(&new_ctx); + self.impl_trait_counter.set(new_ctx.impl_trait_counter.get()); + self.opaque_type_data.replace(new_ctx.opaque_type_data.into_inner()); + result } pub fn with_shifted_in( @@ -64,18 +102,7 @@ impl<'a> TyLoweringContext<'a> { debruijn: DebruijnIndex, f: impl FnOnce(&TyLoweringContext) -> T, ) -> T { - let new_ctx = Self { - in_binders: self.in_binders.shifted_in_from(debruijn), - impl_trait_counter: std::cell::Cell::new(self.impl_trait_counter.get()), - ..*self - }; - let result = f(&new_ctx); - self.impl_trait_counter.set(new_ctx.impl_trait_counter.get()); - result - } - - pub fn shifted_in(self, debruijn: DebruijnIndex) -> Self { - Self { in_binders: self.in_binders.shifted_in_from(debruijn), ..self } + self.with_debruijn(self.in_binders.shifted_in_from(debruijn), f) } pub fn with_impl_trait_mode(self, impl_trait_mode: ImplTraitLoweringMode) -> Self { @@ -167,20 +194,44 @@ impl Ty { TypeRef::ImplTrait(bounds) => { match ctx.impl_trait_mode { ImplTraitLoweringMode::Opaque => { - let self_ty = Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, 0)); - let predicates = ctx.with_shifted_in(DebruijnIndex::ONE, |ctx| { - bounds - .iter() - .flat_map(|b| { - GenericPredicate::from_type_bound(ctx, b, self_ty.clone()) - }) - .collect() - }); - Ty::Opaque(predicates) + let idx = ctx.impl_trait_counter.get(); + ctx.impl_trait_counter.set(idx + 1); + + assert!(idx as usize == ctx.opaque_type_data.borrow().len()); + // this dance is to make sure the data is in the right + // place even if we encounter more opaque types while + // lowering the bounds + ctx.opaque_type_data + .borrow_mut() + .push(ReturnTypeImplTrait { bounds: Binders::new(1, Vec::new()) }); + // We don't want to lower the bounds inside the binders + // we're currently in, because they don't end up inside + // those binders. E.g. when we have `impl Trait>`, the `impl OtherTrait` can't refer + // to the self parameter from `impl Trait`, and the + // bounds aren't actually stored nested within each + // other, but separately. So if the `T` refers to a type + // parameter of the outer function, it's just one binder + // away instead of two. + let actual_opaque_type_data = ctx + .with_debruijn(DebruijnIndex::INNERMOST, |ctx| { + ReturnTypeImplTrait::from_hir(ctx, &bounds) + }); + ctx.opaque_type_data.borrow_mut()[idx as usize] = actual_opaque_type_data; + + let func = match ctx.resolver.generic_def() { + Some(GenericDefId::FunctionId(f)) => f, + _ => panic!("opaque impl trait lowering in non-function"), + }; + let impl_trait_id = OpaqueTyId::ReturnTypeImplTrait(func, idx); + let generics = generics(ctx.db.upcast(), func.into()); + let parameters = Substs::bound_vars(&generics, ctx.in_binders); + Ty::Opaque(OpaqueTy { opaque_ty_id: impl_trait_id, parameters }) } ImplTraitLoweringMode::Param => { let idx = ctx.impl_trait_counter.get(); - ctx.impl_trait_counter.set(idx + 1); + // FIXME we're probably doing something wrong here + ctx.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16); if let Some(def) = ctx.resolver.generic_def() { let generics = generics(ctx.db.upcast(), def); let param = generics @@ -197,7 +248,8 @@ impl Ty { } ImplTraitLoweringMode::Variable => { let idx = ctx.impl_trait_counter.get(); - ctx.impl_trait_counter.set(idx + 1); + // FIXME we're probably doing something wrong here + ctx.impl_trait_counter.set(idx + count_impl_traits(type_ref) as u16); let (parent_params, self_params, list_params, _impl_trait_params) = if let Some(def) = ctx.resolver.generic_def() { let generics = generics(ctx.db.upcast(), def); @@ -271,6 +323,7 @@ impl Ty { resolution: TypeNs, resolved_segment: PathSegment<'_>, remaining_segments: PathSegments<'_>, + infer_args: bool, ) -> (Ty, Option) { let ty = match resolution { TypeNs::TraitId(trait_) => { @@ -348,9 +401,15 @@ impl Ty { ctx.db.ty(adt.into()).subst(&substs) } - TypeNs::AdtId(it) => Ty::from_hir_path_inner(ctx, resolved_segment, it.into()), - TypeNs::BuiltinType(it) => Ty::from_hir_path_inner(ctx, resolved_segment, it.into()), - TypeNs::TypeAliasId(it) => Ty::from_hir_path_inner(ctx, resolved_segment, it.into()), + TypeNs::AdtId(it) => { + Ty::from_hir_path_inner(ctx, resolved_segment, it.into(), infer_args) + } + TypeNs::BuiltinType(it) => { + Ty::from_hir_path_inner(ctx, resolved_segment, it.into(), infer_args) + } + TypeNs::TypeAliasId(it) => { + Ty::from_hir_path_inner(ctx, resolved_segment, it.into(), infer_args) + } // FIXME: report error TypeNs::EnumVariantId(_) => return (Ty::Unknown, None), }; @@ -376,7 +435,13 @@ impl Ty { ), Some(i) => (path.segments().get(i - 1).unwrap(), path.segments().skip(i)), }; - Ty::from_partly_resolved_hir_path(ctx, resolution, resolved_segment, remaining_segments) + Ty::from_partly_resolved_hir_path( + ctx, + resolution, + resolved_segment, + remaining_segments, + false, + ) } fn select_associated_type( @@ -422,13 +487,14 @@ impl Ty { ctx: &TyLoweringContext<'_>, segment: PathSegment<'_>, typable: TyDefId, + infer_args: bool, ) -> Ty { let generic_def = match typable { TyDefId::BuiltinType(_) => None, TyDefId::AdtId(it) => Some(it.into()), TyDefId::TypeAliasId(it) => Some(it.into()), }; - let substs = substs_from_path_segment(ctx, segment, generic_def, false); + let substs = substs_from_path_segment(ctx, segment, generic_def, infer_args); ctx.db.ty(typable).subst(&substs) } @@ -441,6 +507,7 @@ impl Ty { // `ValueTyDefId` is just a convenient way to pass generics and // special-case enum variants resolved: ValueTyDefId, + infer_args: bool, ) -> Substs { let last = path.segments().last().expect("path should have at least one segment"); let (segment, generic_def) = match resolved { @@ -463,22 +530,27 @@ impl Ty { (segment, Some(var.parent.into())) } }; - substs_from_path_segment(ctx, segment, generic_def, false) + substs_from_path_segment(ctx, segment, generic_def, infer_args) } } -pub(super) fn substs_from_path_segment( +fn substs_from_path_segment( ctx: &TyLoweringContext<'_>, segment: PathSegment<'_>, def_generic: Option, - _add_self_param: bool, + infer_args: bool, ) -> Substs { let mut substs = Vec::new(); let def_generics = def_generic.map(|def| generics(ctx.db.upcast(), def)); let (parent_params, self_params, type_params, impl_trait_params) = def_generics.map_or((0, 0, 0, 0), |g| g.provenance_split()); + let total_len = parent_params + self_params + type_params + impl_trait_params; + substs.extend(iter::repeat(Ty::Unknown).take(parent_params)); + + let mut had_explicit_args = false; + if let Some(generic_args) = &segment.args_and_bindings { if !generic_args.has_self_type { substs.extend(iter::repeat(Ty::Unknown).take(self_params)); @@ -490,31 +562,35 @@ pub(super) fn substs_from_path_segment( for arg in generic_args.args.iter().skip(skip).take(expected_num) { match arg { GenericArg::Type(type_ref) => { + had_explicit_args = true; let ty = Ty::from_hir(ctx, type_ref); substs.push(ty); } } } } - let total_len = parent_params + self_params + type_params + impl_trait_params; + + // handle defaults. In expression or pattern path segments without + // explicitly specified type arguments, missing type arguments are inferred + // (i.e. defaults aren't used). + if !infer_args || had_explicit_args { + if let Some(def_generic) = def_generic { + let default_substs = ctx.db.generic_defaults(def_generic); + assert_eq!(total_len, default_substs.len()); + + for default_ty in default_substs.iter().skip(substs.len()) { + substs.push(default_ty.clone()); + } + } + } + // add placeholders for args that were not provided + // FIXME: emit diagnostics in contexts where this is not allowed for _ in substs.len()..total_len { substs.push(Ty::Unknown); } assert_eq!(substs.len(), total_len); - // handle defaults - if let Some(def_generic) = def_generic { - let default_substs = ctx.db.generic_defaults(def_generic); - assert_eq!(substs.len(), default_substs.len()); - - for (i, default_ty) in default_substs.iter().enumerate() { - if substs[i] == Ty::Unknown { - substs[i] = default_ty.clone(); - } - } - } - Substs(substs.into()) } @@ -563,9 +639,7 @@ impl TraitRef { segment: PathSegment<'_>, resolved: TraitId, ) -> Substs { - let has_self_param = - segment.args_and_bindings.as_ref().map(|a| a.has_self_type).unwrap_or(false); - substs_from_path_segment(ctx, segment, Some(resolved.into()), !has_self_param) + substs_from_path_segment(ctx, segment, Some(resolved.into()), false) } pub(crate) fn from_type_bound( @@ -663,6 +737,30 @@ fn assoc_type_bindings_from_type_bound<'a>( }) } +impl ReturnTypeImplTrait { + fn from_hir(ctx: &TyLoweringContext, bounds: &[TypeBound]) -> Self { + mark::hit!(lower_rpit); + let self_ty = Ty::Bound(BoundVar::new(DebruijnIndex::INNERMOST, 0)); + let predicates = ctx.with_shifted_in(DebruijnIndex::ONE, |ctx| { + bounds + .iter() + .flat_map(|b| GenericPredicate::from_type_bound(ctx, b, self_ty.clone())) + .collect() + }); + ReturnTypeImplTrait { bounds: Binders::new(1, predicates) } + } +} + +fn count_impl_traits(type_ref: &TypeRef) -> usize { + let mut count = 0; + type_ref.walk(&mut |type_ref| { + if matches!(type_ref, TypeRef::ImplTrait(_)) { + count += 1; + } + }); + count +} + /// Build the signature of a callable item (function, struct or enum variant). pub fn callable_item_sig(db: &dyn HirDatabase, def: CallableDef) -> PolyFnSig { match def { @@ -864,7 +962,9 @@ fn fn_sig_for_fn(db: &dyn HirDatabase, def: FunctionId) -> PolyFnSig { .with_impl_trait_mode(ImplTraitLoweringMode::Variable) .with_type_param_mode(TypeParamLoweringMode::Variable); let params = data.params.iter().map(|tr| Ty::from_hir(&ctx_params, tr)).collect::>(); - let ctx_ret = ctx_params.with_impl_trait_mode(ImplTraitLoweringMode::Opaque); + let ctx_ret = TyLoweringContext::new(db, &resolver) + .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) + .with_type_param_mode(TypeParamLoweringMode::Variable); let ret = Ty::from_hir(&ctx_ret, &data.ret_type); let generics = generics(db.upcast(), def.into()); let num_binders = generics.len(); @@ -1084,3 +1184,25 @@ pub(crate) fn impl_trait_query(db: &dyn HirDatabase, impl_id: ImplId) -> Option< TraitRef::from_hir(&ctx, target_trait, Some(self_ty.value))?, )) } + +pub(crate) fn return_type_impl_traits( + db: &impl HirDatabase, + def: hir_def::FunctionId, +) -> Option>> { + // FIXME unify with fn_sig_for_fn instead of doing lowering twice, maybe + let data = db.function_data(def); + let resolver = def.resolver(db.upcast()); + let ctx_ret = TyLoweringContext::new(db, &resolver) + .with_impl_trait_mode(ImplTraitLoweringMode::Opaque) + .with_type_param_mode(TypeParamLoweringMode::Variable); + let _ret = Ty::from_hir(&ctx_ret, &data.ret_type); + let generics = generics(db.upcast(), def.into()); + let num_binders = generics.len(); + let return_type_impl_traits = + ReturnTypeImplTraits { impl_traits: ctx_ret.opaque_type_data.into_inner() }; + if return_type_impl_traits.impl_traits.is_empty() { + None + } else { + Some(Arc::new(Binders::new(num_binders, return_type_impl_traits))) + } +} diff --git a/crates/ra_hir_ty/src/method_resolution.rs b/crates/ra_hir_ty/src/method_resolution.rs index e19628fdf728..e83b394566b1 100644 --- a/crates/ra_hir_ty/src/method_resolution.rs +++ b/crates/ra_hir_ty/src/method_resolution.rs @@ -16,12 +16,8 @@ use rustc_hash::{FxHashMap, FxHashSet}; use super::Substs; use crate::{ - autoderef, - db::HirDatabase, - primitive::{FloatBitness, Uncertain}, - utils::all_super_traits, - ApplicationTy, Canonical, DebruijnIndex, InEnvironment, TraitEnvironment, TraitRef, Ty, - TypeCtor, TypeWalk, + autoderef, db::HirDatabase, primitive::FloatBitness, utils::all_super_traits, ApplicationTy, + Canonical, DebruijnIndex, InEnvironment, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, }; /// This is used as a key for indexing impls. @@ -147,12 +143,12 @@ impl Ty { } TypeCtor::Bool => lang_item_crate!("bool"), TypeCtor::Char => lang_item_crate!("char"), - TypeCtor::Float(Uncertain::Known(f)) => match f.bitness { + TypeCtor::Float(f) => match f.bitness { // There are two lang items: one in libcore (fXX) and one in libstd (fXX_runtime) FloatBitness::X32 => lang_item_crate!("f32", "f32_runtime"), FloatBitness::X64 => lang_item_crate!("f64", "f64_runtime"), }, - TypeCtor::Int(Uncertain::Known(i)) => lang_item_crate!(i.ty_to_string()), + TypeCtor::Int(i) => lang_item_crate!(i.ty_to_string()), TypeCtor::Str => lang_item_crate!("str_alloc", "str"), TypeCtor::Slice => lang_item_crate!("slice_alloc", "slice"), TypeCtor::RawPtr(Mutability::Shared) => lang_item_crate!("const_ptr"), diff --git a/crates/ra_hir_ty/src/primitive.rs b/crates/ra_hir_ty/src/primitive.rs index 02a8179d94db..37966b709848 100644 --- a/crates/ra_hir_ty/src/primitive.rs +++ b/crates/ra_hir_ty/src/primitive.rs @@ -7,42 +7,6 @@ use std::fmt; pub use hir_def::builtin_type::{BuiltinFloat, BuiltinInt, FloatBitness, IntBitness, Signedness}; -#[derive(Clone, Copy, Eq, PartialEq, Hash, Debug)] -pub enum Uncertain { - Unknown, - Known(T), -} - -impl From for Uncertain { - fn from(ty: IntTy) -> Self { - Uncertain::Known(ty) - } -} - -impl fmt::Display for Uncertain { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Uncertain::Unknown => write!(f, "{{integer}}"), - Uncertain::Known(ty) => write!(f, "{}", ty), - } - } -} - -impl From for Uncertain { - fn from(ty: FloatTy) -> Self { - Uncertain::Known(ty) - } -} - -impl fmt::Display for Uncertain { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Uncertain::Unknown => write!(f, "{{float}}"), - Uncertain::Known(ty) => write!(f, "{}", ty), - } - } -} - #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct IntTy { pub signedness: Signedness, @@ -173,21 +137,3 @@ impl From for FloatTy { FloatTy { bitness: t.bitness } } } - -impl From> for Uncertain { - fn from(t: Option) -> Self { - match t { - None => Uncertain::Unknown, - Some(t) => Uncertain::Known(t.into()), - } - } -} - -impl From> for Uncertain { - fn from(t: Option) -> Self { - match t { - None => Uncertain::Unknown, - Some(t) => Uncertain::Known(t.into()), - } - } -} diff --git a/crates/ra_hir_ty/src/test_db.rs b/crates/ra_hir_ty/src/test_db.rs index 8498d3d966d0..ad04e3e0f908 100644 --- a/crates/ra_hir_ty/src/test_db.rs +++ b/crates/ra_hir_ty/src/test_db.rs @@ -7,9 +7,8 @@ use std::{ use hir_def::{db::DefDatabase, AssocItemId, ModuleDefId, ModuleId}; use hir_expand::{db::AstDatabase, diagnostics::DiagnosticSink}; -use ra_db::{ - salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, SourceDatabase, Upcast, -}; +use ra_db::{salsa, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, Upcast}; +use rustc_hash::FxHashSet; use stdx::format_to; use crate::{db::HirDatabase, diagnostics::Diagnostic, expr::ExprValidator}; @@ -72,23 +71,12 @@ impl FileLoader for TestDB { fn file_text(&self, file_id: FileId) -> Arc { FileLoaderDelegate(self).file_text(file_id) } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + fn resolve_path(&self, anchor: FileId, path: &str) -> Option { + FileLoaderDelegate(self).resolve_path(anchor, path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } - fn resolve_extern_path( - &self, - extern_id: ra_db::ExternSourceId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path) - } } impl TestDB { diff --git a/crates/ra_hir_ty/src/tests/display_source_code.rs b/crates/ra_hir_ty/src/tests/display_source_code.rs index 4088b1d22d94..5dfa0a014599 100644 --- a/crates/ra_hir_ty/src/tests/display_source_code.rs +++ b/crates/ra_hir_ty/src/tests/display_source_code.rs @@ -29,7 +29,7 @@ fn omit_default_type_parameters() { //- /main.rs struct Foo { t: T } fn main() { - let foo = Foo { t: 5 }; + let foo = Foo { t: 5u8 }; foo<|>; } ", @@ -41,7 +41,7 @@ fn omit_default_type_parameters() { //- /main.rs struct Foo { k: K, t: T } fn main() { - let foo = Foo { k: 400, t: 5 }; + let foo = Foo { k: 400, t: 5u8 }; foo<|>; } ", diff --git a/crates/ra_hir_ty/src/tests/method_resolution.rs b/crates/ra_hir_ty/src/tests/method_resolution.rs index 558a70022dce..804297315c4d 100644 --- a/crates/ra_hir_ty/src/tests/method_resolution.rs +++ b/crates/ra_hir_ty/src/tests/method_resolution.rs @@ -183,60 +183,6 @@ fn test() { ); } -#[test] -fn infer_associated_method_generics_with_default_param() { - assert_snapshot!( - infer(r#" -struct Gen { - val: T -} - -impl Gen { - pub fn make() -> Gen { - loop { } - } -} - -fn test() { - let a = Gen::make(); -} -"#), - @r###" - 80..104 '{ ... }': Gen - 90..98 'loop { }': ! - 95..98 '{ }': () - 118..146 '{ ...e(); }': () - 128..129 'a': Gen - 132..141 'Gen::make': fn make() -> Gen - 132..143 'Gen::make()': Gen - "### - ); -} - -#[test] -fn infer_associated_method_generics_with_default_tuple_param() { - let t = type_at( - r#" -//- /main.rs -struct Gen { - val: T -} - -impl Gen { - pub fn make() -> Gen { - loop { } - } -} - -fn test() { - let a = Gen::make(); - a.val<|>; -} -"#, - ); - assert_eq!(t, "()"); -} - #[test] fn infer_associated_method_generics_without_args() { assert_snapshot!( diff --git a/crates/ra_hir_ty/src/tests/simple.rs b/crates/ra_hir_ty/src/tests/simple.rs index 88309157b78c..37659cd02c81 100644 --- a/crates/ra_hir_ty/src/tests/simple.rs +++ b/crates/ra_hir_ty/src/tests/simple.rs @@ -95,7 +95,7 @@ fn foo() { fn infer_ranges() { let (db, pos) = TestDB::with_position( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core fn test() { let a = ..; let b = 1..; @@ -108,7 +108,7 @@ fn test() { t<|>; } -//- /std.rs crate:std +//- /core.rs crate:core #[prelude_import] use prelude::*; mod prelude {} @@ -1997,3 +1997,111 @@ fn foo() { "### ); } + +#[test] +fn generic_default() { + assert_snapshot!( + infer(r#" +struct Thing { t: T } +enum OtherThing { + One { t: T }, + Two(T), +} + +fn test(t1: Thing, t2: OtherThing, t3: Thing, t4: OtherThing) { + t1.t; + t3.t; + match t2 { + OtherThing::One { t } => { t; }, + OtherThing::Two(t) => { t; }, + } + match t4 { + OtherThing::One { t } => { t; }, + OtherThing::Two(t) => { t; }, + } +} +"#), + @r###" + 98..100 't1': Thing<()> + 109..111 't2': OtherThing<()> + 125..127 't3': Thing + 141..143 't4': OtherThing + 162..385 '{ ... } }': () + 168..170 't1': Thing<()> + 168..172 't1.t': () + 178..180 't3': Thing + 178..182 't3.t': i32 + 188..283 'match ... }': () + 194..196 't2': OtherThing<()> + 207..228 'OtherT... { t }': OtherThing<()> + 225..226 't': () + 232..238 '{ t; }': () + 234..235 't': () + 248..266 'OtherT...Two(t)': OtherThing<()> + 264..265 't': () + 270..276 '{ t; }': () + 272..273 't': () + 288..383 'match ... }': () + 294..296 't4': OtherThing + 307..328 'OtherT... { t }': OtherThing + 325..326 't': i32 + 332..338 '{ t; }': () + 334..335 't': i32 + 348..366 'OtherT...Two(t)': OtherThing + 364..365 't': i32 + 370..376 '{ t; }': () + 372..373 't': i32 + "### + ); +} + +#[test] +fn generic_default_in_struct_literal() { + assert_snapshot!( + infer(r#" +struct Thing { t: T } +enum OtherThing { + One { t: T }, + Two(T), +} + +fn test() { + let x = Thing { t: loop {} }; + let y = Thing { t: () }; + let z = Thing { t: 1i32 }; + if let Thing { t } = z { + t; + } + + let a = OtherThing::One { t: 1i32 }; + let b = OtherThing::Two(1i32); +} +"#), + @r###" + 100..320 '{ ...32); }': () + 110..111 'x': Thing + 114..134 'Thing ...p {} }': Thing + 125..132 'loop {}': ! + 130..132 '{}': () + 144..145 'y': Thing<()> + 148..163 'Thing { t: () }': Thing<()> + 159..161 '()': () + 173..174 'z': Thing + 177..194 'Thing ...1i32 }': Thing + 188..192 '1i32': i32 + 200..241 'if let... }': () + 207..218 'Thing { t }': Thing + 215..216 't': i32 + 221..222 'z': Thing + 223..241 '{ ... }': () + 233..234 't': i32 + 251..252 'a': OtherThing + 255..282 'OtherT...1i32 }': OtherThing + 276..280 '1i32': i32 + 292..293 'b': OtherThing + 296..311 'OtherThing::Two': Two(i32) -> OtherThing + 296..317 'OtherT...(1i32)': OtherThing + 312..316 '1i32': i32 + "### + ); +} diff --git a/crates/ra_hir_ty/src/tests/traits.rs b/crates/ra_hir_ty/src/tests/traits.rs index e8778d419be7..e81193a3c30a 100644 --- a/crates/ra_hir_ty/src/tests/traits.rs +++ b/crates/ra_hir_ty/src/tests/traits.rs @@ -10,7 +10,7 @@ use super::{infer, infer_with_mismatches, type_at, type_at_pos}; fn infer_await() { let (db, pos) = TestDB::with_position( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core struct IntFuture; @@ -24,7 +24,7 @@ fn test() { v<|>; } -//- /std.rs crate:std +//- /core.rs crate:core #[prelude_import] use future::*; mod future { #[lang = "future_trait"] @@ -42,7 +42,7 @@ mod future { fn infer_async() { let (db, pos) = TestDB::with_position( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core async fn foo() -> u64 { 128 @@ -54,7 +54,7 @@ fn test() { v<|>; } -//- /std.rs crate:std +//- /core.rs crate:core #[prelude_import] use future::*; mod future { #[lang = "future_trait"] @@ -72,7 +72,7 @@ mod future { fn infer_desugar_async() { let (db, pos) = TestDB::with_position( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core async fn foo() -> u64 { 128 @@ -83,7 +83,7 @@ fn test() { r<|>; } -//- /std.rs crate:std +//- /core.rs crate:core #[prelude_import] use future::*; mod future { trait Future { @@ -100,7 +100,7 @@ mod future { fn infer_try() { let (db, pos) = TestDB::with_position( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core fn test() { let r: Result = Result::Ok(1); @@ -108,7 +108,7 @@ fn test() { v<|>; } -//- /std.rs crate:std +//- /core.rs crate:core #[prelude_import] use ops::*; mod ops { @@ -140,9 +140,9 @@ mod result { fn infer_for_loop() { let (db, pos) = TestDB::with_position( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core,alloc -use std::collections::Vec; +use alloc::collections::Vec; fn test() { let v = Vec::new(); @@ -152,7 +152,7 @@ fn test() { } } -//- /std.rs crate:std +//- /core.rs crate:core #[prelude_import] use iter::*; mod iter { @@ -161,6 +161,8 @@ mod iter { } } +//- /alloc.rs crate:alloc deps:core + mod collections { struct Vec {} impl Vec { @@ -168,7 +170,7 @@ mod collections { fn push(&mut self, t: T) { } } - impl crate::iter::IntoIterator for Vec { + impl IntoIterator for Vec { type Item=T; } } @@ -1110,7 +1112,6 @@ fn test() { } #[test] -#[ignore] fn impl_trait() { assert_snapshot!( infer(r#" @@ -1160,6 +1161,95 @@ fn test(x: impl Trait, y: &impl Trait) { ); } +#[test] +fn simple_return_pos_impl_trait() { + mark::check!(lower_rpit); + assert_snapshot!( + infer(r#" +trait Trait { + fn foo(&self) -> T; +} +fn bar() -> impl Trait { loop {} } + +fn test() { + let a = bar(); + a.foo(); +} +"#), + @r###" + 30..34 'self': &Self + 72..83 '{ loop {} }': ! + 74..81 'loop {}': ! + 79..81 '{}': () + 95..130 '{ ...o(); }': () + 105..106 'a': impl Trait + 109..112 'bar': fn bar() -> impl Trait + 109..114 'bar()': impl Trait + 120..121 'a': impl Trait + 120..127 'a.foo()': u64 + "### + ); +} + +#[test] +fn more_return_pos_impl_trait() { + assert_snapshot!( + infer(r#" +trait Iterator { + type Item; + fn next(&mut self) -> Self::Item; +} +trait Trait { + fn foo(&self) -> T; +} +fn bar() -> (impl Iterator>, impl Trait) { loop {} } +fn baz(t: T) -> (impl Iterator>, impl Trait) { loop {} } + +fn test() { + let (a, b) = bar(); + a.next().foo(); + b.foo(); + let (c, d) = baz(1u128); + c.next().foo(); + d.foo(); +} +"#), + @r###" + 50..54 'self': &mut Self + 102..106 'self': &Self + 185..196 '{ loop {} }': ({unknown}, {unknown}) + 187..194 'loop {}': ! + 192..194 '{}': () + 207..208 't': T + 269..280 '{ loop {} }': ({unknown}, {unknown}) + 271..278 'loop {}': ! + 276..278 '{}': () + 292..414 '{ ...o(); }': () + 302..308 '(a, b)': (impl Iterator>, impl Trait) + 303..304 'a': impl Iterator> + 306..307 'b': impl Trait + 311..314 'bar': fn bar() -> (impl Iterator>, impl Trait) + 311..316 'bar()': (impl Iterator>, impl Trait) + 322..323 'a': impl Iterator> + 322..330 'a.next()': impl Trait + 322..336 'a.next().foo()': u32 + 342..343 'b': impl Trait + 342..349 'b.foo()': u64 + 359..365 '(c, d)': (impl Iterator>, impl Trait) + 360..361 'c': impl Iterator> + 363..364 'd': impl Trait + 368..371 'baz': fn baz(u128) -> (impl Iterator>, impl Trait) + 368..378 'baz(1u128)': (impl Iterator>, impl Trait) + 372..377 '1u128': u128 + 384..385 'c': impl Iterator> + 384..392 'c.next()': impl Trait + 384..398 'c.next().foo()': u128 + 404..405 'd': impl Trait + 404..411 'd.foo()': u128 + "### + ); +} + #[test] fn dyn_trait() { assert_snapshot!( @@ -1718,33 +1808,33 @@ fn test() { } "#), @r###" -65..69 'self': &Self -166..170 'self': Self -172..176 'args': Args -240..244 'self': &Foo -255..257 '{}': () -335..336 'f': F -355..357 '{}': () -444..690 '{ ...o(); }': () -454..459 'lazy1': Lazy T> -476..485 'Lazy::new': fn new T>(fn() -> T) -> Lazy T> -476..493 'Lazy::...| Foo)': Lazy T> -486..492 '|| Foo': || -> T -489..492 'Foo': Foo -503..505 'r1': {unknown} -508..513 'lazy1': Lazy T> -508..519 'lazy1.foo()': {unknown} -561..576 'make_foo_fn_ptr': fn() -> Foo -592..603 'make_foo_fn': fn make_foo_fn() -> Foo -613..618 'lazy2': Lazy T> -635..644 'Lazy::new': fn new T>(fn() -> T) -> Lazy T> -635..661 'Lazy::...n_ptr)': Lazy T> -645..660 'make_foo_fn_ptr': fn() -> Foo -671..673 'r2': {unknown} -676..681 'lazy2': Lazy T> -676..687 'lazy2.foo()': {unknown} -550..552 '{}': () -"### + 65..69 'self': &Self + 166..170 'self': Self + 172..176 'args': Args + 240..244 'self': &Foo + 255..257 '{}': () + 335..336 'f': F + 355..357 '{}': () + 444..690 '{ ...o(); }': () + 454..459 'lazy1': Lazy Foo> + 476..485 'Lazy::new': fn new Foo>(|| -> Foo) -> Lazy Foo> + 476..493 'Lazy::...| Foo)': Lazy Foo> + 486..492 '|| Foo': || -> Foo + 489..492 'Foo': Foo + 503..505 'r1': usize + 508..513 'lazy1': Lazy Foo> + 508..519 'lazy1.foo()': usize + 561..576 'make_foo_fn_ptr': fn() -> Foo + 592..603 'make_foo_fn': fn make_foo_fn() -> Foo + 613..618 'lazy2': Lazy Foo> + 635..644 'Lazy::new': fn new Foo>(fn() -> Foo) -> Lazy Foo> + 635..661 'Lazy::...n_ptr)': Lazy Foo> + 645..660 'make_foo_fn_ptr': fn() -> Foo + 671..673 'r2': {unknown} + 676..681 'lazy2': Lazy Foo> + 676..687 'lazy2.foo()': {unknown} + 550..552 '{}': () + "### ); } @@ -2758,12 +2848,12 @@ fn test() { fn integer_range_iterate() { let t = type_at( r#" -//- /main.rs crate:main deps:std +//- /main.rs crate:main deps:core fn test() { for x in 0..100 { x<|>; } } -//- /std.rs crate:std +//- /core.rs crate:core pub mod ops { pub struct Range { pub start: Idx, diff --git a/crates/ra_hir_ty/src/traits/chalk.rs b/crates/ra_hir_ty/src/traits/chalk.rs index 61de3cc30c66..a72a82f5a23a 100644 --- a/crates/ra_hir_ty/src/traits/chalk.rs +++ b/crates/ra_hir_ty/src/traits/chalk.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use log::debug; use chalk_ir::{fold::shift::Shift, GenericArg, TypeName}; -use chalk_solve::rust_ir::{self, WellKnownTrait}; +use chalk_solve::rust_ir::{self, OpaqueTyDatumBound, WellKnownTrait}; use hir_def::{ lang_item::{lang_attr, LangItemTarget}, @@ -100,6 +100,7 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { fn associated_ty_value(&self, id: AssociatedTyValueId) -> Arc { self.db.associated_ty_value(self.krate, id) } + fn custom_clauses(&self) -> Vec> { vec![] } @@ -130,11 +131,34 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { self.db.program_clauses_for_chalk_env(self.krate, environment.clone()) } - fn opaque_ty_data( - &self, - _id: chalk_ir::OpaqueTyId, - ) -> Arc> { - unimplemented!() + fn opaque_ty_data(&self, id: chalk_ir::OpaqueTyId) -> Arc { + let interned_id = crate::db::InternedOpaqueTyId::from(id); + let full_id = self.db.lookup_intern_impl_trait_id(interned_id); + let (func, idx) = match full_id { + crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => (func, idx), + }; + let datas = + self.db.return_type_impl_traits(func).expect("impl trait id without impl traits"); + let data = &datas.value.impl_traits[idx as usize]; + let bound = OpaqueTyDatumBound { + bounds: make_binders( + data.bounds + .value + .iter() + .cloned() + .filter(|b| !b.is_error()) + .map(|b| b.to_chalk(self.db)) + .collect(), + 1, + ), + }; + let num_vars = datas.num_binders; + Arc::new(OpaqueTyDatum { opaque_ty_id: id, bound: make_binders(bound, num_vars) }) + } + + fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId) -> chalk_ir::Ty { + // FIXME: actually provide the hidden type; it is relevant for auto traits + Ty::Unknown.to_chalk(self.db) } fn force_impl_for( @@ -150,10 +174,6 @@ impl<'a> chalk_solve::RustIrDatabase for ChalkContext<'a> { // FIXME: implement actual object safety true } - - fn hidden_opaque_type(&self, _id: chalk_ir::OpaqueTyId) -> chalk_ir::Ty { - Ty::Unknown.to_chalk(self.db) - } } pub(crate) fn program_clauses_for_chalk_env_query( @@ -460,6 +480,18 @@ impl From for ImplId { } } +impl From for crate::db::InternedOpaqueTyId { + fn from(id: OpaqueTyId) -> Self { + InternKey::from_intern_id(id.0) + } +} + +impl From for OpaqueTyId { + fn from(id: crate::db::InternedOpaqueTyId) -> Self { + chalk_ir::OpaqueTyId(id.as_intern_id()) + } +} + impl From> for crate::traits::AssocTyValueId { fn from(id: rust_ir::AssociatedTyValueId) -> Self { Self::from_intern_id(id.0) diff --git a/crates/ra_hir_ty/src/traits/chalk/interner.rs b/crates/ra_hir_ty/src/traits/chalk/interner.rs index e27074ba6fad..56aab640c85f 100644 --- a/crates/ra_hir_ty/src/traits/chalk/interner.rs +++ b/crates/ra_hir_ty/src/traits/chalk/interner.rs @@ -22,6 +22,8 @@ pub type AssociatedTyValueId = chalk_solve::rust_ir::AssociatedTyValueId; pub type FnDefId = chalk_ir::FnDefId; pub type FnDefDatum = chalk_solve::rust_ir::FnDefDatum; +pub type OpaqueTyId = chalk_ir::OpaqueTyId; +pub type OpaqueTyDatum = chalk_solve::rust_ir::OpaqueTyDatum; impl chalk_ir::interner::Interner for Interner { type InternedType = Box>; // FIXME use Arc? diff --git a/crates/ra_hir_ty/src/traits/chalk/mapping.rs b/crates/ra_hir_ty/src/traits/chalk/mapping.rs index 5f6daf842be6..18e5c9c16fca 100644 --- a/crates/ra_hir_ty/src/traits/chalk/mapping.rs +++ b/crates/ra_hir_ty/src/traits/chalk/mapping.rs @@ -14,10 +14,10 @@ use ra_db::salsa::InternKey; use crate::{ db::HirDatabase, - primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness, Uncertain}, + primitive::{FloatBitness, FloatTy, IntBitness, IntTy, Signedness}, traits::{builtin, AssocTyValue, Canonical, Impl, Obligation}, - ApplicationTy, CallableDef, GenericPredicate, InEnvironment, ProjectionPredicate, ProjectionTy, - Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, + ApplicationTy, CallableDef, GenericPredicate, InEnvironment, OpaqueTy, OpaqueTyId, + ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, }; use super::interner::*; @@ -68,7 +68,16 @@ impl ToChalk for Ty { let bounded_ty = chalk_ir::DynTy { bounds: make_binders(where_clauses, 1) }; chalk_ir::TyData::Dyn(bounded_ty).intern(&Interner) } - Ty::Opaque(_) | Ty::Unknown => { + Ty::Opaque(opaque_ty) => { + let opaque_ty_id = opaque_ty.opaque_ty_id.to_chalk(db); + let substitution = opaque_ty.parameters.to_chalk(db); + chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(chalk_ir::OpaqueTy { + opaque_ty_id, + substitution, + })) + .intern(&Interner) + } + Ty::Unknown => { let substitution = chalk_ir::Substitution::empty(&Interner); let name = TypeName::Error; chalk_ir::ApplicationTy { name, substitution }.cast(&Interner).intern(&Interner) @@ -98,7 +107,11 @@ impl ToChalk for Ty { let parameters = from_chalk(db, proj.substitution); Ty::Projection(ProjectionTy { associated_ty, parameters }) } - chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(_)) => unimplemented!(), + chalk_ir::TyData::Alias(chalk_ir::AliasTy::Opaque(opaque_ty)) => { + let impl_trait_id = from_chalk(db, opaque_ty.opaque_ty_id); + let parameters = from_chalk(db, opaque_ty.substitution); + Ty::Opaque(OpaqueTy { opaque_ty_id: impl_trait_id, parameters }) + } chalk_ir::TyData::Function(chalk_ir::Fn { num_binders: _, substitution }) => { let parameters: Substs = from_chalk(db, substitution); Ty::Apply(ApplicationTy { @@ -204,6 +217,21 @@ impl ToChalk for hir_def::TraitId { } } +impl ToChalk for OpaqueTyId { + type Chalk = chalk_ir::OpaqueTyId; + + fn to_chalk(self, db: &dyn HirDatabase) -> chalk_ir::OpaqueTyId { + db.intern_impl_trait_id(self).into() + } + + fn from_chalk( + db: &dyn HirDatabase, + opaque_ty_id: chalk_ir::OpaqueTyId, + ) -> OpaqueTyId { + db.lookup_intern_impl_trait_id(opaque_ty_id.into()) + } +} + impl ToChalk for TypeCtor { type Chalk = TypeName; @@ -214,13 +242,18 @@ impl ToChalk for TypeCtor { TypeName::AssociatedType(type_id) } + TypeCtor::OpaqueType(impl_trait_id) => { + let id = impl_trait_id.to_chalk(db); + TypeName::OpaqueType(id) + } + TypeCtor::Bool => TypeName::Scalar(Scalar::Bool), TypeCtor::Char => TypeName::Scalar(Scalar::Char), - TypeCtor::Int(Uncertain::Known(int_ty)) => TypeName::Scalar(int_ty_to_chalk(int_ty)), - TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) => { + TypeCtor::Int(int_ty) => TypeName::Scalar(int_ty_to_chalk(int_ty)), + TypeCtor::Float(FloatTy { bitness: FloatBitness::X32 }) => { TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) } - TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) => { + TypeCtor::Float(FloatTy { bitness: FloatBitness::X64 }) => { TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) } @@ -235,9 +268,7 @@ impl ToChalk for TypeCtor { } TypeCtor::Never => TypeName::Never, - TypeCtor::Int(Uncertain::Unknown) - | TypeCtor::Float(Uncertain::Unknown) - | TypeCtor::Adt(_) + TypeCtor::Adt(_) | TypeCtor::Array | TypeCtor::FnPtr { .. } | TypeCtor::Closure { .. } => { @@ -252,23 +283,25 @@ impl ToChalk for TypeCtor { match type_name { TypeName::Adt(struct_id) => db.lookup_intern_type_ctor(struct_id.into()), TypeName::AssociatedType(type_id) => TypeCtor::AssociatedType(from_chalk(db, type_id)), - TypeName::OpaqueType(_) => unreachable!(), + TypeName::OpaqueType(opaque_type_id) => { + TypeCtor::OpaqueType(from_chalk(db, opaque_type_id)) + } TypeName::Scalar(Scalar::Bool) => TypeCtor::Bool, TypeName::Scalar(Scalar::Char) => TypeCtor::Char, - TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(Uncertain::Known(IntTy { + TypeName::Scalar(Scalar::Int(int_ty)) => TypeCtor::Int(IntTy { signedness: Signedness::Signed, bitness: bitness_from_chalk_int(int_ty), - })), - TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(Uncertain::Known(IntTy { + }), + TypeName::Scalar(Scalar::Uint(uint_ty)) => TypeCtor::Int(IntTy { signedness: Signedness::Unsigned, bitness: bitness_from_chalk_uint(uint_ty), - })), + }), TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F32)) => { - TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X32 })) + TypeCtor::Float(FloatTy { bitness: FloatBitness::X32 }) } TypeName::Scalar(Scalar::Float(chalk_ir::FloatTy::F64)) => { - TypeCtor::Float(Uncertain::Known(FloatTy { bitness: FloatBitness::X64 })) + TypeCtor::Float(FloatTy { bitness: FloatBitness::X64 }) } TypeName::Tuple(cardinality) => TypeCtor::Tuple { cardinality: cardinality as u16 }, TypeName::Raw(mutability) => TypeCtor::RawPtr(from_chalk(db, mutability)), @@ -447,6 +480,11 @@ impl ToChalk for GenericPredicate { let ty = from_chalk(db, projection_eq.ty); GenericPredicate::Projection(ProjectionPredicate { projection_ty, ty }) } + + chalk_ir::WhereClause::LifetimeOutlives(_) => { + // we shouldn't get these from Chalk + panic!("encountered LifetimeOutlives from Chalk") + } } } } diff --git a/crates/ra_hir_ty/src/traits/chalk/tls.rs b/crates/ra_hir_ty/src/traits/chalk/tls.rs index d88828c7c0a0..556af70989fe 100644 --- a/crates/ra_hir_ty/src/traits/chalk/tls.rs +++ b/crates/ra_hir_ty/src/traits/chalk/tls.rs @@ -69,6 +69,11 @@ impl DebugContext<'_> { let name = self.0.type_alias_data(type_alias).name.clone(); write!(f, "{}::{}", trait_name, name)?; } + TypeCtor::OpaqueType(opaque_ty_id) => match opaque_ty_id { + crate::OpaqueTyId::ReturnTypeImplTrait(func, idx) => { + write!(f, "{{impl trait {} of {:?}}}", idx, func)?; + } + }, TypeCtor::Closure { def, expr } => { write!(f, "{{closure {:?} in ", expr.into_raw())?; match def { diff --git a/crates/ra_ide/src/completion.rs b/crates/ra_ide/src/completion.rs index fa37b695576b..e1fcf379d76e 100644 --- a/crates/ra_ide/src/completion.rs +++ b/crates/ra_ide/src/completion.rs @@ -126,3 +126,81 @@ pub(crate) fn completions( Some(acc) } + +#[cfg(test)] +mod tests { + use crate::completion::completion_config::CompletionConfig; + use crate::mock_analysis::analysis_and_position; + + struct DetailAndDocumentation<'a> { + detail: &'a str, + documentation: &'a str, + } + + fn check_detail_and_documentation(fixture: &str, expected: DetailAndDocumentation) { + let (analysis, position) = analysis_and_position(fixture); + let config = CompletionConfig::default(); + let completions = analysis.completions(&config, position).unwrap().unwrap(); + for item in completions { + if item.detail() == Some(expected.detail) { + let opt = item.documentation(); + let doc = opt.as_ref().map(|it| it.as_str()); + assert_eq!(doc, Some(expected.documentation)); + return; + } + } + panic!("completion detail not found: {}", expected.detail) + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_attr() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: "Do the foo" }, + ); + } + + #[test] + fn test_completion_detail_from_macro_generated_struct_fn_doc_comment() { + check_detail_and_documentation( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>; + } + "#, + DetailAndDocumentation { detail: "fn foo(&self)", documentation: " Do the foo" }, + ); + } +} diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 15dc50cf143d..e1bfd72f96af 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs @@ -21,7 +21,7 @@ use ra_syntax::{ }; use ra_text_edit::{TextEdit, TextEditBuilder}; -use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit}; +use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceFileEdit}; #[derive(Debug, Copy, Clone)] pub enum Severity { @@ -115,7 +115,7 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec let node = d.ast(db); let replacement = format!("Ok({})", node.syntax()); let edit = TextEdit::replace(node.syntax().text_range(), replacement); - let source_change = SourceChange::source_file_edit_from(file_id, edit); + let source_change = SourceFileEdit { file_id, edit }.into(); let fix = Fix::new("Wrap with ok", source_change); res.borrow_mut().push(Diagnostic { range: sema.diagnostics_range(d).range, @@ -187,7 +187,8 @@ fn check_struct_shorthand_initialization( if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { let field_name = name_ref.syntax().text().to_string(); let field_expr = expr.syntax().text().to_string(); - if field_name == field_expr { + let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); + if field_name == field_expr && !field_name_is_tup_index { let mut edit_builder = TextEditBuilder::default(); edit_builder.delete(record_field.syntax().text_range()); edit_builder.insert(record_field.syntax().text_range().start(), field_name); @@ -321,29 +322,26 @@ mod tests { fn test_wrap_return_type() { let before = r#" //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; + use core::result::Result::{self, Ok, Err}; - fn div(x: i32, y: i32) -> Result { + fn div(x: i32, y: i32) -> Result { if y == 0 { - return Err("div by zero".into()); + return Err(()); } x / y<|> } - //- /std/lib.rs - pub mod string { - pub struct String { } - } + //- /core/lib.rs pub mod result { pub enum Result { Ok(T), Err(E) } } "#; let after = r#" - use std::{string::String, result::Result::{self, Ok, Err}}; + use core::result::Result::{self, Ok, Err}; - fn div(x: i32, y: i32) -> Result { + fn div(x: i32, y: i32) -> Result { if y == 0 { - return Err("div by zero".into()); + return Err(()); } Ok(x / y) } @@ -355,7 +353,7 @@ mod tests { fn test_wrap_return_type_handles_generic_functions() { let before = r#" //- /main.rs - use std::result::Result::{self, Ok, Err}; + use core::result::Result::{self, Ok, Err}; fn div(x: T) -> Result { if x == 0 { @@ -364,13 +362,13 @@ mod tests { <|>x } - //- /std/lib.rs + //- /core/lib.rs pub mod result { pub enum Result { Ok(T), Err(E) } } "#; let after = r#" - use std::result::Result::{self, Ok, Err}; + use core::result::Result::{self, Ok, Err}; fn div(x: T) -> Result { if x == 0 { @@ -386,32 +384,29 @@ mod tests { fn test_wrap_return_type_handles_type_aliases() { let before = r#" //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; + use core::result::Result::{self, Ok, Err}; - type MyResult = Result; + type MyResult = Result; fn div(x: i32, y: i32) -> MyResult { if y == 0 { - return Err("div by zero".into()); + return Err(()); } x <|>/ y } - //- /std/lib.rs - pub mod string { - pub struct String { } - } + //- /core/lib.rs pub mod result { pub enum Result { Ok(T), Err(E) } } "#; let after = r#" - use std::{string::String, result::Result::{self, Ok, Err}}; + use core::result::Result::{self, Ok, Err}; - type MyResult = Result; + type MyResult = Result; fn div(x: i32, y: i32) -> MyResult { if y == 0 { - return Err("div by zero".into()); + return Err(()); } Ok(x / y) } @@ -423,16 +418,13 @@ mod tests { fn test_wrap_return_type_not_applicable_when_expr_type_does_not_match_ok_type() { let content = r#" //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; + use core::result::Result::{self, Ok, Err}; - fn foo() -> Result { + fn foo() -> Result<(), i32> { 0<|> } - //- /std/lib.rs - pub mod string { - pub struct String { } - } + //- /core/lib.rs pub mod result { pub enum Result { Ok(T), Err(E) } } @@ -444,7 +436,7 @@ mod tests { fn test_wrap_return_type_not_applicable_when_return_type_is_not_result() { let content = r#" //- /main.rs - use std::{string::String, result::Result::{self, Ok, Err}}; + use core::result::Result::{self, Ok, Err}; enum SomeOtherEnum { Ok(i32), @@ -455,10 +447,7 @@ mod tests { 0<|> } - //- /std/lib.rs - pub mod string { - pub struct String { } - } + //- /core/lib.rs pub mod result { pub enum Result { Ok(T), Err(E) } } @@ -731,6 +720,18 @@ mod tests { "#, check_struct_shorthand_initialization, ); + check_not_applicable( + r#" + struct A(usize); + + fn main() { + A { + 0: 0 + } + } + "#, + check_struct_shorthand_initialization, + ); check_apply( r#" diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index 9572debd822c..ca8a6a650994 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -10,7 +10,7 @@ use std::{ use hir::{Docs, Documentation, HasSource, HirDisplay}; use ra_ide_db::RootDatabase; use ra_syntax::ast::{self, AstNode, NameOwner, VisibilityOwner}; -use stdx::SepBy; +use stdx::{split1, SepBy}; use crate::display::{generic_parameters, where_predicates}; @@ -207,7 +207,16 @@ impl From<&'_ ast::FnDef> for FunctionSignature { res.push(raw_param); } - res.extend(param_list.params().map(|param| param.syntax().text().to_string())); + // macro-generated functions are missing whitespace + fn fmt_param(param: ast::Param) -> String { + let text = param.syntax().text().to_string(); + match split1(&text, ':') { + Some((left, right)) => format!("{}: {}", left.trim(), right.trim()), + _ => text, + } + } + + res.extend(param_list.params().map(fmt_param)); res_types.extend(param_list.params().map(|param| { let param_text = param.syntax().text().to_string(); match param_text.split(':').nth(1).and_then(|it| it.get(1..)) { diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 5da28edd2414..c7bb1e69f8a5 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs @@ -92,15 +92,16 @@ impl NavigationTarget { let name = module.name(db).map(|it| it.to_string().into()).unwrap_or_default(); if let Some(src) = module.declaration_source(db) { let frange = original_range(db, src.as_ref().map(|it| it.syntax())); - return NavigationTarget::from_syntax( + let mut res = NavigationTarget::from_syntax( frange.file_id, name, None, frange.range, src.value.syntax().kind(), - src.value.doc_comment_text(), - src.value.short_label(), ); + res.docs = src.value.doc_comment_text(); + res.description = src.value.short_label(); + return res; } module.to_nav(db) } @@ -130,11 +131,9 @@ impl NavigationTarget { } /// Allows `NavigationTarget` to be created from a `NameOwner` - fn from_named( + pub(crate) fn from_named( db: &RootDatabase, node: InFile<&dyn ast::NameOwner>, - docs: Option, - description: Option, ) -> NavigationTarget { //FIXME: use `_` instead of empty string let name = node.value.name().map(|it| it.text().clone()).unwrap_or_default(); @@ -148,8 +147,6 @@ impl NavigationTarget { focus_range, frange.range, node.value.syntax().kind(), - docs, - description, ) } @@ -159,8 +156,6 @@ impl NavigationTarget { focus_range: Option, full_range: TextRange, kind: SyntaxKind, - docs: Option, - description: Option, ) -> NavigationTarget { NavigationTarget { file_id, @@ -169,8 +164,8 @@ impl NavigationTarget { full_range, focus_range, container_name: None, - description, - docs, + description: None, + docs: None, } } } @@ -238,12 +233,11 @@ where { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { let src = self.source(db); - NavigationTarget::from_named( - db, - src.as_ref().map(|it| it as &dyn ast::NameOwner), - src.value.doc_comment_text(), - src.value.short_label(), - ) + let mut res = + NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); + res.docs = src.value.doc_comment_text(); + res.description = src.value.short_label(); + res } } @@ -258,15 +252,7 @@ impl ToNav for hir::Module { } }; let frange = original_range(db, src.with_value(syntax)); - NavigationTarget::from_syntax( - frange.file_id, - name, - focus, - frange.range, - syntax.kind(), - None, - None, - ) + NavigationTarget::from_syntax(frange.file_id, name, focus, frange.range, syntax.kind()) } } @@ -285,8 +271,6 @@ impl ToNav for hir::ImplDef { None, frange.range, src.value.syntax().kind(), - None, - None, ) } } @@ -296,12 +280,12 @@ impl ToNav for hir::Field { let src = self.source(db); match &src.value { - FieldSource::Named(it) => NavigationTarget::from_named( - db, - src.with_value(it), - it.doc_comment_text(), - it.short_label(), - ), + FieldSource::Named(it) => { + let mut res = NavigationTarget::from_named(db, src.with_value(it)); + res.docs = it.doc_comment_text(); + res.description = it.short_label(); + res + } FieldSource::Pos(it) => { let frange = original_range(db, src.with_value(it.syntax())); NavigationTarget::from_syntax( @@ -310,8 +294,6 @@ impl ToNav for hir::Field { None, frange.range, it.syntax().kind(), - None, - None, ) } } @@ -322,12 +304,10 @@ impl ToNav for hir::MacroDef { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { let src = self.source(db); log::debug!("nav target {:#?}", src.value.syntax()); - NavigationTarget::from_named( - db, - src.as_ref().map(|it| it as &dyn ast::NameOwner), - src.value.doc_comment_text(), - None, - ) + let mut res = + NavigationTarget::from_named(db, src.as_ref().map(|it| it as &dyn ast::NameOwner)); + res.docs = src.value.doc_comment_text(); + res } } diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index a6c86e99c95e..0798d2c36170 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs @@ -1,6 +1,6 @@ use hir::Semantics; use ra_ide_db::{ - defs::{classify_name, classify_name_ref}, + defs::{classify_name, classify_name_ref, NameClass}, symbol_index, RootDatabase, }; use ra_syntax::{ @@ -39,7 +39,10 @@ pub(crate) fn goto_definition( reference_definition(&sema, &name_ref).to_vec() }, ast::Name(name) => { - let def = classify_name(&sema, &name)?.definition(); + let def = match classify_name(&sema, &name)? { + NameClass::Definition(def) | NameClass::ConstReference(def) => def, + NameClass::FieldShorthand { local: _, field } => field, + }; let nav = def.try_to_nav(sema.db)?; vec![nav] }, @@ -886,4 +889,23 @@ mod tests { "x", ) } + + #[test] + fn goto_def_for_enum_variant_field() { + check_goto( + " + //- /lib.rs + enum Foo { + Bar { x: i32 } + } + fn baz(foo: Foo) { + match foo { + Foo::Bar { x<|> } => x + }; + } + ", + "x RECORD_FIELD_DEF FileId(1) 21..27 21..22", + "x: i32|x", + ); + } } diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index d96cb5596918..ad78b767120c 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -1,8 +1,8 @@ use std::iter::once; use hir::{ - Adt, AsAssocItem, AssocItemContainer, FieldSource, HasSource, HirDisplay, ModuleDef, - ModuleSource, Semantics, + Adt, AsAssocItem, AssocItemContainer, Documentation, FieldSource, HasSource, HirDisplay, + ModuleDef, ModuleSource, Semantics, }; use itertools::Itertools; use ra_db::SourceDatabase; @@ -10,22 +10,55 @@ use ra_ide_db::{ defs::{classify_name, classify_name_ref, Definition}, RootDatabase, }; -use ra_syntax::{ - ast::{self, DocCommentsOwner}, - match_ast, AstNode, - SyntaxKind::*, - SyntaxToken, TokenAtOffset, -}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxKind::*, SyntaxToken, TokenAtOffset}; use crate::{ - display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel}, - FilePosition, RangeInfo, + display::{macro_label, rust_code_markup, rust_code_markup_with_doc, ShortLabel, ToNav}, + runnables::runnable, + FileId, FilePosition, NavigationTarget, RangeInfo, Runnable, }; +use test_utils::mark; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct HoverConfig { + pub implementations: bool, + pub run: bool, + pub debug: bool, +} + +impl Default for HoverConfig { + fn default() -> Self { + Self { implementations: true, run: true, debug: true } + } +} + +impl HoverConfig { + pub const NO_ACTIONS: Self = Self { implementations: false, run: false, debug: false }; + + pub fn any(&self) -> bool { + self.implementations || self.runnable() + } + + pub fn none(&self) -> bool { + !self.any() + } + + pub fn runnable(&self) -> bool { + self.run || self.debug + } +} + +#[derive(Debug, Clone)] +pub enum HoverAction { + Runnable(Runnable), + Implementaion(FilePosition), +} /// Contains the results when hovering over an item #[derive(Debug, Default)] pub struct HoverResult { results: Vec, + actions: Vec, } impl HoverResult { @@ -53,10 +86,20 @@ impl HoverResult { &self.results } + pub fn actions(&self) -> &[HoverAction] { + &self.actions + } + + pub fn push_action(&mut self, action: HoverAction) { + self.actions.push(action); + } + /// Returns the results converted into markup /// for displaying in a UI + /// + /// Does not process actions! pub fn to_markup(&self) -> String { - self.results.join("\n\n---\n") + self.results.join("\n\n___\n") } } @@ -87,6 +130,14 @@ pub(crate) fn hover(db: &RootDatabase, position: FilePosition) -> Option Option Option { + fn to_action(nav_target: NavigationTarget) -> HoverAction { + HoverAction::Implementaion(FilePosition { + file_id: nav_target.file_id(), + offset: nav_target.range().start(), + }) + } + + match def { + Definition::ModuleDef(it) => match it { + ModuleDef::Adt(Adt::Struct(it)) => Some(to_action(it.to_nav(db))), + ModuleDef::Adt(Adt::Union(it)) => Some(to_action(it.to_nav(db))), + ModuleDef::Adt(Adt::Enum(it)) => Some(to_action(it.to_nav(db))), + ModuleDef::Trait(it) => Some(to_action(it.to_nav(db))), + _ => None, + }, + _ => None, + } +} + +fn runnable_action( + sema: &Semantics, + def: Definition, + file_id: FileId, +) -> Option { + match def { + Definition::ModuleDef(it) => match it { + ModuleDef::Module(it) => match it.definition_source(sema.db).value { + ModuleSource::Module(it) => runnable(&sema, it.syntax().clone(), file_id) + .map(|it| HoverAction::Runnable(it)), + _ => None, + }, + ModuleDef::Function(it) => { + let src = it.source(sema.db); + if src.file_id != file_id.into() { + mark::hit!(hover_macro_generated_struct_fn_doc_comment); + mark::hit!(hover_macro_generated_struct_fn_doc_attr); + + return None; + } + + runnable(&sema, src.value.syntax().clone(), file_id) + .map(|it| HoverAction::Runnable(it)) + } + _ => None, + }, + _ => None, + } +} + fn hover_text( docs: Option, desc: Option, @@ -169,13 +270,15 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option { let src = it.source(db); - hover_text(src.value.doc_comment_text(), Some(macro_label(&src.value)), mod_path) + let docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, Some(macro_label(&src.value)), mod_path) } Definition::Field(it) => { let src = it.source(db); match src.value { FieldSource::Named(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, } @@ -183,7 +286,8 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option match it { ModuleDef::Module(it) => match it.definition_source(db).value { ModuleSource::Module(it) => { - hover_text(it.doc_comment_text(), it.short_label(), mod_path) + let docs = Documentation::from_ast(&it).map(Into::into); + hover_text(docs, it.short_label(), mod_path) } _ => None, }, @@ -208,10 +312,11 @@ fn hover_text_from_name_kind(db: &RootDatabase, def: Definition) -> Option(db: &RootDatabase, def: D, mod_path: Option) -> Option where D: HasSource, - A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel, + A: ast::DocCommentsOwner + ast::NameOwner + ShortLabel + ast::AttrsOwner, { let src = def.source(db); - hover_text(src.value.doc_comment_text(), src.value.short_label(), mod_path) + let docs = Documentation::from_ast(&src.value).map(Into::into); + hover_text(docs, src.value.short_label(), mod_path) } } @@ -229,6 +334,9 @@ fn pick_best(tokens: TokenAtOffset) -> Option { #[cfg(test)] mod tests { + use super::*; + use insta::assert_debug_snapshot; + use ra_db::FileLoader; use ra_syntax::TextRange; @@ -242,7 +350,15 @@ mod tests { s.map(trim_markup) } - fn check_hover_result(fixture: &str, expected: &[&str]) -> String { + fn assert_impl_action(action: &HoverAction, position: u32) { + let offset = match action { + HoverAction::Implementaion(pos) => pos.offset, + it => panic!("Unexpected hover action: {:#?}", it), + }; + assert_eq!(offset, position.into()); + } + + fn check_hover_result(fixture: &str, expected: &[&str]) -> (String, Vec) { let (analysis, position) = analysis_and_position(fixture); let hover = analysis.hover(position).unwrap().unwrap(); let mut results = Vec::from(hover.info.results()); @@ -257,7 +373,7 @@ mod tests { assert_eq!(hover.info.len(), expected.len()); let content = analysis.db.file_text(position.file_id); - content[hover.range].to_string() + (content[hover.range].to_string(), hover.info.actions().to_vec()) } fn check_hover_no_result(fixture: &str) { @@ -458,7 +574,7 @@ struct Test { } fn main() { - let zz<|> = Test { t: 23, k: 33 }; + let zz<|> = Test { t: 23u8, k: 33 }; }"#, &["Test"], ); @@ -747,7 +863,7 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_through_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id { @@ -768,7 +884,7 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_through_expr_in_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id { @@ -786,7 +902,7 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_through_expr_in_macro_recursive() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id_deep { @@ -807,7 +923,7 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_through_func_in_macro_recursive() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( " //- /lib.rs macro_rules! id_deep { @@ -831,7 +947,7 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_through_literal_string_in_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( r#" //- /lib.rs macro_rules! arr { @@ -850,7 +966,7 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_through_assert_macro() { - let hover_on = check_hover_result( + let (hover_on, _) = check_hover_result( r#" //- /lib.rs #[rustc_builtin_macro] @@ -926,13 +1042,14 @@ fn func(foo: i32) { if true { <|>foo; }; } #[test] fn test_hover_trait_show_qualifiers() { - check_hover_result( + let (_, actions) = check_hover_result( " //- /lib.rs unsafe trait foo<|>() {} ", &["unsafe trait foo"], ); + assert_impl_action(&actions[0], 13); } #[test] @@ -951,4 +1068,246 @@ fn func(foo: i32) { if true { <|>foo; }; } &["mod my"], ); } + + #[test] + fn test_hover_struct_doc_comment() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr() { + check_hover_result( + r#" + //- /lib.rs + #[doc = "bar docs"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs"], + ); + } + + #[test] + fn test_hover_struct_doc_attr_multiple_and_mixed() { + check_hover_result( + r#" + //- /lib.rs + /// bar docs 0 + #[doc = "bar docs 1"] + #[doc = "bar docs 2"] + struct Bar; + + fn foo() { + let bar = Ba<|>r; + } + "#, + &["struct Bar\n```\n___\n\nbar docs 0\n\nbar docs 1\n\nbar docs 2"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_comment() { + mark::check!(hover_macro_generated_struct_fn_doc_comment); + + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + /// Do the foo + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\n Do the foo"], + ); + } + + #[test] + fn test_hover_macro_generated_struct_fn_doc_attr() { + mark::check!(hover_macro_generated_struct_fn_doc_attr); + + check_hover_result( + r#" + //- /lib.rs + macro_rules! bar { + () => { + struct Bar; + impl Bar { + #[doc = "Do the foo"] + fn foo(&self) {} + } + } + } + + bar!(); + + fn foo() { + let bar = Bar; + bar.fo<|>o(); + } + "#, + &["Bar\n```\n\n```rust\nfn foo(&self)\n```\n___\n\nDo the foo"], + ); + } + + #[test] + fn test_hover_trait_has_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + trait foo<|>() {} + ", + &["trait foo"], + ); + assert_impl_action(&actions[0], 6); + } + + #[test] + fn test_hover_struct_has_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + struct foo<|>() {} + ", + &["struct foo"], + ); + assert_impl_action(&actions[0], 7); + } + + #[test] + fn test_hover_union_has_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + union foo<|>() {} + ", + &["union foo"], + ); + assert_impl_action(&actions[0], 6); + } + + #[test] + fn test_hover_enum_has_impl_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + enum foo<|>() { + A, + B + } + ", + &["enum foo"], + ); + assert_impl_action(&actions[0], 5); + } + + #[test] + fn test_hover_test_has_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + #[test] + fn foo_<|>test() {} + ", + &["fn foo_test()"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + Runnable( + Runnable { + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..24, + name: "foo_test", + kind: FN_DEF, + focus_range: Some( + 11..19, + ), + container_name: None, + description: None, + docs: None, + }, + kind: Test { + test_id: Path( + "foo_test", + ), + attr: TestAttr { + ignore: false, + }, + }, + cfg_exprs: [], + }, + ), + ] + "###); + } + + #[test] + fn test_hover_test_mod_has_action() { + let (_, actions) = check_hover_result( + " + //- /lib.rs + mod tests<|> { + #[test] + fn foo_test() {} + } + ", + &["mod tests"], + ); + assert_debug_snapshot!(actions, + @r###" + [ + Runnable( + Runnable { + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 0..46, + name: "tests", + kind: MODULE, + focus_range: Some( + 4..9, + ), + container_name: None, + description: None, + docs: None, + }, + kind: TestMod { + path: "tests", + }, + cfg_exprs: [], + }, + ), + ] + "###); + } } diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs index 75bd3c96bb1f..7eb2cef735f4 100644 --- a/crates/ra_ide/src/inlay_hints.rs +++ b/crates/ra_ide/src/inlay_hints.rs @@ -149,11 +149,10 @@ fn get_param_name_hints( ast::Expr::MethodCallExpr(expr) => expr.arg_list()?.args(), _ => return None, }; - let args_count = args.clone().count(); let fn_signature = get_fn_signature(sema, &expr)?; let n_params_to_skip = - if fn_signature.has_self_param && fn_signature.parameter_names.len() > args_count { + if fn_signature.has_self_param && matches!(&expr, ast::Expr::MethodCallExpr(_)) { 1 } else { 0 @@ -416,7 +415,7 @@ struct Test { } fn main() { - let zz = Test { t: 23, k: 33 }; + let zz = Test { t: 23u8, k: 33 }; let zz_ref = &zz; }"#, ); @@ -429,7 +428,7 @@ fn main() { label: "Test", }, InlayHint { - range: 105..111, + range: 107..113, kind: TypeHint, label: "&Test", }, diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 12d5716e8359..28f6867672c0 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs @@ -66,7 +66,7 @@ pub use crate::{ display::{file_structure, FunctionSignature, NavigationTarget, StructureNode}, expand_macro::ExpandedMacro, folding_ranges::{Fold, FoldKind}, - hover::HoverResult, + hover::{HoverAction, HoverConfig, HoverResult}, inlay_hints::{InlayHint, InlayHintsConfig, InlayKind}, references::{Declaration, Reference, ReferenceAccess, ReferenceKind, ReferenceSearchResult}, runnables::{Runnable, RunnableKind, TestId}, @@ -77,7 +77,7 @@ pub use crate::{ }; pub use hir::Documentation; -pub use ra_assists::{AssistConfig, AssistId}; +pub use ra_assists::{Assist, AssistConfig, AssistId, ResolvedAssist}; pub use ra_db::{ Canceled, CrateGraph, CrateId, Edition, FileId, FilePosition, FileRange, SourceRootId, }; @@ -142,14 +142,6 @@ pub struct AnalysisHost { db: RootDatabase, } -#[derive(Debug)] -pub struct Assist { - pub id: AssistId, - pub label: String, - pub group_label: Option, - pub source_change: SourceChange, -} - impl AnalysisHost { pub fn new(lru_capacity: Option) -> AnalysisHost { AnalysisHost { db: RootDatabase::new(lru_capacity) } @@ -470,20 +462,23 @@ impl Analysis { self.with_db(|db| completion::completions(db, config, position).map(Into::into)) } - /// Computes assists (aka code actions aka intentions) for the given + /// Computes resolved assists with source changes for the given position. + pub fn resolved_assists( + &self, + config: &AssistConfig, + frange: FileRange, + ) -> Cancelable> { + self.with_db(|db| ra_assists::Assist::resolved(db, config, frange)) + } + + /// Computes unresolved assists (aka code actions aka intentions) for the given /// position. - pub fn assists(&self, config: &AssistConfig, frange: FileRange) -> Cancelable> { - self.with_db(|db| { - ra_assists::Assist::resolved(db, config, frange) - .into_iter() - .map(|assist| Assist { - id: assist.assist.id, - label: assist.assist.label, - group_label: assist.assist.group.map(|it| it.0), - source_change: assist.source_change, - }) - .collect() - }) + pub fn unresolved_assists( + &self, + config: &AssistConfig, + frange: FileRange, + ) -> Cancelable> { + self.with_db(|db| Assist::unresolved(db, config, frange)) } /// Computes the set of diagnostics for the given file. @@ -508,7 +503,7 @@ impl Analysis { ) -> Cancelable> { self.with_db(|db| { let edits = ssr::parse_search_replace(query, parse_only, db)?; - Ok(SourceChange::source_file_edits(edits)) + Ok(SourceChange::from(edits)) }) } diff --git a/crates/ra_ide/src/references/rename.rs b/crates/ra_ide/src/references/rename.rs index 28c6349b1b4e..915d4f4d3b56 100644 --- a/crates/ra_ide/src/references/rename.rs +++ b/crates/ra_ide/src/references/rename.rs @@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum TestId { Name(String), Path(String), } -impl Display for TestId { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { +impl fmt::Display for TestId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { TestId::Name(name) => write!(f, "{}", name), TestId::Path(path) => write!(f, "{}", path), @@ -33,7 +33,7 @@ impl Display for TestId { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum RunnableKind { Test { test_id: TestId, attr: TestAttr }, TestMod { path: String }, @@ -42,6 +42,42 @@ pub enum RunnableKind { Bin, } +#[derive(Debug, Eq, PartialEq)] +pub struct RunnableAction { + pub run_title: &'static str, + pub debugee: bool, +} + +const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true }; +const DOCTEST: RunnableAction = + RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false }; +const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true }; +const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true }; + +impl Runnable { + // test package::module::testname + pub fn label(&self, target: Option) -> String { + match &self.kind { + RunnableKind::Test { test_id, .. } => format!("test {}", test_id), + RunnableKind::TestMod { path } => format!("test-mod {}", path), + RunnableKind::Bench { test_id } => format!("bench {}", test_id), + RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id), + RunnableKind::Bin => { + target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) + } + } + } + + pub fn action(&self) -> &'static RunnableAction { + match &self.kind { + RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST, + RunnableKind::DocTest { .. } => &DOCTEST, + RunnableKind::Bench { .. } => &BENCH, + RunnableKind::Bin => &BIN, + } + } +} + // Feature: Run // // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor @@ -59,7 +95,11 @@ pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec { source_file.syntax().descendants().filter_map(|i| runnable(&sema, i, file_id)).collect() } -fn runnable(sema: &Semantics, item: SyntaxNode, file_id: FileId) -> Option { +pub(crate) fn runnable( + sema: &Semantics, + item: SyntaxNode, + file_id: FileId, +) -> Option { match_ast! { match item { ast::FnDef(it) => runnable_fn(sema, it, file_id), @@ -131,10 +171,11 @@ fn runnable_fn( let cfg_exprs = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); - Some(Runnable { range: fn_def.syntax().text_range(), kind, cfg_exprs }) + let nav = NavigationTarget::from_named(sema.db, InFile::new(file_id.into(), &fn_def)); + Some(Runnable { nav, kind, cfg_exprs }) } -#[derive(Debug)] +#[derive(Debug, Copy, Clone)] pub struct TestAttr { pub ignore: bool, } @@ -183,7 +224,6 @@ fn runnable_mod( if !has_test_function { return None; } - let range = module.syntax().text_range(); let module_def = sema.to_def(&module)?; let path = module_def @@ -197,7 +237,8 @@ fn runnable_mod( let cfg_exprs = attrs.by_key("cfg").tt_values().map(|subtree| ra_cfg::parse_cfg(subtree)).collect(); - Some(Runnable { range, kind: RunnableKind::TestMod { path }, cfg_exprs }) + let nav = module_def.to_nav(sema.db); + Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg_exprs }) } #[cfg(test)] @@ -206,6 +247,15 @@ mod tests { use crate::mock_analysis::analysis_and_position; + use super::{Runnable, RunnableAction, BENCH, BIN, DOCTEST, TEST}; + + fn assert_actions(runnables: &[Runnable], actions: &[&RunnableAction]) { + assert_eq!( + actions, + runnables.into_iter().map(|it| it.action()).collect::>().as_slice() + ); + } + #[test] fn test_runnables() { let (analysis, pos) = analysis_and_position( @@ -220,6 +270,9 @@ mod tests { #[test] #[ignore] fn test_foo() {} + + #[bench] + fn bench() {} "#, ); let runnables = analysis.runnables(pos.file_id).unwrap(); @@ -227,12 +280,38 @@ mod tests { @r###" [ Runnable { - range: 1..21, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..21, + name: "main", + kind: FN_DEF, + focus_range: Some( + 12..16, + ), + container_name: None, + description: None, + docs: None, + }, kind: Bin, cfg_exprs: [], }, Runnable { - range: 22..46, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 22..46, + name: "test_foo", + kind: FN_DEF, + focus_range: Some( + 33..41, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "test_foo", @@ -244,7 +323,20 @@ mod tests { cfg_exprs: [], }, Runnable { - range: 47..81, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 47..81, + name: "test_foo", + kind: FN_DEF, + focus_range: Some( + 68..76, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "test_foo", @@ -255,9 +347,32 @@ mod tests { }, cfg_exprs: [], }, + Runnable { + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 82..104, + name: "bench", + kind: FN_DEF, + focus_range: Some( + 94..99, + ), + container_name: None, + description: None, + docs: None, + }, + kind: Bench { + test_id: Path( + "bench", + ), + }, + cfg_exprs: [], + }, ] "### ); + assert_actions(&runnables, &[&BIN, &TEST, &TEST, &BENCH]); } #[test] @@ -279,12 +394,38 @@ mod tests { @r###" [ Runnable { - range: 1..21, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..21, + name: "main", + kind: FN_DEF, + focus_range: Some( + 12..16, + ), + container_name: None, + description: None, + docs: None, + }, kind: Bin, cfg_exprs: [], }, Runnable { - range: 22..64, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 22..64, + name: "foo", + kind: FN_DEF, + focus_range: Some( + 56..59, + ), + container_name: None, + description: None, + docs: None, + }, kind: DocTest { test_id: Path( "foo", @@ -295,6 +436,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&BIN, &DOCTEST]); } #[test] @@ -319,12 +461,38 @@ mod tests { @r###" [ Runnable { - range: 1..21, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..21, + name: "main", + kind: FN_DEF, + focus_range: Some( + 12..16, + ), + container_name: None, + description: None, + docs: None, + }, kind: Bin, cfg_exprs: [], }, Runnable { - range: 51..105, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 51..105, + name: "foo", + kind: FN_DEF, + focus_range: Some( + 97..100, + ), + container_name: None, + description: None, + docs: None, + }, kind: DocTest { test_id: Path( "Data::foo", @@ -335,6 +503,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&BIN, &DOCTEST]); } #[test] @@ -354,14 +523,40 @@ mod tests { @r###" [ Runnable { - range: 1..59, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..59, + name: "test_mod", + kind: MODULE, + focus_range: Some( + 13..21, + ), + container_name: None, + description: None, + docs: None, + }, kind: TestMod { path: "test_mod", }, cfg_exprs: [], }, Runnable { - range: 28..57, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 28..57, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 43..52, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "test_mod::test_foo1", @@ -375,6 +570,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&TEST, &TEST]); } #[test] @@ -396,14 +592,40 @@ mod tests { @r###" [ Runnable { - range: 23..85, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 23..85, + name: "test_mod", + kind: MODULE, + focus_range: Some( + 27..35, + ), + container_name: None, + description: None, + docs: None, + }, kind: TestMod { path: "foo::test_mod", }, cfg_exprs: [], }, Runnable { - range: 46..79, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 46..79, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 65..74, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "foo::test_mod::test_foo1", @@ -417,6 +639,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&TEST, &TEST]); } #[test] @@ -440,14 +663,40 @@ mod tests { @r###" [ Runnable { - range: 41..115, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 41..115, + name: "test_mod", + kind: MODULE, + focus_range: Some( + 45..53, + ), + container_name: None, + description: None, + docs: None, + }, kind: TestMod { path: "foo::bar::test_mod", }, cfg_exprs: [], }, Runnable { - range: 68..105, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 68..105, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 91..100, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "foo::bar::test_mod::test_foo1", @@ -461,6 +710,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&TEST, &TEST]); } #[test] @@ -479,7 +729,20 @@ mod tests { @r###" [ Runnable { - range: 1..58, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..58, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 44..53, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "test_foo1", @@ -498,6 +761,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&TEST]); } #[test] @@ -516,7 +780,20 @@ mod tests { @r###" [ Runnable { - range: 1..80, + nav: NavigationTarget { + file_id: FileId( + 1, + ), + full_range: 1..80, + name: "test_foo1", + kind: FN_DEF, + focus_range: Some( + 66..75, + ), + container_name: None, + description: None, + docs: None, + }, kind: Test { test_id: Path( "test_foo1", @@ -543,6 +820,7 @@ mod tests { ] "### ); + assert_actions(&runnables, &[&TEST]); } #[test] diff --git a/crates/ra_ide/src/snapshots/highlight_doctest.html b/crates/ra_ide/src/snapshots/highlight_doctest.html new file mode 100644 index 000000000000..0ae8c7efcd6b --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_doctest.html @@ -0,0 +1,71 @@ + + +
impl Foo {
+    /// Constructs a new `Foo`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// # #![allow(unused_mut)]
+    /// let mut foo: Foo = Foo::new();
+    /// ```
+    pub const fn new() -> Foo {
+        Foo { }
+    }
+
+    /// `bar` method on `Foo`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// let foo = Foo::new();
+    ///
+    /// // calls bar on foo
+    /// assert!(foo.bar());
+    ///
+    /// /* multi-line
+    ///        comment */
+    ///
+    /// let multi_line_string = "Foo
+    ///   bar
+    ///          ";
+    ///
+    /// ```
+    ///
+    /// ```
+    /// let foobar = Foo::new().bar();
+    /// ```
+    pub fn foo(&self) -> bool {
+        true
+    }
+}
\ No newline at end of file diff --git a/crates/ra_ide/src/snapshots/highlight_injection.html b/crates/ra_ide/src/snapshots/highlight_injection.html index 68fc589bc785..dec06eb518f5 100644 --- a/crates/ra_ide/src/snapshots/highlight_injection.html +++ b/crates/ra_ide/src/snapshots/highlight_injection.html @@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.function.unsafe { color: #BC8383; } +.operator.unsafe { color: #BC8383; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/snapshots/highlight_strings.html b/crates/ra_ide/src/snapshots/highlight_strings.html index 326744361c68..849eb3b7349b 100644 --- a/crates/ra_ide/src/snapshots/highlight_strings.html +++ b/crates/ra_ide/src/snapshots/highlight_strings.html @@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.function.unsafe { color: #BC8383; } +.operator.unsafe { color: #BC8383; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } @@ -52,6 +54,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd println!("{argument}", argument = "test"); // => "test" println!("{name} {}", 1, name = 2); // => "2 1" println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" + println!("{{{}}}", 2); // => "{2}" println!("Hello {:5}!", "x"); println!("Hello {:1$}!", "x", 5); println!("Hello {1:0$}!", 5, "x"); @@ -61,7 +64,7 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd println!("Hello {:^5}!", "x"); println!("Hello {:>5}!", "x"); println!("Hello {:+}!", 5); - println!("{:#x}!", 27); + println!("{:#x}!", 27); println!("Hello {:05}!", 5); println!("Hello {:05}!", -5); println!("{:#010x}!", 27); diff --git a/crates/ra_ide/src/snapshots/highlight_unsafe.html b/crates/ra_ide/src/snapshots/highlight_unsafe.html new file mode 100644 index 000000000000..bd24e6e381ef --- /dev/null +++ b/crates/ra_ide/src/snapshots/highlight_unsafe.html @@ -0,0 +1,49 @@ + + +
unsafe fn unsafe_fn() {}
+
+struct HasUnsafeFn;
+
+impl HasUnsafeFn {
+    unsafe fn unsafe_method(&self) {}
+}
+
+fn main() {
+    let x = &5 as *const usize;
+    unsafe {
+        unsafe_fn();
+        HasUnsafeFn.unsafe_method();
+        let y = *(x);
+        let z = -x;
+    }
+}
\ No newline at end of file diff --git a/crates/ra_ide/src/snapshots/highlighting.html b/crates/ra_ide/src/snapshots/highlighting.html index 352e350955fc..33548d43ccd5 100644 --- a/crates/ra_ide/src/snapshots/highlighting.html +++ b/crates/ra_ide/src/snapshots/highlighting.html @@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.function.unsafe { color: #BC8383; } +.operator.unsafe { color: #BC8383; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } @@ -82,7 +84,9 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd let y = &mut x; let z = &y; - y; + let Foo { x: z, y } = Foo { x: z, y }; + + y; } enum Option<T> { diff --git a/crates/ra_ide/src/snapshots/rainbow_highlighting.html b/crates/ra_ide/src/snapshots/rainbow_highlighting.html index 2a0294f719f5..1ab06182c040 100644 --- a/crates/ra_ide/src/snapshots/rainbow_highlighting.html +++ b/crates/ra_ide/src/snapshots/rainbow_highlighting.html @@ -10,6 +10,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.function.unsafe { color: #BC8383; } +.operator.unsafe { color: #BC8383; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/syntax_highlighting.rs b/crates/ra_ide/src/syntax_highlighting.rs index 0b53ebe69563..ab45c364a81f 100644 --- a/crates/ra_ide/src/syntax_highlighting.rs +++ b/crates/ra_ide/src/syntax_highlighting.rs @@ -1,5 +1,6 @@ mod tags; mod html; +mod injection; #[cfg(test)] mod tests; @@ -10,14 +11,14 @@ use ra_ide_db::{ }; use ra_prof::profile; use ra_syntax::{ - ast::{self, HasFormatSpecifier, HasQuotes, HasStringValue}, + ast::{self, HasFormatSpecifier}, AstNode, AstToken, Direction, NodeOrToken, SyntaxElement, SyntaxKind::*, - SyntaxToken, TextRange, WalkEvent, T, + TextRange, WalkEvent, T, }; use rustc_hash::FxHashMap; -use crate::{call_info::ActiveParameter, Analysis, FileId}; +use crate::FileId; use ast::FormatSpecifier; pub(crate) use html::highlight_as_html; @@ -123,6 +124,23 @@ pub(crate) fn highlight( _ => (), } + // Check for Rust code in documentation + match &event { + WalkEvent::Leave(NodeOrToken::Node(node)) => { + if let Some((doctest, range_mapping, new_comments)) = + injection::extract_doc_comments(node) + { + injection::highlight_doc_comment( + doctest, + range_mapping, + new_comments, + &mut stack, + ); + } + } + _ => (), + } + let element = match event { WalkEvent::Enter(it) => it, WalkEvent::Leave(_) => continue, @@ -173,7 +191,7 @@ pub(crate) fn highlight( if let Some(token) = element.as_token().cloned().and_then(ast::RawString::cast) { let expanded = element_to_highlight.as_token().unwrap().clone(); - if highlight_injection(&mut stack, &sema, token, expanded).is_some() { + if injection::highlight_injection(&mut stack, &sema, token, expanded).is_some() { continue; } } @@ -259,9 +277,8 @@ impl HighlightedRangeStack { let mut parent = prev.pop().unwrap(); for ele in children { assert!(parent.range.contains_range(ele.range)); - let mut cloned = parent.clone(); - parent.range = TextRange::new(parent.range.start(), ele.range.start()); - cloned.range = TextRange::new(ele.range.end(), cloned.range.end()); + + let cloned = Self::intersect(&mut parent, &ele); if !parent.range.is_empty() { prev.push(parent); } @@ -274,6 +291,62 @@ impl HighlightedRangeStack { } } + /// Intersects the `HighlightedRange` `parent` with `child`. + /// `parent` is mutated in place, becoming the range before `child`. + /// Returns the range (of the same type as `parent`) *after* `child`. + fn intersect(parent: &mut HighlightedRange, child: &HighlightedRange) -> HighlightedRange { + assert!(parent.range.contains_range(child.range)); + + let mut cloned = parent.clone(); + parent.range = TextRange::new(parent.range.start(), child.range.start()); + cloned.range = TextRange::new(child.range.end(), cloned.range.end()); + + cloned + } + + /// Similar to `pop`, but can modify arbitrary prior ranges (where `pop`) + /// can only modify the last range currently on the stack. + /// Can be used to do injections that span multiple ranges, like the + /// doctest injection below. + /// If `delete` is set to true, the parent range is deleted instead of + /// intersected. + /// + /// Note that `pop` can be simulated by `pop_and_inject(false)` but the + /// latter is computationally more expensive. + fn pop_and_inject(&mut self, delete: bool) { + let mut children = self.stack.pop().unwrap(); + let prev = self.stack.last_mut().unwrap(); + children.sort_by_key(|range| range.range.start()); + prev.sort_by_key(|range| range.range.start()); + + for child in children { + if let Some(idx) = + prev.iter().position(|parent| parent.range.contains_range(child.range)) + { + let cloned = Self::intersect(&mut prev[idx], &child); + let insert_idx = if delete || prev[idx].range.is_empty() { + prev.remove(idx); + idx + } else { + idx + 1 + }; + prev.insert(insert_idx, child); + if !delete && !cloned.range.is_empty() { + prev.insert(insert_idx + 1, cloned); + } + } else if let Some(_idx) = + prev.iter().position(|parent| parent.range.contains(child.range.start())) + { + unreachable!("child range should be completely contained in parent range"); + } else { + let idx = prev + .binary_search_by_key(&child.range.start(), |range| range.range.start()) + .unwrap_or_else(|x| x); + prev.insert(idx, child); + } + } + } + fn add(&mut self, range: HighlightedRange) { self.stack .last_mut() @@ -363,6 +436,7 @@ fn highlight_element( highlight_name(db, def) | HighlightModifier::Definition } Some(NameClass::ConstReference(def)) => highlight_name(db, def), + Some(NameClass::FieldShorthand { .. }) => HighlightTag::Field.into(), None => highlight_name_by_syntax(name) | HighlightModifier::Definition, } } @@ -406,6 +480,19 @@ fn highlight_element( _ => h, } } + T![*] => { + let prefix_expr = element.parent().and_then(ast::PrefixExpr::cast)?; + + let expr = prefix_expr.expr()?; + let ty = sema.type_of_expr(&expr)?; + if !ty.is_raw_ptr() { + return None; + } + + let mut h = Highlight::new(HighlightTag::Operator); + h |= HighlightModifier::Unsafe; + h + } k if k.is_keyword() => { let h = Highlight::new(HighlightTag::Keyword); @@ -458,7 +545,13 @@ fn highlight_name(db: &RootDatabase, def: Definition) -> Highlight { Definition::Field(_) => HighlightTag::Field, Definition::ModuleDef(def) => match def { hir::ModuleDef::Module(_) => HighlightTag::Module, - hir::ModuleDef::Function(_) => HighlightTag::Function, + hir::ModuleDef::Function(func) => { + let mut h = HighlightTag::Function.into(); + if func.is_unsafe(db) { + h |= HighlightModifier::Unsafe; + } + return h; + } hir::ModuleDef::Adt(hir::Adt::Struct(_)) => HighlightTag::Struct, hir::ModuleDef::Adt(hir::Adt::Enum(_)) => HighlightTag::Enum, hir::ModuleDef::Adt(hir::Adt::Union(_)) => HighlightTag::Union, @@ -516,42 +609,3 @@ fn highlight_name_by_syntax(name: ast::Name) -> Highlight { tag.into() } - -fn highlight_injection( - acc: &mut HighlightedRangeStack, - sema: &Semantics, - literal: ast::RawString, - expanded: SyntaxToken, -) -> Option<()> { - let active_parameter = ActiveParameter::at_token(&sema, expanded)?; - if !active_parameter.name.starts_with("ra_fixture") { - return None; - } - let value = literal.value()?; - let (analysis, tmp_file_id) = Analysis::from_single_file(value); - - if let Some(range) = literal.open_quote_text_range() { - acc.add(HighlightedRange { - range, - highlight: HighlightTag::StringLiteral.into(), - binding_hash: None, - }) - } - - for mut h in analysis.highlight(tmp_file_id).unwrap() { - if let Some(r) = literal.map_range_up(h.range) { - h.range = r; - acc.add(h) - } - } - - if let Some(range) = literal.close_quote_text_range() { - acc.add(HighlightedRange { - range, - highlight: HighlightTag::StringLiteral.into(), - binding_hash: None, - }) - } - - Some(()) -} diff --git a/crates/ra_ide/src/syntax_highlighting/html.rs b/crates/ra_ide/src/syntax_highlighting/html.rs index edfe61f39a13..5bada6252c0f 100644 --- a/crates/ra_ide/src/syntax_highlighting/html.rs +++ b/crates/ra_ide/src/syntax_highlighting/html.rs @@ -69,6 +69,8 @@ pre { color: #DCDCCC; background: #3F3F3F; font-size: 22px; padd .string_literal { color: #CC9393; } .field { color: #94BFF3; } .function { color: #93E0E3; } +.function.unsafe { color: #BC8383; } +.operator.unsafe { color: #BC8383; } .parameter { color: #94BFF3; } .text { color: #DCDCCC; } .type { color: #7CB8BB; } diff --git a/crates/ra_ide/src/syntax_highlighting/injection.rs b/crates/ra_ide/src/syntax_highlighting/injection.rs new file mode 100644 index 000000000000..3575a0fc6e9f --- /dev/null +++ b/crates/ra_ide/src/syntax_highlighting/injection.rs @@ -0,0 +1,168 @@ +//! Syntax highlighting injections such as highlighting of documentation tests. + +use std::{collections::BTreeMap, convert::TryFrom}; + +use ast::{HasQuotes, HasStringValue}; +use hir::Semantics; +use ra_syntax::{ast, AstToken, SyntaxNode, SyntaxToken, TextRange, TextSize}; +use stdx::SepBy; + +use crate::{call_info::ActiveParameter, Analysis, HighlightTag, HighlightedRange, RootDatabase}; + +use super::HighlightedRangeStack; + +pub(super) fn highlight_injection( + acc: &mut HighlightedRangeStack, + sema: &Semantics, + literal: ast::RawString, + expanded: SyntaxToken, +) -> Option<()> { + let active_parameter = ActiveParameter::at_token(&sema, expanded)?; + if !active_parameter.name.starts_with("ra_fixture") { + return None; + } + let value = literal.value()?; + let (analysis, tmp_file_id) = Analysis::from_single_file(value); + + if let Some(range) = literal.open_quote_text_range() { + acc.add(HighlightedRange { + range, + highlight: HighlightTag::StringLiteral.into(), + binding_hash: None, + }) + } + + for mut h in analysis.highlight(tmp_file_id).unwrap() { + if let Some(r) = literal.map_range_up(h.range) { + h.range = r; + acc.add(h) + } + } + + if let Some(range) = literal.close_quote_text_range() { + acc.add(HighlightedRange { + range, + highlight: HighlightTag::StringLiteral.into(), + binding_hash: None, + }) + } + + Some(()) +} + +/// Mapping from extracted documentation code to original code +type RangesMap = BTreeMap; + +/// Extracts Rust code from documentation comments as well as a mapping from +/// the extracted source code back to the original source ranges. +/// Lastly, a vector of new comment highlight ranges (spanning only the +/// comment prefix) is returned which is used in the syntax highlighting +/// injection to replace the previous (line-spanning) comment ranges. +pub(super) fn extract_doc_comments( + node: &SyntaxNode, +) -> Option<(String, RangesMap, Vec)> { + // wrap the doctest into function body to get correct syntax highlighting + let prefix = "fn doctest() {\n"; + let suffix = "}\n"; + // Mapping from extracted documentation code to original code + let mut range_mapping: RangesMap = BTreeMap::new(); + let mut line_start = TextSize::try_from(prefix.len()).unwrap(); + let mut is_doctest = false; + // Replace the original, line-spanning comment ranges by new, only comment-prefix + // spanning comment ranges. + let mut new_comments = Vec::new(); + let doctest = node + .children_with_tokens() + .filter_map(|el| el.into_token().and_then(ast::Comment::cast)) + .filter(|comment| comment.kind().doc.is_some()) + .filter(|comment| { + if comment.text().contains("```") { + is_doctest = !is_doctest; + false + } else { + is_doctest + } + }) + .map(|comment| { + let prefix_len = comment.prefix().len(); + let line: &str = comment.text().as_str(); + let range = comment.syntax().text_range(); + + // whitespace after comment is ignored + let pos = if let Some(ws) = line.chars().nth(prefix_len).filter(|c| c.is_whitespace()) { + prefix_len + ws.len_utf8() + } else { + prefix_len + }; + + // lines marked with `#` should be ignored in output, we skip the `#` char + let pos = if let Some(ws) = line.chars().nth(pos).filter(|&c| c == '#') { + pos + ws.len_utf8() + } else { + pos + }; + + range_mapping.insert(line_start, range.start() + TextSize::try_from(pos).unwrap()); + new_comments.push(HighlightedRange { + range: TextRange::new( + range.start(), + range.start() + TextSize::try_from(pos).unwrap(), + ), + highlight: HighlightTag::Comment.into(), + binding_hash: None, + }); + line_start += range.len() - TextSize::try_from(pos).unwrap(); + line_start += TextSize::try_from('\n'.len_utf8()).unwrap(); + + line[pos..].to_owned() + }) + .sep_by("\n") + .to_string(); + + if doctest.is_empty() { + return None; + } + + let doctest = format!("{}{}{}", prefix, doctest, suffix); + Some((doctest, range_mapping, new_comments)) +} + +/// Injection of syntax highlighting of doctests. +pub(super) fn highlight_doc_comment( + text: String, + range_mapping: RangesMap, + new_comments: Vec, + stack: &mut HighlightedRangeStack, +) { + let (analysis, tmp_file_id) = Analysis::from_single_file(text); + + stack.push(); + for mut h in analysis.highlight(tmp_file_id).unwrap() { + // Determine start offset and end offset in case of multi-line ranges + let mut start_offset = None; + let mut end_offset = None; + for (line_start, orig_line_start) in range_mapping.range(..h.range.end()).rev() { + if line_start <= &h.range.start() { + start_offset.get_or_insert(orig_line_start - line_start); + break; + } else { + end_offset.get_or_insert(orig_line_start - line_start); + } + } + if let Some(start_offset) = start_offset { + h.range = TextRange::new( + h.range.start() + start_offset, + h.range.end() + end_offset.unwrap_or(start_offset), + ); + stack.add(h); + } + } + + // Inject the comment prefix highlight ranges + stack.push(); + for comment in new_comments { + stack.add(comment); + } + stack.pop_and_inject(false); + stack.pop_and_inject(true); +} diff --git a/crates/ra_ide/src/syntax_highlighting/tags.rs b/crates/ra_ide/src/syntax_highlighting/tags.rs index 1514531de2e3..94f466966a3a 100644 --- a/crates/ra_ide/src/syntax_highlighting/tags.rs +++ b/crates/ra_ide/src/syntax_highlighting/tags.rs @@ -24,12 +24,14 @@ pub enum HighlightTag { Enum, EnumVariant, Field, + FormatSpecifier, Function, Keyword, Lifetime, Macro, Module, NumericLiteral, + Operator, SelfKeyword, SelfType, Static, @@ -41,8 +43,6 @@ pub enum HighlightTag { Union, Local, UnresolvedReference, - FormatSpecifier, - Operator, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -72,12 +72,14 @@ impl HighlightTag { HighlightTag::Enum => "enum", HighlightTag::EnumVariant => "enum_variant", HighlightTag::Field => "field", + HighlightTag::FormatSpecifier => "format_specifier", HighlightTag::Function => "function", HighlightTag::Keyword => "keyword", HighlightTag::Lifetime => "lifetime", HighlightTag::Macro => "macro", HighlightTag::Module => "module", HighlightTag::NumericLiteral => "numeric_literal", + HighlightTag::Operator => "operator", HighlightTag::SelfKeyword => "self_keyword", HighlightTag::SelfType => "self_type", HighlightTag::Static => "static", @@ -89,8 +91,6 @@ impl HighlightTag { HighlightTag::Union => "union", HighlightTag::Local => "variable", HighlightTag::UnresolvedReference => "unresolved_reference", - HighlightTag::FormatSpecifier => "format_specifier", - HighlightTag::Operator => "operator", } } } diff --git a/crates/ra_ide/src/syntax_highlighting/tests.rs b/crates/ra_ide/src/syntax_highlighting/tests.rs index eb43a23da682..949bf59a0d24 100644 --- a/crates/ra_ide/src/syntax_highlighting/tests.rs +++ b/crates/ra_ide/src/syntax_highlighting/tests.rs @@ -9,7 +9,7 @@ use crate::{ #[test] fn test_highlighting() { - let (analysis, file_id) = single_file( + check_highlighting( r#" #[derive(Clone, Debug)] struct Foo { @@ -65,6 +65,8 @@ fn main() { let y = &mut x; let z = &y; + let Foo { x: z, y } = Foo { x: z, y }; + y; } @@ -84,17 +86,14 @@ impl Option { } "# .trim(), + "crates/ra_ide/src/snapshots/highlighting.html", + false, ); - let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlighting.html"); - let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); - let expected_html = &read_text(&dst_file); - fs::write(dst_file, &actual_html).unwrap(); - assert_eq_text!(expected_html, actual_html); } #[test] fn test_rainbow_highlighting() { - let (analysis, file_id) = single_file( + check_highlighting( r#" fn main() { let hello = "hello"; @@ -110,12 +109,9 @@ fn bar() { } "# .trim(), + "crates/ra_ide/src/snapshots/rainbow_highlighting.html", + true, ); - let dst_file = project_dir().join("crates/ra_ide/src/snapshots/rainbow_highlighting.html"); - let actual_html = &analysis.highlight_as_html(file_id, true).unwrap(); - let expected_html = &read_text(&dst_file); - fs::write(dst_file, &actual_html).unwrap(); - assert_eq_text!(expected_html, actual_html); } #[test] @@ -153,7 +149,7 @@ fn test_ranges() { #[test] fn test_flattening() { - let (analysis, file_id) = single_file( + check_highlighting( r##" fn fixture(ra_fixture: &str) {} @@ -167,13 +163,9 @@ fn main() { ); }"## .trim(), + "crates/ra_ide/src/snapshots/highlight_injection.html", + false, ); - - let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_injection.html"); - let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); - let expected_html = &read_text(&dst_file); - fs::write(dst_file, &actual_html).unwrap(); - assert_eq_text!(expected_html, actual_html); } #[test] @@ -192,7 +184,7 @@ macro_rules! test {} fn test_string_highlighting() { // The format string detection is based on macro-expansion, // thus, we have to copy the macro definition from `std` - let (analysis, file_id) = single_file( + check_highlighting( r#" macro_rules! println { ($($arg:tt)*) => ({ @@ -218,6 +210,7 @@ fn main() { println!("{argument}", argument = "test"); // => "test" println!("{name} {}", 1, name = 2); // => "2 1" println!("{a} {c} {b}", a="a", b='b', c=3); // => "a 3 b" + println!("{{{}}}", 2); // => "{2}" println!("Hello {:5}!", "x"); println!("Hello {:1$}!", "x", 5); println!("Hello {1:0$}!", 5, "x"); @@ -249,10 +242,96 @@ fn main() { println!("{ничоси}", ничоси = 92); }"# .trim(), + "crates/ra_ide/src/snapshots/highlight_strings.html", + false, ); +} - let dst_file = project_dir().join("crates/ra_ide/src/snapshots/highlight_strings.html"); - let actual_html = &analysis.highlight_as_html(file_id, false).unwrap(); +#[test] +fn test_unsafe_highlighting() { + check_highlighting( + r#" +unsafe fn unsafe_fn() {} + +struct HasUnsafeFn; + +impl HasUnsafeFn { + unsafe fn unsafe_method(&self) {} +} + +fn main() { + let x = &5 as *const usize; + unsafe { + unsafe_fn(); + HasUnsafeFn.unsafe_method(); + let y = *(x); + let z = -x; + } +} +"# + .trim(), + "crates/ra_ide/src/snapshots/highlight_unsafe.html", + false, + ); +} + +#[test] +fn test_highlight_doctest() { + check_highlighting( + r#" +impl Foo { + /// Constructs a new `Foo`. + /// + /// # Examples + /// + /// ``` + /// # #![allow(unused_mut)] + /// let mut foo: Foo = Foo::new(); + /// ``` + pub const fn new() -> Foo { + Foo { } + } + + /// `bar` method on `Foo`. + /// + /// # Examples + /// + /// ``` + /// let foo = Foo::new(); + /// + /// // calls bar on foo + /// assert!(foo.bar()); + /// + /// /* multi-line + /// comment */ + /// + /// let multi_line_string = "Foo + /// bar + /// "; + /// + /// ``` + /// + /// ``` + /// let foobar = Foo::new().bar(); + /// ``` + pub fn foo(&self) -> bool { + true + } +} +"# + .trim(), + "crates/ra_ide/src/snapshots/highlight_doctest.html", + false, + ) +} + +/// Highlights the code given by the `ra_fixture` argument, renders the +/// result as HTML, and compares it with the HTML file given as `snapshot`. +/// Note that the `snapshot` file is overwritten by the rendered HTML. +fn check_highlighting(ra_fixture: &str, snapshot: &str, rainbow: bool) { + let (analysis, file_id) = single_file(ra_fixture); + let dst_file = project_dir().join(snapshot); + let actual_html = &analysis.highlight_as_html(file_id, rainbow).unwrap(); let expected_html = &read_text(&dst_file); fs::write(dst_file, &actual_html).unwrap(); assert_eq_text!(expected_html, actual_html); diff --git a/crates/ra_ide/src/typing.rs b/crates/ra_ide/src/typing.rs index 67e2c33a000e..83776d2b6d34 100644 --- a/crates/ra_ide/src/typing.rs +++ b/crates/ra_ide/src/typing.rs @@ -17,11 +17,13 @@ mod on_enter; use ra_db::{FilePosition, SourceDatabase}; use ra_fmt::leading_indent; -use ra_ide_db::RootDatabase; +use ra_ide_db::{source_change::SourceFileEdit, RootDatabase}; use ra_syntax::{ algo::find_node_at_offset, ast::{self, AstToken}, - AstNode, SourceFile, TextRange, TextSize, + AstNode, SourceFile, + SyntaxKind::{FIELD_EXPR, METHOD_CALL_EXPR}, + TextRange, TextSize, }; use ra_text_edit::TextEdit; @@ -47,8 +49,8 @@ pub(crate) fn on_char_typed( assert!(TRIGGER_CHARS.contains(char_typed)); let file = &db.parse(position.file_id).tree(); assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed)); - let text_edit = on_char_typed_inner(file, position.offset, char_typed)?; - Some(SourceChange::source_file_edit_from(position.file_id, text_edit)) + let edit = on_char_typed_inner(file, position.offset, char_typed)?; + Some(SourceFileEdit { file_id: position.file_id, edit }.into()) } fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option { @@ -98,9 +100,12 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option { }; let current_indent_len = TextSize::of(current_indent); + let parent = whitespace.syntax().parent(); // Make sure dot is a part of call chain - let field_expr = ast::FieldExpr::cast(whitespace.syntax().parent())?; - let prev_indent = leading_indent(field_expr.syntax())?; + if !matches!(parent.kind(), FIELD_EXPR | METHOD_CALL_EXPR) { + return None; + } + let prev_indent = leading_indent(&parent)?; let target_indent = format!(" {}", prev_indent); let target_indent_len = TextSize::of(&target_indent); if current_indent_len == target_indent_len { @@ -143,11 +148,11 @@ mod tests { }) } - fn type_char(char_typed: char, before: &str, after: &str) { - let actual = do_type_char(char_typed, before) + fn type_char(char_typed: char, ra_fixture_before: &str, ra_fixture_after: &str) { + let actual = do_type_char(char_typed, ra_fixture_before) .unwrap_or_else(|| panic!("typing `{}` did nothing", char_typed)); - assert_eq_text!(after, &actual); + assert_eq_text!(ra_fixture_after, &actual); } fn type_char_noop(char_typed: char, before: &str) { @@ -248,6 +253,27 @@ fn foo() { ) } + #[test] + fn indents_new_chain_call_with_let() { + type_char( + '.', + r#" +fn main() { + let _ = foo + <|> + bar() +} +"#, + r#" +fn main() { + let _ = foo + . + bar() +} +"#, + ); + } + #[test] fn indents_continued_chain_call() { type_char( diff --git a/crates/ra_ide_db/src/change.rs b/crates/ra_ide_db/src/change.rs index 8446ef88e8c5..2fc796a85922 100644 --- a/crates/ra_ide_db/src/change.rs +++ b/crates/ra_ide_db/src/change.rs @@ -16,7 +16,7 @@ use rustc_hash::FxHashMap; use crate::{ symbol_index::{SymbolIndex, SymbolsDatabase}, - DebugData, RootDatabase, + RootDatabase, }; #[derive(Default)] @@ -26,7 +26,6 @@ pub struct AnalysisChange { files_changed: Vec<(FileId, Arc)>, libraries_added: Vec, crate_graph: Option, - debug_data: DebugData, } impl fmt::Debug for AnalysisChange { @@ -87,10 +86,6 @@ impl AnalysisChange { pub fn set_crate_graph(&mut self, graph: CrateGraph) { self.crate_graph = Some(graph); } - - pub fn set_debug_root_path(&mut self, source_root_id: SourceRootId, path: String) { - self.debug_data.root_paths.insert(source_root_id, path); - } } #[derive(Debug)] @@ -218,8 +213,6 @@ impl RootDatabase { if let Some(crate_graph) = change.crate_graph { self.set_crate_graph_with_durability(Arc::new(crate_graph), Durability::HIGH) } - - Arc::make_mut(&mut self.debug_data).merge(change.debug_data) } fn apply_root_change(&mut self, root_id: SourceRootId, root_change: RootChange) { @@ -334,6 +327,7 @@ impl RootDatabase { hir::db::CrateLangItemsQuery hir::db::LangItemQuery hir::db::DocumentationQuery + hir::db::ImportMapQuery // InternDatabase hir::db::InternFunctionQuery @@ -369,6 +363,7 @@ impl RootDatabase { hir::db::ImplDatumQuery hir::db::AssociatedTyValueQuery hir::db::TraitSolveQuery + hir::db::ReturnTypeImplTraitsQuery // SymbolsDatabase crate::symbol_index::FileSymbolsQuery diff --git a/crates/ra_ide_db/src/defs.rs b/crates/ra_ide_db/src/defs.rs index 8b06cbfc54b7..3ef5e74b6997 100644 --- a/crates/ra_ide_db/src/defs.rs +++ b/crates/ra_ide_db/src/defs.rs @@ -18,7 +18,7 @@ use ra_syntax::{ use crate::RootDatabase; // FIXME: a more precise name would probably be `Symbol`? -#[derive(Debug, PartialEq, Eq)] +#[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Definition { Macro(MacroDef), Field(Field), @@ -78,10 +78,15 @@ impl Definition { } } +#[derive(Debug)] pub enum NameClass { Definition(Definition), /// `None` in `if let None = Some(82) {}` ConstReference(Definition), + FieldShorthand { + local: Local, + field: Definition, + }, } impl NameClass { @@ -89,12 +94,14 @@ impl NameClass { match self { NameClass::Definition(it) => Some(it), NameClass::ConstReference(_) => None, + NameClass::FieldShorthand { local, field: _ } => Some(Definition::Local(local)), } } pub fn definition(self) -> Definition { match self { NameClass::Definition(it) | NameClass::ConstReference(it) => it, + NameClass::FieldShorthand { local: _, field } => field, } } } @@ -102,18 +109,14 @@ impl NameClass { pub fn classify_name(sema: &Semantics, name: &ast::Name) -> Option { let _p = profile("classify_name"); - if let Some(bind_pat) = name.syntax().parent().and_then(ast::BindPat::cast) { + let parent = name.syntax().parent()?; + + if let Some(bind_pat) = ast::BindPat::cast(parent.clone()) { if let Some(def) = sema.resolve_bind_pat_to_const(&bind_pat) { return Some(NameClass::ConstReference(Definition::ModuleDef(def))); } } - classify_name_inner(sema, name).map(NameClass::Definition) -} - -fn classify_name_inner(sema: &Semantics, name: &ast::Name) -> Option { - let parent = name.syntax().parent()?; - match_ast! { match parent { ast::Alias(it) => { @@ -123,63 +126,73 @@ fn classify_name_inner(sema: &Semantics, name: &ast::Name) -> Opti let name_ref = path_segment.name_ref()?; let name_ref_class = classify_name_ref(sema, &name_ref)?; - Some(name_ref_class.definition()) + Some(NameClass::Definition(name_ref_class.definition())) }, ast::BindPat(it) => { let local = sema.to_def(&it)?; - Some(Definition::Local(local)) + + if let Some(record_field_pat) = it.syntax().parent().and_then(ast::RecordFieldPat::cast) { + if record_field_pat.name_ref().is_none() { + if let Some(field) = sema.resolve_record_field_pat(&record_field_pat) { + let field = Definition::Field(field); + return Some(NameClass::FieldShorthand { local, field }); + } + } + } + + Some(NameClass::Definition(Definition::Local(local))) }, ast::RecordFieldDef(it) => { let field: hir::Field = sema.to_def(&it)?; - Some(Definition::Field(field)) + Some(NameClass::Definition(Definition::Field(field))) }, ast::Module(it) => { let def = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::StructDef(it) => { let def: hir::Struct = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::UnionDef(it) => { let def: hir::Union = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::EnumDef(it) => { let def: hir::Enum = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::TraitDef(it) => { let def: hir::Trait = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::StaticDef(it) => { let def: hir::Static = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::EnumVariant(it) => { let def: hir::EnumVariant = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::FnDef(it) => { let def: hir::Function = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::ConstDef(it) => { let def: hir::Const = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::TypeAliasDef(it) => { let def: hir::TypeAlias = sema.to_def(&it)?; - Some(Definition::ModuleDef(def.into())) + Some(NameClass::Definition(Definition::ModuleDef(def.into()))) }, ast::MacroCall(it) => { let def = sema.to_def(&it)?; - Some(Definition::Macro(def)) + Some(NameClass::Definition(Definition::Macro(def))) }, ast::TypeParam(it) => { let def = sema.to_def(&it)?; - Some(Definition::TypeParam(def)) + Some(NameClass::Definition(Definition::TypeParam(def))) }, _ => None, } diff --git a/crates/ra_ide_db/src/imports_locator.rs b/crates/ra_ide_db/src/imports_locator.rs index bf0d8db6067b..fff112e66103 100644 --- a/crates/ra_ide_db/src/imports_locator.rs +++ b/crates/ra_ide_db/src/imports_locator.rs @@ -1,7 +1,7 @@ //! This module contains an import search funcionality that is provided to the ra_assists module. //! Later, this should be moved away to a separate crate that is accessible from the ra_assists module. -use hir::{MacroDef, ModuleDef, Semantics}; +use hir::{Crate, MacroDef, ModuleDef, Semantics}; use ra_prof::profile; use ra_syntax::{ast, AstNode, SyntaxKind::NAME}; @@ -11,44 +11,46 @@ use crate::{ RootDatabase, }; use either::Either; +use rustc_hash::FxHashSet; pub struct ImportsLocator<'a> { sema: Semantics<'a, RootDatabase>, + krate: Crate, } impl<'a> ImportsLocator<'a> { - pub fn new(db: &'a RootDatabase) -> Self { - Self { sema: Semantics::new(db) } + pub fn new(db: &'a RootDatabase, krate: Crate) -> Self { + Self { sema: Semantics::new(db), krate } } pub fn find_imports(&mut self, name_to_import: &str) -> Vec> { let _p = profile("search_for_imports"); let db = self.sema.db; - let project_results = { + // Query dependencies first. + let mut candidates: FxHashSet<_> = + self.krate.query_external_importables(db, name_to_import).collect(); + + // Query the local crate using the symbol index. + let local_results = { let mut query = Query::new(name_to_import.to_string()); query.exact(); query.limit(40); - symbol_index::world_symbols(db, query) - }; - let lib_results = { - let mut query = Query::new(name_to_import.to_string()); - query.libs(); - query.exact(); - query.limit(40); - symbol_index::world_symbols(db, query) + symbol_index::crate_symbols(db, self.krate.into(), query) }; - project_results - .into_iter() - .chain(lib_results.into_iter()) - .filter_map(|import_candidate| self.get_name_definition(&import_candidate)) - .filter_map(|name_definition_to_import| match name_definition_to_import { - Definition::ModuleDef(module_def) => Some(Either::Left(module_def)), - Definition::Macro(macro_def) => Some(Either::Right(macro_def)), - _ => None, - }) - .collect() + candidates.extend( + local_results + .into_iter() + .filter_map(|import_candidate| self.get_name_definition(&import_candidate)) + .filter_map(|name_definition_to_import| match name_definition_to_import { + Definition::ModuleDef(module_def) => Some(Either::Left(module_def)), + Definition::Macro(macro_def) => Some(Either::Right(macro_def)), + _ => None, + }), + ); + + candidates.into_iter().collect() } fn get_name_definition(&mut self, import_candidate: &FileSymbol) -> Option { diff --git a/crates/ra_ide_db/src/lib.rs b/crates/ra_ide_db/src/lib.rs index 1b74e6558613..a808de4f1135 100644 --- a/crates/ra_ide_db/src/lib.rs +++ b/crates/ra_ide_db/src/lib.rs @@ -16,10 +16,10 @@ use std::sync::Arc; use hir::db::{AstDatabase, DefDatabase}; use ra_db::{ salsa::{self, Database, Durability}, - Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, RelativePath, - SourceDatabase, SourceRootId, Upcast, + Canceled, CheckCanceled, CrateId, FileId, FileLoader, FileLoaderDelegate, SourceDatabase, + Upcast, }; -use rustc_hash::FxHashMap; +use rustc_hash::FxHashSet; use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase}; @@ -36,7 +36,6 @@ use crate::{line_index::LineIndex, symbol_index::SymbolsDatabase}; #[derive(Debug)] pub struct RootDatabase { runtime: salsa::Runtime, - pub(crate) debug_data: Arc, pub last_gc: crate::wasm_shims::Instant, pub last_gc_check: crate::wasm_shims::Instant, } @@ -57,23 +56,12 @@ impl FileLoader for RootDatabase { fn file_text(&self, file_id: FileId) -> Arc { FileLoaderDelegate(self).file_text(file_id) } - fn resolve_relative_path( - &self, - anchor: FileId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_relative_path(anchor, relative_path) + fn resolve_path(&self, anchor: FileId, path: &str) -> Option { + FileLoaderDelegate(self).resolve_path(anchor, path) } - fn relevant_crates(&self, file_id: FileId) -> Arc> { + fn relevant_crates(&self, file_id: FileId) -> Arc> { FileLoaderDelegate(self).relevant_crates(file_id) } - fn resolve_extern_path( - &self, - extern_id: ra_db::ExternSourceId, - relative_path: &RelativePath, - ) -> Option { - FileLoaderDelegate(self).resolve_extern_path(extern_id, relative_path) - } } impl salsa::Database for RootDatabase { @@ -109,7 +97,6 @@ impl RootDatabase { runtime: salsa::Runtime::default(), last_gc: crate::wasm_shims::Instant::now(), last_gc_check: crate::wasm_shims::Instant::now(), - debug_data: Default::default(), }; db.set_crate_graph_with_durability(Default::default(), Durability::HIGH); db.set_local_roots_with_durability(Default::default(), Durability::HIGH); @@ -132,7 +119,6 @@ impl salsa::ParallelDatabase for RootDatabase { runtime: self.runtime.snapshot(self), last_gc: self.last_gc, last_gc_check: self.last_gc_check, - debug_data: Arc::clone(&self.debug_data), }) } } @@ -146,14 +132,3 @@ fn line_index(db: &impl LineIndexDatabase, file_id: FileId) -> Arc { let text = db.file_text(file_id); Arc::new(LineIndex::new(&*text)) } - -#[derive(Debug, Default, Clone)] -pub(crate) struct DebugData { - pub(crate) root_paths: FxHashMap, -} - -impl DebugData { - pub(crate) fn merge(&mut self, other: DebugData) { - self.root_paths.extend(other.root_paths.into_iter()); - } -} diff --git a/crates/ra_ide_db/src/source_change.rs b/crates/ra_ide_db/src/source_change.rs index e713f4b7e80c..f40ae8304a42 100644 --- a/crates/ra_ide_db/src/source_change.rs +++ b/crates/ra_ide_db/src/source_change.rs @@ -22,17 +22,6 @@ impl SourceChange { ) -> Self { SourceChange { source_file_edits, file_system_edits, is_snippet: false } } - - /// Creates a new SourceChange with the given label, - /// containing only the given `SourceFileEdits`. - pub fn source_file_edits(edits: Vec) -> Self { - SourceChange { source_file_edits: edits, file_system_edits: vec![], is_snippet: false } - } - /// Creates a new SourceChange with the given label - /// from the given `FileId` and `TextEdit` - pub fn source_file_edit_from(file_id: FileId, edit: TextEdit) -> Self { - SourceFileEdit { file_id, edit }.into() - } } #[derive(Debug, Clone)] @@ -43,11 +32,13 @@ pub struct SourceFileEdit { impl From for SourceChange { fn from(edit: SourceFileEdit) -> SourceChange { - SourceChange { - source_file_edits: vec![edit], - file_system_edits: Vec::new(), - is_snippet: false, - } + vec![edit].into() + } +} + +impl From> for SourceChange { + fn from(source_file_edits: Vec) -> SourceChange { + SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false } } } diff --git a/crates/ra_ide_db/src/symbol_index.rs b/crates/ra_ide_db/src/symbol_index.rs index acc31fe3b51b..aab91897320a 100644 --- a/crates/ra_ide_db/src/symbol_index.rs +++ b/crates/ra_ide_db/src/symbol_index.rs @@ -29,9 +29,10 @@ use std::{ }; use fst::{self, Streamer}; +use hir::db::DefDatabase; use ra_db::{ salsa::{self, ParallelDatabase}, - FileId, SourceDatabaseExt, SourceRootId, + CrateId, FileId, SourceDatabaseExt, SourceRootId, }; use ra_syntax::{ ast::{self, NameOwner}, @@ -110,6 +111,14 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc Arc::new(SymbolIndex::new(symbols)) } +/// Need to wrap Snapshot to provide `Clone` impl for `map_with` +struct Snap(salsa::Snapshot); +impl Clone for Snap { + fn clone(&self) -> Snap { + Snap(self.0.snapshot()) + } +} + // Feature: Workspace Symbol // // Uses fuzzy-search to find types, modules and functions by name across your @@ -132,13 +141,7 @@ fn file_symbols(db: &impl SymbolsDatabase, file_id: FileId) -> Arc // | VS Code | kbd:[Ctrl+T] // |=== pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec { - /// Need to wrap Snapshot to provide `Clone` impl for `map_with` - struct Snap(salsa::Snapshot); - impl Clone for Snap { - fn clone(&self) -> Snap { - Snap(self.0.snapshot()) - } - } + let _p = ra_prof::profile("world_symbols").detail(|| query.query.clone()); let buf: Vec> = if query.libs { let snap = Snap(db.snapshot()); @@ -173,6 +176,33 @@ pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec { query.search(&buf) } +pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec { + // FIXME(#4842): This now depends on CrateDefMap, why not build the entire symbol index from + // that instead? + + let def_map = db.crate_def_map(krate); + let mut files = Vec::new(); + let mut modules = vec![def_map.root]; + while let Some(module) = modules.pop() { + let data = &def_map[module]; + files.extend(data.origin.file_id()); + modules.extend(data.children.values()); + } + + let snap = Snap(db.snapshot()); + + #[cfg(not(feature = "wasm"))] + let buf = files + .par_iter() + .map_with(snap, |db, &file_id| db.0.file_symbols(file_id)) + .collect::>(); + + #[cfg(feature = "wasm")] + let buf = files.iter().map(|&file_id| snap.0.file_symbols(file_id)).collect::>(); + + query.search(&buf) +} + pub fn index_resolve(db: &RootDatabase, name_ref: &ast::NameRef) -> Vec { let name = name_ref.text(); let mut query = Query::new(name.to_string()); @@ -298,9 +328,6 @@ impl Query { let mut stream = op.union(); let mut res = Vec::new(); while let Some((_, indexed_values)) = stream.next() { - if res.len() >= self.limit { - break; - } for indexed_value in indexed_values { let symbol_index = &indices[indexed_value.index]; let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value); @@ -312,7 +339,11 @@ impl Query { if self.exact && symbol.name != self.query { continue; } + res.push(symbol.clone()); + if res.len() >= self.limit { + return res; + } } } } diff --git a/crates/ra_parser/src/grammar.rs b/crates/ra_parser/src/grammar.rs index be0cd5661bd6..293baecf6a2c 100644 --- a/crates/ra_parser/src/grammar.rs +++ b/crates/ra_parser/src/grammar.rs @@ -18,9 +18,10 @@ //! // fn foo() {} //! ``` //! -//! After adding a new inline-test, run `cargo collect-tests` to extract -//! it as a standalone text-fixture into `tests/data/parser/inline`, and -//! run `cargo test` once to create the "gold" value. +//! After adding a new inline-test, run `cargo xtask codegen` to +//! extract it as a standalone text-fixture into +//! `crates/ra_syntax/test_data/parser/`, and run `cargo test` once to +//! create the "gold" value. //! //! Coding convention: rules like `where_clause` always produce either a //! node or an error, rules like `opt_where_clause` may produce nothing. diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs index 67a924de5397..97642bc24499 100644 --- a/crates/ra_parser/src/grammar/items.rs +++ b/crates/ra_parser/src/grammar/items.rs @@ -118,7 +118,22 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul && p.at_contextual_kw("default") && (match p.nth(1) { T![impl] => true, - T![fn] | T![type] => { + T![unsafe] => { + // test default_unsafe_impl + // default unsafe impl Foo {} + + // test default_unsafe_fn + // impl T for Foo { + // default unsafe fn foo() {} + // } + if p.nth(2) == T![impl] || p.nth(2) == T![fn] { + p.bump_remap(T![default]); + p.bump(T![unsafe]); + has_mods = true; + } + false + } + T![fn] | T![type] | T![const] => { if let ItemFlavor::Mod = flavor { true } else { @@ -198,6 +213,9 @@ pub(super) fn maybe_item(p: &mut Parser, m: Marker, flavor: ItemFlavor) -> Resul // default type T = Bar; // default fn foo() {} // } + T![const] => { + consts::const_def(p, m); + } // test unsafe_default_impl // unsafe default impl Foo {} diff --git a/crates/ra_parser/src/grammar/paths.rs b/crates/ra_parser/src/grammar/paths.rs index 332acc1a018a..428aa711e161 100644 --- a/crates/ra_parser/src/grammar/paths.rs +++ b/crates/ra_parser/src/grammar/paths.rs @@ -3,7 +3,7 @@ use super::*; pub(super) const PATH_FIRST: TokenSet = - token_set![IDENT, SELF_KW, SUPER_KW, CRATE_KW, COLON, L_ANGLE]; + token_set![IDENT, T![self], T![super], T![crate], T![:], T![<]]; pub(super) fn is_path_start(p: &Parser) -> bool { is_use_path_start(p) || p.at(T![<]) diff --git a/crates/ra_parser/src/grammar/patterns.rs b/crates/ra_parser/src/grammar/patterns.rs index 68fb2fc7337a..427c0eb49c2f 100644 --- a/crates/ra_parser/src/grammar/patterns.rs +++ b/crates/ra_parser/src/grammar/patterns.rs @@ -4,7 +4,7 @@ use super::*; pub(super) const PATTERN_FIRST: TokenSet = expressions::LITERAL_FIRST .union(paths::PATH_FIRST) - .union(token_set![BOX_KW, REF_KW, MUT_KW, L_PAREN, L_BRACK, AMP, UNDERSCORE, MINUS, DOT]); + .union(token_set![T![box], T![ref], T![mut], T!['('], T!['['], T![&], T![_], T![-], T![.]]); pub(crate) fn pattern(p: &mut Parser) { pattern_r(p, PAT_RECOVERY_SET); @@ -88,7 +88,9 @@ fn atom_pat(p: &mut Parser, recovery_set: TokenSet) -> Option { _ => bind_pat(p, true), }, - _ if paths::is_use_path_start(p) => path_or_macro_pat(p), + // test type_path_in_pattern + // fn main() { let <_>::Foo = (); } + _ if paths::is_path_start(p) => path_or_macro_pat(p), _ if is_literal_pat_start(p) => literal_pat(p), T![.] if p.at(T![..]) => dot_dot_pat(p), @@ -138,7 +140,7 @@ fn literal_pat(p: &mut Parser) -> CompletedMarker { // let Bar(..) = (); // } fn path_or_macro_pat(p: &mut Parser) -> CompletedMarker { - assert!(paths::is_use_path_start(p)); + assert!(paths::is_path_start(p)); let m = p.start(); paths::expr_path(p); let kind = match p.current() { diff --git a/crates/ra_parser/src/grammar/type_params.rs b/crates/ra_parser/src/grammar/type_params.rs index 50e4900c31a7..325d566adee0 100644 --- a/crates/ra_parser/src/grammar/type_params.rs +++ b/crates/ra_parser/src/grammar/type_params.rs @@ -191,10 +191,14 @@ fn where_predicate(p: &mut Parser) { } _ => { // test where_pred_for - // fn test() + // fn for_trait() // where // for<'a> F: Fn(&'a str) // { } + if p.at(T![for]) { + types::for_binder(p); + } + types::type_(p); if p.at(T![:]) { diff --git a/crates/ra_parser/src/grammar/types.rs b/crates/ra_parser/src/grammar/types.rs index fe1a039cbf64..9e8e3bd97f82 100644 --- a/crates/ra_parser/src/grammar/types.rs +++ b/crates/ra_parser/src/grammar/types.rs @@ -216,19 +216,21 @@ pub(super) fn for_binder(p: &mut Parser) { // test for_type // type A = for<'a> fn() -> (); -// fn foo(_t: &T) where for<'a> &'a T: Iterator {} -// fn bar(_t: &T) where for<'a> &'a mut T: Iterator {} -// fn baz(_t: &T) where for<'a> <&'a T as Baz>::Foo: Iterator {} +// type B = for<'a> unsafe extern "C" fn(&'a ()) -> (); +// type Obj = for<'a> PartialEq<&'a i32>; pub(super) fn for_type(p: &mut Parser) { assert!(p.at(T![for])); let m = p.start(); for_binder(p); match p.current() { - T![fn] | T![unsafe] | T![extern] => fn_pointer_type(p), - T![&] => reference_type(p), - _ if paths::is_path_start(p) => path_type_(p, false), - _ => p.error("expected a path"), + T![fn] | T![unsafe] | T![extern] => {} + // OK: legacy trait object format + _ if paths::is_use_path_start(p) => {} + _ => { + p.error("expected a function pointer or path"); + } } + type_no_bounds(p); m.complete(p, FOR_TYPE); } diff --git a/crates/ra_proc_macro_srv/Cargo.toml b/crates/ra_proc_macro_srv/Cargo.toml index bb3003278516..5821029450f5 100644 --- a/crates/ra_proc_macro_srv/Cargo.toml +++ b/crates/ra_proc_macro_srv/Cargo.toml @@ -22,3 +22,4 @@ cargo_metadata = "0.10.0" difference = "2.0.0" # used as proc macro test target serde_derive = "1.0.106" +ra_toolchain = { path = "../ra_toolchain" } diff --git a/crates/ra_proc_macro_srv/src/tests/utils.rs b/crates/ra_proc_macro_srv/src/tests/utils.rs index 84348b5defce..8d85f2d8a775 100644 --- a/crates/ra_proc_macro_srv/src/tests/utils.rs +++ b/crates/ra_proc_macro_srv/src/tests/utils.rs @@ -2,7 +2,6 @@ use crate::dylib; use crate::ProcMacroSrv; -pub use difference::Changeset as __Changeset; use ra_proc_macro::ListMacrosTask; use std::str::FromStr; use test_utils::assert_eq_text; @@ -13,7 +12,7 @@ mod fixtures { // Use current project metadata to get the proc-macro dylib path pub fn dylib_path(crate_name: &str, version: &str) -> std::path::PathBuf { - let command = Command::new("cargo") + let command = Command::new(ra_toolchain::cargo()) .args(&["check", "--message-format", "json"]) .output() .unwrap() diff --git a/crates/ra_project_model/src/cargo_workspace.rs b/crates/ra_project_model/src/cargo_workspace.rs index a306ce95f36e..4b7444039e7b 100644 --- a/crates/ra_project_model/src/cargo_workspace.rs +++ b/crates/ra_project_model/src/cargo_workspace.rs @@ -64,7 +64,7 @@ impl Default for CargoConfig { fn default() -> Self { CargoConfig { no_default_features: false, - all_features: true, + all_features: false, features: Vec::new(), load_out_dirs_from_check: false, target: None, diff --git a/crates/ra_project_model/src/json_project.rs b/crates/ra_project_model/src/json_project.rs index b030c8a6a18f..ee2de4c25691 100644 --- a/crates/ra_project_model/src/json_project.rs +++ b/crates/ra_project_model/src/json_project.rs @@ -2,9 +2,16 @@ use std::path::PathBuf; -use rustc_hash::{FxHashMap, FxHashSet}; +use rustc_hash::FxHashSet; use serde::Deserialize; +/// Roots and crates that compose this Rust project. +#[derive(Clone, Debug, Deserialize)] +pub struct JsonProject { + pub(crate) roots: Vec, + pub(crate) crates: Vec, +} + /// A root points to the directory which contains Rust crates. rust-analyzer watches all files in /// all roots. Roots might be nested. #[derive(Clone, Debug, Deserialize)] @@ -20,8 +27,10 @@ pub struct Crate { pub(crate) root_module: PathBuf, pub(crate) edition: Edition, pub(crate) deps: Vec, - pub(crate) atom_cfgs: FxHashSet, - pub(crate) key_value_cfgs: FxHashMap, + + #[serde(default)] + pub(crate) cfg: FxHashSet, + pub(crate) out_dir: Option, pub(crate) proc_macro_dylib_path: Option, } @@ -48,9 +57,39 @@ pub struct Dep { pub(crate) name: String, } -/// Roots and crates that compose this Rust project. -#[derive(Clone, Debug, Deserialize)] -pub struct JsonProject { - pub(crate) roots: Vec, - pub(crate) crates: Vec, +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + #[test] + fn test_crate_deserialization() { + let raw_json = json!( { + "crate_id": 2, + "root_module": "this/is/a/file/path.rs", + "deps": [ + { + "crate": 1, + "name": "some_dep_crate" + }, + ], + "edition": "2015", + "cfg": [ + "atom_1", + "atom_2", + "feature=feature_1", + "feature=feature_2", + "other=value", + ], + + }); + + let krate: Crate = serde_json::from_value(raw_json).unwrap(); + + assert!(krate.cfg.contains(&"atom_1".to_string())); + assert!(krate.cfg.contains(&"atom_2".to_string())); + assert!(krate.cfg.contains(&"feature=feature_1".to_string())); + assert!(krate.cfg.contains(&"feature=feature_2".to_string())); + assert!(krate.cfg.contains(&"other=value".to_string())); + } } diff --git a/crates/ra_project_model/src/lib.rs b/crates/ra_project_model/src/lib.rs index a2e9f65effc0..cb0e27dce217 100644 --- a/crates/ra_project_model/src/lib.rs +++ b/crates/ra_project_model/src/lib.rs @@ -14,7 +14,7 @@ use std::{ use anyhow::{bail, Context, Result}; use ra_cfg::CfgOptions; use ra_db::{CrateGraph, CrateName, Edition, Env, ExternSource, ExternSourceId, FileId}; -use rustc_hash::FxHashMap; +use rustc_hash::{FxHashMap, FxHashSet}; use serde_json::from_reader; pub use crate::{ @@ -32,6 +32,12 @@ pub enum ProjectWorkspace { Json { project: JsonProject }, } +impl From for ProjectWorkspace { + fn from(project: JsonProject) -> ProjectWorkspace { + ProjectWorkspace::Json { project } + } +} + /// `PackageRoot` describes a package root folder. /// Which may be an external dependency, or a member of /// the current workspace. @@ -41,41 +47,45 @@ pub struct PackageRoot { path: PathBuf, /// Is a member of the current workspace is_member: bool, + out_dir: Option, } impl PackageRoot { pub fn new_member(path: PathBuf) -> PackageRoot { - Self { path, is_member: true } + Self { path, is_member: true, out_dir: None } } pub fn new_non_member(path: PathBuf) -> PackageRoot { - Self { path, is_member: false } + Self { path, is_member: false, out_dir: None } } pub fn path(&self) -> &Path { &self.path } + pub fn out_dir(&self) -> Option<&Path> { + self.out_dir.as_deref() + } pub fn is_member(&self) -> bool { self.is_member } } -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum ProjectRoot { +#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum ProjectManifest { ProjectJson(PathBuf), CargoToml(PathBuf), } -impl ProjectRoot { - pub fn from_manifest_file(path: PathBuf) -> Result { +impl ProjectManifest { + pub fn from_manifest_file(path: PathBuf) -> Result { if path.ends_with("rust-project.json") { - return Ok(ProjectRoot::ProjectJson(path)); + return Ok(ProjectManifest::ProjectJson(path)); } if path.ends_with("Cargo.toml") { - return Ok(ProjectRoot::CargoToml(path)); + return Ok(ProjectManifest::CargoToml(path)); } bail!("project root must point to Cargo.toml or rust-project.json: {}", path.display()) } - pub fn discover_single(path: &Path) -> Result { - let mut candidates = ProjectRoot::discover(path)?; + pub fn discover_single(path: &Path) -> Result { + let mut candidates = ProjectManifest::discover(path)?; let res = match candidates.pop() { None => bail!("no projects"), Some(it) => it, @@ -87,12 +97,12 @@ impl ProjectRoot { Ok(res) } - pub fn discover(path: &Path) -> io::Result> { + pub fn discover(path: &Path) -> io::Result> { if let Some(project_json) = find_in_parent_dirs(path, "rust-project.json") { - return Ok(vec![ProjectRoot::ProjectJson(project_json)]); + return Ok(vec![ProjectManifest::ProjectJson(project_json)]); } return find_cargo_toml(path) - .map(|paths| paths.into_iter().map(ProjectRoot::CargoToml).collect()); + .map(|paths| paths.into_iter().map(ProjectManifest::CargoToml).collect()); fn find_cargo_toml(path: &Path) -> io::Result> { match find_in_parent_dirs(path, "Cargo.toml") { @@ -128,16 +138,28 @@ impl ProjectRoot { .collect() } } + + pub fn discover_all(paths: &[impl AsRef]) -> Vec { + let mut res = paths + .iter() + .filter_map(|it| ProjectManifest::discover(it.as_ref()).ok()) + .flatten() + .collect::>() + .into_iter() + .collect::>(); + res.sort(); + res + } } impl ProjectWorkspace { pub fn load( - root: ProjectRoot, + manifest: ProjectManifest, cargo_features: &CargoConfig, with_sysroot: bool, ) -> Result { - let res = match root { - ProjectRoot::ProjectJson(project_json) => { + let res = match manifest { + ProjectManifest::ProjectJson(project_json) => { let file = File::open(&project_json).with_context(|| { format!("Failed to open json file {}", project_json.display()) })?; @@ -148,7 +170,7 @@ impl ProjectWorkspace { })?, } } - ProjectRoot::CargoToml(cargo_toml) => { + ProjectManifest::CargoToml(cargo_toml) => { let cargo = CargoWorkspace::from_cargo_metadata(&cargo_toml, cargo_features) .with_context(|| { format!( @@ -186,6 +208,7 @@ impl ProjectWorkspace { .map(|pkg| PackageRoot { path: cargo[pkg].root().to_path_buf(), is_member: cargo[pkg].is_member, + out_dir: cargo[pkg].out_dir.clone(), }) .chain(sysroot.crates().map(|krate| { PackageRoot::new_non_member(sysroot[krate].root_dir().to_path_buf()) @@ -194,17 +217,6 @@ impl ProjectWorkspace { } } - pub fn out_dirs(&self) -> Vec { - match self { - ProjectWorkspace::Json { project } => { - project.crates.iter().filter_map(|krate| krate.out_dir.as_ref()).cloned().collect() - } - ProjectWorkspace::Cargo { cargo, sysroot: _ } => { - cargo.packages().filter_map(|pkg| cargo[pkg].out_dir.as_ref()).cloned().collect() - } - } - } - pub fn proc_macro_dylib_paths(&self) -> Vec { match self { ProjectWorkspace::Json { project } => project @@ -232,7 +244,7 @@ impl ProjectWorkspace { pub fn to_crate_graph( &self, - default_cfg_options: &CfgOptions, + target: Option<&str>, extern_source_roots: &FxHashMap, proc_macro_client: &ProcMacroClient, load: &mut dyn FnMut(&Path) -> Option, @@ -251,12 +263,16 @@ impl ProjectWorkspace { json_project::Edition::Edition2018 => Edition::Edition2018, }; let cfg_options = { - let mut opts = default_cfg_options.clone(); - for name in &krate.atom_cfgs { - opts.insert_atom(name.into()); - } - for (key, value) in &krate.key_value_cfgs { - opts.insert_key_value(key.into(), value.into()); + let mut opts = CfgOptions::default(); + for cfg in &krate.cfg { + match cfg.find('=') { + None => opts.insert_atom(cfg.into()), + Some(pos) => { + let key = &cfg[..pos]; + let value = cfg[pos + 1..].trim_matches('"'); + opts.insert_key_value(key.into(), value.into()); + } + } } opts }; @@ -315,18 +331,13 @@ impl ProjectWorkspace { } } ProjectWorkspace::Cargo { cargo, sysroot } => { + let mut cfg_options = get_rustc_cfg_options(target); + let sysroot_crates: FxHashMap<_, _> = sysroot .crates() .filter_map(|krate| { let file_id = load(&sysroot[krate].root)?; - // Crates from sysroot have `cfg(test)` disabled - let cfg_options = { - let mut opts = default_cfg_options.clone(); - opts.remove_atom("test"); - opts - }; - let env = Env::default(); let extern_source = ExternSource::default(); let proc_macro = vec![]; @@ -337,7 +348,7 @@ impl ProjectWorkspace { file_id, Edition::Edition2018, Some(crate_name), - cfg_options, + cfg_options.clone(), env, extern_source, proc_macro, @@ -368,6 +379,10 @@ impl ProjectWorkspace { let mut pkg_to_lib_crate = FxHashMap::default(); let mut pkg_crates = FxHashMap::default(); + + // Add test cfg for non-sysroot crates + cfg_options.insert_atom("test".into()); + // Next, create crates for each package, target pair for pkg in cargo.packages() { let mut lib_tgt = None; @@ -376,7 +391,7 @@ impl ProjectWorkspace { if let Some(file_id) = load(root) { let edition = cargo[pkg].edition; let cfg_options = { - let mut opts = default_cfg_options.clone(); + let mut opts = cfg_options.clone(); for feature in cargo[pkg].features.iter() { opts.insert_key_value("feature".into(), feature.into()); } @@ -533,7 +548,7 @@ impl ProjectWorkspace { } } -pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { +fn get_rustc_cfg_options(target: Option<&str>) -> CfgOptions { let mut cfg_options = CfgOptions::default(); // Some nightly-only cfgs, which are required for stdlib @@ -551,7 +566,7 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { let mut cmd = Command::new(ra_toolchain::rustc()); cmd.args(&["--print", "cfg", "-O"]); if let Some(target) = target { - cmd.args(&["--target", target.as_str()]); + cmd.args(&["--target", target]); } let output = output(cmd)?; Ok(String::from_utf8(output.stdout)?) @@ -573,6 +588,8 @@ pub fn get_rustc_cfg_options(target: Option<&String>) -> CfgOptions { Err(e) => log::error!("failed to get rustc cfgs: {:#}", e), } + cfg_options.insert_atom("debug_assertions".into()); + cfg_options } diff --git a/crates/ra_syntax/src/ast.rs b/crates/ra_syntax/src/ast.rs index 1876afe958e0..9d02aeef3fa2 100644 --- a/crates/ra_syntax/src/ast.rs +++ b/crates/ra_syntax/src/ast.rs @@ -75,7 +75,7 @@ impl AstChildren { impl Iterator for AstChildren { type Item = N; fn next(&mut self) -> Option { - self.inner.by_ref().find_map(N::cast) + self.inner.find_map(N::cast) } } @@ -285,6 +285,8 @@ where let pred = predicates.next().unwrap(); let mut bounds = pred.type_bound_list().unwrap().bounds(); + assert!(pred.for_token().is_none()); + assert!(pred.type_param_list().is_none()); assert_eq!("T", pred.type_ref().unwrap().syntax().text().to_string()); assert_bound("Clone", bounds.next()); assert_bound("Copy", bounds.next()); @@ -322,6 +324,8 @@ where let pred = predicates.next().unwrap(); let mut bounds = pred.type_bound_list().unwrap().bounds(); - assert_eq!("for<'a> F", pred.type_ref().unwrap().syntax().text().to_string()); + assert!(pred.for_token().is_some()); + assert_eq!("<'a>", pred.type_param_list().unwrap().syntax().text().to_string()); + assert_eq!("F", pred.type_ref().unwrap().syntax().text().to_string()); assert_bound("Fn(&'a str)", bounds.next()); } diff --git a/crates/ra_syntax/src/ast/edit.rs b/crates/ra_syntax/src/ast/edit.rs index 29eb3fcb9ccc..2ef173a03970 100644 --- a/crates/ra_syntax/src/ast/edit.rs +++ b/crates/ra_syntax/src/ast/edit.rs @@ -579,12 +579,17 @@ pub trait AstNodeEdit: AstNode + Clone + Sized { rewriter.rewrite_ast(self) } #[must_use] - fn indent(&self, indent: IndentLevel) -> Self { - Self::cast(indent.increase_indent(self.syntax().clone())).unwrap() + fn indent(&self, level: IndentLevel) -> Self { + Self::cast(level.increase_indent(self.syntax().clone())).unwrap() } #[must_use] - fn dedent(&self, indent: IndentLevel) -> Self { - Self::cast(indent.decrease_indent(self.syntax().clone())).unwrap() + fn dedent(&self, level: IndentLevel) -> Self { + Self::cast(level.decrease_indent(self.syntax().clone())).unwrap() + } + #[must_use] + fn reset_indent(&self) -> Self { + let level = IndentLevel::from_node(self.syntax()); + self.dedent(level) } } diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs index cb430ca01352..58141da11426 100644 --- a/crates/ra_syntax/src/ast/generated/nodes.rs +++ b/crates/ra_syntax/src/ast/generated/nodes.rs @@ -2052,6 +2052,8 @@ pub struct WherePred { } impl ast::TypeBoundsOwner for WherePred {} impl WherePred { + pub fn for_token(&self) -> Option { support::token(&self.syntax, T![for]) } + pub fn type_param_list(&self) -> Option { support::child(&self.syntax) } pub fn lifetime_token(&self) -> Option { support::token(&self.syntax, T![lifetime]) } @@ -4849,687 +4851,687 @@ impl AstNode for FieldDefList { } } impl std::fmt::Display for NominalDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for GenericParam { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for GenericArg { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeRef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ModuleItem { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for AssocItem { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ExternItem { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Expr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Pat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordInnerPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for AttrInput { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Stmt { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for FieldDefList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for SourceFile { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for FnDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RetType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for StructDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for UnionDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordFieldDefList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordFieldDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TupleFieldDefList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TupleFieldDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for EnumDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for EnumVariantList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for EnumVariant { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TraitDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Module { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ItemList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ConstDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for StaticDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeAliasDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ImplDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ParenType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TupleType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for NeverType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PathType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PointerType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ArrayType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for SliceType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ReferenceType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PlaceholderType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for FnPointerType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ForType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ImplTraitType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for DynTraitType { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TupleExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ArrayExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ParenExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PathExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for LambdaExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for IfExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for LoopExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for EffectExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ForExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for WhileExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ContinueExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for BreakExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Label { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for BlockExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ReturnExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for CallExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MethodCallExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for IndexExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for FieldExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for AwaitExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TryExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for CastExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RefExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PrefixExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for BoxExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RangeExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for BinExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Literal { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MatchExpr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MatchArmList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MatchArm { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MatchGuard { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordLit { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordFieldList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordField { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for OrPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ParenPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RefPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for BoxPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for BindPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PlaceholderPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for DotDotPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PathPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for SlicePat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RangePat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for LiteralPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MacroPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordFieldPatList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for RecordFieldPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TupleStructPat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TuplePat { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Visibility { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Name { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for NameRef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MacroCall { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Attr { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TokenTree { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeParamList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeParam { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ConstParam { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for LifetimeParam { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeBound { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeBoundList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for WherePred { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for WhereClause { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Abi { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ExprStmt { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for LetStmt { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Condition { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ParamList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for SelfParam { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Param { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for UseItem { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for UseTree { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Alias { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for UseTreeList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ExternCrateItem { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ArgList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for Path { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for PathSegment { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeArgList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for TypeArg { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for AssocTypeArg { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for LifetimeArg { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ConstArg { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MacroItems { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MacroStmts { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ExternItemList { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for ExternBlock { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MetaItem { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } impl std::fmt::Display for MacroDef { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } diff --git a/crates/ra_syntax/src/ast/generated/tokens.rs b/crates/ra_syntax/src/ast/generated/tokens.rs index f91befaac78a..abadd0b61c6f 100644 --- a/crates/ra_syntax/src/ast/generated/tokens.rs +++ b/crates/ra_syntax/src/ast/generated/tokens.rs @@ -11,7 +11,7 @@ pub struct Whitespace { pub(crate) syntax: SyntaxToken, } impl std::fmt::Display for Whitespace { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.syntax, f) } } @@ -32,7 +32,7 @@ pub struct Comment { pub(crate) syntax: SyntaxToken, } impl std::fmt::Display for Comment { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.syntax, f) } } @@ -53,7 +53,7 @@ pub struct String { pub(crate) syntax: SyntaxToken, } impl std::fmt::Display for String { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.syntax, f) } } @@ -74,7 +74,7 @@ pub struct RawString { pub(crate) syntax: SyntaxToken, } impl std::fmt::Display for RawString { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.syntax, f) } } diff --git a/crates/ra_syntax/src/ast/tokens.rs b/crates/ra_syntax/src/ast/tokens.rs index 3cd6d99c3889..2e72d4927589 100644 --- a/crates/ra_syntax/src/ast/tokens.rs +++ b/crates/ra_syntax/src/ast/tokens.rs @@ -84,7 +84,7 @@ impl Whitespace { } pub struct QuoteOffsets { - pub quotes: [TextRange; 2], + pub quotes: (TextRange, TextRange), pub contents: TextRange, } @@ -103,7 +103,7 @@ impl QuoteOffsets { let end = TextSize::of(literal); let res = QuoteOffsets { - quotes: [TextRange::new(start, left_quote), TextRange::new(right_quote, end)], + quotes: (TextRange::new(start, left_quote), TextRange::new(right_quote, end)), contents: TextRange::new(left_quote, right_quote), }; Some(res) @@ -116,17 +116,17 @@ pub trait HasQuotes: AstToken { let offsets = QuoteOffsets::new(text)?; let o = self.syntax().text_range().start(); let offsets = QuoteOffsets { - quotes: [offsets.quotes[0] + o, offsets.quotes[1] + o], + quotes: (offsets.quotes.0 + o, offsets.quotes.1 + o), contents: offsets.contents + o, }; Some(offsets) } fn open_quote_text_range(&self) -> Option { - self.quote_offsets().map(|it| it.quotes[0]) + self.quote_offsets().map(|it| it.quotes.0) } fn close_quote_text_range(&self) -> Option { - self.quote_offsets().map(|it| it.quotes[1]) + self.quote_offsets().map(|it| it.quotes.1) } fn text_range_between_quotes(&self) -> Option { @@ -335,16 +335,26 @@ pub trait HasFormatSpecifier: AstToken { } c if c == '_' || c.is_alphabetic() => { read_identifier(&mut chars, &mut callback); - if chars.peek().and_then(|next| next.1.as_ref().ok()).copied() - != Some('$') - { - continue; - } - skip_char_and_emit( - &mut chars, - FormatSpecifier::DollarSign, - &mut callback, - ); + // can be either width (indicated by dollar sign, or type in which case + // the next sign has to be `}`) + let next = + chars.peek().and_then(|next| next.1.as_ref().ok()).copied(); + match next { + Some('$') => skip_char_and_emit( + &mut chars, + FormatSpecifier::DollarSign, + &mut callback, + ), + Some('}') => { + skip_char_and_emit( + &mut chars, + FormatSpecifier::Close, + &mut callback, + ); + continue; + } + _ => continue, + }; } _ => {} } @@ -416,17 +426,11 @@ pub trait HasFormatSpecifier: AstToken { } } - let mut cloned = chars.clone().take(2); - let first = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); - let second = cloned.next().and_then(|next| next.1.as_ref().ok()).copied(); - if first != Some('}') { + if let Some((_, Ok('}'))) = chars.peek() { + skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); + } else { continue; } - if second == Some('}') { - // Escaped format end specifier, `}}` - continue; - } - skip_char_and_emit(&mut chars, FormatSpecifier::Close, &mut callback); } _ => { while let Some((_, Ok(next_char))) = chars.peek() { diff --git a/crates/ra_syntax/src/ast/traits.rs b/crates/ra_syntax/src/ast/traits.rs index bfc05e08bf24..a8f2454fd96e 100644 --- a/crates/ra_syntax/src/ast/traits.rs +++ b/crates/ra_syntax/src/ast/traits.rs @@ -83,13 +83,22 @@ pub trait DocCommentsOwner: AstNode { CommentIter { iter: self.syntax().children_with_tokens() } } + fn doc_comment_text(&self) -> Option { + self.doc_comments().doc_comment_text() + } +} + +impl CommentIter { + pub fn from_syntax_node(syntax_node: &ast::SyntaxNode) -> CommentIter { + CommentIter { iter: syntax_node.children_with_tokens() } + } + /// Returns the textual content of a doc comment block as a single string. /// That is, strips leading `///` (+ optional 1 character of whitespace), /// trailing `*/`, trailing whitespace and then joins the lines. - fn doc_comment_text(&self) -> Option { + pub fn doc_comment_text(self) -> Option { let mut has_comments = false; let docs = self - .doc_comments() .filter(|comment| comment.kind().doc.is_some()) .map(|comment| { has_comments = true; diff --git a/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.rast b/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.rast index 7c957fdde236..48610a5ebbe2 100644 --- a/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.rast +++ b/crates/ra_syntax/test_data/parser/err/0024_many_type_parens.rast @@ -180,44 +180,45 @@ SOURCE_FILE@0..240 EXPR_STMT@150..180 TUPLE_EXPR@150..180 L_PAREN@150..151 "(" - BIN_EXPR@151..180 - BIN_EXPR@151..178 - BIN_EXPR@151..169 - BIN_EXPR@151..167 - BIN_EXPR@151..164 - FOR_EXPR@151..157 - FOR_KW@151..154 "for" - ERROR@154..155 - L_ANGLE@154..155 "<" - ERROR@155..157 - LIFETIME@155..157 "\'a" - R_ANGLE@157..158 ">" - WHITESPACE@158..159 " " + FOR_EXPR@151..180 + FOR_KW@151..154 "for" + PATH_PAT@154..158 + PATH@154..158 + PATH_SEGMENT@154..158 + L_ANGLE@154..155 "<" + ERROR@155..157 + LIFETIME@155..157 "\'a" + R_ANGLE@157..158 ">" + WHITESPACE@158..159 " " + BIN_EXPR@159..180 + BIN_EXPR@159..178 + BIN_EXPR@159..169 + BIN_EXPR@159..167 PATH_EXPR@159..164 PATH@159..164 PATH_SEGMENT@159..164 NAME_REF@159..164 IDENT@159..164 "Trait" - L_ANGLE@164..165 "<" - ERROR@165..167 - LIFETIME@165..167 "\'a" - R_ANGLE@167..168 ">" - ERROR@168..169 - R_PAREN@168..169 ")" - WHITESPACE@169..170 " " - PLUS@170..171 "+" - WHITESPACE@171..172 " " - PAREN_EXPR@172..178 - L_PAREN@172..173 "(" - PATH_EXPR@173..177 - PATH@173..177 - PATH_SEGMENT@173..177 - NAME_REF@173..177 - IDENT@173..177 "Copy" - R_PAREN@177..178 ")" - R_ANGLE@178..179 ">" - ERROR@179..180 - SEMICOLON@179..180 ";" + L_ANGLE@164..165 "<" + ERROR@165..167 + LIFETIME@165..167 "\'a" + R_ANGLE@167..168 ">" + ERROR@168..169 + R_PAREN@168..169 ")" + WHITESPACE@169..170 " " + PLUS@170..171 "+" + WHITESPACE@171..172 " " + PAREN_EXPR@172..178 + L_PAREN@172..173 "(" + PATH_EXPR@173..177 + PATH@173..177 + PATH_SEGMENT@173..177 + NAME_REF@173..177 + IDENT@173..177 "Copy" + R_PAREN@177..178 ")" + R_ANGLE@178..179 ">" + ERROR@179..180 + SEMICOLON@179..180 ";" WHITESPACE@180..185 "\n " LET_STMT@185..235 LET_KW@185..188 "let" @@ -302,13 +303,12 @@ error 146..146: expected expression error 147..147: expected SEMICOLON error 148..148: expected expression error 149..149: expected SEMICOLON -error 154..154: expected pattern -error 155..155: expected IN_KW -error 155..155: expected expression -error 157..157: expected a block +error 155..155: expected type +error 158..158: expected IN_KW error 165..165: expected expression error 168..168: expected expression error 179..179: expected expression +error 180..180: expected a block error 180..180: expected COMMA error 180..180: expected expression error 180..180: expected R_PAREN diff --git a/crates/ra_syntax/test_data/parser/err/0027_incomplere_where_for.rast b/crates/ra_syntax/test_data/parser/err/0027_incomplere_where_for.rast index 568a4cc028eb..4d6461d1e195 100644 --- a/crates/ra_syntax/test_data/parser/err/0027_incomplere_where_for.rast +++ b/crates/ra_syntax/test_data/parser/err/0027_incomplere_where_for.rast @@ -12,17 +12,16 @@ SOURCE_FILE@0..30 WHERE_KW@13..18 "where" WHITESPACE@18..19 " " WHERE_PRED@19..26 - FOR_TYPE@19..26 - FOR_KW@19..22 "for" - TYPE_PARAM_LIST@22..26 - L_ANGLE@22..23 "<" - LIFETIME_PARAM@23..25 - LIFETIME@23..25 "\'a" - R_ANGLE@25..26 ">" + FOR_KW@19..22 "for" + TYPE_PARAM_LIST@22..26 + L_ANGLE@22..23 "<" + LIFETIME_PARAM@23..25 + LIFETIME@23..25 "\'a" + R_ANGLE@25..26 ">" WHITESPACE@26..27 "\n" BLOCK_EXPR@27..29 L_CURLY@27..28 "{" R_CURLY@28..29 "}" WHITESPACE@29..30 "\n" -error 26..26: expected a path +error 26..26: expected type error 26..26: expected colon diff --git a/crates/ra_syntax/test_data/parser/err/0043_default_const.rast b/crates/ra_syntax/test_data/parser/err/0043_default_const.rast new file mode 100644 index 000000000000..8eb583ef85cc --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0043_default_const.rast @@ -0,0 +1,40 @@ +SOURCE_FILE@0..39 + TRAIT_DEF@0..38 + TRAIT_KW@0..5 "trait" + WHITESPACE@5..6 " " + NAME@6..7 + IDENT@6..7 "T" + WHITESPACE@7..8 " " + ITEM_LIST@8..38 + L_CURLY@8..9 "{" + WHITESPACE@9..12 "\n " + MACRO_CALL@12..19 + PATH@12..19 + PATH_SEGMENT@12..19 + NAME_REF@12..19 + IDENT@12..19 "default" + WHITESPACE@19..20 " " + CONST_DEF@20..36 + CONST_KW@20..25 "const" + WHITESPACE@25..26 " " + NAME@26..27 + IDENT@26..27 "f" + COLON@27..28 ":" + WHITESPACE@28..29 " " + PATH_TYPE@29..31 + PATH@29..31 + PATH_SEGMENT@29..31 + NAME_REF@29..31 + IDENT@29..31 "u8" + WHITESPACE@31..32 " " + EQ@32..33 "=" + WHITESPACE@33..34 " " + LITERAL@34..35 + INT_NUMBER@34..35 "0" + SEMICOLON@35..36 ";" + WHITESPACE@36..37 "\n" + R_CURLY@37..38 "}" + WHITESPACE@38..39 "\n" +error 19..19: expected BANG +error 19..19: expected `{`, `[`, `(` +error 19..19: expected SEMICOLON diff --git a/crates/ra_syntax/test_data/parser/err/0043_default_const.rs b/crates/ra_syntax/test_data/parser/err/0043_default_const.rs new file mode 100644 index 000000000000..80f15474a5d0 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0043_default_const.rs @@ -0,0 +1,3 @@ +trait T { + default const f: u8 = 0; +} diff --git a/crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rast b/crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rast new file mode 100644 index 000000000000..cb90f28bc74a --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rast @@ -0,0 +1,240 @@ +SOURCE_FILE@0..239 + TYPE_ALIAS_DEF@0..30 + TYPE_KW@0..4 "type" + WHITESPACE@4..5 " " + NAME@5..11 + IDENT@5..11 "ForRef" + WHITESPACE@11..12 " " + EQ@12..13 "=" + WHITESPACE@13..14 " " + FOR_TYPE@14..29 + FOR_KW@14..17 "for" + TYPE_PARAM_LIST@17..21 + L_ANGLE@17..18 "<" + LIFETIME_PARAM@18..20 + LIFETIME@18..20 "\'a" + R_ANGLE@20..21 ">" + WHITESPACE@21..22 " " + REFERENCE_TYPE@22..29 + AMP@22..23 "&" + LIFETIME@23..25 "\'a" + WHITESPACE@25..26 " " + PATH_TYPE@26..29 + PATH@26..29 + PATH_SEGMENT@26..29 + NAME_REF@26..29 + IDENT@26..29 "u32" + SEMICOLON@29..30 ";" + WHITESPACE@30..31 "\n" + TYPE_ALIAS_DEF@31..64 + TYPE_KW@31..35 "type" + WHITESPACE@35..36 " " + NAME@36..42 + IDENT@36..42 "ForTup" + WHITESPACE@42..43 " " + EQ@43..44 "=" + WHITESPACE@44..45 " " + FOR_TYPE@45..63 + FOR_KW@45..48 "for" + TYPE_PARAM_LIST@48..52 + L_ANGLE@48..49 "<" + LIFETIME_PARAM@49..51 + LIFETIME@49..51 "\'a" + R_ANGLE@51..52 ">" + WHITESPACE@52..53 " " + TUPLE_TYPE@53..63 + L_PAREN@53..54 "(" + REFERENCE_TYPE@54..61 + AMP@54..55 "&" + LIFETIME@55..57 "\'a" + WHITESPACE@57..58 " " + PATH_TYPE@58..61 + PATH@58..61 + PATH_SEGMENT@58..61 + NAME_REF@58..61 + IDENT@58..61 "u32" + COMMA@61..62 "," + R_PAREN@62..63 ")" + SEMICOLON@63..64 ";" + WHITESPACE@64..65 "\n" + TYPE_ALIAS_DEF@65..95 + TYPE_KW@65..69 "type" + WHITESPACE@69..70 " " + NAME@70..78 + IDENT@70..78 "ForSlice" + WHITESPACE@78..79 " " + EQ@79..80 "=" + WHITESPACE@80..81 " " + FOR_TYPE@81..94 + FOR_KW@81..84 "for" + TYPE_PARAM_LIST@84..88 + L_ANGLE@84..85 "<" + LIFETIME_PARAM@85..87 + LIFETIME@85..87 "\'a" + R_ANGLE@87..88 ">" + WHITESPACE@88..89 " " + SLICE_TYPE@89..94 + L_BRACK@89..90 "[" + PATH_TYPE@90..93 + PATH@90..93 + PATH_SEGMENT@90..93 + NAME_REF@90..93 + IDENT@90..93 "u32" + R_BRACK@93..94 "]" + SEMICOLON@94..95 ";" + WHITESPACE@95..96 "\n" + TYPE_ALIAS_DEF@96..149 + TYPE_KW@96..100 "type" + WHITESPACE@100..101 " " + NAME@101..109 + IDENT@101..109 "ForForFn" + WHITESPACE@109..110 " " + EQ@110..111 "=" + WHITESPACE@111..112 " " + FOR_TYPE@112..148 + FOR_KW@112..115 "for" + TYPE_PARAM_LIST@115..119 + L_ANGLE@115..116 "<" + LIFETIME_PARAM@116..118 + LIFETIME@116..118 "\'a" + R_ANGLE@118..119 ">" + WHITESPACE@119..120 " " + FOR_TYPE@120..148 + FOR_KW@120..123 "for" + TYPE_PARAM_LIST@123..127 + L_ANGLE@123..124 "<" + LIFETIME_PARAM@124..126 + LIFETIME@124..126 "\'b" + R_ANGLE@126..127 ">" + WHITESPACE@127..128 " " + FN_POINTER_TYPE@128..148 + FN_KW@128..130 "fn" + PARAM_LIST@130..148 + L_PAREN@130..131 "(" + PARAM@131..138 + REFERENCE_TYPE@131..138 + AMP@131..132 "&" + LIFETIME@132..134 "\'a" + WHITESPACE@134..135 " " + PATH_TYPE@135..138 + PATH@135..138 + PATH_SEGMENT@135..138 + NAME_REF@135..138 + IDENT@135..138 "i32" + COMMA@138..139 "," + WHITESPACE@139..140 " " + PARAM@140..147 + REFERENCE_TYPE@140..147 + AMP@140..141 "&" + LIFETIME@141..143 "\'b" + WHITESPACE@143..144 " " + PATH_TYPE@144..147 + PATH@144..147 + PATH_SEGMENT@144..147 + NAME_REF@144..147 + IDENT@144..147 "i32" + R_PAREN@147..148 ")" + SEMICOLON@148..149 ";" + WHITESPACE@149..150 "\n" + FN_DEF@150..238 + FN_KW@150..152 "fn" + WHITESPACE@152..153 " " + NAME@153..164 + IDENT@153..164 "for_for_for" + TYPE_PARAM_LIST@164..167 + L_ANGLE@164..165 "<" + TYPE_PARAM@165..166 + NAME@165..166 + IDENT@165..166 "T" + R_ANGLE@166..167 ">" + PARAM_LIST@167..169 + L_PAREN@167..168 "(" + R_PAREN@168..169 ")" + WHITESPACE@169..170 "\n" + WHERE_CLAUSE@170..234 + WHERE_KW@170..175 "where" + WHITESPACE@175..180 "\n " + WHERE_PRED@180..233 + FOR_KW@180..183 "for" + TYPE_PARAM_LIST@183..187 + L_ANGLE@183..184 "<" + LIFETIME_PARAM@184..186 + LIFETIME@184..186 "\'a" + R_ANGLE@186..187 ">" + WHITESPACE@187..188 " " + FOR_TYPE@188..227 + FOR_KW@188..191 "for" + TYPE_PARAM_LIST@191..195 + L_ANGLE@191..192 "<" + LIFETIME_PARAM@192..194 + LIFETIME@192..194 "\'b" + R_ANGLE@194..195 ">" + WHITESPACE@195..196 " " + FOR_TYPE@196..227 + FOR_KW@196..199 "for" + TYPE_PARAM_LIST@199..203 + L_ANGLE@199..200 "<" + LIFETIME_PARAM@200..202 + LIFETIME@200..202 "\'c" + R_ANGLE@202..203 ">" + WHITESPACE@203..204 " " + FN_POINTER_TYPE@204..227 + FN_KW@204..206 "fn" + PARAM_LIST@206..227 + L_PAREN@206..207 "(" + PARAM@207..212 + REFERENCE_TYPE@207..212 + AMP@207..208 "&" + LIFETIME@208..210 "\'a" + WHITESPACE@210..211 " " + PATH_TYPE@211..212 + PATH@211..212 + PATH_SEGMENT@211..212 + NAME_REF@211..212 + IDENT@211..212 "T" + COMMA@212..213 "," + WHITESPACE@213..214 " " + PARAM@214..219 + REFERENCE_TYPE@214..219 + AMP@214..215 "&" + LIFETIME@215..217 "\'b" + WHITESPACE@217..218 " " + PATH_TYPE@218..219 + PATH@218..219 + PATH_SEGMENT@218..219 + NAME_REF@218..219 + IDENT@218..219 "T" + COMMA@219..220 "," + WHITESPACE@220..221 " " + PARAM@221..226 + REFERENCE_TYPE@221..226 + AMP@221..222 "&" + LIFETIME@222..224 "\'c" + WHITESPACE@224..225 " " + PATH_TYPE@225..226 + PATH@225..226 + PATH_SEGMENT@225..226 + NAME_REF@225..226 + IDENT@225..226 "T" + R_PAREN@226..227 ")" + COLON@227..228 ":" + WHITESPACE@228..229 " " + TYPE_BOUND_LIST@229..233 + TYPE_BOUND@229..233 + PATH_TYPE@229..233 + PATH@229..233 + PATH_SEGMENT@229..233 + NAME_REF@229..233 + IDENT@229..233 "Copy" + COMMA@233..234 "," + WHITESPACE@234..235 "\n" + BLOCK_EXPR@235..238 + L_CURLY@235..236 "{" + WHITESPACE@236..237 "\n" + R_CURLY@237..238 "}" + WHITESPACE@238..239 "\n" +error 21..21: expected a function pointer or path +error 52..52: expected a function pointer or path +error 88..88: expected a function pointer or path +error 119..119: expected a function pointer or path +error 195..195: expected a function pointer or path diff --git a/crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rs b/crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rs new file mode 100644 index 000000000000..0e9f8ccb4f84 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/err/0044_unexpected_for_type.rs @@ -0,0 +1,9 @@ +type ForRef = for<'a> &'a u32; +type ForTup = for<'a> (&'a u32,); +type ForSlice = for<'a> [u32]; +type ForForFn = for<'a> for<'b> fn(&'a i32, &'b i32); +fn for_for_for() +where + for<'a> for<'b> for<'c> fn(&'a T, &'b T, &'c T): Copy, +{ +} diff --git a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast index 9be441110ff4..53f7ebaf9a72 100644 --- a/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast +++ b/crates/ra_syntax/test_data/parser/inline/err/0010_wrong_order_fns.rast @@ -17,23 +17,29 @@ SOURCE_FILE@0..50 L_CURLY@22..23 "{" R_CURLY@23..24 "}" WHITESPACE@24..25 "\n" - ERROR@25..31 + CONST_DEF@25..46 UNSAFE_KW@25..31 "unsafe" - WHITESPACE@31..32 " " - FN_DEF@32..49 + WHITESPACE@31..32 " " CONST_KW@32..37 "const" WHITESPACE@37..38 " " - FN_KW@38..40 "fn" + ERROR@38..40 + FN_KW@38..40 "fn" WHITESPACE@40..41 " " - NAME@41..44 - IDENT@41..44 "bar" - PARAM_LIST@44..46 - L_PAREN@44..45 "(" - R_PAREN@45..46 ")" - WHITESPACE@46..47 " " - BLOCK_EXPR@47..49 - L_CURLY@47..48 "{" - R_CURLY@48..49 "}" + PATH_TYPE@41..46 + PATH@41..46 + PATH_SEGMENT@41..46 + NAME_REF@41..44 + IDENT@41..44 "bar" + PARAM_LIST@44..46 + L_PAREN@44..45 "(" + R_PAREN@45..46 ")" + WHITESPACE@46..47 " " + ERROR@47..49 + L_CURLY@47..48 "{" + R_CURLY@48..49 "}" WHITESPACE@49..50 "\n" error 6..6: expected existential, fn, trait or impl -error 31..31: expected existential, fn, trait or impl +error 38..38: expected a name +error 40..40: expected COLON +error 46..46: expected SEMICOLON +error 47..47: expected an item diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rast b/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rast index 9dc473e43c7f..cd0892451d9e 100644 --- a/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rast +++ b/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rast @@ -1,61 +1,60 @@ -SOURCE_FILE@0..49 - FN_DEF@0..48 +SOURCE_FILE@0..54 + FN_DEF@0..53 FN_KW@0..2 "fn" WHITESPACE@2..3 " " - NAME@3..7 - IDENT@3..7 "test" - TYPE_PARAM_LIST@7..10 - L_ANGLE@7..8 "<" - TYPE_PARAM@8..9 - NAME@8..9 - IDENT@8..9 "F" - R_ANGLE@9..10 ">" - PARAM_LIST@10..12 - L_PAREN@10..11 "(" - R_PAREN@11..12 ")" - WHITESPACE@12..13 "\n" - WHERE_CLAUSE@13..44 - WHERE_KW@13..18 "where" - WHITESPACE@18..22 "\n " - WHERE_PRED@22..44 - FOR_TYPE@22..31 - FOR_KW@22..25 "for" - TYPE_PARAM_LIST@25..29 - L_ANGLE@25..26 "<" - LIFETIME_PARAM@26..28 - LIFETIME@26..28 "\'a" - R_ANGLE@28..29 ">" - WHITESPACE@29..30 " " - PATH_TYPE@30..31 - PATH@30..31 - PATH_SEGMENT@30..31 - NAME_REF@30..31 - IDENT@30..31 "F" - COLON@31..32 ":" - WHITESPACE@32..33 " " - TYPE_BOUND_LIST@33..44 - TYPE_BOUND@33..44 - PATH_TYPE@33..44 - PATH@33..44 - PATH_SEGMENT@33..44 - NAME_REF@33..35 - IDENT@33..35 "Fn" - PARAM_LIST@35..44 - L_PAREN@35..36 "(" - PARAM@36..43 - REFERENCE_TYPE@36..43 - AMP@36..37 "&" - LIFETIME@37..39 "\'a" - WHITESPACE@39..40 " " - PATH_TYPE@40..43 - PATH@40..43 - PATH_SEGMENT@40..43 - NAME_REF@40..43 - IDENT@40..43 "str" - R_PAREN@43..44 ")" - WHITESPACE@44..45 "\n" - BLOCK_EXPR@45..48 - L_CURLY@45..46 "{" - WHITESPACE@46..47 " " - R_CURLY@47..48 "}" - WHITESPACE@48..49 "\n" + NAME@3..12 + IDENT@3..12 "for_trait" + TYPE_PARAM_LIST@12..15 + L_ANGLE@12..13 "<" + TYPE_PARAM@13..14 + NAME@13..14 + IDENT@13..14 "F" + R_ANGLE@14..15 ">" + PARAM_LIST@15..17 + L_PAREN@15..16 "(" + R_PAREN@16..17 ")" + WHITESPACE@17..18 "\n" + WHERE_CLAUSE@18..49 + WHERE_KW@18..23 "where" + WHITESPACE@23..27 "\n " + WHERE_PRED@27..49 + FOR_KW@27..30 "for" + TYPE_PARAM_LIST@30..34 + L_ANGLE@30..31 "<" + LIFETIME_PARAM@31..33 + LIFETIME@31..33 "\'a" + R_ANGLE@33..34 ">" + WHITESPACE@34..35 " " + PATH_TYPE@35..36 + PATH@35..36 + PATH_SEGMENT@35..36 + NAME_REF@35..36 + IDENT@35..36 "F" + COLON@36..37 ":" + WHITESPACE@37..38 " " + TYPE_BOUND_LIST@38..49 + TYPE_BOUND@38..49 + PATH_TYPE@38..49 + PATH@38..49 + PATH_SEGMENT@38..49 + NAME_REF@38..40 + IDENT@38..40 "Fn" + PARAM_LIST@40..49 + L_PAREN@40..41 "(" + PARAM@41..48 + REFERENCE_TYPE@41..48 + AMP@41..42 "&" + LIFETIME@42..44 "\'a" + WHITESPACE@44..45 " " + PATH_TYPE@45..48 + PATH@45..48 + PATH_SEGMENT@45..48 + NAME_REF@45..48 + IDENT@45..48 "str" + R_PAREN@48..49 ")" + WHITESPACE@49..50 "\n" + BLOCK_EXPR@50..53 + L_CURLY@50..51 "{" + WHITESPACE@51..52 " " + R_CURLY@52..53 "}" + WHITESPACE@53..54 "\n" diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rs b/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rs index b448c6178087..423bc105bd7b 100644 --- a/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rs +++ b/crates/ra_syntax/test_data/parser/inline/ok/0003_where_pred_for.rs @@ -1,4 +1,4 @@ -fn test() +fn for_trait() where for<'a> F: Fn(&'a str) { } diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rast b/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rast index dfb8d57ad844..b26ac2d365b9 100644 --- a/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rast +++ b/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rast @@ -1,4 +1,4 @@ -SOURCE_FILE@0..200 +SOURCE_FILE@0..121 TYPE_ALIAS_DEF@0..28 TYPE_KW@0..4 "type" WHITESPACE@4..5 " " @@ -29,212 +29,84 @@ SOURCE_FILE@0..200 R_PAREN@26..27 ")" SEMICOLON@27..28 ";" WHITESPACE@28..29 "\n" - FN_DEF@29..79 - FN_KW@29..31 "fn" - WHITESPACE@31..32 " " - NAME@32..35 - IDENT@32..35 "foo" - TYPE_PARAM_LIST@35..38 - L_ANGLE@35..36 "<" - TYPE_PARAM@36..37 - NAME@36..37 - IDENT@36..37 "T" - R_ANGLE@37..38 ">" - PARAM_LIST@38..46 - L_PAREN@38..39 "(" - PARAM@39..45 - BIND_PAT@39..41 - NAME@39..41 - IDENT@39..41 "_t" - COLON@41..42 ":" - WHITESPACE@42..43 " " - REFERENCE_TYPE@43..45 - AMP@43..44 "&" - PATH_TYPE@44..45 - PATH@44..45 - PATH_SEGMENT@44..45 - NAME_REF@44..45 - IDENT@44..45 "T" - R_PAREN@45..46 ")" - WHITESPACE@46..47 " " - WHERE_CLAUSE@47..76 - WHERE_KW@47..52 "where" - WHITESPACE@52..53 " " - WHERE_PRED@53..76 - FOR_TYPE@53..66 - FOR_KW@53..56 "for" - TYPE_PARAM_LIST@56..60 - L_ANGLE@56..57 "<" - LIFETIME_PARAM@57..59 - LIFETIME@57..59 "\'a" - R_ANGLE@59..60 ">" - WHITESPACE@60..61 " " - REFERENCE_TYPE@61..66 - AMP@61..62 "&" - LIFETIME@62..64 "\'a" - WHITESPACE@64..65 " " - PATH_TYPE@65..66 - PATH@65..66 - PATH_SEGMENT@65..66 - NAME_REF@65..66 - IDENT@65..66 "T" - COLON@66..67 ":" - WHITESPACE@67..68 " " - TYPE_BOUND_LIST@68..76 - TYPE_BOUND@68..76 - PATH_TYPE@68..76 - PATH@68..76 - PATH_SEGMENT@68..76 - NAME_REF@68..76 - IDENT@68..76 "Iterator" - WHITESPACE@76..77 " " - BLOCK_EXPR@77..79 - L_CURLY@77..78 "{" - R_CURLY@78..79 "}" - WHITESPACE@79..80 "\n" - FN_DEF@80..134 - FN_KW@80..82 "fn" - WHITESPACE@82..83 " " - NAME@83..86 - IDENT@83..86 "bar" - TYPE_PARAM_LIST@86..89 - L_ANGLE@86..87 "<" - TYPE_PARAM@87..88 - NAME@87..88 - IDENT@87..88 "T" - R_ANGLE@88..89 ">" - PARAM_LIST@89..97 - L_PAREN@89..90 "(" - PARAM@90..96 - BIND_PAT@90..92 - NAME@90..92 - IDENT@90..92 "_t" - COLON@92..93 ":" - WHITESPACE@93..94 " " - REFERENCE_TYPE@94..96 - AMP@94..95 "&" - PATH_TYPE@95..96 - PATH@95..96 - PATH_SEGMENT@95..96 - NAME_REF@95..96 - IDENT@95..96 "T" - R_PAREN@96..97 ")" - WHITESPACE@97..98 " " - WHERE_CLAUSE@98..131 - WHERE_KW@98..103 "where" - WHITESPACE@103..104 " " - WHERE_PRED@104..131 - FOR_TYPE@104..121 - FOR_KW@104..107 "for" - TYPE_PARAM_LIST@107..111 - L_ANGLE@107..108 "<" - LIFETIME_PARAM@108..110 - LIFETIME@108..110 "\'a" - R_ANGLE@110..111 ">" - WHITESPACE@111..112 " " - REFERENCE_TYPE@112..121 - AMP@112..113 "&" - LIFETIME@113..115 "\'a" - WHITESPACE@115..116 " " - MUT_KW@116..119 "mut" - WHITESPACE@119..120 " " - PATH_TYPE@120..121 - PATH@120..121 - PATH_SEGMENT@120..121 - NAME_REF@120..121 - IDENT@120..121 "T" - COLON@121..122 ":" - WHITESPACE@122..123 " " - TYPE_BOUND_LIST@123..131 - TYPE_BOUND@123..131 - PATH_TYPE@123..131 - PATH@123..131 - PATH_SEGMENT@123..131 - NAME_REF@123..131 - IDENT@123..131 "Iterator" - WHITESPACE@131..132 " " - BLOCK_EXPR@132..134 - L_CURLY@132..133 "{" - R_CURLY@133..134 "}" - WHITESPACE@134..135 "\n" - FN_DEF@135..199 - FN_KW@135..137 "fn" - WHITESPACE@137..138 " " - NAME@138..141 - IDENT@138..141 "baz" - TYPE_PARAM_LIST@141..144 - L_ANGLE@141..142 "<" - TYPE_PARAM@142..143 - NAME@142..143 - IDENT@142..143 "T" - R_ANGLE@143..144 ">" - PARAM_LIST@144..152 - L_PAREN@144..145 "(" - PARAM@145..151 - BIND_PAT@145..147 - NAME@145..147 - IDENT@145..147 "_t" - COLON@147..148 ":" - WHITESPACE@148..149 " " - REFERENCE_TYPE@149..151 - AMP@149..150 "&" - PATH_TYPE@150..151 - PATH@150..151 - PATH_SEGMENT@150..151 - NAME_REF@150..151 - IDENT@150..151 "T" - R_PAREN@151..152 ")" - WHITESPACE@152..153 " " - WHERE_CLAUSE@153..196 - WHERE_KW@153..158 "where" - WHITESPACE@158..159 " " - WHERE_PRED@159..196 - FOR_TYPE@159..186 - FOR_KW@159..162 "for" - TYPE_PARAM_LIST@162..166 - L_ANGLE@162..163 "<" - LIFETIME_PARAM@163..165 - LIFETIME@163..165 "\'a" - R_ANGLE@165..166 ">" - WHITESPACE@166..167 " " - PATH_TYPE@167..186 - PATH@167..186 - PATH@167..181 - PATH_SEGMENT@167..181 - L_ANGLE@167..168 "<" - REFERENCE_TYPE@168..173 - AMP@168..169 "&" - LIFETIME@169..171 "\'a" - WHITESPACE@171..172 " " - PATH_TYPE@172..173 - PATH@172..173 - PATH_SEGMENT@172..173 - NAME_REF@172..173 - IDENT@172..173 "T" - WHITESPACE@173..174 " " - AS_KW@174..176 "as" - WHITESPACE@176..177 " " - PATH_TYPE@177..180 - PATH@177..180 - PATH_SEGMENT@177..180 - NAME_REF@177..180 - IDENT@177..180 "Baz" - R_ANGLE@180..181 ">" - COLON2@181..183 "::" - PATH_SEGMENT@183..186 - NAME_REF@183..186 - IDENT@183..186 "Foo" - COLON@186..187 ":" - WHITESPACE@187..188 " " - TYPE_BOUND_LIST@188..196 - TYPE_BOUND@188..196 - PATH_TYPE@188..196 - PATH@188..196 - PATH_SEGMENT@188..196 - NAME_REF@188..196 - IDENT@188..196 "Iterator" - WHITESPACE@196..197 " " - BLOCK_EXPR@197..199 - L_CURLY@197..198 "{" - R_CURLY@198..199 "}" - WHITESPACE@199..200 "\n" + TYPE_ALIAS_DEF@29..81 + TYPE_KW@29..33 "type" + WHITESPACE@33..34 " " + NAME@34..35 + IDENT@34..35 "B" + WHITESPACE@35..36 " " + EQ@36..37 "=" + WHITESPACE@37..38 " " + FOR_TYPE@38..80 + FOR_KW@38..41 "for" + TYPE_PARAM_LIST@41..45 + L_ANGLE@41..42 "<" + LIFETIME_PARAM@42..44 + LIFETIME@42..44 "\'a" + R_ANGLE@44..45 ">" + WHITESPACE@45..46 " " + FN_POINTER_TYPE@46..80 + UNSAFE_KW@46..52 "unsafe" + WHITESPACE@52..53 " " + ABI@53..63 + EXTERN_KW@53..59 "extern" + WHITESPACE@59..60 " " + STRING@60..63 "\"C\"" + WHITESPACE@63..64 " " + FN_KW@64..66 "fn" + PARAM_LIST@66..74 + L_PAREN@66..67 "(" + PARAM@67..73 + REFERENCE_TYPE@67..73 + AMP@67..68 "&" + LIFETIME@68..70 "\'a" + WHITESPACE@70..71 " " + TUPLE_TYPE@71..73 + L_PAREN@71..72 "(" + R_PAREN@72..73 ")" + R_PAREN@73..74 ")" + WHITESPACE@74..75 " " + RET_TYPE@75..80 + THIN_ARROW@75..77 "->" + WHITESPACE@77..78 " " + TUPLE_TYPE@78..80 + L_PAREN@78..79 "(" + R_PAREN@79..80 ")" + SEMICOLON@80..81 ";" + WHITESPACE@81..82 "\n" + TYPE_ALIAS_DEF@82..120 + TYPE_KW@82..86 "type" + WHITESPACE@86..87 " " + NAME@87..90 + IDENT@87..90 "Obj" + WHITESPACE@90..91 " " + EQ@91..92 "=" + WHITESPACE@92..93 " " + FOR_TYPE@93..119 + FOR_KW@93..96 "for" + TYPE_PARAM_LIST@96..100 + L_ANGLE@96..97 "<" + LIFETIME_PARAM@97..99 + LIFETIME@97..99 "\'a" + R_ANGLE@99..100 ">" + WHITESPACE@100..101 " " + PATH_TYPE@101..119 + PATH@101..119 + PATH_SEGMENT@101..119 + NAME_REF@101..110 + IDENT@101..110 "PartialEq" + TYPE_ARG_LIST@110..119 + L_ANGLE@110..111 "<" + TYPE_ARG@111..118 + REFERENCE_TYPE@111..118 + AMP@111..112 "&" + LIFETIME@112..114 "\'a" + WHITESPACE@114..115 " " + PATH_TYPE@115..118 + PATH@115..118 + PATH_SEGMENT@115..118 + NAME_REF@115..118 + IDENT@115..118 "i32" + R_ANGLE@118..119 ">" + SEMICOLON@119..120 ";" + WHITESPACE@120..121 "\n" diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rs b/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rs index d6774d438bc6..8ac7b9e10380 100644 --- a/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rs +++ b/crates/ra_syntax/test_data/parser/inline/ok/0081_for_type.rs @@ -1,4 +1,3 @@ type A = for<'a> fn() -> (); -fn foo(_t: &T) where for<'a> &'a T: Iterator {} -fn bar(_t: &T) where for<'a> &'a mut T: Iterator {} -fn baz(_t: &T) where for<'a> <&'a T as Baz>::Foo: Iterator {} +type B = for<'a> unsafe extern "C" fn(&'a ()) -> (); +type Obj = for<'a> PartialEq<&'a i32>; diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rast b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rast new file mode 100644 index 000000000000..adb6159f4725 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rast @@ -0,0 +1,40 @@ +SOURCE_FILE@0..50 + IMPL_DEF@0..49 + IMPL_KW@0..4 "impl" + WHITESPACE@4..5 " " + PATH_TYPE@5..6 + PATH@5..6 + PATH_SEGMENT@5..6 + NAME_REF@5..6 + IDENT@5..6 "T" + WHITESPACE@6..7 " " + FOR_KW@7..10 "for" + WHITESPACE@10..11 " " + PATH_TYPE@11..14 + PATH@11..14 + PATH_SEGMENT@11..14 + NAME_REF@11..14 + IDENT@11..14 "Foo" + WHITESPACE@14..15 " " + ITEM_LIST@15..49 + L_CURLY@15..16 "{" + WHITESPACE@16..21 "\n " + FN_DEF@21..47 + DEFAULT_KW@21..28 "default" + WHITESPACE@28..29 " " + UNSAFE_KW@29..35 "unsafe" + WHITESPACE@35..36 " " + FN_KW@36..38 "fn" + WHITESPACE@38..39 " " + NAME@39..42 + IDENT@39..42 "foo" + PARAM_LIST@42..44 + L_PAREN@42..43 "(" + R_PAREN@43..44 ")" + WHITESPACE@44..45 " " + BLOCK_EXPR@45..47 + L_CURLY@45..46 "{" + R_CURLY@46..47 "}" + WHITESPACE@47..48 "\n" + R_CURLY@48..49 "}" + WHITESPACE@49..50 "\n" diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rs b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rs new file mode 100644 index 000000000000..12926cd8a1b4 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_fn.rs @@ -0,0 +1,3 @@ +impl T for Foo { + default unsafe fn foo() {} +} diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast new file mode 100644 index 000000000000..a9eda5668112 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rast @@ -0,0 +1,18 @@ +SOURCE_FILE@0..27 + IMPL_DEF@0..26 + DEFAULT_KW@0..7 "default" + WHITESPACE@7..8 " " + UNSAFE_KW@8..14 "unsafe" + WHITESPACE@14..15 " " + IMPL_KW@15..19 "impl" + WHITESPACE@19..20 " " + PATH_TYPE@20..23 + PATH@20..23 + PATH_SEGMENT@20..23 + NAME_REF@20..23 + IDENT@20..23 "Foo" + WHITESPACE@23..24 " " + ITEM_LIST@24..26 + L_CURLY@24..25 "{" + R_CURLY@25..26 "}" + WHITESPACE@26..27 "\n" diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs new file mode 100644 index 000000000000..ba0998ff4d99 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/inline/ok/0163_default_unsafe_impl.rs @@ -0,0 +1 @@ +default unsafe impl Foo {} diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rast b/crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rast new file mode 100644 index 000000000000..8688992750d6 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rast @@ -0,0 +1,38 @@ +SOURCE_FILE@0..33 + FN_DEF@0..32 + FN_KW@0..2 "fn" + WHITESPACE@2..3 " " + NAME@3..7 + IDENT@3..7 "main" + PARAM_LIST@7..9 + L_PAREN@7..8 "(" + R_PAREN@8..9 ")" + WHITESPACE@9..10 " " + BLOCK_EXPR@10..32 + L_CURLY@10..11 "{" + WHITESPACE@11..12 " " + LET_STMT@12..30 + LET_KW@12..15 "let" + WHITESPACE@15..16 " " + PATH_PAT@16..24 + PATH@16..24 + PATH@16..19 + PATH_SEGMENT@16..19 + L_ANGLE@16..17 "<" + PLACEHOLDER_TYPE@17..18 + UNDERSCORE@17..18 "_" + R_ANGLE@18..19 ">" + COLON2@19..21 "::" + PATH_SEGMENT@21..24 + NAME_REF@21..24 + IDENT@21..24 "Foo" + WHITESPACE@24..25 " " + EQ@25..26 "=" + WHITESPACE@26..27 " " + TUPLE_EXPR@27..29 + L_PAREN@27..28 "(" + R_PAREN@28..29 ")" + SEMICOLON@29..30 ";" + WHITESPACE@30..31 " " + R_CURLY@31..32 "}" + WHITESPACE@32..33 "\n" diff --git a/crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rs b/crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rs new file mode 100644 index 000000000000..ebe26834d883 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/inline/ok/0164_type_path_in_pattern.rs @@ -0,0 +1 @@ +fn main() { let <_>::Foo = (); } diff --git a/crates/ra_syntax/test_data/parser/ok/0066_default_const.rast b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rast new file mode 100644 index 000000000000..dab0247ee6fa --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rast @@ -0,0 +1,44 @@ +SOURCE_FILE@0..46 + IMPL_DEF@0..45 + IMPL_KW@0..4 "impl" + WHITESPACE@4..5 " " + PATH_TYPE@5..6 + PATH@5..6 + PATH_SEGMENT@5..6 + NAME_REF@5..6 + IDENT@5..6 "T" + WHITESPACE@6..7 " " + FOR_KW@7..10 "for" + WHITESPACE@10..11 " " + PATH_TYPE@11..14 + PATH@11..14 + PATH_SEGMENT@11..14 + NAME_REF@11..14 + IDENT@11..14 "Foo" + WHITESPACE@14..15 " " + ITEM_LIST@15..45 + L_CURLY@15..16 "{" + WHITESPACE@16..19 "\n " + CONST_DEF@19..43 + DEFAULT_KW@19..26 "default" + WHITESPACE@26..27 " " + CONST_KW@27..32 "const" + WHITESPACE@32..33 " " + NAME@33..34 + IDENT@33..34 "f" + COLON@34..35 ":" + WHITESPACE@35..36 " " + PATH_TYPE@36..38 + PATH@36..38 + PATH_SEGMENT@36..38 + NAME_REF@36..38 + IDENT@36..38 "u8" + WHITESPACE@38..39 " " + EQ@39..40 "=" + WHITESPACE@40..41 " " + LITERAL@41..42 + INT_NUMBER@41..42 "0" + SEMICOLON@42..43 ";" + WHITESPACE@43..44 "\n" + R_CURLY@44..45 "}" + WHITESPACE@45..46 "\n" diff --git a/crates/ra_syntax/test_data/parser/ok/0066_default_const.rs b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rs new file mode 100644 index 000000000000..dfb3b92dca79 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0066_default_const.rs @@ -0,0 +1,3 @@ +impl T for Foo { + default const f: u8 = 0; +} diff --git a/crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rast b/crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rast new file mode 100644 index 000000000000..5035851031f6 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rast @@ -0,0 +1,392 @@ +SOURCE_FILE@0..374 + FN_DEF@0..55 + FN_KW@0..2 "fn" + WHITESPACE@2..3 " " + NAME@3..12 + IDENT@3..12 "for_trait" + TYPE_PARAM_LIST@12..15 + L_ANGLE@12..13 "<" + TYPE_PARAM@13..14 + NAME@13..14 + IDENT@13..14 "F" + R_ANGLE@14..15 ">" + PARAM_LIST@15..17 + L_PAREN@15..16 "(" + R_PAREN@16..17 ")" + WHITESPACE@17..18 "\n" + WHERE_CLAUSE@18..51 + WHERE_KW@18..23 "where" + WHITESPACE@23..28 "\n " + WHERE_PRED@28..50 + FOR_KW@28..31 "for" + TYPE_PARAM_LIST@31..35 + L_ANGLE@31..32 "<" + LIFETIME_PARAM@32..34 + LIFETIME@32..34 "\'a" + R_ANGLE@34..35 ">" + WHITESPACE@35..36 " " + PATH_TYPE@36..37 + PATH@36..37 + PATH_SEGMENT@36..37 + NAME_REF@36..37 + IDENT@36..37 "F" + COLON@37..38 ":" + WHITESPACE@38..39 " " + TYPE_BOUND_LIST@39..50 + TYPE_BOUND@39..50 + PATH_TYPE@39..50 + PATH@39..50 + PATH_SEGMENT@39..50 + NAME_REF@39..41 + IDENT@39..41 "Fn" + PARAM_LIST@41..50 + L_PAREN@41..42 "(" + PARAM@42..49 + REFERENCE_TYPE@42..49 + AMP@42..43 "&" + LIFETIME@43..45 "\'a" + WHITESPACE@45..46 " " + PATH_TYPE@46..49 + PATH@46..49 + PATH_SEGMENT@46..49 + NAME_REF@46..49 + IDENT@46..49 "str" + R_PAREN@49..50 ")" + COMMA@50..51 "," + WHITESPACE@51..52 "\n" + BLOCK_EXPR@52..55 + L_CURLY@52..53 "{" + WHITESPACE@53..54 "\n" + R_CURLY@54..55 "}" + WHITESPACE@55..56 "\n" + FN_DEF@56..107 + FN_KW@56..58 "fn" + WHITESPACE@58..59 " " + NAME@59..66 + IDENT@59..66 "for_ref" + TYPE_PARAM_LIST@66..69 + L_ANGLE@66..67 "<" + TYPE_PARAM@67..68 + NAME@67..68 + IDENT@67..68 "F" + R_ANGLE@68..69 ">" + PARAM_LIST@69..71 + L_PAREN@69..70 "(" + R_PAREN@70..71 ")" + WHITESPACE@71..72 "\n" + WHERE_CLAUSE@72..103 + WHERE_KW@72..77 "where" + WHITESPACE@77..82 "\n " + WHERE_PRED@82..102 + FOR_KW@82..85 "for" + TYPE_PARAM_LIST@85..89 + L_ANGLE@85..86 "<" + LIFETIME_PARAM@86..88 + LIFETIME@86..88 "\'a" + R_ANGLE@88..89 ">" + WHITESPACE@89..90 " " + REFERENCE_TYPE@90..95 + AMP@90..91 "&" + LIFETIME@91..93 "\'a" + WHITESPACE@93..94 " " + PATH_TYPE@94..95 + PATH@94..95 + PATH_SEGMENT@94..95 + NAME_REF@94..95 + IDENT@94..95 "F" + COLON@95..96 ":" + WHITESPACE@96..97 " " + TYPE_BOUND_LIST@97..102 + TYPE_BOUND@97..102 + PATH_TYPE@97..102 + PATH@97..102 + PATH_SEGMENT@97..102 + NAME_REF@97..102 + IDENT@97..102 "Debug" + COMMA@102..103 "," + WHITESPACE@103..104 "\n" + BLOCK_EXPR@104..107 + L_CURLY@104..105 "{" + WHITESPACE@105..106 "\n" + R_CURLY@106..107 "}" + WHITESPACE@107..108 "\n" + FN_DEF@108..170 + FN_KW@108..110 "fn" + WHITESPACE@110..111 " " + NAME@111..121 + IDENT@111..121 "for_parens" + TYPE_PARAM_LIST@121..124 + L_ANGLE@121..122 "<" + TYPE_PARAM@122..123 + NAME@122..123 + IDENT@122..123 "F" + R_ANGLE@123..124 ">" + PARAM_LIST@124..126 + L_PAREN@124..125 "(" + R_PAREN@125..126 ")" + WHITESPACE@126..127 "\n" + WHERE_CLAUSE@127..166 + WHERE_KW@127..132 "where" + WHITESPACE@132..137 "\n " + WHERE_PRED@137..165 + FOR_KW@137..140 "for" + TYPE_PARAM_LIST@140..144 + L_ANGLE@140..141 "<" + LIFETIME_PARAM@141..143 + LIFETIME@141..143 "\'a" + R_ANGLE@143..144 ">" + WHITESPACE@144..145 " " + PAREN_TYPE@145..152 + L_PAREN@145..146 "(" + REFERENCE_TYPE@146..151 + AMP@146..147 "&" + LIFETIME@147..149 "\'a" + WHITESPACE@149..150 " " + PATH_TYPE@150..151 + PATH@150..151 + PATH_SEGMENT@150..151 + NAME_REF@150..151 + IDENT@150..151 "F" + R_PAREN@151..152 ")" + COLON@152..153 ":" + WHITESPACE@153..154 " " + TYPE_BOUND_LIST@154..165 + TYPE_BOUND@154..165 + PATH_TYPE@154..165 + PATH@154..165 + PATH_SEGMENT@154..165 + NAME_REF@154..156 + IDENT@154..156 "Fn" + PARAM_LIST@156..165 + L_PAREN@156..157 "(" + PARAM@157..164 + REFERENCE_TYPE@157..164 + AMP@157..158 "&" + LIFETIME@158..160 "\'a" + WHITESPACE@160..161 " " + PATH_TYPE@161..164 + PATH@161..164 + PATH_SEGMENT@161..164 + NAME_REF@161..164 + IDENT@161..164 "str" + R_PAREN@164..165 ")" + COMMA@165..166 "," + WHITESPACE@166..167 "\n" + BLOCK_EXPR@167..170 + L_CURLY@167..168 "{" + WHITESPACE@168..169 "\n" + R_CURLY@169..170 "}" + WHITESPACE@170..171 "\n" + FN_DEF@171..223 + FN_KW@171..173 "fn" + WHITESPACE@173..174 " " + NAME@174..183 + IDENT@174..183 "for_slice" + TYPE_PARAM_LIST@183..186 + L_ANGLE@183..184 "<" + TYPE_PARAM@184..185 + NAME@184..185 + IDENT@184..185 "F" + R_ANGLE@185..186 ">" + PARAM_LIST@186..188 + L_PAREN@186..187 "(" + R_PAREN@187..188 ")" + WHITESPACE@188..189 "\n" + WHERE_CLAUSE@189..219 + WHERE_KW@189..194 "where" + WHITESPACE@194..199 "\n " + WHERE_PRED@199..218 + FOR_KW@199..202 "for" + TYPE_PARAM_LIST@202..206 + L_ANGLE@202..203 "<" + LIFETIME_PARAM@203..205 + LIFETIME@203..205 "\'a" + R_ANGLE@205..206 ">" + WHITESPACE@206..207 " " + SLICE_TYPE@207..214 + L_BRACK@207..208 "[" + REFERENCE_TYPE@208..213 + AMP@208..209 "&" + LIFETIME@209..211 "\'a" + WHITESPACE@211..212 " " + PATH_TYPE@212..213 + PATH@212..213 + PATH_SEGMENT@212..213 + NAME_REF@212..213 + IDENT@212..213 "F" + R_BRACK@213..214 "]" + COLON@214..215 ":" + WHITESPACE@215..216 " " + TYPE_BOUND_LIST@216..218 + TYPE_BOUND@216..218 + PATH_TYPE@216..218 + PATH@216..218 + PATH_SEGMENT@216..218 + NAME_REF@216..218 + IDENT@216..218 "Eq" + COMMA@218..219 "," + WHITESPACE@219..220 "\n" + BLOCK_EXPR@220..223 + L_CURLY@220..221 "{" + WHITESPACE@221..222 "\n" + R_CURLY@222..223 "}" + WHITESPACE@223..224 "\n" + FN_DEF@224..300 + FN_KW@224..226 "fn" + WHITESPACE@226..227 " " + NAME@227..236 + IDENT@227..236 "for_qpath" + TYPE_PARAM_LIST@236..239 + L_ANGLE@236..237 "<" + TYPE_PARAM@237..238 + NAME@237..238 + IDENT@237..238 "T" + R_ANGLE@238..239 ">" + PARAM_LIST@239..247 + L_PAREN@239..240 "(" + PARAM@240..246 + BIND_PAT@240..242 + NAME@240..242 + IDENT@240..242 "_t" + COLON@242..243 ":" + WHITESPACE@243..244 " " + REFERENCE_TYPE@244..246 + AMP@244..245 "&" + PATH_TYPE@245..246 + PATH@245..246 + PATH_SEGMENT@245..246 + NAME_REF@245..246 + IDENT@245..246 "T" + R_PAREN@246..247 ")" + WHITESPACE@247..248 "\n" + WHERE_CLAUSE@248..296 + WHERE_KW@248..253 "where" + WHITESPACE@253..258 "\n " + WHERE_PRED@258..295 + FOR_KW@258..261 "for" + TYPE_PARAM_LIST@261..265 + L_ANGLE@261..262 "<" + LIFETIME_PARAM@262..264 + LIFETIME@262..264 "\'a" + R_ANGLE@264..265 ">" + WHITESPACE@265..266 " " + PATH_TYPE@266..285 + PATH@266..285 + PATH@266..280 + PATH_SEGMENT@266..280 + L_ANGLE@266..267 "<" + REFERENCE_TYPE@267..272 + AMP@267..268 "&" + LIFETIME@268..270 "\'a" + WHITESPACE@270..271 " " + PATH_TYPE@271..272 + PATH@271..272 + PATH_SEGMENT@271..272 + NAME_REF@271..272 + IDENT@271..272 "T" + WHITESPACE@272..273 " " + AS_KW@273..275 "as" + WHITESPACE@275..276 " " + PATH_TYPE@276..279 + PATH@276..279 + PATH_SEGMENT@276..279 + NAME_REF@276..279 + IDENT@276..279 "Baz" + R_ANGLE@279..280 ">" + COLON2@280..282 "::" + PATH_SEGMENT@282..285 + NAME_REF@282..285 + IDENT@282..285 "Foo" + COLON@285..286 ":" + WHITESPACE@286..287 " " + TYPE_BOUND_LIST@287..295 + TYPE_BOUND@287..295 + PATH_TYPE@287..295 + PATH@287..295 + PATH_SEGMENT@287..295 + NAME_REF@287..295 + IDENT@287..295 "Iterator" + COMMA@295..296 "," + WHITESPACE@296..297 "\n" + BLOCK_EXPR@297..300 + L_CURLY@297..298 "{" + WHITESPACE@298..299 "\n" + R_CURLY@299..300 "}" + WHITESPACE@300..301 "\n" + FN_DEF@301..373 + FN_KW@301..303 "fn" + WHITESPACE@303..304 " " + NAME@304..314 + IDENT@304..314 "for_for_fn" + TYPE_PARAM_LIST@314..317 + L_ANGLE@314..315 "<" + TYPE_PARAM@315..316 + NAME@315..316 + IDENT@315..316 "T" + R_ANGLE@316..317 ">" + PARAM_LIST@317..319 + L_PAREN@317..318 "(" + R_PAREN@318..319 ")" + WHITESPACE@319..320 "\n" + WHERE_CLAUSE@320..369 + WHERE_KW@320..325 "where" + WHITESPACE@325..330 "\n " + WHERE_PRED@330..368 + FOR_KW@330..333 "for" + TYPE_PARAM_LIST@333..337 + L_ANGLE@333..334 "<" + LIFETIME_PARAM@334..336 + LIFETIME@334..336 "\'a" + R_ANGLE@336..337 ">" + WHITESPACE@337..338 " " + FOR_TYPE@338..362 + FOR_KW@338..341 "for" + TYPE_PARAM_LIST@341..345 + L_ANGLE@341..342 "<" + LIFETIME_PARAM@342..344 + LIFETIME@342..344 "\'b" + R_ANGLE@344..345 ">" + WHITESPACE@345..346 " " + FN_POINTER_TYPE@346..362 + FN_KW@346..348 "fn" + PARAM_LIST@348..362 + L_PAREN@348..349 "(" + PARAM@349..354 + REFERENCE_TYPE@349..354 + AMP@349..350 "&" + LIFETIME@350..352 "\'a" + WHITESPACE@352..353 " " + PATH_TYPE@353..354 + PATH@353..354 + PATH_SEGMENT@353..354 + NAME_REF@353..354 + IDENT@353..354 "T" + COMMA@354..355 "," + WHITESPACE@355..356 " " + PARAM@356..361 + REFERENCE_TYPE@356..361 + AMP@356..357 "&" + LIFETIME@357..359 "\'b" + WHITESPACE@359..360 " " + PATH_TYPE@360..361 + PATH@360..361 + PATH_SEGMENT@360..361 + NAME_REF@360..361 + IDENT@360..361 "T" + R_PAREN@361..362 ")" + COLON@362..363 ":" + WHITESPACE@363..364 " " + TYPE_BOUND_LIST@364..368 + TYPE_BOUND@364..368 + PATH_TYPE@364..368 + PATH@364..368 + PATH_SEGMENT@364..368 + NAME_REF@364..368 + IDENT@364..368 "Copy" + COMMA@368..369 "," + WHITESPACE@369..370 "\n" + BLOCK_EXPR@370..373 + L_CURLY@370..371 "{" + WHITESPACE@371..372 "\n" + R_CURLY@372..373 "}" + WHITESPACE@373..374 "\n" diff --git a/crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rs b/crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rs new file mode 100644 index 000000000000..9058c46190c4 --- /dev/null +++ b/crates/ra_syntax/test_data/parser/ok/0067_where_for_pred.rs @@ -0,0 +1,30 @@ +fn for_trait() +where + for<'a> F: Fn(&'a str), +{ +} +fn for_ref() +where + for<'a> &'a F: Debug, +{ +} +fn for_parens() +where + for<'a> (&'a F): Fn(&'a str), +{ +} +fn for_slice() +where + for<'a> [&'a F]: Eq, +{ +} +fn for_qpath(_t: &T) +where + for<'a> <&'a T as Baz>::Foo: Iterator, +{ +} +fn for_for_fn() +where + for<'a> for<'b> fn(&'a T, &'b T): Copy, +{ +} diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 65b487db3b98..458089e53922 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -10,7 +10,7 @@ doctest = false [[bin]] name = "rust-analyzer" -path = "./src/bin/main.rs" +path = "src/bin/main.rs" [dependencies] anyhow = "1.0.26" @@ -32,7 +32,7 @@ threadpool = "1.7.1" stdx = { path = "../stdx" } -lsp-server = "0.3.1" +lsp-server = "0.3.2" ra_flycheck = { path = "../ra_flycheck" } ra_ide = { path = "../ra_ide" } ra_prof = { path = "../ra_prof" } diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index e82fd57de84d..8d071ab1ca2b 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -4,9 +4,14 @@ mod args; use lsp_server::Connection; -use rust_analyzer::{cli, config::Config, from_json, Result}; +use rust_analyzer::{ + cli, + config::{Config, LinkedProject}, + from_json, Result, +}; use crate::args::HelpPrinted; +use ra_project_model::ProjectManifest; fn main() -> Result<()> { setup_logging()?; @@ -97,17 +102,6 @@ fn run_server() -> Result<()> { log::info!("Client '{}' {}", client_info.name, client_info.version.unwrap_or_default()); } - let cwd = std::env::current_dir()?; - let root = initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); - - let workspace_roots = initialize_params - .workspace_folders - .map(|workspaces| { - workspaces.into_iter().filter_map(|it| it.uri.to_file_path().ok()).collect::>() - }) - .filter(|workspaces| !workspaces.is_empty()) - .unwrap_or_else(|| vec![root]); - let config = { let mut config = Config::default(); if let Some(value) = &initialize_params.initialization_options { @@ -115,10 +109,31 @@ fn run_server() -> Result<()> { } config.update_caps(&initialize_params.capabilities); + if config.linked_projects.is_empty() { + let cwd = std::env::current_dir()?; + let root = + initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd); + let workspace_roots = initialize_params + .workspace_folders + .map(|workspaces| { + workspaces + .into_iter() + .filter_map(|it| it.uri.to_file_path().ok()) + .collect::>() + }) + .filter(|workspaces| !workspaces.is_empty()) + .unwrap_or_else(|| vec![root]); + + config.linked_projects = ProjectManifest::discover_all(&workspace_roots) + .into_iter() + .map(LinkedProject::from) + .collect(); + } + config }; - rust_analyzer::main_loop(workspace_roots, config, connection)?; + rust_analyzer::main_loop(config, connection)?; log::info!("shutting down IO..."); io_threads.join()?; diff --git a/crates/rust-analyzer/src/caps.rs b/crates/rust-analyzer/src/caps.rs index 345693524ea5..673795e781e6 100644 --- a/crates/rust-analyzer/src/caps.rs +++ b/crates/rust-analyzer/src/caps.rs @@ -87,6 +87,9 @@ pub fn server_capabilities(client_caps: &ClientCapabilities) -> ServerCapabiliti "ssr": true, "onEnter": true, "parentModule": true, + "runnables": { + "kinds": [ "cargo" ], + }, })), } } diff --git a/crates/rust-analyzer/src/cargo_target_spec.rs b/crates/rust-analyzer/src/cargo_target_spec.rs index 441fb61df006..44f856f6b457 100644 --- a/crates/rust-analyzer/src/cargo_target_spec.rs +++ b/crates/rust-analyzer/src/cargo_target_spec.rs @@ -1,10 +1,10 @@ //! See `CargoTargetSpec` +use ra_cfg::CfgExpr; use ra_ide::{FileId, RunnableKind, TestId}; use ra_project_model::{self, ProjectWorkspace, TargetKind}; -use crate::{world::WorldSnapshot, Result}; -use ra_syntax::SmolStr; +use crate::{global_state::GlobalStateSnapshot, Result}; /// Abstract representation of Cargo target. /// @@ -21,7 +21,7 @@ impl CargoTargetSpec { pub(crate) fn runnable_args( spec: Option, kind: &RunnableKind, - features_needed: &Vec, + cfgs: &[CfgExpr], ) -> Result<(Vec, Vec)> { let mut args = Vec::new(); let mut extra_args = Vec::new(); @@ -76,16 +76,20 @@ impl CargoTargetSpec { } } - features_needed.iter().for_each(|feature| { + let mut features = Vec::new(); + for cfg in cfgs { + required_features(cfg, &mut features); + } + for feature in features { args.push("--features".to_string()); - args.push(feature.to_string()); - }); + args.push(feature); + } Ok((args, extra_args)) } pub(crate) fn for_file( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, file_id: FileId, ) -> Result> { let &crate_id = match world.analysis().crate_for(file_id)?.first() { @@ -140,3 +144,74 @@ impl CargoTargetSpec { } } } + +/// Fill minimal features needed +fn required_features(cfg_expr: &CfgExpr, features: &mut Vec) { + match cfg_expr { + CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.to_string()), + CfgExpr::All(preds) => { + preds.iter().for_each(|cfg| required_features(cfg, features)); + } + CfgExpr::Any(preds) => { + for cfg in preds { + let len_features = features.len(); + required_features(cfg, features); + if len_features != features.len() { + break; + } + } + } + _ => {} + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use mbe::{ast_to_token_tree, TokenMap}; + use ra_cfg::parse_cfg; + use ra_syntax::{ + ast::{self, AstNode}, + SmolStr, + }; + + fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) { + let source_file = ast::SourceFile::parse(input).ok().unwrap(); + let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); + ast_to_token_tree(&tt).unwrap() + } + + #[test] + fn test_cfg_expr_minimal_features_needed() { + let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); + let cfg_expr = parse_cfg(&subtree); + let mut min_features = vec![]; + required_features(&cfg_expr, &mut min_features); + + assert_eq!(min_features, vec![SmolStr::new("baz")]); + + let (subtree, _) = + get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#); + let cfg_expr = parse_cfg(&subtree); + + let mut min_features = vec![]; + required_features(&cfg_expr, &mut min_features); + assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]); + + let (subtree, _) = + get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#); + let cfg_expr = parse_cfg(&subtree); + + let mut min_features = vec![]; + required_features(&cfg_expr, &mut min_features); + assert_eq!(min_features, vec![SmolStr::new("baz")]); + + let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#); + let cfg_expr = parse_cfg(&subtree); + + let mut min_features = vec![]; + required_features(&cfg_expr, &mut min_features); + assert!(min_features.is_empty()); + } +} diff --git a/crates/rust-analyzer/src/cli/load_cargo.rs b/crates/rust-analyzer/src/cli/load_cargo.rs index 8eaf75ff6133..97367d7c60e9 100644 --- a/crates/rust-analyzer/src/cli/load_cargo.rs +++ b/crates/rust-analyzer/src/cli/load_cargo.rs @@ -8,7 +8,7 @@ use crossbeam_channel::{unbounded, Receiver}; use ra_db::{ExternSourceId, FileId, SourceRootId}; use ra_ide::{AnalysisChange, AnalysisHost}; use ra_project_model::{ - get_rustc_cfg_options, CargoConfig, PackageRoot, ProcMacroClient, ProjectRoot, ProjectWorkspace, + CargoConfig, PackageRoot, ProcMacroClient, ProjectManifest, ProjectWorkspace, }; use ra_vfs::{RootEntry, Vfs, VfsChange, VfsTask, Watch}; use rustc_hash::{FxHashMap, FxHashSet}; @@ -28,7 +28,7 @@ pub fn load_cargo( with_proc_macro: bool, ) -> Result<(AnalysisHost, FxHashMap)> { let root = std::env::current_dir()?.join(root); - let root = ProjectRoot::discover_single(&root)?; + let root = ProjectManifest::discover_single(&root)?; let ws = ProjectWorkspace::load( root, &CargoConfig { load_out_dirs_from_check, ..Default::default() }, @@ -36,28 +36,28 @@ pub fn load_cargo( )?; let mut extern_dirs = FxHashSet::default(); - extern_dirs.extend(ws.out_dirs()); - - let mut project_roots = ws.to_roots(); - project_roots.extend(extern_dirs.iter().cloned().map(PackageRoot::new_non_member)); let (sender, receiver) = unbounded(); let sender = Box::new(move |t| sender.send(t).unwrap()); - let (mut vfs, roots) = Vfs::new( - project_roots - .iter() - .map(|pkg_root| { - RootEntry::new( - pkg_root.path().to_owned(), - RustPackageFilterBuilder::default() - .set_member(pkg_root.is_member()) - .into_vfs_filter(), - ) - }) - .collect(), - sender, - Watch(false), - ); + + let mut roots = Vec::new(); + let project_roots = ws.to_roots(); + for root in &project_roots { + roots.push(RootEntry::new( + root.path().to_owned(), + RustPackageFilterBuilder::default().set_member(root.is_member()).into_vfs_filter(), + )); + + if let Some(out_dir) = root.out_dir() { + extern_dirs.insert(out_dir.to_path_buf()); + roots.push(RootEntry::new( + out_dir.to_owned(), + RustPackageFilterBuilder::default().set_member(root.is_member()).into_vfs_filter(), + )) + } + } + + let (mut vfs, roots) = Vfs::new(roots, sender, Watch(false)); let source_roots = roots .into_iter() @@ -111,10 +111,6 @@ pub(crate) fn load( vfs.root2path(root) ); analysis_change.add_root(source_root_id, is_local); - analysis_change.set_debug_root_path( - source_root_id, - source_roots[&source_root_id].path().display().to_string(), - ); let vfs_root_path = vfs.root2path(root); if extern_dirs.contains(&vfs_root_path) { @@ -147,26 +143,14 @@ pub(crate) fn load( } } - // FIXME: cfg options? - let default_cfg_options = { - let mut opts = get_rustc_cfg_options(None); - opts.insert_atom("test".into()); - opts.insert_atom("debug_assertion".into()); - opts - }; - - let crate_graph = ws.to_crate_graph( - &default_cfg_options, - &extern_source_roots, - proc_macro_client, - &mut |path: &Path| { + let crate_graph = + ws.to_crate_graph(None, &extern_source_roots, proc_macro_client, &mut |path: &Path| { // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs let path = path.canonicalize().ok()?; let vfs_file = vfs.load(&path); log::debug!("vfs file {:?} -> {:?}", path, vfs_file); vfs_file.map(vfs_file_to_id) - }, - ); + }); log::debug!("crate graph: {:?}", crate_graph); analysis_change.set_crate_graph(crate_graph); diff --git a/crates/rust-analyzer/src/config.rs b/crates/rust-analyzer/src/config.rs index c0f7c2c0c51b..1253db836162 100644 --- a/crates/rust-analyzer/src/config.rs +++ b/crates/rust-analyzer/src/config.rs @@ -11,15 +11,14 @@ use std::{ffi::OsString, path::PathBuf}; use lsp_types::ClientCapabilities; use ra_flycheck::FlycheckConfig; -use ra_ide::{AssistConfig, CompletionConfig, InlayHintsConfig}; -use ra_project_model::CargoConfig; +use ra_ide::{AssistConfig, CompletionConfig, HoverConfig, InlayHintsConfig}; +use ra_project_model::{CargoConfig, JsonProject, ProjectManifest}; use serde::Deserialize; #[derive(Debug, Clone)] pub struct Config { pub client_caps: ClientCapsConfig, - pub with_sysroot: bool, pub publish_diagnostics: bool, pub lru_capacity: Option, pub proc_macro_srv: Option<(PathBuf, Vec)>, @@ -35,6 +34,28 @@ pub struct Config { pub assist: AssistConfig, pub call_info_full: bool, pub lens: LensConfig, + pub hover: HoverConfig, + + pub with_sysroot: bool, + pub linked_projects: Vec, +} + +#[derive(Debug, Clone)] +pub enum LinkedProject { + ProjectManifest(ProjectManifest), + JsonProject(JsonProject), +} + +impl From for LinkedProject { + fn from(v: ProjectManifest) -> Self { + LinkedProject::ProjectManifest(v) + } +} + +impl From for LinkedProject { + fn from(v: JsonProject) -> Self { + LinkedProject::JsonProject(v) + } } #[derive(Clone, Debug, PartialEq, Eq)] @@ -103,6 +124,8 @@ pub struct ClientCapsConfig { pub code_action_literals: bool, pub work_done_progress: bool, pub code_action_group: bool, + pub resolve_code_action: bool, + pub hover_actions: bool, } impl Default for Config { @@ -122,8 +145,9 @@ impl Default for Config { check: Some(FlycheckConfig::CargoCommand { command: "check".to_string(), all_targets: true, - all_features: true, + all_features: false, extra_args: Vec::new(), + features: Vec::new(), }), inlay_hints: InlayHintsConfig { @@ -141,6 +165,8 @@ impl Default for Config { assist: AssistConfig::default(), call_info_full: true, lens: LensConfig::default(), + hover: HoverConfig::default(), + linked_projects: Vec::new(), } } } @@ -209,13 +235,14 @@ impl Config { } // otherwise configure command customizations _ => { - if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features }) + if let Some(FlycheckConfig::CargoCommand { command, extra_args, all_targets, all_features, features }) = &mut self.check { set(value, "/checkOnSave/extraArgs", extra_args); set(value, "/checkOnSave/command", command); set(value, "/checkOnSave/allTargets", all_targets); - set(value, "/checkOnSave/allFeatures", all_features); + *all_features = get(value, "/checkOnSave/allFeatures").unwrap_or(self.cargo.all_features); + *features = get(value, "/checkOnSave/features").unwrap_or(self.cargo.features.clone()); } } }; @@ -240,6 +267,32 @@ impl Config { self.lens = LensConfig::NO_LENS; } + if let Some(linked_projects) = get::>(value, "/linkedProjects") { + if !linked_projects.is_empty() { + self.linked_projects.clear(); + for linked_project in linked_projects { + let linked_project = match linked_project { + ManifestOrJsonProject::Manifest(it) => match ProjectManifest::from_manifest_file(it) { + Ok(it) => it.into(), + Err(_) => continue, + } + ManifestOrJsonProject::JsonProject(it) => it.into(), + }; + self.linked_projects.push(linked_project); + } + } + } + + let mut use_hover_actions = false; + set(value, "/hoverActions/enable", &mut use_hover_actions); + if use_hover_actions { + set(value, "/hoverActions/implementations", &mut self.hover.implementations); + set(value, "/hoverActions/run", &mut self.hover.run); + set(value, "/hoverActions/debug", &mut self.hover.debug); + } else { + self.hover = HoverConfig::NO_ACTIONS; + } + log::info!("Config::update() = {:#?}", self); fn get<'a, T: Deserialize<'a>>(value: &'a serde_json::Value, pointer: &str) -> Option { @@ -293,13 +346,22 @@ impl Config { self.assist.allow_snippets(false); if let Some(experimental) = &caps.experimental { - let snippet_text_edit = - experimental.get("snippetTextEdit").and_then(|it| it.as_bool()) == Some(true); + let get_bool = + |index: &str| experimental.get(index).and_then(|it| it.as_bool()) == Some(true); + + let snippet_text_edit = get_bool("snippetTextEdit"); self.assist.allow_snippets(snippet_text_edit); - let code_action_group = - experimental.get("codeActionGroup").and_then(|it| it.as_bool()) == Some(true); - self.client_caps.code_action_group = code_action_group + self.client_caps.code_action_group = get_bool("codeActionGroup"); + self.client_caps.resolve_code_action = get_bool("resolveCodeAction"); + self.client_caps.hover_actions = get_bool("hoverActions"); } } } + +#[derive(Deserialize)] +#[serde(untagged)] +enum ManifestOrJsonProject { + Manifest(PathBuf), + JsonProject(JsonProject), +} diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap index c40cfdcdcaf0..272057b47ea6 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_multi_line_fix.snap @@ -65,6 +65,7 @@ expression: diag fixes: [ CodeAction { title: "return the expression directly", + id: None, group: None, kind: Some( "quickfix", diff --git a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap index 6dd3fcb2eab8..f0273315e932 100644 --- a/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap +++ b/crates/rust-analyzer/src/diagnostics/snapshots/rust_analyzer__diagnostics__to_proto__tests__snap_rustc_unused_variable.snap @@ -29,7 +29,7 @@ expression: diag }, }, severity: Some( - Warning, + Hint, ), code: Some( String( @@ -50,6 +50,7 @@ expression: diag fixes: [ CodeAction { title: "consider prefixing with an underscore", + id: None, group: None, kind: Some( "quickfix", diff --git a/crates/rust-analyzer/src/diagnostics/to_proto.rs b/crates/rust-analyzer/src/diagnostics/to_proto.rs index a500d670a72c..04e286780c18 100644 --- a/crates/rust-analyzer/src/diagnostics/to_proto.rs +++ b/crates/rust-analyzer/src/diagnostics/to_proto.rs @@ -145,6 +145,7 @@ fn map_rust_child_diagnostic( } else { MappedRustChildDiagnostic::SuggestedFix(lsp_ext::CodeAction { title: rd.message.clone(), + id: None, group: None, kind: Some("quickfix".to_string()), edit: Some(lsp_ext::SnippetWorkspaceEdit { @@ -183,7 +184,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( return Vec::new(); } - let severity = map_level_to_severity(rd.level); + let mut severity = map_level_to_severity(rd.level); let mut source = String::from("rustc"); let mut code = rd.code.as_ref().map(|c| c.code.clone()); @@ -225,6 +226,7 @@ pub(crate) fn map_rust_diagnostic_to_lsp( } if is_unused_or_unnecessary(rd) { + severity = Some(DiagnosticSeverity::Hint); tags.push(DiagnosticTag::Unnecessary); } diff --git a/crates/rust-analyzer/src/from_proto.rs b/crates/rust-analyzer/src/from_proto.rs index 4bb16a496c09..206673829c48 100644 --- a/crates/rust-analyzer/src/from_proto.rs +++ b/crates/rust-analyzer/src/from_proto.rs @@ -3,7 +3,7 @@ use ra_db::{FileId, FilePosition, FileRange}; use ra_ide::{LineCol, LineIndex}; use ra_syntax::{TextRange, TextSize}; -use crate::{world::WorldSnapshot, Result}; +use crate::{global_state::GlobalStateSnapshot, Result}; pub(crate) fn offset(line_index: &LineIndex, position: lsp_types::Position) -> TextSize { let line_col = LineCol { line: position.line as u32, col_utf16: position.character as u32 }; @@ -16,12 +16,12 @@ pub(crate) fn text_range(line_index: &LineIndex, range: lsp_types::Range) -> Tex TextRange::new(start, end) } -pub(crate) fn file_id(world: &WorldSnapshot, url: &lsp_types::Url) -> Result { +pub(crate) fn file_id(world: &GlobalStateSnapshot, url: &lsp_types::Url) -> Result { world.uri_to_file_id(url) } pub(crate) fn file_position( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, tdpp: lsp_types::TextDocumentPositionParams, ) -> Result { let file_id = file_id(world, &tdpp.text_document.uri)?; @@ -31,7 +31,7 @@ pub(crate) fn file_position( } pub(crate) fn file_range( - world: &WorldSnapshot, + world: &GlobalStateSnapshot, text_document_identifier: lsp_types::TextDocumentIdentifier, range: lsp_types::Range, ) -> Result { diff --git a/crates/rust-analyzer/src/world.rs b/crates/rust-analyzer/src/global_state.rs similarity index 86% rename from crates/rust-analyzer/src/world.rs rename to crates/rust-analyzer/src/global_state.rs index 367272925be5..21116e165e72 100644 --- a/crates/rust-analyzer/src/world.rs +++ b/crates/rust-analyzer/src/global_state.rs @@ -15,7 +15,7 @@ use ra_flycheck::{Flycheck, FlycheckConfig}; use ra_ide::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId, }; -use ra_project_model::{get_rustc_cfg_options, ProcMacroClient, ProjectWorkspace}; +use ra_project_model::{ProcMacroClient, ProjectWorkspace}; use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch}; use relative_path::RelativePathBuf; use stdx::format_to; @@ -50,15 +50,15 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> }) } -/// `WorldState` is the primary mutable state of the language server +/// `GlobalState` is the primary mutable state of the language server /// /// The most interesting components are `vfs`, which stores a consistent /// snapshot of the file systems, and `analysis_host`, which stores our /// incremental salsa database. #[derive(Debug)] -pub struct WorldState { +pub struct GlobalState { pub config: Config, - pub roots: Vec, + pub local_roots: Vec, pub workspaces: Arc>, pub analysis_host: AnalysisHost, pub vfs: Arc>, @@ -70,7 +70,7 @@ pub struct WorldState { } /// An immutable snapshot of the world's state at a point in time. -pub struct WorldSnapshot { +pub struct GlobalStateSnapshot { pub config: Config, pub workspaces: Arc>, pub analysis: Analysis, @@ -79,20 +79,19 @@ pub struct WorldSnapshot { vfs: Arc>, } -impl WorldState { +impl GlobalState { pub fn new( - folder_roots: Vec, workspaces: Vec, lru_capacity: Option, exclude_globs: &[Glob], watch: Watch, config: Config, - ) -> WorldState { + ) -> GlobalState { let mut change = AnalysisChange::new(); - let extern_dirs: FxHashSet<_> = - workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect(); + let mut extern_dirs: FxHashSet = FxHashSet::default(); + let mut local_roots = Vec::new(); let roots: Vec<_> = { let create_filter = |is_member| { RustPackageFilterBuilder::default() @@ -100,18 +99,22 @@ impl WorldState { .exclude(exclude_globs.iter().cloned()) .into_vfs_filter() }; - folder_roots - .iter() - .map(|path| RootEntry::new(path.clone(), create_filter(true))) - .chain(workspaces.iter().flat_map(ProjectWorkspace::to_roots).map(|pkg_root| { - RootEntry::new(pkg_root.path().to_owned(), create_filter(pkg_root.is_member())) - })) - .chain( - extern_dirs - .iter() - .map(|path| RootEntry::new(path.to_owned(), create_filter(false))), - ) - .collect() + let mut roots = Vec::new(); + for root in workspaces.iter().flat_map(ProjectWorkspace::to_roots) { + let path = root.path().to_owned(); + if root.is_member() { + local_roots.push(path.clone()); + } + roots.push(RootEntry::new(path, create_filter(root.is_member()))); + if let Some(out_dir) = root.out_dir() { + extern_dirs.insert(out_dir.to_path_buf()); + roots.push(RootEntry::new( + out_dir.to_path_buf(), + create_filter(root.is_member()), + )) + } + } + roots }; let (task_sender, task_receiver) = unbounded(); @@ -121,9 +124,8 @@ impl WorldState { let mut extern_source_roots = FxHashMap::default(); for r in vfs_roots { let vfs_root_path = vfs.root2path(r); - let is_local = folder_roots.iter().any(|it| vfs_root_path.starts_with(it)); + let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it)); change.add_root(SourceRootId(r.0), is_local); - change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string()); // FIXME: add path2root in vfs to simpily this logic if extern_dirs.contains(&vfs_root_path) { @@ -131,14 +133,6 @@ impl WorldState { } } - // FIXME: Read default cfgs from config - let default_cfg_options = { - let mut opts = get_rustc_cfg_options(config.cargo.target.as_ref()); - opts.insert_atom("test".into()); - opts.insert_atom("debug_assertion".into()); - opts - }; - let proc_macro_client = match &config.proc_macro_srv { None => ProcMacroClient::dummy(), Some((path, args)) => match ProcMacroClient::extern_process(path.into(), args) { @@ -164,7 +158,7 @@ impl WorldState { }; for ws in workspaces.iter() { crate_graph.extend(ws.to_crate_graph( - &default_cfg_options, + config.cargo.target.as_deref(), &extern_source_roots, &proc_macro_client, &mut load, @@ -176,9 +170,9 @@ impl WorldState { let mut analysis_host = AnalysisHost::new(lru_capacity); analysis_host.apply_change(change); - WorldState { + GlobalState { config, - roots: folder_roots, + local_roots, workspaces: Arc::new(workspaces), analysis_host, vfs: Arc::new(RwLock::new(vfs)), @@ -216,7 +210,7 @@ impl WorldState { match c { VfsChange::AddRoot { root, files } => { let root_path = self.vfs.read().root2path(root); - let is_local = self.roots.iter().any(|r| root_path.starts_with(r)); + let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r)); if is_local { *roots_scanned += 1; for (file, path, text) in files { @@ -251,8 +245,8 @@ impl WorldState { self.analysis_host.apply_change(change); } - pub fn snapshot(&self) -> WorldSnapshot { - WorldSnapshot { + pub fn snapshot(&self) -> GlobalStateSnapshot { + GlobalStateSnapshot { config: self.config.clone(), workspaces: Arc::clone(&self.workspaces), analysis: self.analysis_host.analysis(), @@ -275,7 +269,7 @@ impl WorldState { } } -impl WorldSnapshot { +impl GlobalStateSnapshot { pub fn analysis(&self) -> &Analysis { &self.analysis } diff --git a/crates/rust-analyzer/src/lib.rs b/crates/rust-analyzer/src/lib.rs index 57d0e92188e3..609cb69d3bff 100644 --- a/crates/rust-analyzer/src/lib.rs +++ b/crates/rust-analyzer/src/lib.rs @@ -26,7 +26,7 @@ mod main_loop; mod markdown; pub mod lsp_ext; pub mod config; -mod world; +mod global_state; mod diagnostics; mod semantic_tokens; diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index acb1dacb6b66..1371f6cb4a41 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -4,7 +4,6 @@ use std::{collections::HashMap, path::PathBuf}; use lsp_types::request::Request; use lsp_types::{Position, Range, TextDocumentIdentifier}; -use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; pub enum AnalyzerStatus {} @@ -98,6 +97,22 @@ pub struct JoinLinesParams { pub ranges: Vec, } +pub enum ResolveCodeActionRequest {} + +impl Request for ResolveCodeActionRequest { + type Params = ResolveCodeActionParams; + type Result = Option; + const METHOD: &'static str = "experimental/resolveCodeAction"; +} + +/// Params for the ResolveCodeActionRequest +#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResolveCodeActionParams { + pub code_action_params: lsp_types::CodeActionParams, + pub id: String, +} + pub enum OnEnter {} impl Request for OnEnter { @@ -111,7 +126,7 @@ pub enum Runnables {} impl Request for Runnables { type Params = RunnablesParams; type Result = Vec; - const METHOD: &'static str = "rust-analyzer/runnables"; + const METHOD: &'static str = "experimental/runnables"; } #[derive(Serialize, Deserialize, Debug)] @@ -124,13 +139,28 @@ pub struct RunnablesParams { #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Runnable { - pub range: Range, pub label: String, - pub bin: String, - pub args: Vec, - pub extra_args: Vec, - pub env: FxHashMap, - pub cwd: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub location: Option, + pub kind: RunnableKind, + pub args: CargoRunnable, +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "lowercase")] +pub enum RunnableKind { + Cargo, +} + +#[derive(Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct CargoRunnable { + #[serde(skip_serializing_if = "Option::is_none")] + pub workspace_root: Option, + // command, --package and --lib stuff + pub cargo_args: Vec, + // stuff after -- + pub executable_args: Vec, } pub enum InlayHints {} @@ -188,6 +218,8 @@ impl Request for CodeActionRequest { pub struct CodeAction { pub title: String, #[serde(skip_serializing_if = "Option::is_none")] + pub id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub group: Option, #[serde(skip_serializing_if = "Option::is_none")] pub kind: Option, @@ -228,3 +260,35 @@ pub struct SnippetTextEdit { #[serde(skip_serializing_if = "Option::is_none")] pub insert_text_format: Option, } + +pub enum HoverRequest {} + +impl Request for HoverRequest { + type Params = lsp_types::HoverParams; + type Result = Option; + const METHOD: &'static str = "textDocument/hover"; +} + +#[derive(Debug, PartialEq, Clone, Deserialize, Serialize)] +pub struct Hover { + #[serde(flatten)] + pub hover: lsp_types::Hover, + #[serde(skip_serializing_if = "Vec::is_empty")] + pub actions: Vec, +} + +#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] +pub struct CommandLinkGroup { + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + pub commands: Vec, +} + +// LSP v3.15 Command does not have a `tooltip` field, vscode supports one. +#[derive(Debug, PartialEq, Clone, Default, Deserialize, Serialize)] +pub struct CommandLink { + #[serde(flatten)] + pub command: lsp_types::Command, + #[serde(skip_serializing_if = "Option::is_none")] + pub tooltip: Option, +} diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index f1287d52cd32..752dbf145298 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -12,13 +12,11 @@ use std::{ fmt, ops::Range, panic, - path::PathBuf, sync::Arc, time::{Duration, Instant}, }; use crossbeam_channel::{never, select, unbounded, RecvError, Sender}; -use itertools::Itertools; use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response}; use lsp_types::{ DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress, @@ -36,14 +34,15 @@ use serde::{de::DeserializeOwned, Serialize}; use threadpool::ThreadPool; use crate::{ - config::{Config, FilesWatcher}, + config::{Config, FilesWatcher, LinkedProject}, diagnostics::{to_proto::url_from_path_with_drive_lowercasing, DiagnosticTask}, - from_proto, lsp_ext, + from_proto, + global_state::{GlobalState, GlobalStateSnapshot}, + lsp_ext, main_loop::{ pending_requests::{PendingRequest, PendingRequests}, subscriptions::Subscriptions, }, - world::{WorldSnapshot, WorldState}, Result, }; @@ -69,7 +68,7 @@ impl fmt::Display for LspError { impl Error for LspError {} -pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) -> Result<()> { +pub fn main_loop(config: Config, connection: Connection) -> Result<()> { log::info!("initial config: {:#?}", config); // Windows scheduler implements priority boosts: if thread waits for an @@ -92,43 +91,37 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) } let mut loop_state = LoopState::default(); - let mut world_state = { + let mut global_state = { let workspaces = { - // FIXME: support dynamic workspace loading. - let project_roots: FxHashSet<_> = ws_roots - .iter() - .filter_map(|it| ra_project_model::ProjectRoot::discover(it).ok()) - .flatten() - .collect(); - - if project_roots.is_empty() && config.notifications.cargo_toml_not_found { + if config.linked_projects.is_empty() && config.notifications.cargo_toml_not_found { show_message( lsp_types::MessageType::Error, - format!( - "rust-analyzer failed to discover workspace, no Cargo.toml found, dirs searched: {}", - ws_roots.iter().format_with(", ", |it, f| f(&it.display())) - ), + "rust-analyzer failed to discover workspace".to_string(), &connection.sender, ); }; - project_roots - .into_iter() - .filter_map(|root| { - ra_project_model::ProjectWorkspace::load( - root, - &config.cargo, - config.with_sysroot, - ) - .map_err(|err| { - log::error!("failed to load workspace: {:#}", err); - show_message( - lsp_types::MessageType::Error, - format!("rust-analyzer failed to load workspace: {:#}", err), - &connection.sender, - ); - }) - .ok() + config + .linked_projects + .iter() + .filter_map(|project| match project { + LinkedProject::ProjectManifest(manifest) => { + ra_project_model::ProjectWorkspace::load( + manifest.clone(), + &config.cargo, + config.with_sysroot, + ) + .map_err(|err| { + log::error!("failed to load workspace: {:#}", err); + show_message( + lsp_types::MessageType::Error, + format!("rust-analyzer failed to load workspace: {:#}", err), + &connection.sender, + ); + }) + .ok() + } + LinkedProject::JsonProject(it) => Some(it.clone().into()), }) .collect::>() }; @@ -163,8 +156,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) connection.sender.send(request.into()).unwrap(); } - WorldState::new( - ws_roots, + GlobalState::new( workspaces, config.lru_capacity, &globs, @@ -173,7 +165,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) ) }; - loop_state.roots_total = world_state.vfs.read().n_roots(); + loop_state.roots_total = global_state.vfs.read().n_roots(); let pool = ThreadPool::default(); let (task_sender, task_receiver) = unbounded::(); @@ -191,12 +183,12 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) Err(RecvError) => return Err("client exited without shutdown".into()), }, recv(task_receiver) -> task => Event::Task(task.unwrap()), - recv(world_state.task_receiver) -> task => match task { + recv(global_state.task_receiver) -> task => match task { Ok(task) => Event::Vfs(task), Err(RecvError) => return Err("vfs died".into()), }, recv(libdata_receiver) -> data => Event::Lib(data.unwrap()), - recv(world_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { + recv(global_state.flycheck.as_ref().map_or(&never(), |it| &it.task_recv)) -> task => match task { Ok(task) => Event::CheckWatcher(task), Err(RecvError) => return Err("check watcher died".into()), } @@ -211,16 +203,16 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) &task_sender, &libdata_sender, &connection, - &mut world_state, + &mut global_state, &mut loop_state, event, )?; } } - world_state.analysis_host.request_cancellation(); + global_state.analysis_host.request_cancellation(); log::info!("waiting for tasks to finish..."); task_receiver.into_iter().for_each(|task| { - on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut world_state) + on_task(task, &connection.sender, &mut loop_state.pending_requests, &mut global_state) }); libdata_receiver.into_iter().for_each(drop); log::info!("...tasks have finished"); @@ -229,7 +221,7 @@ pub fn main_loop(ws_roots: Vec, config: Config, connection: Connection) drop(pool); log::info!("...threadpool has finished"); - let vfs = Arc::try_unwrap(world_state.vfs).expect("all snapshots should be dead"); + let vfs = Arc::try_unwrap(global_state.vfs).expect("all snapshots should be dead"); drop(vfs); Ok(()) @@ -320,7 +312,7 @@ fn loop_turn( task_sender: &Sender, libdata_sender: &Sender, connection: &Connection, - world_state: &mut WorldState, + global_state: &mut GlobalState, loop_state: &mut LoopState, event: Event, ) -> Result<()> { @@ -336,22 +328,22 @@ fn loop_turn( match event { Event::Task(task) => { - on_task(task, &connection.sender, &mut loop_state.pending_requests, world_state); - world_state.maybe_collect_garbage(); + on_task(task, &connection.sender, &mut loop_state.pending_requests, global_state); + global_state.maybe_collect_garbage(); } Event::Vfs(task) => { - world_state.vfs.write().handle_task(task); + global_state.vfs.write().handle_task(task); } Event::Lib(lib) => { - world_state.add_lib(lib); - world_state.maybe_collect_garbage(); + global_state.add_lib(lib); + global_state.maybe_collect_garbage(); loop_state.in_flight_libraries -= 1; loop_state.roots_scanned += 1; } - Event::CheckWatcher(task) => on_check_task(task, world_state, task_sender)?, + Event::CheckWatcher(task) => on_check_task(task, global_state, task_sender)?, Event::Msg(msg) => match msg { Message::Request(req) => on_request( - world_state, + global_state, &mut loop_state.pending_requests, pool, task_sender, @@ -360,7 +352,7 @@ fn loop_turn( req, )?, Message::Notification(not) => { - on_notification(&connection.sender, world_state, loop_state, not)?; + on_notification(&connection.sender, global_state, loop_state, not)?; } Message::Response(resp) => { let removed = loop_state.pending_responses.remove(&resp.id); @@ -379,9 +371,9 @@ fn loop_turn( } (None, Some(configs)) => { if let Some(new_config) = configs.get(0) { - let mut config = world_state.config.clone(); + let mut config = global_state.config.clone(); config.update(&new_config); - world_state.update_configuration(config); + global_state.update_configuration(config); } } (None, None) => { @@ -394,7 +386,7 @@ fn loop_turn( }; let mut state_changed = false; - if let Some(changes) = world_state.process_changes(&mut loop_state.roots_scanned) { + if let Some(changes) = global_state.process_changes(&mut loop_state.roots_scanned) { state_changed = true; loop_state.pending_libraries.extend(changes); } @@ -416,7 +408,7 @@ fn loop_turn( } let show_progress = - !loop_state.workspace_loaded && world_state.config.client_caps.work_done_progress; + !loop_state.workspace_loaded && global_state.config.client_caps.work_done_progress; if !loop_state.workspace_loaded && loop_state.roots_scanned == loop_state.roots_total @@ -425,7 +417,7 @@ fn loop_turn( { state_changed = true; loop_state.workspace_loaded = true; - if let Some(flycheck) = &world_state.flycheck { + if let Some(flycheck) = &global_state.flycheck { flycheck.update(); } } @@ -437,13 +429,13 @@ fn loop_turn( if state_changed && loop_state.workspace_loaded { update_file_notifications_on_threadpool( pool, - world_state.snapshot(), + global_state.snapshot(), task_sender.clone(), loop_state.subscriptions.subscriptions(), ); pool.execute({ let subs = loop_state.subscriptions.subscriptions(); - let snap = world_state.snapshot(); + let snap = global_state.snapshot(); move || snap.analysis().prime_caches(subs).unwrap_or_else(|_: Canceled| ()) }); } @@ -467,7 +459,7 @@ fn on_task( task: Task, msg_sender: &Sender, pending_requests: &mut PendingRequests, - state: &mut WorldState, + state: &mut GlobalState, ) { match task { Task::Respond(response) => { @@ -485,7 +477,7 @@ fn on_task( } fn on_request( - world: &mut WorldState, + global_state: &mut GlobalState, pending_requests: &mut PendingRequests, pool: &ThreadPool, task_sender: &Sender, @@ -496,7 +488,7 @@ fn on_request( let mut pool_dispatcher = PoolDispatcher { req: Some(req), pool, - world, + global_state, task_sender, msg_sender, pending_requests, @@ -517,6 +509,8 @@ fn on_request( .on::(handlers::handle_runnables)? .on::(handlers::handle_inlay_hints)? .on::(handlers::handle_code_action)? + .on::(handlers::handle_resolve_code_action)? + .on::(handlers::handle_hover)? .on::(handlers::handle_on_type_formatting)? .on::(handlers::handle_document_symbol)? .on::(handlers::handle_workspace_symbol)? @@ -528,7 +522,6 @@ fn on_request( .on::(handlers::handle_code_lens_resolve)? .on::(handlers::handle_folding_range)? .on::(handlers::handle_signature_help)? - .on::(handlers::handle_hover)? .on::(handlers::handle_prepare_rename)? .on::(handlers::handle_rename)? .on::(handlers::handle_references)? @@ -552,7 +545,7 @@ fn on_request( fn on_notification( msg_sender: &Sender, - state: &mut WorldState, + state: &mut GlobalState, loop_state: &mut LoopState, not: Notification, ) -> Result<()> { @@ -726,7 +719,7 @@ fn apply_document_changes( fn on_check_task( task: CheckTask, - world_state: &mut WorldState, + global_state: &mut GlobalState, task_sender: &Sender, ) -> Result<()> { match task { @@ -745,7 +738,7 @@ fn on_check_task( .uri .to_file_path() .map_err(|()| format!("invalid uri: {}", diag.location.uri))?; - let file_id = match world_state.vfs.read().path2file(&path) { + let file_id = match global_state.vfs.read().path2file(&path) { Some(file) => FileId(file.0), None => { log::error!( @@ -765,7 +758,7 @@ fn on_check_task( } CheckTask::Status(status) => { - if world_state.config.client_caps.work_done_progress { + if global_state.config.client_caps.work_done_progress { let progress = match status { Status::Being => { lsp_types::WorkDoneProgress::Begin(lsp_types::WorkDoneProgressBegin { @@ -804,7 +797,7 @@ fn on_check_task( Ok(()) } -fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: &mut WorldState) { +fn on_diagnostic_task(task: DiagnosticTask, msg_sender: &Sender, state: &mut GlobalState) { let subscriptions = state.diagnostics.handle_task(task); for file_id in subscriptions { @@ -879,7 +872,7 @@ fn send_startup_progress(sender: &Sender, loop_state: &mut LoopState) { struct PoolDispatcher<'a> { req: Option, pool: &'a ThreadPool, - world: &'a mut WorldState, + global_state: &'a mut GlobalState, pending_requests: &'a mut PendingRequests, msg_sender: &'a Sender, task_sender: &'a Sender, @@ -890,7 +883,7 @@ impl<'a> PoolDispatcher<'a> { /// Dispatches the request onto the current thread fn on_sync( &mut self, - f: fn(&mut WorldState, R::Params) -> Result, + f: fn(&mut GlobalState, R::Params) -> Result, ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, @@ -903,18 +896,21 @@ impl<'a> PoolDispatcher<'a> { return Ok(self); } }; - let world = panic::AssertUnwindSafe(&mut *self.world); + let world = panic::AssertUnwindSafe(&mut *self.global_state); let task = panic::catch_unwind(move || { let result = f(world.0, params); result_to_task::(id, result) }) .map_err(|_| format!("sync task {:?} panicked", R::METHOD))?; - on_task(task, self.msg_sender, self.pending_requests, self.world); + on_task(task, self.msg_sender, self.pending_requests, self.global_state); Ok(self) } /// Dispatches the request onto thread pool - fn on(&mut self, f: fn(WorldSnapshot, R::Params) -> Result) -> Result<&mut Self> + fn on( + &mut self, + f: fn(GlobalStateSnapshot, R::Params) -> Result, + ) -> Result<&mut Self> where R: lsp_types::request::Request + 'static, R::Params: DeserializeOwned + Send + 'static, @@ -928,7 +924,7 @@ impl<'a> PoolDispatcher<'a> { }; self.pool.execute({ - let world = self.world.snapshot(); + let world = self.global_state.snapshot(); let sender = self.task_sender.clone(); move || { let result = f(world, params); @@ -1012,7 +1008,7 @@ where fn update_file_notifications_on_threadpool( pool: &ThreadPool, - world: WorldSnapshot, + world: GlobalStateSnapshot, task_sender: Sender, subscriptions: Vec, ) { diff --git a/crates/rust-analyzer/src/main_loop/handlers.rs b/crates/rust-analyzer/src/main_loop/handlers.rs index 1f910ff82b93..a41adf8b0b51 100644 --- a/crates/rust-analyzer/src/main_loop/handlers.rs +++ b/crates/rust-analyzer/src/main_loop/handlers.rs @@ -12,40 +12,37 @@ use lsp_types::{ CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, CodeLens, Command, CompletionItem, Diagnostic, DocumentFormattingParams, DocumentHighlight, - DocumentSymbol, FoldingRange, FoldingRangeParams, Hover, HoverContents, Location, - MarkupContent, MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, - SemanticTokensParams, SemanticTokensRangeParams, SemanticTokensRangeResult, - SemanticTokensResult, SymbolInformation, TextDocumentIdentifier, Url, WorkspaceEdit, + DocumentSymbol, FoldingRange, FoldingRangeParams, HoverContents, Location, MarkupContent, + MarkupKind, Position, PrepareRenameResponse, Range, RenameParams, SemanticTokensParams, + SemanticTokensRangeParams, SemanticTokensRangeResult, SemanticTokensResult, SymbolInformation, + TextDocumentIdentifier, Url, WorkspaceEdit, }; -use ra_cfg::CfgExpr; use ra_ide::{ - FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, SearchScope, - TextEdit, + FileId, FilePosition, FileRange, HoverAction, Query, RangeInfo, Runnable, RunnableKind, + SearchScope, TextEdit, }; use ra_prof::profile; use ra_project_model::TargetKind; -use ra_syntax::{AstNode, SmolStr, SyntaxKind, TextRange, TextSize}; -use rustc_hash::FxHashMap; +use ra_syntax::{AstNode, SyntaxKind, TextRange, TextSize}; use serde::{Deserialize, Serialize}; use serde_json::to_value; -use stdx::format_to; +use stdx::{format_to, split1}; use crate::{ cargo_target_spec::CargoTargetSpec, config::RustfmtConfig, diagnostics::DiagnosticTask, from_json, from_proto, + global_state::GlobalStateSnapshot, lsp_ext::{self, InlayHint, InlayHintsParams}, - to_proto, - world::WorldSnapshot, - LspError, Result, + to_proto, LspError, Result, }; -pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { +pub fn handle_analyzer_status(snap: GlobalStateSnapshot, _: ()) -> Result { let _p = profile("handle_analyzer_status"); - let mut buf = world.status(); + let mut buf = snap.status(); format_to!(buf, "\n\nrequests:\n"); - let requests = world.latest_requests.read(); + let requests = snap.latest_requests.read(); for (is_last, r) in requests.iter() { let mark = if is_last { "*" } else { " " }; format_to!(buf, "{}{:4} {:<36}{}ms\n", mark, r.id, r.method, r.duration.as_millis()); @@ -54,37 +51,37 @@ pub fn handle_analyzer_status(world: WorldSnapshot, _: ()) -> Result { } pub fn handle_syntax_tree( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::SyntaxTreeParams, ) -> Result { let _p = profile("handle_syntax_tree"); - let id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(id)?; + let id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(id)?; let text_range = params.range.map(|r| from_proto::text_range(&line_index, r)); - let res = world.analysis().syntax_tree(id, text_range)?; + let res = snap.analysis().syntax_tree(id, text_range)?; Ok(res) } pub fn handle_expand_macro( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::ExpandMacroParams, ) -> Result> { let _p = profile("handle_expand_macro"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let offset = from_proto::offset(&line_index, params.position); - let res = world.analysis().expand_macro(FilePosition { file_id, offset })?; + let res = snap.analysis().expand_macro(FilePosition { file_id, offset })?; Ok(res.map(|it| lsp_ext::ExpandedMacro { name: it.name, expansion: it.expansion })) } pub fn handle_selection_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::SelectionRangeParams, ) -> Result>> { let _p = profile("handle_selection_range"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let res: Result> = params .positions .into_iter() @@ -96,7 +93,7 @@ pub fn handle_selection_range( loop { ranges.push(range); let frange = FileRange { file_id, range }; - let next = world.analysis().extend_selection(frange)?; + let next = snap.analysis().extend_selection(frange)?; if next == range { break; } else { @@ -122,18 +119,18 @@ pub fn handle_selection_range( } pub fn handle_matching_brace( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::MatchingBraceParams, ) -> Result> { let _p = profile("handle_matching_brace"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let res = params .positions .into_iter() .map(|position| { let offset = from_proto::offset(&line_index, position); - let offset = match world.analysis().matching_brace(FilePosition { file_id, offset }) { + let offset = match snap.analysis().matching_brace(FilePosition { file_id, offset }) { Ok(Some(matching_brace_offset)) => matching_brace_offset, Err(_) | Ok(None) => offset, }; @@ -144,17 +141,17 @@ pub fn handle_matching_brace( } pub fn handle_join_lines( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::JoinLinesParams, ) -> Result> { let _p = profile("handle_join_lines"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let line_endings = world.file_line_endings(file_id); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let line_endings = snap.file_line_endings(file_id); let mut res = TextEdit::default(); for range in params.ranges { let range = from_proto::text_range(&line_index, range); - let edit = world.analysis().join_lines(FileRange { file_id, range })?; + let edit = snap.analysis().join_lines(FileRange { file_id, range })?; match res.union(edit) { Ok(()) => (), Err(_edit) => { @@ -167,37 +164,37 @@ pub fn handle_join_lines( } pub fn handle_on_enter( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result>> { let _p = profile("handle_on_enter"); - let position = from_proto::file_position(&world, params)?; - let edit = match world.analysis().on_enter(position)? { + let position = from_proto::file_position(&snap, params)?; + let edit = match snap.analysis().on_enter(position)? { None => return Ok(None), Some(it) => it, }; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); let edit = to_proto::snippet_text_edit_vec(&line_index, line_endings, true, edit); Ok(Some(edit)) } // Don't forget to add new trigger characters to `ServerCapabilities` in `caps.rs`. pub fn handle_on_type_formatting( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentOnTypeFormattingParams, ) -> Result>> { let _p = profile("handle_on_type_formatting"); - let mut position = from_proto::file_position(&world, params.text_document_position)?; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let mut position = from_proto::file_position(&snap, params.text_document_position)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); // in `ra_ide`, the `on_type` invariant is that // `text.char_at(position) == typed_char`. position.offset -= TextSize::of('.'); let char_typed = params.ch.chars().next().unwrap_or('\0'); assert!({ - let text = world.analysis().file_text(position.file_id)?; + let text = snap.analysis().file_text(position.file_id)?; text[usize::from(position.offset)..].starts_with(char_typed) }); @@ -209,7 +206,7 @@ pub fn handle_on_type_formatting( return Ok(None); } - let edit = world.analysis().on_char_typed(position, char_typed)?; + let edit = snap.analysis().on_char_typed(position, char_typed)?; let mut edit = match edit { Some(it) => it, None => return Ok(None), @@ -223,16 +220,16 @@ pub fn handle_on_type_formatting( } pub fn handle_document_symbol( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentSymbolParams, ) -> Result> { let _p = profile("handle_document_symbol"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let mut parents: Vec<(DocumentSymbol, Option)> = Vec::new(); - for symbol in world.analysis().file_structure(file_id)? { + for symbol in snap.analysis().file_structure(file_id)? { let doc_symbol = DocumentSymbol { name: symbol.label, detail: symbol.detail, @@ -258,10 +255,10 @@ pub fn handle_document_symbol( } } - let res = if world.config.client_caps.hierarchical_symbols { + let res = if snap.config.client_caps.hierarchical_symbols { document_symbols.into() } else { - let url = to_proto::url(&world, file_id)?; + let url = to_proto::url(&snap, file_id)?; let mut symbol_information = Vec::::new(); for symbol in document_symbols { flatten_document_symbol(&symbol, None, &url, &mut symbol_information); @@ -291,7 +288,7 @@ pub fn handle_document_symbol( } pub fn handle_workspace_symbol( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::WorkspaceSymbolParams, ) -> Result>> { let _p = profile("handle_workspace_symbol"); @@ -309,22 +306,22 @@ pub fn handle_workspace_symbol( q.limit(128); q }; - let mut res = exec_query(&world, query)?; + let mut res = exec_query(&snap, query)?; if res.is_empty() && !all_symbols { let mut query = Query::new(params.query); query.limit(128); - res = exec_query(&world, query)?; + res = exec_query(&snap, query)?; } return Ok(Some(res)); - fn exec_query(world: &WorldSnapshot, query: Query) -> Result> { + fn exec_query(snap: &GlobalStateSnapshot, query: Query) -> Result> { let mut res = Vec::new(); - for nav in world.analysis().symbol_search(query)? { + for nav in snap.analysis().symbol_search(query)? { let info = SymbolInformation { name: nav.name().to_string(), kind: to_proto::symbol_kind(nav.kind()), - location: to_proto::location(world, nav.file_range())?, + location: to_proto::location(snap, nav.file_range())?, container_name: nav.container_name().map(|v| v.to_string()), deprecated: None, }; @@ -335,88 +332,83 @@ pub fn handle_workspace_symbol( } pub fn handle_goto_definition( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::GotoDefinitionParams, ) -> Result> { let _p = profile("handle_goto_definition"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_definition(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_definition(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_goto_implementation( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::request::GotoImplementationParams, ) -> Result> { let _p = profile("handle_goto_implementation"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_implementation(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_implementation(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_goto_type_definition( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::request::GotoTypeDefinitionParams, ) -> Result> { let _p = profile("handle_goto_type_definition"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let nav_info = match world.analysis().goto_type_definition(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let nav_info = match snap.analysis().goto_type_definition(position)? { None => return Ok(None), Some(it) => it, }; let src = FileRange { file_id: position.file_id, range: nav_info.range }; - let res = to_proto::goto_definition_response(&world, Some(src), nav_info.info)?; + let res = to_proto::goto_definition_response(&snap, Some(src), nav_info.info)?; Ok(Some(res)) } pub fn handle_parent_module( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result> { let _p = profile("handle_parent_module"); - let position = from_proto::file_position(&world, params)?; - let navs = world.analysis().parent_module(position)?; - let res = to_proto::goto_definition_response(&world, None, navs)?; + let position = from_proto::file_position(&snap, params)?; + let navs = snap.analysis().parent_module(position)?; + let res = to_proto::goto_definition_response(&snap, None, navs)?; Ok(Some(res)) } pub fn handle_runnables( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::RunnablesParams, ) -> Result> { let _p = profile("handle_runnables"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let offset = params.position.map(|it| from_proto::offset(&line_index, it)); let mut res = Vec::new(); - let workspace_root = world.workspace_root_for(file_id); - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; - for runnable in world.analysis().runnables(file_id)? { + let workspace_root = snap.workspace_root_for(file_id); + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; + for runnable in snap.analysis().runnables(file_id)? { if let Some(offset) = offset { - if !runnable.range.contains_inclusive(offset) { + if !runnable.nav.full_range().contains_inclusive(offset) { continue; } } - // Do not suggest binary run on other target than binary - if let RunnableKind::Bin = runnable.kind { - if let Some(spec) = &cargo_spec { - match spec.target_kind { - TargetKind::Bin => {} - _ => continue, - } - } + if should_skip_target(&runnable, cargo_spec.as_ref()) { + continue; } - res.push(to_lsp_runnable(&world, file_id, runnable)?); + + res.push(to_proto::runnable(&snap, file_id, runnable)?); } // Add `cargo check` and `cargo test` for the whole package @@ -424,25 +416,31 @@ pub fn handle_runnables( Some(spec) => { for &cmd in ["check", "test"].iter() { res.push(lsp_ext::Runnable { - range: Default::default(), label: format!("cargo {} -p {}", cmd, spec.package), - bin: "cargo".to_string(), - args: vec![cmd.to_string(), "--package".to_string(), spec.package.clone()], - extra_args: Vec::new(), - env: FxHashMap::default(), - cwd: workspace_root.map(|root| root.to_owned()), + location: None, + kind: lsp_ext::RunnableKind::Cargo, + args: lsp_ext::CargoRunnable { + workspace_root: workspace_root.map(|root| root.to_owned()), + cargo_args: vec![ + cmd.to_string(), + "--package".to_string(), + spec.package.clone(), + ], + executable_args: Vec::new(), + }, }) } } None => { res.push(lsp_ext::Runnable { - range: Default::default(), label: "cargo check --workspace".to_string(), - bin: "cargo".to_string(), - args: vec!["check".to_string(), "--workspace".to_string()], - extra_args: Vec::new(), - env: FxHashMap::default(), - cwd: workspace_root.map(|root| root.to_owned()), + location: None, + kind: lsp_ext::RunnableKind::Cargo, + args: lsp_ext::CargoRunnable { + workspace_root: workspace_root.map(|root| root.to_owned()), + cargo_args: vec!["check".to_string(), "--workspace".to_string()], + executable_args: Vec::new(), + }, }); } } @@ -450,16 +448,16 @@ pub fn handle_runnables( } pub fn handle_completion( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CompletionParams, ) -> Result> { let _p = profile("handle_completion"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; let completion_triggered_after_single_colon = { let mut res = false; if let Some(ctx) = params.context { if ctx.trigger_character.unwrap_or_default() == ":" { - let source_file = world.analysis().parse(position.file_id)?; + let source_file = snap.analysis().parse(position.file_id)?; let syntax = source_file.syntax(); let text = syntax.text(); if let Some(next_char) = text.char_at(position.offset) { @@ -477,12 +475,12 @@ pub fn handle_completion( return Ok(None); } - let items = match world.analysis().completions(&world.config.completion, position)? { + let items = match snap.analysis().completions(&snap.config.completion, position)? { None => return Ok(None), Some(items) => items, }; - let line_index = world.analysis().file_line_index(position.file_id)?; - let line_endings = world.file_line_endings(position.file_id); + let line_index = snap.analysis().file_line_index(position.file_id)?; + let line_endings = snap.file_line_endings(position.file_id); let items: Vec = items .into_iter() .map(|item| to_proto::completion_item(&line_index, line_endings, item)) @@ -492,15 +490,15 @@ pub fn handle_completion( } pub fn handle_folding_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: FoldingRangeParams, ) -> Result>> { let _p = profile("handle_folding_range"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let folds = world.analysis().folding_ranges(file_id)?; - let text = world.analysis().file_text(file_id)?; - let line_index = world.analysis().file_line_index(file_id)?; - let line_folding_only = world.config.client_caps.line_folding_only; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let folds = snap.analysis().folding_ranges(file_id)?; + let text = snap.analysis().file_text(file_id)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let line_folding_only = snap.config.client_caps.line_folding_only; let res = folds .into_iter() .map(|it| to_proto::folding_range(&*text, &line_index, line_folding_only, it)) @@ -509,16 +507,16 @@ pub fn handle_folding_range( } pub fn handle_signature_help( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::SignatureHelpParams, ) -> Result> { let _p = profile("handle_signature_help"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let call_info = match world.analysis().call_info(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let call_info = match snap.analysis().call_info(position)? { None => return Ok(None), Some(it) => it, }; - let concise = !world.config.call_info_full; + let concise = !snap.config.call_info_full; let mut active_parameter = call_info.active_parameter.map(|it| it as i64); if concise && call_info.signature.has_self_param { active_parameter = active_parameter.map(|it| it.saturating_sub(1)); @@ -532,46 +530,56 @@ pub fn handle_signature_help( })) } -pub fn handle_hover(world: WorldSnapshot, params: lsp_types::HoverParams) -> Result> { +pub fn handle_hover( + snap: GlobalStateSnapshot, + params: lsp_types::HoverParams, +) -> Result> { let _p = profile("handle_hover"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let info = match world.analysis().hover(position)? { + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let info = match snap.analysis().hover(position)? { None => return Ok(None), Some(info) => info, }; - let line_index = world.analysis.file_line_index(position.file_id)?; + let line_index = snap.analysis.file_line_index(position.file_id)?; let range = to_proto::range(&line_index, info.range); - let res = Hover { - contents: HoverContents::Markup(MarkupContent { - kind: MarkupKind::Markdown, - value: crate::markdown::format_docs(&info.info.to_markup()), - }), - range: Some(range), + let hover = lsp_ext::Hover { + hover: lsp_types::Hover { + contents: HoverContents::Markup(MarkupContent { + kind: MarkupKind::Markdown, + value: crate::markdown::format_docs(&info.info.to_markup()), + }), + range: Some(range), + }, + actions: prepare_hover_actions(&snap, position.file_id, info.info.actions()), }; - Ok(Some(res)) + + Ok(Some(hover)) } pub fn handle_prepare_rename( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::TextDocumentPositionParams, ) -> Result> { let _p = profile("handle_prepare_rename"); - let position = from_proto::file_position(&world, params)?; + let position = from_proto::file_position(&snap, params)?; - let optional_change = world.analysis().rename(position, "dummy")?; + let optional_change = snap.analysis().rename(position, "dummy")?; let range = match optional_change { None => return Ok(None), Some(it) => it.range, }; - let line_index = world.analysis().file_line_index(position.file_id)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; let range = to_proto::range(&line_index, range); Ok(Some(PrepareRenameResponse::Range(range))) } -pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result> { +pub fn handle_rename( + snap: GlobalStateSnapshot, + params: RenameParams, +) -> Result> { let _p = profile("handle_rename"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; if params.new_name.is_empty() { return Err(LspError::new( @@ -581,36 +589,36 @@ pub fn handle_rename(world: WorldSnapshot, params: RenameParams) -> Result return Ok(None), Some(it) => it.info, }; - let workspace_edit = to_proto::workspace_edit(&world, source_change)?; + let workspace_edit = to_proto::workspace_edit(&snap, source_change)?; Ok(Some(workspace_edit)) } pub fn handle_references( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::ReferenceParams, ) -> Result>> { let _p = profile("handle_references"); - let position = from_proto::file_position(&world, params.text_document_position)?; + let position = from_proto::file_position(&snap, params.text_document_position)?; - let refs = match world.analysis().find_all_refs(position, None)? { + let refs = match snap.analysis().find_all_refs(position, None)? { None => return Ok(None), Some(refs) => refs, }; let locations = if params.context.include_declaration { refs.into_iter() - .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) + .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .collect() } else { // Only iterate over the references if include_declaration was false refs.references() .iter() - .filter_map(|reference| to_proto::location(&world, reference.file_range).ok()) + .filter_map(|reference| to_proto::location(&snap, reference.file_range).ok()) .collect() }; @@ -618,24 +626,24 @@ pub fn handle_references( } pub fn handle_formatting( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: DocumentFormattingParams, ) -> Result>> { let _p = profile("handle_formatting"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let file = world.analysis().file_text(file_id)?; - let crate_ids = world.analysis().crate_for(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let file = snap.analysis().file_text(file_id)?; + let crate_ids = snap.analysis().crate_for(file_id)?; - let file_line_index = world.analysis().file_line_index(file_id)?; + let file_line_index = snap.analysis().file_line_index(file_id)?; let end_position = to_proto::position(&file_line_index, TextSize::of(file.as_str())); - let mut rustfmt = match &world.config.rustfmt { + let mut rustfmt = match &snap.config.rustfmt { RustfmtConfig::Rustfmt { extra_args } => { let mut cmd = process::Command::new("rustfmt"); cmd.args(extra_args); if let Some(&crate_id) = crate_ids.first() { // Assume all crates are in the same edition - let edition = world.analysis().crate_edition(crate_id)?; + let edition = snap.analysis().crate_edition(crate_id)?; cmd.arg("--edition"); cmd.arg(edition.to_string()); } @@ -693,136 +701,145 @@ pub fn handle_formatting( }])) } -pub fn handle_code_action( - world: WorldSnapshot, - params: lsp_types::CodeActionParams, -) -> Result>> { - let _p = profile("handle_code_action"); - // We intentionally don't support command-based actions, as those either - // requires custom client-code anyway, or requires server-initiated edits. - // Server initiated edits break causality, so we avoid those as well. - if !world.config.client_caps.code_action_literals { - return Ok(None); - } - - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; +fn handle_fixes( + snap: &GlobalStateSnapshot, + params: &lsp_types::CodeActionParams, + res: &mut Vec, +) -> Result<()> { + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; let range = from_proto::text_range(&line_index, params.range); - let frange = FileRange { file_id, range }; - - let diagnostics = world.analysis().diagnostics(file_id)?; - let mut res: Vec = Vec::new(); + let diagnostics = snap.analysis().diagnostics(file_id)?; let fixes_from_diagnostics = diagnostics .into_iter() .filter_map(|d| Some((d.range, d.fix?))) .filter(|(diag_range, _fix)| diag_range.intersect(range).is_some()) .map(|(_range, fix)| fix); - for fix in fixes_from_diagnostics { let title = fix.label; - let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?; - let action = - lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None }; + let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; + let action = lsp_ext::CodeAction { + title, + id: None, + group: None, + kind: Some(lsp_types::code_action_kind::QUICKFIX.into()), + edit: Some(edit), + command: None, + }; res.push(action); } - for fix in world.check_fixes.get(&file_id).into_iter().flatten() { + for fix in snap.check_fixes.get(&file_id).into_iter().flatten() { let fix_range = from_proto::text_range(&line_index, fix.range); if fix_range.intersect(range).is_none() { continue; } res.push(fix.action.clone()); } + Ok(()) +} - for assist in world.analysis().assists(&world.config.assist, frange)?.into_iter() { - res.push(to_proto::code_action(&world, assist)?.into()); +pub fn handle_code_action( + snap: GlobalStateSnapshot, + params: lsp_types::CodeActionParams, +) -> Result>> { + let _p = profile("handle_code_action"); + // We intentionally don't support command-based actions, as those either + // requires custom client-code anyway, or requires server-initiated edits. + // Server initiated edits break causality, so we avoid those as well. + if !snap.config.client_caps.code_action_literals { + return Ok(None); } + + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let range = from_proto::text_range(&line_index, params.range); + let frange = FileRange { file_id, range }; + let mut res: Vec = Vec::new(); + + handle_fixes(&snap, ¶ms, &mut res)?; + + if snap.config.client_caps.resolve_code_action { + for (index, assist) in + snap.analysis().unresolved_assists(&snap.config.assist, frange)?.into_iter().enumerate() + { + res.push(to_proto::unresolved_code_action(&snap, assist, index)?); + } + } else { + for assist in snap.analysis().resolved_assists(&snap.config.assist, frange)?.into_iter() { + res.push(to_proto::resolved_code_action(&snap, assist)?); + } + } + Ok(Some(res)) } +pub fn handle_resolve_code_action( + snap: GlobalStateSnapshot, + params: lsp_ext::ResolveCodeActionParams, +) -> Result> { + let _p = profile("handle_resolve_code_action"); + let file_id = from_proto::file_id(&snap, ¶ms.code_action_params.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let range = from_proto::text_range(&line_index, params.code_action_params.range); + let frange = FileRange { file_id, range }; + + let assists = snap.analysis().resolved_assists(&snap.config.assist, frange)?; + let (id_string, index) = split1(¶ms.id, ':').unwrap(); + let index = index.parse::().unwrap(); + let assist = &assists[index]; + assert!(assist.assist.id.0 == id_string); + Ok(to_proto::resolved_code_action(&snap, assist.clone())?.edit) +} + pub fn handle_code_lens( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::CodeLensParams, ) -> Result>> { let _p = profile("handle_code_lens"); let mut lenses: Vec = Default::default(); - if world.config.lens.none() { + if snap.config.lens.none() { // early return before any db query! return Ok(Some(lenses)); } - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let line_index = world.analysis().file_line_index(file_id)?; - let cargo_spec = CargoTargetSpec::for_file(&world, file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id)?; - if world.config.lens.runnable() { + if snap.config.lens.runnable() { // Gather runnables - for runnable in world.analysis().runnables(file_id)? { - let (run_title, debugee) = match &runnable.kind { - RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => { - ("▶\u{fe0e} Run Test", true) - } - RunnableKind::DocTest { .. } => { - // cargo does not support -no-run for doctests - ("▶\u{fe0e} Run Doctest", false) - } - RunnableKind::Bench { .. } => { - // Nothing wrong with bench debugging - ("Run Bench", true) - } - RunnableKind::Bin => { - // Do not suggest binary run on other target than binary - match &cargo_spec { - Some(spec) => match spec.target_kind { - TargetKind::Bin => ("Run", true), - _ => continue, - }, - None => continue, - } - } - }; + for runnable in snap.analysis().runnables(file_id)? { + if should_skip_target(&runnable, cargo_spec.as_ref()) { + continue; + } - let mut r = to_lsp_runnable(&world, file_id, runnable)?; - if world.config.lens.run { + let action = runnable.action(); + let range = to_proto::range(&line_index, runnable.nav.range()); + let r = to_proto::runnable(&snap, file_id, runnable)?; + if snap.config.lens.run { let lens = CodeLens { - range: r.range, - command: Some(Command { - title: run_title.to_string(), - command: "rust-analyzer.runSingle".into(), - arguments: Some(vec![to_value(&r).unwrap()]), - }), + range, + command: Some(run_single_command(&r, action.run_title)), data: None, }; lenses.push(lens); } - if debugee && world.config.lens.debug { - if r.args[0] == "run" { - r.args[0] = "build".into(); - } else { - r.args.push("--no-run".into()); - } - let debug_lens = CodeLens { - range: r.range, - command: Some(Command { - title: "Debug".into(), - command: "rust-analyzer.debugSingle".into(), - arguments: Some(vec![to_value(r).unwrap()]), - }), - data: None, - }; + if action.debugee && snap.config.lens.debug { + let debug_lens = + CodeLens { range, command: Some(debug_single_command(&r)), data: None }; lenses.push(debug_lens); } } } - if world.config.lens.impementations { + if snap.config.lens.impementations { // Handle impls lenses.extend( - world - .analysis() + snap.analysis() .file_structure(file_id)? .into_iter() .filter(|it| match it.kind { @@ -857,14 +874,17 @@ enum CodeLensResolveData { Impls(lsp_types::request::GotoImplementationParams), } -pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result { +pub fn handle_code_lens_resolve( + snap: GlobalStateSnapshot, + code_lens: CodeLens, +) -> Result { let _p = profile("handle_code_lens_resolve"); let data = code_lens.data.unwrap(); let resolve = from_json::>("CodeLensResolveData", data)?; match resolve { Some(CodeLensResolveData::Impls(lens_params)) => { let locations: Vec = - match handle_goto_implementation(world, lens_params.clone())? { + match handle_goto_implementation(snap, lens_params.clone())? { Some(lsp_types::GotoDefinitionResponse::Scalar(loc)) => vec![loc], Some(lsp_types::GotoDefinitionResponse::Array(locs)) => locs, Some(lsp_types::GotoDefinitionResponse::Link(links)) => links @@ -874,24 +894,13 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re _ => vec![], }; - let title = if locations.len() == 1 { - "1 implementation".into() - } else { - format!("{} implementations", locations.len()) - }; - - // We cannot use the 'editor.action.showReferences' command directly - // because that command requires vscode types which we convert in the handler - // on the client side. - let cmd = Command { + let title = implementation_title(locations.len()); + let cmd = show_references_command( title, - command: "rust-analyzer.showReferences".into(), - arguments: Some(vec![ - to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(), - to_value(code_lens.range.start).unwrap(), - to_value(locations).unwrap(), - ]), - }; + &lens_params.text_document_position_params.text_document.uri, + code_lens.range.start, + locations, + ); Ok(CodeLens { range: code_lens.range, command: Some(cmd), data: None }) } None => Ok(CodeLens { @@ -903,14 +912,14 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re } pub fn handle_document_highlight( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_types::DocumentHighlightParams, ) -> Result>> { let _p = profile("handle_document_highlight"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; - let line_index = world.analysis().file_line_index(position.file_id)?; + let position = from_proto::file_position(&snap, params.text_document_position_params)?; + let line_index = snap.analysis().file_line_index(position.file_id)?; - let refs = match world + let refs = match snap .analysis() .find_all_refs(position, Some(SearchScope::single_file(position.file_id)))? { @@ -930,19 +939,19 @@ pub fn handle_document_highlight( } pub fn handle_ssr( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: lsp_ext::SsrParams, ) -> Result { let _p = profile("handle_ssr"); let source_change = - world.analysis().structural_search_replace(¶ms.query, params.parse_only)??; - to_proto::workspace_edit(&world, source_change) + snap.analysis().structural_search_replace(¶ms.query, params.parse_only)??; + to_proto::workspace_edit(&snap, source_change) } -pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result { +pub fn publish_diagnostics(snap: &GlobalStateSnapshot, file_id: FileId) -> Result { let _p = profile("publish_diagnostics"); - let line_index = world.analysis().file_line_index(file_id)?; - let diagnostics: Vec = world + let line_index = snap.analysis().file_line_index(file_id)?; + let diagnostics: Vec = snap .analysis() .diagnostics(file_id)? .into_iter() @@ -959,87 +968,29 @@ pub fn publish_diagnostics(world: &WorldSnapshot, file_id: FileId) -> Result Result { - let spec = CargoTargetSpec::for_file(world, file_id)?; - let target = spec.as_ref().map(|s| s.target.clone()); - let mut features_needed = vec![]; - for cfg_expr in &runnable.cfg_exprs { - collect_minimal_features_needed(cfg_expr, &mut features_needed); - } - let (args, extra_args) = - CargoTargetSpec::runnable_args(spec, &runnable.kind, &features_needed)?; - let line_index = world.analysis().file_line_index(file_id)?; - let label = match &runnable.kind { - RunnableKind::Test { test_id, .. } => format!("test {}", test_id), - RunnableKind::TestMod { path } => format!("test-mod {}", path), - RunnableKind::Bench { test_id } => format!("bench {}", test_id), - RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id), - RunnableKind::Bin => { - target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t)) - } - }; - Ok(lsp_ext::Runnable { - range: to_proto::range(&line_index, runnable.range), - label, - bin: "cargo".to_string(), - args, - extra_args, - env: { - let mut m = FxHashMap::default(); - m.insert("RUST_BACKTRACE".to_string(), "short".to_string()); - m - }, - cwd: world.workspace_root_for(file_id).map(|root| root.to_owned()), - }) -} - -/// Fill minimal features needed -fn collect_minimal_features_needed(cfg_expr: &CfgExpr, features: &mut Vec) { - match cfg_expr { - CfgExpr::KeyValue { key, value } if key == "feature" => features.push(value.clone()), - CfgExpr::All(preds) => { - preds.iter().for_each(|cfg| collect_minimal_features_needed(cfg, features)); - } - CfgExpr::Any(preds) => { - for cfg in preds { - let len_features = features.len(); - collect_minimal_features_needed(cfg, features); - if len_features != features.len() { - break; - } - } - } - _ => {} - } -} - pub fn handle_inlay_hints( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: InlayHintsParams, ) -> Result> { let _p = profile("handle_inlay_hints"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let analysis = world.analysis(); + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let analysis = snap.analysis(); let line_index = analysis.file_line_index(file_id)?; Ok(analysis - .inlay_hints(file_id, &world.config.inlay_hints)? + .inlay_hints(file_id, &snap.config.inlay_hints)? .into_iter() .map(|it| to_proto::inlay_int(&line_index, it)) .collect()) } pub fn handle_call_hierarchy_prepare( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyPrepareParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_prepare"); - let position = from_proto::file_position(&world, params.text_document_position_params)?; + let position = from_proto::file_position(&snap, params.text_document_position_params)?; - let nav_info = match world.analysis().call_hierarchy(position)? { + let nav_info = match snap.analysis().call_hierarchy(position)? { None => return Ok(None), Some(it) => it, }; @@ -1048,24 +999,24 @@ pub fn handle_call_hierarchy_prepare( let res = navs .into_iter() .filter(|it| it.kind() == SyntaxKind::FN_DEF) - .map(|it| to_proto::call_hierarchy_item(&world, it)) + .map(|it| to_proto::call_hierarchy_item(&snap, it)) .collect::>>()?; Ok(Some(res)) } pub fn handle_call_hierarchy_incoming( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyIncomingCallsParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_incoming"); let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&world, doc, item.range)?; + let frange = from_proto::file_range(&snap, doc, item.range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match world.analysis().incoming_calls(fpos)? { + let call_items = match snap.analysis().incoming_calls(fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1074,8 +1025,8 @@ pub fn handle_call_hierarchy_incoming( for call_item in call_items.into_iter() { let file_id = call_item.target.file_id(); - let line_index = world.analysis().file_line_index(file_id)?; - let item = to_proto::call_hierarchy_item(&world, call_item.target)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; res.push(CallHierarchyIncomingCall { from: item, from_ranges: call_item @@ -1090,17 +1041,17 @@ pub fn handle_call_hierarchy_incoming( } pub fn handle_call_hierarchy_outgoing( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: CallHierarchyOutgoingCallsParams, ) -> Result>> { let _p = profile("handle_call_hierarchy_outgoing"); let item = params.item; let doc = TextDocumentIdentifier::new(item.uri); - let frange = from_proto::file_range(&world, doc, item.range)?; + let frange = from_proto::file_range(&snap, doc, item.range)?; let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; - let call_items = match world.analysis().outgoing_calls(fpos)? { + let call_items = match snap.analysis().outgoing_calls(fpos)? { None => return Ok(None), Some(it) => it, }; @@ -1109,8 +1060,8 @@ pub fn handle_call_hierarchy_outgoing( for call_item in call_items.into_iter() { let file_id = call_item.target.file_id(); - let line_index = world.analysis().file_line_index(file_id)?; - let item = to_proto::call_hierarchy_item(&world, call_item.target)?; + let line_index = snap.analysis().file_line_index(file_id)?; + let item = to_proto::call_hierarchy_item(&snap, call_item.target)?; res.push(CallHierarchyOutgoingCall { to: item, from_ranges: call_item @@ -1125,82 +1076,165 @@ pub fn handle_call_hierarchy_outgoing( } pub fn handle_semantic_tokens( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: SemanticTokensParams, ) -> Result> { let _p = profile("handle_semantic_tokens"); - let file_id = from_proto::file_id(&world, ¶ms.text_document.uri)?; - let text = world.analysis().file_text(file_id)?; - let line_index = world.analysis().file_line_index(file_id)?; + let file_id = from_proto::file_id(&snap, ¶ms.text_document.uri)?; + let text = snap.analysis().file_text(file_id)?; + let line_index = snap.analysis().file_line_index(file_id)?; - let highlights = world.analysis().highlight(file_id)?; + let highlights = snap.analysis().highlight(file_id)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } pub fn handle_semantic_tokens_range( - world: WorldSnapshot, + snap: GlobalStateSnapshot, params: SemanticTokensRangeParams, ) -> Result> { let _p = profile("handle_semantic_tokens_range"); - let frange = from_proto::file_range(&world, params.text_document, params.range)?; - let text = world.analysis().file_text(frange.file_id)?; - let line_index = world.analysis().file_line_index(frange.file_id)?; + let frange = from_proto::file_range(&snap, params.text_document, params.range)?; + let text = snap.analysis().file_text(frange.file_id)?; + let line_index = snap.analysis().file_line_index(frange.file_id)?; - let highlights = world.analysis().highlight_range(frange)?; + let highlights = snap.analysis().highlight_range(frange)?; let semantic_tokens = to_proto::semantic_tokens(&text, &line_index, highlights); Ok(Some(semantic_tokens.into())) } -#[cfg(test)] -mod tests { - use super::*; - - use mbe::{ast_to_token_tree, TokenMap}; - use ra_cfg::parse_cfg; - use ra_syntax::{ - ast::{self, AstNode}, - SmolStr, - }; - - fn get_token_tree_generated(input: &str) -> (tt::Subtree, TokenMap) { - let source_file = ast::SourceFile::parse(input).ok().unwrap(); - let tt = source_file.syntax().descendants().find_map(ast::TokenTree::cast).unwrap(); - ast_to_token_tree(&tt).unwrap() - } - - #[test] - fn test_cfg_expr_minimal_features_needed() { - let (subtree, _) = get_token_tree_generated(r#"#![cfg(feature = "baz")]"#); - let cfg_expr = parse_cfg(&subtree); - let mut min_features = vec![]; - collect_minimal_features_needed(&cfg_expr, &mut min_features); - - assert_eq!(min_features, vec![SmolStr::new("baz")]); - - let (subtree, _) = - get_token_tree_generated(r#"#![cfg(all(feature = "baz", feature = "foo"))]"#); - let cfg_expr = parse_cfg(&subtree); - - let mut min_features = vec![]; - collect_minimal_features_needed(&cfg_expr, &mut min_features); - assert_eq!(min_features, vec![SmolStr::new("baz"), SmolStr::new("foo")]); - - let (subtree, _) = - get_token_tree_generated(r#"#![cfg(any(feature = "baz", feature = "foo", unix))]"#); - let cfg_expr = parse_cfg(&subtree); - - let mut min_features = vec![]; - collect_minimal_features_needed(&cfg_expr, &mut min_features); - assert_eq!(min_features, vec![SmolStr::new("baz")]); - - let (subtree, _) = get_token_tree_generated(r#"#![cfg(foo)]"#); - let cfg_expr = parse_cfg(&subtree); - - let mut min_features = vec![]; - collect_minimal_features_needed(&cfg_expr, &mut min_features); - assert!(min_features.is_empty()); +fn implementation_title(count: usize) -> String { + if count == 1 { + "1 implementation".into() + } else { + format!("{} implementations", count) + } +} + +fn show_references_command( + title: String, + uri: &lsp_types::Url, + position: lsp_types::Position, + locations: Vec, +) -> Command { + // We cannot use the 'editor.action.showReferences' command directly + // because that command requires vscode types which we convert in the handler + // on the client side. + + Command { + title, + command: "rust-analyzer.showReferences".into(), + arguments: Some(vec![ + to_value(uri).unwrap(), + to_value(position).unwrap(), + to_value(locations).unwrap(), + ]), + } +} + +fn run_single_command(runnable: &lsp_ext::Runnable, title: &str) -> Command { + Command { + title: title.to_string(), + command: "rust-analyzer.runSingle".into(), + arguments: Some(vec![to_value(runnable).unwrap()]), + } +} + +fn debug_single_command(runnable: &lsp_ext::Runnable) -> Command { + Command { + title: "Debug".into(), + command: "rust-analyzer.debugSingle".into(), + arguments: Some(vec![to_value(runnable).unwrap()]), + } +} + +fn to_command_link(command: Command, tooltip: String) -> lsp_ext::CommandLink { + lsp_ext::CommandLink { tooltip: Some(tooltip), command } +} + +fn show_impl_command_link( + snap: &GlobalStateSnapshot, + position: &FilePosition, +) -> Option { + if snap.config.hover.implementations { + if let Some(nav_data) = snap.analysis().goto_implementation(*position).unwrap_or(None) { + let uri = to_proto::url(snap, position.file_id).ok()?; + let line_index = snap.analysis().file_line_index(position.file_id).ok()?; + let position = to_proto::position(&line_index, position.offset); + let locations: Vec<_> = nav_data + .info + .iter() + .filter_map(|it| to_proto::location(snap, it.file_range()).ok()) + .collect(); + let title = implementation_title(locations.len()); + let command = show_references_command(title, &uri, position, locations); + + return Some(lsp_ext::CommandLinkGroup { + commands: vec![to_command_link(command, "Go to implementations".into())], + ..Default::default() + }); + } + } + None +} + +fn to_runnable_action( + snap: &GlobalStateSnapshot, + file_id: FileId, + runnable: Runnable, +) -> Option { + let cargo_spec = CargoTargetSpec::for_file(&snap, file_id).ok()?; + if should_skip_target(&runnable, cargo_spec.as_ref()) { + return None; + } + + let action: &'static _ = runnable.action(); + to_proto::runnable(snap, file_id, runnable).ok().map(|r| { + let mut group = lsp_ext::CommandLinkGroup::default(); + + if snap.config.hover.run { + let run_command = run_single_command(&r, action.run_title); + group.commands.push(to_command_link(run_command, r.label.clone())); + } + + if snap.config.hover.debug { + let dbg_command = debug_single_command(&r); + group.commands.push(to_command_link(dbg_command, r.label)); + } + + group + }) +} + +fn prepare_hover_actions( + snap: &GlobalStateSnapshot, + file_id: FileId, + actions: &[HoverAction], +) -> Vec { + if snap.config.hover.none() || !snap.config.client_caps.hover_actions { + return Vec::new(); + } + + 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()), + }) + .collect() +} + +fn should_skip_target(runnable: &Runnable, cargo_spec: Option<&CargoTargetSpec>) -> bool { + match runnable.kind { + RunnableKind::Bin => { + // Do not suggest binary run on other target than binary + match &cargo_spec { + Some(spec) => spec.target_kind != TargetKind::Bin, + None => true, + } + } + _ => false, } } diff --git a/crates/rust-analyzer/src/to_proto.rs b/crates/rust-analyzer/src/to_proto.rs index 2fbbb4e632d9..710df1fbdec4 100644 --- a/crates/rust-analyzer/src/to_proto.rs +++ b/crates/rust-analyzer/src/to_proto.rs @@ -3,13 +3,16 @@ use ra_db::{FileId, FileRange}; use ra_ide::{ Assist, CompletionItem, CompletionItemKind, Documentation, FileSystemEdit, Fold, FoldKind, FunctionSignature, Highlight, HighlightModifier, HighlightTag, HighlightedRange, Indel, - InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, Severity, - SourceChange, SourceFileEdit, TextEdit, + InlayHint, InlayKind, InsertTextFormat, LineIndex, NavigationTarget, ReferenceAccess, + ResolvedAssist, Runnable, Severity, SourceChange, SourceFileEdit, TextEdit, }; use ra_syntax::{SyntaxKind, TextRange, TextSize}; use ra_vfs::LineEndings; -use crate::{lsp_ext, semantic_tokens, world::WorldSnapshot, Result}; +use crate::{ + cargo_target_spec::CargoTargetSpec, global_state::GlobalStateSnapshot, lsp_ext, + semantic_tokens, Result, +}; pub(crate) fn position(line_index: &LineIndex, offset: TextSize) -> lsp_types::Position { let line_col = line_index.line_col(offset); @@ -382,41 +385,44 @@ pub(crate) fn folding_range( } } -pub(crate) fn url(world: &WorldSnapshot, file_id: FileId) -> Result { - world.file_id_to_uri(file_id) +pub(crate) fn url(snap: &GlobalStateSnapshot, file_id: FileId) -> Result { + snap.file_id_to_uri(file_id) } pub(crate) fn versioned_text_document_identifier( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_id: FileId, version: Option, ) -> Result { - let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(world, file_id)?, version }; + let res = lsp_types::VersionedTextDocumentIdentifier { uri: url(snap, file_id)?, version }; Ok(res) } -pub(crate) fn location(world: &WorldSnapshot, frange: FileRange) -> Result { - let url = url(world, frange.file_id)?; - let line_index = world.analysis().file_line_index(frange.file_id)?; +pub(crate) fn location( + snap: &GlobalStateSnapshot, + frange: FileRange, +) -> Result { + let url = url(snap, frange.file_id)?; + let line_index = snap.analysis().file_line_index(frange.file_id)?; let range = range(&line_index, frange.range); let loc = lsp_types::Location::new(url, range); Ok(loc) } pub(crate) fn location_link( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, src: Option, target: NavigationTarget, ) -> Result { let origin_selection_range = match src { Some(src) => { - let line_index = world.analysis().file_line_index(src.file_id)?; + let line_index = snap.analysis().file_line_index(src.file_id)?; let range = range(&line_index, src.range); Some(range) } None => None, }; - let (target_uri, target_range, target_selection_range) = location_info(world, target)?; + let (target_uri, target_range, target_selection_range) = location_info(snap, target)?; let res = lsp_types::LocationLink { origin_selection_range, target_uri, @@ -427,12 +433,12 @@ pub(crate) fn location_link( } fn location_info( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Result<(lsp_types::Url, lsp_types::Range, lsp_types::Range)> { - let line_index = world.analysis().file_line_index(target.file_id())?; + let line_index = snap.analysis().file_line_index(target.file_id())?; - let target_uri = url(world, target.file_id())?; + let target_uri = url(snap, target.file_id())?; let target_range = range(&line_index, target.full_range()); let target_selection_range = target.focus_range().map(|it| range(&line_index, it)).unwrap_or(target_range); @@ -440,14 +446,14 @@ fn location_info( } pub(crate) fn goto_definition_response( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, src: Option, targets: Vec, ) -> Result { - if world.config.client_caps.location_link { + if snap.config.client_caps.location_link { let links = targets .into_iter() - .map(|nav| location_link(world, src, nav)) + .map(|nav| location_link(snap, src, nav)) .collect::>>()?; Ok(links.into()) } else { @@ -455,7 +461,7 @@ pub(crate) fn goto_definition_response( .into_iter() .map(|nav| { location( - world, + snap, FileRange { file_id: nav.file_id(), range: nav.focus_range().unwrap_or(nav.range()), @@ -468,13 +474,13 @@ pub(crate) fn goto_definition_response( } pub(crate) fn snippet_text_document_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, is_snippet: bool, source_file_edit: SourceFileEdit, ) -> Result { - let text_document = versioned_text_document_identifier(world, source_file_edit.file_id, None)?; - let line_index = world.analysis().file_line_index(source_file_edit.file_id)?; - let line_endings = world.file_line_endings(source_file_edit.file_id); + let text_document = versioned_text_document_identifier(snap, source_file_edit.file_id, None)?; + let line_index = snap.analysis().file_line_index(source_file_edit.file_id)?; + let line_endings = snap.file_line_endings(source_file_edit.file_id); let edits = source_file_edit .edit .into_iter() @@ -484,17 +490,17 @@ pub(crate) fn snippet_text_document_edit( } pub(crate) fn resource_op( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, file_system_edit: FileSystemEdit, ) -> Result { let res = match file_system_edit { FileSystemEdit::CreateFile { source_root, path } => { - let uri = world.path_to_uri(source_root, &path)?; + let uri = snap.path_to_uri(source_root, &path)?; lsp_types::ResourceOp::Create(lsp_types::CreateFile { uri, options: None }) } FileSystemEdit::MoveFile { src, dst_source_root, dst_path } => { - let old_uri = world.file_id_to_uri(src)?; - let new_uri = world.path_to_uri(dst_source_root, &dst_path)?; + let old_uri = snap.file_id_to_uri(src)?; + let new_uri = snap.path_to_uri(dst_source_root, &dst_path)?; lsp_types::ResourceOp::Rename(lsp_types::RenameFile { old_uri, new_uri, options: None }) } }; @@ -502,16 +508,16 @@ pub(crate) fn resource_op( } pub(crate) fn snippet_workspace_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Result { let mut document_changes: Vec = Vec::new(); for op in source_change.file_system_edits { - let op = resource_op(&world, op)?; + let op = resource_op(&snap, op)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Op(op)); } for edit in source_change.source_file_edits { - let edit = snippet_text_document_edit(&world, source_change.is_snippet, edit)?; + let edit = snippet_text_document_edit(&snap, source_change.is_snippet, edit)?; document_changes.push(lsp_ext::SnippetDocumentChangeOperation::Edit(edit)); } let workspace_edit = @@ -520,11 +526,11 @@ pub(crate) fn snippet_workspace_edit( } pub(crate) fn workspace_edit( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, source_change: SourceChange, ) -> Result { assert!(!source_change.is_snippet); - snippet_workspace_edit(world, source_change).map(|it| it.into()) + snippet_workspace_edit(snap, source_change).map(|it| it.into()) } impl From for lsp_types::WorkspaceEdit { @@ -563,13 +569,13 @@ impl From for lsp_types::WorkspaceEdit { } pub fn call_hierarchy_item( - world: &WorldSnapshot, + snap: &GlobalStateSnapshot, target: NavigationTarget, ) -> Result { let name = target.name().to_string(); let detail = target.description().map(|it| it.to_string()); let kind = symbol_kind(target.kind()); - let (uri, range, selection_range) = location_info(world, target)?; + let (uri, range, selection_range) = location_info(snap, target)?; Ok(lsp_types::CallHierarchyItem { name, kind, tags: None, detail, uri, range, selection_range }) } @@ -617,13 +623,56 @@ fn main() { } } -pub(crate) fn code_action(world: &WorldSnapshot, assist: Assist) -> Result { +pub(crate) fn unresolved_code_action( + snap: &GlobalStateSnapshot, + assist: Assist, + index: usize, +) -> Result { let res = lsp_ext::CodeAction { title: assist.label, - group: if world.config.client_caps.code_action_group { assist.group_label } else { None }, + id: Some(format!("{}:{}", assist.id.0.to_owned(), index.to_string())), + group: assist.group.filter(|_| snap.config.client_caps.code_action_group).map(|gr| gr.0), kind: Some(String::new()), - edit: Some(snippet_workspace_edit(world, assist.source_change)?), + edit: None, command: None, }; Ok(res) } + +pub(crate) fn resolved_code_action( + snap: &GlobalStateSnapshot, + assist: ResolvedAssist, +) -> Result { + let change = assist.source_change; + unresolved_code_action(snap, assist.assist, 0).and_then(|it| { + Ok(lsp_ext::CodeAction { + id: None, + edit: Some(snippet_workspace_edit(snap, change)?), + ..it + }) + }) +} + +pub(crate) fn runnable( + snap: &GlobalStateSnapshot, + file_id: FileId, + runnable: Runnable, +) -> Result { + let spec = CargoTargetSpec::for_file(snap, file_id)?; + let target = spec.as_ref().map(|s| s.target.clone()); + let (cargo_args, executable_args) = + CargoTargetSpec::runnable_args(spec, &runnable.kind, &runnable.cfg_exprs)?; + let label = runnable.label(target); + let location = location_link(snap, None, runnable.nav)?; + + Ok(lsp_ext::Runnable { + label, + location: Some(location), + kind: lsp_ext::RunnableKind::Cargo, + args: lsp_ext::CargoRunnable { + workspace_root: snap.workspace_root_for(file_id).map(|root| root.to_owned()), + cargo_args, + executable_args, + }, + }) +} diff --git a/crates/rust-analyzer/tests/heavy_tests/main.rs b/crates/rust-analyzer/tests/heavy_tests/main.rs index 405ddb362969..0e2a83c6a0e7 100644 --- a/crates/rust-analyzer/tests/heavy_tests/main.rs +++ b/crates/rust-analyzer/tests/heavy_tests/main.rs @@ -4,9 +4,7 @@ use std::{collections::HashMap, path::PathBuf, time::Instant}; use lsp_types::{ notification::DidOpenTextDocument, - request::{ - CodeActionRequest, Completion, Formatting, GotoDefinition, GotoTypeDefinition, HoverRequest, - }, + request::{CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest}, CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions, GotoDefinitionParams, HoverParams, PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams, @@ -58,52 +56,6 @@ use std::collections::Spam; eprintln!("completion took {:?}", completion_start.elapsed()); } -#[test] -fn test_runnables_no_project() { - if skip_slow_tests() { - return; - } - - let server = project( - r" -//- lib.rs -#[test] -fn foo() { -} -", - ); - server.wait_until_workspace_is_loaded(); - server.request::( - RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, - json!([ - { - "args": [ "test" ], - "extraArgs": [ "foo", "--nocapture" ], - "bin": "cargo", - "env": { "RUST_BACKTRACE": "short" }, - "cwd": null, - "label": "test foo", - "range": { - "end": { "character": 1, "line": 2 }, - "start": { "character": 0, "line": 0 } - } - }, - { - "args": ["check", "--workspace"], - "extraArgs": [], - "bin": "cargo", - "env": {}, - "cwd": null, - "label": "cargo check --workspace", - "range": { - "end": { "character": 0, "line": 0 }, - "start": { "character": 0, "line": 0 } - } - } - ]), - ); -} - #[test] fn test_runnables_project() { if skip_slow_tests() { @@ -138,42 +90,44 @@ fn main() {} server.request::( RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None }, json!([ - { - "args": [ "test", "--package", "foo", "--test", "spam" ], - "extraArgs": [ "test_eggs", "--exact", "--nocapture" ], - "bin": "cargo", - "env": { "RUST_BACKTRACE": "short" }, - "label": "test test_eggs", - "range": { + { + "args": { + "cargoArgs": ["test", "--package", "foo", "--test", "spam"], + "executableArgs": ["test_eggs", "--exact", "--nocapture"], + "workspaceRoot": server.path().join("foo") + }, + "kind": "cargo", + "label": "test test_eggs", + "location": { + "targetRange": { "end": { "character": 17, "line": 1 }, "start": { "character": 0, "line": 0 } }, - "cwd": server.path().join("foo") - }, - { - "args": [ "check", "--package", "foo" ], - "extraArgs": [], - "bin": "cargo", - "env": {}, - "label": "cargo check -p foo", - "range": { - "end": { "character": 0, "line": 0 }, - "start": { "character": 0, "line": 0 } + "targetSelectionRange": { + "end": { "character": 12, "line": 1 }, + "start": { "character": 3, "line": 1 } }, - "cwd": server.path().join("foo") - }, - { - "args": [ "test", "--package", "foo" ], - "extraArgs": [], - "bin": "cargo", - "env": {}, - "label": "cargo test -p foo", - "range": { - "end": { "character": 0, "line": 0 }, - "start": { "character": 0, "line": 0 } - }, - "cwd": server.path().join("foo") + "targetUri": "file:///[..]/tests/spam.rs" } + }, + { + "args": { + "cargoArgs": ["check", "--package", "foo"], + "executableArgs": [], + "workspaceRoot": server.path().join("foo") + }, + "kind": "cargo", + "label": "cargo check -p foo" + }, + { + "args": { + "cargoArgs": ["test", "--package", "foo"], + "executableArgs": [], + "workspaceRoot": server.path().join("foo") + }, + "kind": "cargo", + "label": "cargo test -p foo" + } ]), ); } @@ -342,6 +296,7 @@ fn main() {} } ] }, + "kind": "quickfix", "title": "Create module" }]), ); @@ -374,8 +329,7 @@ fn test_missing_module_code_action_in_json_project() { "root_module": path.join("src/lib.rs"), "deps": [], "edition": "2015", - "atom_cfgs": [], - "key_value_cfgs": {} + "cfg": [ "cfg_atom_1", "feature=cfg_1"], } ] }); @@ -413,6 +367,7 @@ fn main() {{}} } ] }, + "kind": "quickfix", "title": "Create module" }]), ); @@ -550,6 +505,10 @@ fn main() { println!("cargo:rerun-if-changed=build.rs"); } //- src/main.rs +#[rustc_builtin_macro] macro_rules! include {} +#[rustc_builtin_macro] macro_rules! concat {} +#[rustc_builtin_macro] macro_rules! env {} + include!(concat!(env!("OUT_DIR"), "/hello.rs")); #[cfg(atom_cfg)] @@ -564,10 +523,8 @@ struct B; fn main() { let va = A; let vb = B; - message(); + let should_be_str = message(); } - -fn main() { message(); } "###, ) .with_config(|config| { @@ -575,54 +532,35 @@ fn main() { message(); } }) .server(); server.wait_until_workspace_is_loaded(); - let res = server.send_request::(GotoDefinitionParams { + let res = server.send_request::(HoverParams { text_document_position_params: TextDocumentPositionParams::new( server.doc_id("src/main.rs"), - Position::new(14, 8), + Position::new(18, 10), ), work_done_progress_params: Default::default(), - partial_result_params: Default::default(), }); - assert!(format!("{}", res).contains("hello.rs")); + assert!(res.to_string().contains("&str")); server.request::( GotoDefinitionParams { text_document_position_params: TextDocumentPositionParams::new( server.doc_id("src/main.rs"), - Position::new(12, 9), + Position::new(16, 9), ), work_done_progress_params: Default::default(), partial_result_params: Default::default(), }, json!([{ "originSelectionRange": { - "end": { - "character": 10, - "line": 12 - }, - "start": { - "character": 8, - "line": 12 - } + "end": { "character": 10, "line": 16 }, + "start": { "character": 8, "line": 16 } }, "targetRange": { - "end": { - "character": 9, - "line": 3 - }, - "start": { - "character": 0, - "line": 2 - } + "end": { "character": 9, "line": 7 }, + "start": { "character": 0, "line": 6 } }, "targetSelectionRange": { - "end": { - "character": 8, - "line": 3 - }, - "start": { - "character": 7, - "line": 3 - } + "end": { "character": 8, "line": 7 }, + "start": { "character": 7, "line": 7 } }, "targetUri": "file:///[..]src/main.rs" }]), @@ -631,41 +569,23 @@ fn main() { message(); } GotoDefinitionParams { text_document_position_params: TextDocumentPositionParams::new( server.doc_id("src/main.rs"), - Position::new(13, 9), + Position::new(17, 9), ), work_done_progress_params: Default::default(), partial_result_params: Default::default(), }, json!([{ "originSelectionRange": { - "end": { - "character": 10, - "line": 13 - }, - "start": { - "character": 8, - "line":13 - } + "end": { "character": 10, "line": 17 }, + "start": { "character": 8, "line": 17 } }, "targetRange": { - "end": { - "character": 9, - "line": 7 - }, - "start": { - "character": 0, - "line":6 - } + "end": { "character": 9, "line": 11 }, + "start": { "character": 0, "line":10 } }, "targetSelectionRange": { - "end": { - "character": 8, - "line": 7 - }, - "start": { - "character": 7, - "line": 7 - } + "end": { "character": 8, "line": 11 }, + "start": { "character": 7, "line": 11 } }, "targetUri": "file:///[..]src/main.rs" }]), diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs index 66a6f4d541a2..30d03b622b91 100644 --- a/crates/rust-analyzer/tests/heavy_tests/support.rs +++ b/crates/rust-analyzer/tests/heavy_tests/support.rs @@ -19,8 +19,9 @@ use serde_json::{to_string_pretty, Value}; use tempfile::TempDir; use test_utils::{find_mismatch, parse_fixture}; +use ra_project_model::ProjectManifest; use rust_analyzer::{ - config::{ClientCapsConfig, Config}, + config::{ClientCapsConfig, Config, LinkedProject}, main_loop, }; @@ -42,7 +43,7 @@ impl<'a> Project<'a> { self } - pub fn root(mut self, path: &str) -> Project<'a> { + pub(crate) fn root(mut self, path: &str) -> Project<'a> { self.roots.push(path.into()); self } @@ -74,7 +75,16 @@ impl<'a> Project<'a> { paths.push((path, entry.text)); } - let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); + let mut roots = + self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect::>(); + if roots.is_empty() { + roots.push(tmp_dir.path().to_path_buf()); + } + let linked_projects = roots + .into_iter() + .map(|it| ProjectManifest::discover_single(&it).unwrap()) + .map(LinkedProject::from) + .collect::>(); let mut config = Config { client_caps: ClientCapsConfig { @@ -84,6 +94,7 @@ impl<'a> Project<'a> { ..Default::default() }, with_sysroot: self.with_sysroot, + linked_projects, ..Config::default() }; @@ -91,7 +102,7 @@ impl<'a> Project<'a> { f(&mut config) } - Server::new(tmp_dir, config, roots, paths) + Server::new(tmp_dir, config, paths) } } @@ -109,20 +120,12 @@ pub struct Server { } impl Server { - fn new( - dir: TempDir, - config: Config, - roots: Vec, - files: Vec<(PathBuf, String)>, - ) -> Server { - let path = dir.path().to_path_buf(); - - let roots = if roots.is_empty() { vec![path] } else { roots }; + fn new(dir: TempDir, config: Config, files: Vec<(PathBuf, String)>) -> Server { let (connection, client) = Connection::memory(); let _thread = jod_thread::Builder::new() .name("test server".to_string()) - .spawn(move || main_loop(roots, config, connection).unwrap()) + .spawn(move || main_loop(config, connection).unwrap()) .expect("failed to spawn a thread"); let res = diff --git a/crates/stdx/src/lib.rs b/crates/stdx/src/lib.rs index 71a57fba230f..c0356344ca24 100644 --- a/crates/stdx/src/lib.rs +++ b/crates/stdx/src/lib.rs @@ -124,3 +124,8 @@ pub fn replace(buf: &mut String, from: char, to: &str) { // FIXME: do this in place. *buf = buf.replace(from, to) } + +pub fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { + let idx = haystack.find(delim)?; + Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) +} diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml index 4d185b01c752..8840bf36ae32 100644 --- a/crates/test_utils/Cargo.toml +++ b/crates/test_utils/Cargo.toml @@ -14,4 +14,5 @@ serde_json = "1.0.48" relative-path = "1.0.0" rustc-hash = "1.1.0" -ra_cfg = { path = "../ra_cfg" } \ No newline at end of file +ra_cfg = { path = "../ra_cfg" } +stdx = { path = "../stdx" } \ No newline at end of file diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 1bd97215cb83..2141bfc20277 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -15,6 +15,7 @@ use std::{ }; pub use ra_cfg::CfgOptions; +use stdx::split1; pub use relative_path::{RelativePath, RelativePathBuf}; pub use rustc_hash::FxHashMap; @@ -332,11 +333,6 @@ fn parse_meta(meta: &str) -> FixtureMeta { FixtureMeta::File(FileMeta { path, crate_name: krate, deps, edition, cfg, env }) } -fn split1(haystack: &str, delim: char) -> Option<(&str, &str)> { - let idx = haystack.find(delim)?; - Some((&haystack[..idx], &haystack[idx + delim.len_utf8()..])) -} - /// Adjusts the indentation of the first line to the minimum indentation of the rest of the lines. /// This allows fixtures to start off in a different indentation, e.g. to align the first line with /// the other lines visually: diff --git a/docs/dev/README.md b/docs/dev/README.md index 65cc9fc12c0c..ef5ffbf597ae 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -30,7 +30,7 @@ https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0 * [good-first-issue](https://github.com/rust-analyzer/rust-analyzer/labels/good%20first%20issue) are good issues to get into the project. -* [E-mentor](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-mentor) +* [E-has-instructions](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-has-instructions) issues have links to the code in question and tests. * [E-easy](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy), [E-medium](https://github.com/rust-analyzer/rust-analyzer/issues?q=is%3Aopen+is%3Aissue+label%3AE-medium), @@ -55,7 +55,7 @@ You can run `cargo xtask install-pre-commit-hook` to install git-hook to run rus All Rust code lives in the `crates` top-level directory, and is organized as a single Cargo workspace. The `editors` top-level directory contains code for integrating with editors. Currently, it contains the plugin for VS Code (in -typescript). The `docs` top-level directory contains both developer and user +TypeScript). The `docs` top-level directory contains both developer and user documentation. We have some automation infra in Rust in the `xtask` package. It contains @@ -79,8 +79,8 @@ possible. There's **"Run Extension (Debug Build)"** launch configuration for thi In general, I use one of the following workflows for fixing bugs and implementing features. -If the problem concerns only internal parts of rust-analyzer (ie, I don't need -to touch `rust-analyzer` crate or typescript code), there is a unit-test for it. +If the problem concerns only internal parts of rust-analyzer (i.e. I don't need +to touch `rust-analyzer` crate or TypeScript code), there is a unit-test for it. So, I use **Rust Analyzer: Run** action in VS Code to run this single test, and then just do printf-driven development/debugging. As a sanity check after I'm done, I use `cargo xtask install --server` and **Reload Window** action in VS @@ -117,6 +117,203 @@ Additionally, I use `cargo run --release -p rust-analyzer -- analysis-stats path/to/some/rust/crate` to run a batch analysis. This is primarily useful for performance optimizations, or for bug minimization. +# Code Style & Review Process + +Our approach to "clean code" is two fold: + +* We generally don't block PRs on style changes. +* At the same time, all code in rust-analyzer is constantly refactored. + +It is explicitly OK for reviewer to flag only some nits in the PR, and than send a follow up cleanup PR for things which are easier to explain by example, cc-ing the original author. +Sending small cleanup PRs (like rename a single local variable) is encouraged. + +## Scale of Changes + +Everyone knows that it's better to send small & focused pull requests. +The problem is, sometimes you *have* to, eg, rewrite the whole compiler, and that just doesn't fit into a set of isolated PRs. + +The main thing too keep an eye on is the boundaries between various components. +There are three kinds of changes: + +1. Internals of a single component are changed. + Specifically, you don't change any `pub` items. + A good example here would be an addition of a new assist. + +2. API of a component is expanded. + Specifically, you add a new `pub` function which wasn't there before. + A good example here would be expansion of assist API, for example, to implement lazy assists or assists groups. + +3. A new dependency between components is introduced. + Specifically, you add a `pub use` reexport from another crate or you add a new line to `[dependencies]` section of `Cargo.toml`. + A good example here would be adding reference search capability to the assists crates. + +For the first group, the change is generally merged as long as: + +* it works for the happy case, +* it has tests, +* it doesn't panic for unhappy case. + +For the second group, the change would be subjected to quite a bit of scrutiny and iteration. +The new API needs to be right (or at least easy to change later). +The actual implementation doesn't matter that much. +It's very important to minimize the amount of changed lines of code for changes of the second kind. +Often, you start doing change of the first kind, only to realise that you need to elevate to a change of the second kind. +In this case, we'll probably ask you to split API changes into a separate PR. + +Changes of the third group should be pretty rare, so we don't specify any specific process for them. +That said, adding an innocent-looking `pub use` is a very simple way to break encapsulation, keep an eye on it! + +Note: if you enjoyed this abstract hand-waving about boundaries, you might appreciate +https://www.tedinski.com/2018/02/06/system-boundaries.html + +## Order of Imports + +We separate import groups with blank lines + +```rust +mod x; +mod y; + +use std::{ ... } + +use crate_foo::{ ... } +use crate_bar::{ ... } + +use crate::{} + +use super::{} // but prefer `use crate::` +``` + +## Import Style + +Items from `hir` and `ast` should be used qualified: + +```rust +// Good +use ra_syntax::ast; + +fn frobnicate(func: hir::Function, strukt: ast::StructDef) {} + +// Not as good +use hir::Function; +use ra_syntax::ast::StructDef; + +fn frobnicate(func: Function, strukt: StructDef) {} +``` + +Avoid local `use MyEnum::*` imports. + +Prefer `use crate::foo::bar` to `use super::bar`. + +## Order of Items + +Optimize for the reader who sees the file for the first time, and wants to get the general idea about what's going on. +People read things from top to bottom, so place most important things first. + +Specifically, if all items except one are private, always put the non-private item on top. + +Put `struct`s and `enum`s first, functions and impls last. + +Do + +```rust +// Good +struct Foo { + bars: Vec +} + +struct Bar; +``` + +rather than + +```rust +// Not as good +struct Bar; + +struct Foo { + bars: Vec +} +``` + +## Documentation + +For `.md` and `.adoc` files, prefer a sentence-per-line format, don't wrap lines. +If the line is too long, you want to split the sentence in two :-) + +## Preconditions + +Function preconditions should generally be expressed in types and provided by the caller (rather than checked by callee): + +```rust +// Good +fn frbonicate(walrus: Walrus) { + ... +} + +// Not as good +fn frobnicate(walrus: Option) { + let walrus = match walrus { + Some(it) => it, + None => return, + }; + ... +} +``` + +## Commit Style + +We don't have specific rules around git history hygiene. +Maintaining clean git history is encouraged, but not enforced. +We use rebase workflow, it's OK to rewrite history during PR review process. + +Avoid @mentioning people in commit messages, as such messages create a lot of duplicate notification traffic during rebases. + +# Architecture Invariants + +This section tries to document high-level design constraints, which are not +always obvious from the low-level code. + +## Incomplete syntax trees + +Syntax trees are by design incomplete and do not enforce well-formedness. +If ast method returns an `Option`, it *can* be `None` at runtime, even if this is forbidden by the grammar. + +## LSP independence + +rust-analyzer is independent from LSP. +It provides features for a hypothetical perfect Rust-specific IDE client. +Internal representations are lowered to LSP in the `rust-analyzer` crate (the only crate which is allowed to use LSP types). + +## IDE/Compiler split + +There's a semi-hard split between "compiler" and "IDE", at the `ra_hir` crate. +Compiler derives new facts about source code. +It explicitly acknowledges that not all info is available (i.e. you can't look at types during name resolution). + +IDE assumes that all information is available at all times. + +IDE should use only types from `ra_hir`, and should not depend on the underling compiler types. +`ra_hir` is a facade. + +## IDE API + +The main IDE crate (`ra_ide`) uses "Plain Old Data" for the API. +Rather than talking in definitions and references, it talks in Strings and textual offsets. +In general, API is centered around UI concerns -- the result of the call is what the user sees in the editor, and not what the compiler sees underneath. +The results are 100% Rust specific though. + +## Parser Tests + +Test for parser (`ra_parser`) live in `ra_syntax` crate (see `test_data` direcotory). +There are two kinds of tests: + +* Manually written test cases in `parser/ok` and `parser/err` +* "Inline" tests in `parser/inline` (these are generated) from comments in `ra_parser` crate. + +The purpose of inline tests is not to achieve full coverage by test cases, but to explain to the reader of the code what each particular `if` and `match` is responsible for. +If you are tempted to add a large inline test, it might be a good idea to leave only the simplest example in place, and move the test to a manual `parser/ok` test. + # Logging Logging is done by both rust-analyzer and VS Code, so it might be tricky to @@ -159,8 +356,8 @@ There's also two VS Code commands which might be of interest: rust code that it refers to and the rust editor will also highlight the proper text range. - If you press Ctrl (i.e. trigger goto definition) in the inspected - Rust source file the syntax tree read-only editor should scroll to and select the + If you trigger Go to Definition in the inspected Rust source file, + the syntax tree read-only editor should scroll to and select the appropriate syntax node token. ![demo](https://user-images.githubusercontent.com/36276403/78225773-6636a480-74d3-11ea-9d9f-1c9d42da03b0.png) diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index c57a93f122b0..a0847dad3a8f 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -97,6 +97,30 @@ Invoking code action at this position will yield two code actions for importing * Is a fixed two-level structure enough? * Should we devise a general way to encode custom interaction protocols for GUI refactorings? +## Lazy assists with `ResolveCodeAction` + +**Issue:** https://github.com/microsoft/language-server-protocol/issues/787 + +**Client Capability** `{ "resolveCodeAction": boolean }` + +If this capability is set, the assists will be computed lazily. Thus `CodeAction` returned from the server will only contain `id` but not `edit` or `command` fields. The only exclusion from the rule is the diagnostic edits. + +After the client got the id, it should then call `experimental/resolveCodeAction` command on the server and provide the following payload: + +```typescript +interface ResolveCodeActionParams { + id: string; + codeActionParams: lc.CodeActionParams; +} +``` + +As a result of the command call the client will get the respective workspace edit (`lc.WorkspaceEdit`). + +### Unresolved Questions + +* Apply smarter filtering for ids? +* Upon `resolveCodeAction` command only call the assits which should be resolved and not all of them? + ## Parent Module **Issue:** https://github.com/microsoft/language-server-protocol/issues/1002 @@ -311,6 +335,50 @@ Moreover, it would be cool if editors didn't need to implement even basic langua This is how `SelectionRange` request works. * Alternatively, should we perhaps flag certain `SelectionRange`s as being brace pairs? +## Runnables + +**Issue:** https://github.com/microsoft/language-server-protocol/issues/944 + +**Server Capability:** `{ "runnables": { "kinds": string[] } }` + +This request is send from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`). + +**Method:** `experimental/runnables` + +**Request:** + +```typescript +interface RunnablesParams { + textDocument: TextDocumentIdentifier; + /// If null, compute runnables for the whole file. + position?: Position; +} +``` + +**Response:** `Runnable[]` + +```typescript +interface Runnable { + label: string; + /// If this Runnable is associated with a specific function/module, etc, the location of this item + location?: LocationLink; + /// Running things is necessary technology specific, `kind` needs to be advertised via server capabilities, + // the type of `args` is specific to `kind`. The actual running is handled by the client. + kind: string; + args: any; +} +``` + +rust-analyzer supports only one `kind`, `"cargo"`. The `args` for `"cargo"` look like this: + +```typescript +{ + workspaceRoot?: string; + cargoArgs: string[]; + executableArgs: string[]; +} +``` + ## Analyzer Status **Method:** `rust-analyzer/analyzerStatus` @@ -400,38 +468,40 @@ interface InlayHint { } ``` -## Runnables +## Hover Actions -**Method:** `rust-analyzer/runnables` +**Client Capability:** `{ "hoverActions": boolean }` -This request is send from client to server to get the list of things that can be run (tests, binaries, `cargo check -p`). -Note that we plan to move this request to `experimental/runnables`, as it is not really Rust-specific, but the current API is not necessary the right one. -Upstream issue: https://github.com/microsoft/language-server-protocol/issues/944 - -**Request:** +If this capability is set, `Hover` request returned from the server might contain an additional field, `actions`: ```typescript -interface RunnablesParams { - textDocument: TextDocumentIdentifier; - /// If null, compute runnables for the whole file. - position?: Position; +interface Hover { + ... + actions?: CommandLinkGroup[]; +} + +interface CommandLink extends Command { + /** + * A tooltip for the command, when represented in the UI. + */ + tooltip?: string; +} + +interface CommandLinkGroup { + title?: string; + commands: CommandLink[]; } ``` -**Response:** `Runnable[]` - -```typescript -interface Runnable { - /// The range this runnable is applicable for. - range: lc.Range; - /// The label to show in the UI. - label: string; - /// The following fields describe a process to spawn. - bin: string; - args: string[]; - /// Args for cargo after `--`. - extraArgs: string[]; - env: { [key: string]: string }; - cwd: string | null; -} +Such actions on the client side are appended to a hover bottom as command links: ``` + +-----------------------------+ + | Hover content | + | | + +-----------------------------+ + | _Action1_ | _Action2_ | <- first group, no TITLE + +-----------------------------+ + | TITLE _Action1_ | _Action2_ | <- second group + +-----------------------------+ + ... +``` \ No newline at end of file diff --git a/docs/user/generated_assists.adoc b/docs/user/generated_assists.adoc deleted file mode 100644 index 4d2fb31d4840..000000000000 --- a/docs/user/generated_assists.adoc +++ /dev/null @@ -1,1015 +0,0 @@ -[discrete] -=== `add_custom_impl` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_custom_impl.rs#L14[add_custom_impl.rs] - -Adds impl block for derived trait. - -.Before -```rust -#[derive(Deb┃ug, Display)] -struct S; -``` - -.After -```rust -#[derive(Display)] -struct S; - -impl Debug for S { - $0 -} -``` - - -[discrete] -=== `add_derive` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_derive.rs#L9[add_derive.rs] - -Adds a new `#[derive()]` clause to a struct or enum. - -.Before -```rust -struct Point { - x: u32, - y: u32,┃ -} -``` - -.After -```rust -#[derive($0)] -struct Point { - x: u32, - y: u32, -} -``` - - -[discrete] -=== `add_explicit_type` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_explicit_type.rs#L9[add_explicit_type.rs] - -Specify type for a let binding. - -.Before -```rust -fn main() { - let x┃ = 92; -} -``` - -.After -```rust -fn main() { - let x: i32 = 92; -} -``` - - -[discrete] -=== `add_from_impl_for_enum` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs#L7[add_from_impl_for_enum.rs] - -Adds a From impl for an enum variant with one tuple field. - -.Before -```rust -enum A { ┃One(u32) } -``` - -.After -```rust -enum A { One(u32) } - -impl From for A { - fn from(v: u32) -> Self { - A::One(v) - } -} -``` - - -[discrete] -=== `add_function` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_function.rs#L19[add_function.rs] - -Adds a stub function with a signature matching the function under the cursor. - -.Before -```rust -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar┃("", baz()); -} - -``` - -.After -```rust -struct Baz; -fn baz() -> Baz { Baz } -fn foo() { - bar("", baz()); -} - -fn bar(arg: &str, baz: Baz) { - ${0:todo!()} -} - -``` - - -[discrete] -=== `add_hash` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L65[raw_string.rs] - -Adds a hash to a raw string literal. - -.Before -```rust -fn main() { - r#"Hello,┃ World!"#; -} -``` - -.After -```rust -fn main() { - r##"Hello, World!"##; -} -``` - - -[discrete] -=== `add_impl` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_impl.rs#L6[add_impl.rs] - -Adds a new inherent impl for a type. - -.Before -```rust -struct Ctx { - data: T,┃ -} -``` - -.After -```rust -struct Ctx { - data: T, -} - -impl Ctx { - $0 -} -``` - - -[discrete] -=== `add_impl_default_members` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_missing_impl_members.rs#L64[add_missing_impl_members.rs] - -Adds scaffold for overriding default impl members. - -.Before -```rust -trait Trait { - Type X; - fn foo(&self); - fn bar(&self) {} -} - -impl Trait for () { - Type X = (); - fn foo(&self) {}┃ - -} -``` - -.After -```rust -trait Trait { - Type X; - fn foo(&self); - fn bar(&self) {} -} - -impl Trait for () { - Type X = (); - fn foo(&self) {} - $0fn bar(&self) {} - -} -``` - - -[discrete] -=== `add_impl_missing_members` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_missing_impl_members.rs#L24[add_missing_impl_members.rs] - -Adds scaffold for required impl members. - -.Before -```rust -trait Trait { - Type X; - fn foo(&self) -> T; - fn bar(&self) {} -} - -impl Trait for () {┃ - -} -``` - -.After -```rust -trait Trait { - Type X; - fn foo(&self) -> T; - fn bar(&self) {} -} - -impl Trait for () { - fn foo(&self) -> u32 { - ${0:todo!()} - } - -} -``` - - -[discrete] -=== `add_new` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_new.rs#L12[add_new.rs] - -Adds a new inherent impl for a type. - -.Before -```rust -struct Ctx { - data: T,┃ -} -``` - -.After -```rust -struct Ctx { - data: T, -} - -impl Ctx { - fn $0new(data: T) -> Self { Self { data } } -} - -``` - - -[discrete] -=== `add_turbo_fish` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/add_turbo_fish.rs#L10[add_turbo_fish.rs] - -Adds `::<_>` to a call of a generic method or function. - -.Before -```rust -fn make() -> T { todo!() } -fn main() { - let x = make┃(); -} -``` - -.After -```rust -fn make() -> T { todo!() } -fn main() { - let x = make::<${0:_}>(); -} -``` - - -[discrete] -=== `apply_demorgan` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/apply_demorgan.rs#L5[apply_demorgan.rs] - -Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws). -This transforms expressions of the form `!l || !r` into `!(l && r)`. -This also works with `&&`. This assist can only be applied with the cursor -on either `||` or `&&`, with both operands being a negation of some kind. -This means something of the form `!x` or `x != y`. - -.Before -```rust -fn main() { - if x != 4 ||┃ !y {} -} -``` - -.After -```rust -fn main() { - if !(x == 4 && y) {} -} -``` - - -[discrete] -=== `auto_import` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/auto_import.rs#L18[auto_import.rs] - -If the name is unresolved, provides all possible imports for it. - -.Before -```rust -fn main() { - let map = HashMap┃::new(); -} -``` - -.After -```rust -use std::collections::HashMap; - -fn main() { - let map = HashMap::new(); -} -``` - - -[discrete] -=== `change_return_type_to_result` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_return_type_to_result.rs#L8[change_return_type_to_result.rs] - -Change the function's return type to Result. - -.Before -```rust -fn foo() -> i32┃ { 42i32 } -``` - -.After -```rust -fn foo() -> Result { Ok(42i32) } -``` - - -[discrete] -=== `change_visibility` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/change_visibility.rs#L14[change_visibility.rs] - -Adds or changes existing visibility specifier. - -.Before -```rust -┃fn frobnicate() {} -``` - -.After -```rust -pub(crate) fn frobnicate() {} -``` - - -[discrete] -=== `convert_to_guarded_return` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/early_return.rs#L21[early_return.rs] - -Replace a large conditional with a guarded return. - -.Before -```rust -fn main() { - ┃if cond { - foo(); - bar(); - } -} -``` - -.After -```rust -fn main() { - if !cond { - return; - } - foo(); - bar(); -} -``` - - -[discrete] -=== `fill_match_arms` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fill_match_arms.rs#L14[fill_match_arms.rs] - -Adds missing clauses to a `match` expression. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - ┃ - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - $0Action::Move { distance } => {} - Action::Stop => {} - } -} -``` - - -[discrete] -=== `fix_visibility` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/fix_visibility.rs#L13[fix_visibility.rs] - -Makes inaccessible item public. - -.Before -```rust -mod m { - fn frobnicate() {} -} -fn main() { - m::frobnicate┃() {} -} -``` - -.After -```rust -mod m { - $0pub(crate) fn frobnicate() {} -} -fn main() { - m::frobnicate() {} -} -``` - - -[discrete] -=== `flip_binexpr` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_binexpr.rs#L5[flip_binexpr.rs] - -Flips operands of a binary expression. - -.Before -```rust -fn main() { - let _ = 90 +┃ 2; -} -``` - -.After -```rust -fn main() { - let _ = 2 + 90; -} -``` - - -[discrete] -=== `flip_comma` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_comma.rs#L5[flip_comma.rs] - -Flips two comma-separated items. - -.Before -```rust -fn main() { - ((1, 2),┃ (3, 4)); -} -``` - -.After -```rust -fn main() { - ((3, 4), (1, 2)); -} -``` - - -[discrete] -=== `flip_trait_bound` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/flip_trait_bound.rs#L9[flip_trait_bound.rs] - -Flips two trait bounds. - -.Before -```rust -fn foo() { } -``` - -.After -```rust -fn foo() { } -``` - - -[discrete] -=== `inline_local_variable` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/inline_local_variable.rs#L13[inline_local_variable.rs] - -Inlines local variable. - -.Before -```rust -fn main() { - let x┃ = 1 + 2; - x * 4; -} -``` - -.After -```rust -fn main() { - (1 + 2) * 4; -} -``` - - -[discrete] -=== `introduce_named_lifetime` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_named_lifetime.rs#L12[introduce_named_lifetime.rs] - -Change an anonymous lifetime to a named lifetime. - -.Before -```rust -impl Cursor<'_┃> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } -} -``` - -.After -```rust -impl<'a> Cursor<'a> { - fn node(self) -> &SyntaxNode { - match self { - Cursor::Replace(node) | Cursor::Before(node) => node, - } - } -} -``` - - -[discrete] -=== `introduce_variable` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/introduce_variable.rs#L14[introduce_variable.rs] - -Extracts subexpression into a variable. - -.Before -```rust -fn main() { - ┃(1 + 2)┃ * 4; -} -``` - -.After -```rust -fn main() { - let $0var_name = (1 + 2); - var_name * 4; -} -``` - - -[discrete] -=== `invert_if` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/invert_if.rs#L12[invert_if.rs] - -Apply invert_if -This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}` -This also works with `!=`. This assist can only be applied with the cursor -on `if`. - -.Before -```rust -fn main() { - if┃ !y { A } else { B } -} -``` - -.After -```rust -fn main() { - if y { B } else { A } -} -``` - - -[discrete] -=== `make_raw_string` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L10[raw_string.rs] - -Adds `r#` to a plain string literal. - -.Before -```rust -fn main() { - "Hello,┃ World!"; -} -``` - -.After -```rust -fn main() { - r#"Hello, World!"#; -} -``` - - -[discrete] -=== `make_usual_string` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L39[raw_string.rs] - -Turns a raw string into a plain string. - -.Before -```rust -fn main() { - r#"Hello,┃ "World!""#; -} -``` - -.After -```rust -fn main() { - "Hello, \"World!\""; -} -``` - - -[discrete] -=== `merge_imports` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_imports.rs#L14[merge_imports.rs] - -Merges two imports with a common prefix. - -.Before -```rust -use std::┃fmt::Formatter; -use std::io; -``` - -.After -```rust -use std::{fmt::Formatter, io}; -``` - - -[discrete] -=== `merge_match_arms` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/merge_match_arms.rs#L11[merge_match_arms.rs] - -Merges identical match arms. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - ┃Action::Move(..) => foo(), - Action::Stop => foo(), - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move(..) | Action::Stop => foo(), - } -} -``` - - -[discrete] -=== `move_arm_cond_to_match_guard` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L56[move_guard.rs] - -Moves if expression from match arm body into a guard. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } => ┃if distance > 10 { foo() }, - _ => (), - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } if distance > 10 => foo(), - _ => (), - } -} -``` - - -[discrete] -=== `move_bounds_to_where_clause` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_bounds.rs#L10[move_bounds.rs] - -Moves inline type bounds to a where clause. - -.Before -```rust -fn apply U>(f: F, x: T) -> U { - f(x) -} -``` - -.After -```rust -fn apply(f: F, x: T) -> U where F: FnOnce(T) -> U { - f(x) -} -``` - - -[discrete] -=== `move_guard_to_arm_body` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/move_guard.rs#L8[move_guard.rs] - -Moves match guard into match arm body. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } ┃if distance > 10 => foo(), - _ => (), - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } => if distance > 10 { foo() }, - _ => (), - } -} -``` - - -[discrete] -=== `remove_dbg` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_dbg.rs#L8[remove_dbg.rs] - -Removes `dbg!()` macro call. - -.Before -```rust -fn main() { - ┃dbg!(92); -} -``` - -.After -```rust -fn main() { - 92; -} -``` - - -[discrete] -=== `remove_hash` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/raw_string.rs#L89[raw_string.rs] - -Removes a hash from a raw string literal. - -.Before -```rust -fn main() { - r#"Hello,┃ World!"#; -} -``` - -.After -```rust -fn main() { - r"Hello, World!"; -} -``` - - -[discrete] -=== `remove_mut` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/remove_mut.rs#L5[remove_mut.rs] - -Removes the `mut` keyword. - -.Before -```rust -impl Walrus { - fn feed(&mut┃ self, amount: u32) {} -} -``` - -.After -```rust -impl Walrus { - fn feed(&self, amount: u32) {} -} -``` - - -[discrete] -=== `reorder_fields` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/reorder_fields.rs#L10[reorder_fields.rs] - -Reorder the fields of record literals and record patterns in the same order as in -the definition. - -.Before -```rust -struct Foo {foo: i32, bar: i32}; -const test: Foo = ┃Foo {bar: 0, foo: 1} -``` - -.After -```rust -struct Foo {foo: i32, bar: i32}; -const test: Foo = Foo {foo: 1, bar: 0} -``` - - -[discrete] -=== `replace_if_let_with_match` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_if_let_with_match.rs#L13[replace_if_let_with_match.rs] - -Replaces `if let` with an else branch with a `match` expression. - -.Before -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - ┃if let Action::Move { distance } = action { - foo(distance) - } else { - bar() - } -} -``` - -.After -```rust -enum Action { Move { distance: u32 }, Stop } - -fn handle(action: Action) { - match action { - Action::Move { distance } => foo(distance), - _ => bar(), - } -} -``` - - -[discrete] -=== `replace_let_with_if_let` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_let_with_if_let.rs#L14[replace_let_with_if_let.rs] - -Replaces `let` with an `if-let`. - -.Before -```rust - -fn main(action: Action) { - ┃let x = compute(); -} - -fn compute() -> Option { None } -``` - -.After -```rust - -fn main(action: Action) { - if let Some(x) = compute() { - } -} - -fn compute() -> Option { None } -``` - - -[discrete] -=== `replace_qualified_name_with_use` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs#L6[replace_qualified_name_with_use.rs] - -Adds a use statement for a given fully-qualified name. - -.Before -```rust -fn process(map: std::collections::┃HashMap) {} -``` - -.After -```rust -use std::collections::HashMap; - -fn process(map: HashMap) {} -``` - - -[discrete] -=== `replace_unwrap_with_match` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs#L17[replace_unwrap_with_match.rs] - -Replaces `unwrap` a `match` expression. Works for Result and Option. - -.Before -```rust -enum Result { Ok(T), Err(E) } -fn main() { - let x: Result = Result::Ok(92); - let y = x.┃unwrap(); -} -``` - -.After -```rust -enum Result { Ok(T), Err(E) } -fn main() { - let x: Result = Result::Ok(92); - let y = match x { - Ok(a) => a, - $0_ => unreachable!(), - }; -} -``` - - -[discrete] -=== `split_import` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/split_import.rs#L7[split_import.rs] - -Wraps the tail of import into braces. - -.Before -```rust -use std::┃collections::HashMap; -``` - -.After -```rust -use std::{collections::HashMap}; -``` - - -[discrete] -=== `unwrap_block` -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_assists/src/handlers/unwrap_block.rs#L9[unwrap_block.rs] - -This assist removes if...else, for, while and loop control statements to just keep the body. - -.Before -```rust -fn foo() { - if true {┃ - println!("foo"); - } -} -``` - -.After -```rust -fn foo() { - println!("foo"); -} -``` diff --git a/docs/user/generated_features.adoc b/docs/user/generated_features.adoc index 56538ca9032e..23a071cf1b42 100644 --- a/docs/user/generated_features.adoc +++ b/docs/user/generated_features.adoc @@ -76,7 +76,7 @@ Navigates to the type of an identifier. === Hover -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L63[hover.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/hover.rs#L106[hover.rs] Shows additional information, like type of an expression or documentation for definition when "focusing" code. Focusing is usually hovering with a mouse, but can also be triggered with a shortcut. @@ -177,7 +177,7 @@ braces, so it won't confuse generics with comparisons. === On Typing Assists -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs#L35[typing.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/typing.rs#L37[typing.rs] Some features trigger on typing certain characters: @@ -199,7 +199,7 @@ Navigates to the parent module of the current module. === Run -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs#L45[runnables.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/runnables.rs#L81[runnables.rs] Shows a popup suggesting to run a test/benchmark/binary **at the current cursor location**. Super useful for repeatedly running just a single test. Do bind this @@ -213,7 +213,7 @@ to a shortcut! === Semantic Syntax Highlighting -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs#L33[syntax_highlighting.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide/src/syntax_highlighting.rs#L34[syntax_highlighting.rs] rust-analyzer highlights the code semantically. For example, `bar` in `foo::Bar` might be colored differently depending on whether `Bar` is an enum or a trait. @@ -275,7 +275,7 @@ String::from((y + 5).foo(z)) === Workspace Symbol -**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs#L113[symbol_index.rs] +**Source:** https://github.com/rust-analyzer/rust-analyzer/blob/master/crates/ra_ide_db/src/symbol_index.rs#L122[symbol_index.rs] Uses fuzzy-search to find types, modules and functions by name across your project and dependencies. This is **the** most useful feature, which improves code diff --git a/docs/user/manual.adoc b/docs/user/manual.adoc index 202783fd9538..ea714f49addf 100644 --- a/docs/user/manual.adoc +++ b/docs/user/manual.adoc @@ -269,6 +269,57 @@ Gnome Builder currently has support for RLS, and there's no way to configure the 1. Rename, symlink or copy the `rust-analyzer` binary to `rls` and place it somewhere Builder can find (in `PATH`, or under `~/.cargo/bin`). 2. Enable the Rust Builder plugin. +== Non-Cargo Based Projects + +rust-analyzer does not require Cargo. +However, if you use some other build system, you'll have to describe the structure of your project for rust-analyzer in the `rust-project.json` format: + +[source,TypeScript] +---- +interface JsonProject { + /// The set of paths containing the crates for this project. + /// Any `Crate` must be nested inside some `root`. + roots: string[]; + /// The set of crates comprising the current project. + /// Must include all transitive dependencies as well as sysroot crate (libstd, libcore and such). + crates: Crate[]; +} + +interface Crate { + /// Path to the root module of the crate. + root_module: string; + /// Edition of the crate. + edition: "2015" | "2018"; + /// Dependencies + deps: Dep[]; + /// The set of cfgs activated for a given crate, like `["unix", "feature=foo", "feature=bar"]`. + cfg: string[]; + + /// value of the OUT_DIR env variable. + out_dir?: string; + /// For proc-macro crates, path to compiles proc-macro (.so file). + proc_macro_dylib_path?: string; +} + +interface Dep { + /// Index of a crate in the `crates` array. + crate: number, + /// Name as should appear in the (implicit) `extern crate name` declaration. + name: string, +} +---- + +This format is provisional and subject to change. +Specifically, the `roots` setup will be different eventually. + +There are tree ways to feed `rust-project.json` to rust-analyzer: + +* Place `rust-project.json` file at the root of the project, and rust-anlayzer will discover it. +* Specify `"rust-analyzer.linkedProjects": [ "path/to/rust-project.json" ]` in the settings (and make sure that your LSP client sends settings as a part of initialize request). +* Specify `"rust-analyzer.linkedProjects": [ { "roots": [...], "crates": [...] }]` inline. + +See https://github.com/rust-analyzer/rust-project.json-example for a small example. + == Features include::./generated_features.adoc[] diff --git a/editors/code/package.json b/editors/code/package.json index 75dbafc05a3b..e2027970db86 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -238,7 +238,7 @@ }, "rust-analyzer.cargo.allFeatures": { "type": "boolean", - "default": true, + "default": false, "description": "Activate all available features" }, "rust-analyzer.cargo.features": { @@ -318,9 +318,23 @@ "markdownDescription": "Check all targets and tests (will be passed as `--all-targets`)" }, "rust-analyzer.checkOnSave.allFeatures": { - "type": "boolean", - "default": true, - "markdownDescription": "Check with all features (will be passed as `--all-features`)" + "type": [ + "null", + "boolean" + ], + "default": null, + "markdownDescription": "Check with all features (will be passed as `--all-features`). Defaults to `rust-analyzer.cargo.allFeatures`." + }, + "rust-analyzer.checkOnSave.features": { + "type": [ + "null", + "array" + ], + "items": { + "type": "string" + }, + "default": null, + "description": "List of features to activate. Defaults to `rust-analyzer.cargo.features`." }, "rust-analyzer.inlayHints.enable": { "type": "boolean", @@ -462,17 +476,53 @@ "default": true }, "rust-analyzer.lens.run": { - "markdownDescription": "Whether to show Run lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Run` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true }, "rust-analyzer.lens.debug": { - "markdownDescription": "Whether to show Debug lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Debug` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", "type": "boolean", "default": true }, "rust-analyzer.lens.implementations": { - "markdownDescription": "Whether to show Implementations lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "markdownDescription": "Whether to show `Implementations` lens. Only applies when `#rust-analyzer.lens.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.enable": { + "description": "Whether to show HoverActions in Rust files.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.implementations": { + "markdownDescription": "Whether to show `Implementations` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.run": { + "markdownDescription": "Whether to show `Run` action. Only applies when `#rust-analyzer.hoverActions.enable#` is set.", + "type": "boolean", + "default": true + }, + "rust-analyzer.hoverActions.debug": { + "markdownDescription": "Whether to show `Debug` 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", + "items": { + "type": [ + "string", + "object" + ] + }, + "default": null + }, + "rust-analyzer.withSysroot": { + "markdownDescription": "Internal config for debugging, disables loading of sysroot crates", "type": "boolean", "default": true } diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index d64f9a3f9793..65ad573d8c91 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts @@ -1,8 +1,25 @@ import * as lc from 'vscode-languageclient'; import * as vscode from 'vscode'; +import * as ra from '../src/lsp_ext'; +import * as Is from 'vscode-languageclient/lib/utils/is'; import { CallHierarchyFeature } from 'vscode-languageclient/lib/callHierarchy.proposed'; import { SemanticTokensFeature, DocumentSemanticsTokensSignature } from 'vscode-languageclient/lib/semanticTokens.proposed'; +import { assert } from './util'; + +function renderCommand(cmd: ra.CommandLink) { + return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; +} + +function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { + const text = actions.map(group => + (group.title ? (group.title + " ") : "") + group.commands.map(renderCommand).join(' | ') + ).join('___'); + + const result = new vscode.MarkdownString(text); + result.isTrusted = true; + return result; +} export function createClient(serverPath: string, cwd: string): lc.LanguageClient { // '.' Is the fallback if no folder is open @@ -32,6 +49,25 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient if (res === undefined) throw new Error('busy'); return res; }, + async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken, _next: lc.ProvideHoverSignature) { + return client.sendRequest(lc.HoverRequest.type, client.code2ProtocolConverter.asTextDocumentPositionParams(document, position), token).then( + (result) => { + const hover = client.protocol2CodeConverter.asHover(result); + if (hover) { + const actions = (result).actions; + if (actions) { + hover.contents.push(renderHoverActions(actions)); + } + } + return hover; + }, + (error) => { + client.logFailedRequest(lc.HoverRequest.type, error); + return Promise.resolve(null); + }); + }, + // Using custom handling of CodeActions where each code action is resloved lazily + // That's why we are not waiting for any command or edits async provideCodeActions(document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, token: vscode.CancellationToken, _next: lc.ProvideCodeActionsSignature) { const params: lc.CodeActionParams = { textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document), @@ -43,32 +79,36 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient const result: (vscode.CodeAction | vscode.Command)[] = []; const groups = new Map(); for (const item of values) { + // In our case we expect to get code edits only from diagnostics if (lc.CodeAction.is(item)) { + assert(!item.command, "We don't expect to receive commands in CodeActions"); const action = client.protocol2CodeConverter.asCodeAction(item); - const group = actionGroup(item); - if (isSnippetEdit(item) || group) { - action.command = { - command: "rust-analyzer.applySnippetWorkspaceEdit", - title: "", - arguments: [action.edit], - }; - action.edit = undefined; - } - - if (group) { - let entry = groups.get(group); - if (!entry) { - entry = { index: result.length, items: [] }; - groups.set(group, entry); - result.push(action); - } - entry.items.push(action); - } else { + result.push(action); + continue; + } + assert(isCodeActionWithoutEditsAndCommands(item), "We don't expect edits or commands here"); + const action = new vscode.CodeAction(item.title); + const group = (item as any).group; + const id = (item as any).id; + const resolveParams: ra.ResolveCodeActionParams = { + id: id, + codeActionParams: params + }; + action.command = { + command: "rust-analyzer.resolveCodeAction", + title: item.title, + arguments: [resolveParams], + }; + if (group) { + let entry = groups.get(group); + if (!entry) { + entry = { index: result.length, items: [] }; + groups.set(group, entry); result.push(action); } + entry.items.push(action); } else { - const command = client.protocol2CodeConverter.asCommand(item); - result.push(command); + result.push(action); } } for (const [group, { index, items }] of groups) { @@ -80,7 +120,7 @@ export function createClient(serverPath: string, cwd: string): lc.LanguageClient command: "rust-analyzer.applyActionGroup", title: "", arguments: [items.map((item) => { - return { label: item.title, edit: item.command!!.arguments!![0] }; + return { label: item.title, arguments: item.command!!.arguments!![0] }; })], }; result[index] = action; @@ -119,24 +159,18 @@ class ExperimentalFeatures implements lc.StaticFeature { const caps: any = capabilities.experimental ?? {}; caps.snippetTextEdit = true; caps.codeActionGroup = true; + caps.resolveCodeAction = true; + caps.hoverActions = true; capabilities.experimental = caps; } initialize(_capabilities: lc.ServerCapabilities, _documentSelector: lc.DocumentSelector | undefined): void { } } -function isSnippetEdit(action: lc.CodeAction): boolean { - const documentChanges = action.edit?.documentChanges ?? []; - for (const edit of documentChanges) { - if (lc.TextDocumentEdit.is(edit)) { - if (edit.edits.some((indel) => (indel as any).insertTextFormat === lc.InsertTextFormat.Snippet)) { - return true; - } - } - } - return false; -} - -function actionGroup(action: lc.CodeAction): string | undefined { - return (action as any).group; +function isCodeActionWithoutEditsAndCommands(value: any): boolean { + const candidate: lc.CodeAction = value; + return candidate && Is.string(candidate.title) && + (candidate.diagnostics === void 0 || Is.typedArray(candidate.diagnostics, lc.Diagnostic.is)) && + (candidate.kind === void 0 || Is.string(candidate.kind)) && + (candidate.edit === void 0 && candidate.command === void 0); } diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 86302db37c1e..3e9c3aa0e59b 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -8,6 +8,7 @@ import { spawnSync } from 'child_process'; import { RunnableQuickPick, selectRunnable, createTask } from './run'; import { AstInspector } from './ast_inspector'; import { isRustDocument, sleep, isRustEditor } from './util'; +import { startDebugSession, makeDebugConfig } from './debug'; export * from './ast_inspector'; export * from './run'; @@ -197,20 +198,6 @@ export function toggleInlayHints(ctx: Ctx): Cmd { }; } -export function run(ctx: Ctx): Cmd { - let prevRunnable: RunnableQuickPick | undefined; - - return async () => { - const item = await selectRunnable(ctx, prevRunnable); - if (!item) return; - - item.detail = 'rerun'; - prevRunnable = item; - const task = createTask(item.runnable); - return await vscode.tasks.executeTask(task); - }; -} - // Opens the virtual file that will show the syntax tree // // The contents of the file come from the `TextDocumentContentProvider` @@ -356,10 +343,25 @@ export function showReferences(ctx: Ctx): Cmd { } export function applyActionGroup(_ctx: Ctx): Cmd { - return async (actions: { label: string; edit: vscode.WorkspaceEdit }[]) => { + return async (actions: { label: string; arguments: ra.ResolveCodeActionParams }[]) => { const selectedAction = await vscode.window.showQuickPick(actions); if (!selectedAction) return; - await applySnippetWorkspaceEdit(selectedAction.edit); + vscode.commands.executeCommand( + 'rust-analyzer.resolveCodeAction', + selectedAction.arguments, + ); + }; +} + +export function resolveCodeAction(ctx: Ctx): Cmd { + const client = ctx.client; + return async (params: ra.ResolveCodeActionParams) => { + const item: lc.WorkspaceEdit = await client.sendRequest(ra.resolveCodeAction, params); + if (!item) { + return; + } + const edit = client.protocol2CodeConverter.asWorkspaceEdit(item); + await applySnippetWorkspaceEdit(edit); }; } @@ -368,3 +370,62 @@ export function applySnippetWorkspaceEditCommand(_ctx: Ctx): Cmd { await applySnippetWorkspaceEdit(edit); }; } + +export function run(ctx: Ctx): Cmd { + let prevRunnable: RunnableQuickPick | undefined; + + return async () => { + const item = await selectRunnable(ctx, prevRunnable); + if (!item) return; + + item.detail = 'rerun'; + prevRunnable = item; + const task = createTask(item.runnable); + return await vscode.tasks.executeTask(task); + }; +} + +export function runSingle(ctx: Ctx): Cmd { + return async (runnable: ra.Runnable) => { + const editor = ctx.activeRustEditor; + if (!editor) return; + + const task = createTask(runnable); + task.group = vscode.TaskGroup.Build; + task.presentationOptions = { + reveal: vscode.TaskRevealKind.Always, + panel: vscode.TaskPanelKind.Dedicated, + clear: true, + }; + + return vscode.tasks.executeTask(task); + }; +} + +export function debug(ctx: Ctx): Cmd { + let prevDebuggee: RunnableQuickPick | undefined; + + return async () => { + const item = await selectRunnable(ctx, prevDebuggee, true); + if (!item) return; + + item.detail = 'restart'; + prevDebuggee = item; + return await startDebugSession(ctx, item.runnable); + }; +} + +export function debugSingle(ctx: Ctx): Cmd { + return async (config: ra.Runnable) => { + await startDebugSession(ctx, config); + }; +} + +export function newDebugConfig(ctx: Ctx): Cmd { + return async () => { + const item = await selectRunnable(ctx, undefined, true, false); + if (!item) return; + + await makeDebugConfig(ctx, item.runnable); + }; +} diff --git a/editors/code/src/config.ts b/editors/code/src/config.ts index e8abf8284eb7..d8f0037d4c95 100644 --- a/editors/code/src/config.ts +++ b/editors/code/src/config.ts @@ -16,10 +16,8 @@ export class Config { "files", "highlighting", "updates.channel", - "lens.enable", - "lens.run", - "lens.debug", - "lens.implementations", + "lens", // works as lens.* + "hoverActions", // works as hoverActions.* ] .map(opt => `${this.rootSection}.${opt}`); @@ -132,4 +130,11 @@ export class Config { implementations: this.get("lens.implementations"), }; } + + get hoverActions() { + return { + enable: this.get("hoverActions.enable"), + implementations: this.get("hoverActions.implementations"), + }; + } } diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 027504ecd2a8..a0c9b3ab2e64 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts @@ -3,46 +3,59 @@ import * as vscode from 'vscode'; import * as path from 'path'; import * as ra from './lsp_ext'; -import { Cargo } from './cargo'; +import { Cargo } from './toolchain'; import { Ctx } from "./ctx"; const debugOutput = vscode.window.createOutputChannel("Debug"); type DebugConfigProvider = (config: ra.Runnable, executable: string, sourceFileMap?: Record) => vscode.DebugConfiguration; -function getLldbDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { - return { - type: "lldb", - request: "launch", - name: config.label, - program: executable, - args: config.extraArgs, - cwd: config.cwd, - sourceMap: sourceFileMap, - sourceLanguages: ["rust"] - }; +export async function makeDebugConfig(ctx: Ctx, runnable: ra.Runnable): Promise { + const scope = ctx.activeRustEditor?.document.uri; + if (!scope) return; + + const debugConfig = await getDebugConfiguration(ctx, runnable); + if (!debugConfig) return; + + const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); + const configurations = wsLaunchSection.get("configurations") || []; + + const index = configurations.findIndex(c => c.name === debugConfig.name); + if (index !== -1) { + const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); + if (answer === "Cancel") return; + + configurations[index] = debugConfig; + } else { + configurations.push(debugConfig); + } + + await wsLaunchSection.update("configurations", configurations); } -function getCppvsDebugConfig(config: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { - return { - type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", - request: "launch", - name: config.label, - program: executable, - args: config.extraArgs, - cwd: config.cwd, - sourceFileMap: sourceFileMap, - }; +export async function startDebugSession(ctx: Ctx, runnable: ra.Runnable): Promise { + let debugConfig: vscode.DebugConfiguration | undefined = undefined; + let message = ""; + + const wsLaunchSection = vscode.workspace.getConfiguration("launch"); + const configurations = wsLaunchSection.get("configurations") || []; + + const index = configurations.findIndex(c => c.name === runnable.label); + if (-1 !== index) { + debugConfig = configurations[index]; + message = " (from launch.json)"; + debugOutput.clear(); + } else { + debugConfig = await getDebugConfiguration(ctx, runnable); + } + + if (!debugConfig) return false; + + debugOutput.appendLine(`Launching debug configuration${message}:`); + debugOutput.appendLine(JSON.stringify(debugConfig, null, 2)); + return vscode.debug.startDebugging(undefined, debugConfig); } -async function getDebugExecutable(config: ra.Runnable): Promise { - const cargo = new Cargo(config.cwd || '.', debugOutput); - const executable = await cargo.executableFromArgs(config.args); - - // if we are here, there were no compilation errors. - return executable; -} - -export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Promise { +async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise { const editor = ctx.activeRustEditor; if (!editor) return; @@ -78,8 +91,8 @@ export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Prom return path.normalize(p).replace(wsFolder, '${workspaceRoot}'); } - const executable = await getDebugExecutable(config); - const debugConfig = knownEngines[debugEngine.id](config, simplifyPath(executable), debugOptions.sourceFileMap); + const executable = await getDebugExecutable(runnable); + const debugConfig = knownEngines[debugEngine.id](runnable, simplifyPath(executable), debugOptions.sourceFileMap); if (debugConfig.type in debugOptions.engineSettings) { const settingsMap = (debugOptions.engineSettings as any)[debugConfig.type]; for (var key in settingsMap) { @@ -100,25 +113,35 @@ export async function getDebugConfiguration(ctx: Ctx, config: ra.Runnable): Prom return debugConfig; } -export async function startDebugSession(ctx: Ctx, config: ra.Runnable): Promise { - let debugConfig: vscode.DebugConfiguration | undefined = undefined; - let message = ""; +async function getDebugExecutable(runnable: ra.Runnable): Promise { + const cargo = new Cargo(runnable.args.workspaceRoot || '.', debugOutput); + const executable = await cargo.executableFromArgs(runnable.args.cargoArgs); - const wsLaunchSection = vscode.workspace.getConfiguration("launch"); - const configurations = wsLaunchSection.get("configurations") || []; - - const index = configurations.findIndex(c => c.name === config.label); - if (-1 !== index) { - debugConfig = configurations[index]; - message = " (from launch.json)"; - debugOutput.clear(); - } else { - debugConfig = await getDebugConfiguration(ctx, config); - } - - if (!debugConfig) return false; - - debugOutput.appendLine(`Launching debug configuration${message}:`); - debugOutput.appendLine(JSON.stringify(debugConfig, null, 2)); - return vscode.debug.startDebugging(undefined, debugConfig); + // if we are here, there were no compilation errors. + return executable; +} + +function getLldbDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { + return { + type: "lldb", + request: "launch", + name: runnable.label, + program: executable, + args: runnable.args.executableArgs, + cwd: runnable.args.workspaceRoot, + sourceMap: sourceFileMap, + sourceLanguages: ["rust"] + }; +} + +function getCppvsDebugConfig(runnable: ra.Runnable, executable: string, sourceFileMap?: Record): vscode.DebugConfiguration { + return { + type: (os.platform() === "win32") ? "cppvsdbg" : "cppdbg", + request: "launch", + name: runnable.label, + program: executable, + args: runnable.args.executableArgs, + cwd: runnable.args.workspaceRoot, + sourceFileMap: sourceFileMap, + }; } diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index 4da12eb3092a..e16ea799ce01 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -33,6 +33,12 @@ export const matchingBrace = new lc.RequestType("experimental/parentModule"); +export interface ResolveCodeActionParams { + id: string; + codeActionParams: lc.CodeActionParams; +} +export const resolveCodeAction = new lc.RequestType('experimental/resolveCodeAction'); + export interface JoinLinesParams { textDocument: lc.TextDocumentIdentifier; ranges: lc.Range[]; @@ -45,16 +51,18 @@ export interface RunnablesParams { textDocument: lc.TextDocumentIdentifier; position: lc.Position | null; } + export interface Runnable { - range: lc.Range; label: string; - bin: string; - args: string[]; - extraArgs: string[]; - env: { [key: string]: string }; - cwd: string | null; + location?: lc.LocationLink; + kind: "cargo"; + args: { + workspaceRoot?: string; + cargoArgs: string[]; + executableArgs: string[]; + }; } -export const runnables = new lc.RequestType("rust-analyzer/runnables"); +export const runnables = new lc.RequestType("experimental/runnables"); export type InlayHint = InlayHint.TypeHint | InlayHint.ParamHint | InlayHint.ChainingHint; @@ -82,3 +90,15 @@ export interface SsrParams { parseOnly: boolean; } export const ssr = new lc.RequestType('experimental/ssr'); + +export interface CommandLink extends lc.Command { + /** + * A tooltip for the command, when represented in the UI. + */ + tooltip?: string; +} + +export interface CommandLinkGroup { + title?: string; + commands: CommandLink[]; +} diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index b7337621cb61..a92c676fa2dd 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -98,6 +98,7 @@ export async function activate(context: vscode.ExtensionContext) { ctx.registerCommand('debugSingle', commands.debugSingle); ctx.registerCommand('showReferences', commands.showReferences); ctx.registerCommand('applySnippetWorkspaceEdit', commands.applySnippetWorkspaceEditCommand); + ctx.registerCommand('resolveCodeAction', commands.resolveCodeAction); ctx.registerCommand('applyActionGroup', commands.applyActionGroup); ctx.pushCleanup(activateTaskProvider(workspaceFolder)); diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index 8e1ba83ed676..bb060cfe1554 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts @@ -1,9 +1,10 @@ import * as vscode from 'vscode'; import * as lc from 'vscode-languageclient'; import * as ra from './lsp_ext'; +import * as toolchain from "./toolchain"; -import { Ctx, Cmd } from './ctx'; -import { startDebugSession, getDebugConfiguration } from './debug'; +import { Ctx } from './ctx'; +import { makeDebugConfig } from './debug'; const quickPickButtons = [{ iconPath: new vscode.ThemeIcon("save"), tooltip: "Save as a launch.json configurtation." }]; @@ -64,7 +65,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, quickPick.onDidHide(() => close()), quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), quickPick.onDidTriggerButton((_button) => { - (async () => await makeDebugConfig(ctx, quickPick.activeItems[0]))(); + (async () => await makeDebugConfig(ctx, quickPick.activeItems[0].runnable))(); close(); }), quickPick.onDidChangeActive((active) => { @@ -83,74 +84,6 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, }); } -export function runSingle(ctx: Ctx): Cmd { - return async (runnable: ra.Runnable) => { - const editor = ctx.activeRustEditor; - if (!editor) return; - - const task = createTask(runnable); - task.group = vscode.TaskGroup.Build; - task.presentationOptions = { - reveal: vscode.TaskRevealKind.Always, - panel: vscode.TaskPanelKind.Dedicated, - clear: true, - }; - - return vscode.tasks.executeTask(task); - }; -} - -export function debug(ctx: Ctx): Cmd { - let prevDebuggee: RunnableQuickPick | undefined; - - return async () => { - const item = await selectRunnable(ctx, prevDebuggee, true); - if (!item) return; - - item.detail = 'restart'; - prevDebuggee = item; - return await startDebugSession(ctx, item.runnable); - }; -} - -export function debugSingle(ctx: Ctx): Cmd { - return async (config: ra.Runnable) => { - await startDebugSession(ctx, config); - }; -} - -async function makeDebugConfig(ctx: Ctx, item: RunnableQuickPick): Promise { - const scope = ctx.activeRustEditor?.document.uri; - if (!scope) return; - - const debugConfig = await getDebugConfiguration(ctx, item.runnable); - if (!debugConfig) return; - - const wsLaunchSection = vscode.workspace.getConfiguration("launch", scope); - const configurations = wsLaunchSection.get("configurations") || []; - - const index = configurations.findIndex(c => c.name === debugConfig.name); - if (index !== -1) { - const answer = await vscode.window.showErrorMessage(`Launch configuration '${debugConfig.name}' already exists!`, 'Cancel', 'Update'); - if (answer === "Cancel") return; - - configurations[index] = debugConfig; - } else { - configurations.push(debugConfig); - } - - await wsLaunchSection.update("configurations", configurations); -} - -export function newDebugConfig(ctx: Ctx): Cmd { - return async () => { - const item = await selectRunnable(ctx, undefined, true, false); - if (!item) return; - - await makeDebugConfig(ctx, item); - }; -} - export class RunnableQuickPick implements vscode.QuickPickItem { public label: string; public description?: string | undefined; @@ -170,18 +103,27 @@ interface CargoTaskDefinition extends vscode.TaskDefinition { env?: { [key: string]: string }; } -export function createTask(spec: ra.Runnable): vscode.Task { +export function createTask(runnable: ra.Runnable): vscode.Task { const TASK_SOURCE = 'Rust'; + + let command; + switch (runnable.kind) { + case "cargo": command = toolchain.getPathForExecutable("cargo"); + } + const args = [...runnable.args.cargoArgs]; // should be a copy! + if (runnable.args.executableArgs.length > 0) { + args.push('--', ...runnable.args.executableArgs); + } const definition: CargoTaskDefinition = { type: 'cargo', - label: spec.label, - command: spec.bin, - args: spec.extraArgs ? [...spec.args, '--', ...spec.extraArgs] : spec.args, - env: Object.assign({}, process.env, spec.env), + label: runnable.label, + command, + args, + env: Object.assign({}, process.env as { [key: string]: string }, { "RUST_BACKTRACE": "short" }), }; const execOption: vscode.ShellExecutionOptions = { - cwd: spec.cwd || '.', + cwd: runnable.args.workspaceRoot || '.', env: definition.env, }; const exec = new vscode.ShellExecution( diff --git a/editors/code/src/tasks.ts b/editors/code/src/tasks.ts index 1366c76d6bda..9748824df38f 100644 --- a/editors/code/src/tasks.ts +++ b/editors/code/src/tasks.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import * as toolchain from "./toolchain"; // This ends up as the `type` key in tasks.json. RLS also uses `cargo` and // our configuration should be compatible with it so use the same key. @@ -24,6 +25,8 @@ class CargoTaskProvider implements vscode.TaskProvider { // set of tasks that always exist. These tasks cannot be removed in // tasks.json - only tweaked. + const cargoPath = toolchain.cargoPath(); + return [ { command: 'build', group: vscode.TaskGroup.Build }, { command: 'check', group: vscode.TaskGroup.Build }, @@ -46,7 +49,7 @@ class CargoTaskProvider implements vscode.TaskProvider { `cargo ${command}`, 'rust', // What to do when this command is executed. - new vscode.ShellExecution('cargo', [command]), + new vscode.ShellExecution(cargoPath, [command]), // Problem matchers. ['$rustc'], ); @@ -80,4 +83,4 @@ class CargoTaskProvider implements vscode.TaskProvider { export function activateTaskProvider(target: vscode.WorkspaceFolder): vscode.Disposable { const provider = new CargoTaskProvider(target); return vscode.tasks.registerTaskProvider(TASK_TYPE, provider); -} \ No newline at end of file +} diff --git a/editors/code/src/cargo.ts b/editors/code/src/toolchain.ts similarity index 53% rename from editors/code/src/cargo.ts rename to editors/code/src/toolchain.ts index a55b2f860f68..80a7915e90e8 100644 --- a/editors/code/src/cargo.ts +++ b/editors/code/src/toolchain.ts @@ -1,9 +1,10 @@ import * as cp from 'child_process'; import * as os from 'os'; import * as path from 'path'; +import * as fs from 'fs'; import * as readline from 'readline'; import { OutputChannel } from 'vscode'; -import { isValidExecutable } from './util'; +import { log, memoize } from './util'; interface CompilationArtifact { fileName: string; @@ -17,34 +18,35 @@ export interface ArtifactSpec { filter?: (artifacts: CompilationArtifact[]) => CompilationArtifact[]; } -export function artifactSpec(args: readonly string[]): ArtifactSpec { - const cargoArgs = [...args, "--message-format=json"]; - - // arguments for a runnable from the quick pick should be updated. - // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens - switch (cargoArgs[0]) { - case "run": cargoArgs[0] = "build"; break; - case "test": { - if (!cargoArgs.includes("--no-run")) { - cargoArgs.push("--no-run"); - } - break; - } - } - - const result: ArtifactSpec = { cargoArgs: cargoArgs }; - if (cargoArgs[0] === "test") { - // for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests - // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"} - result.filter = (artifacts) => artifacts.filter(it => it.isTest); - } - - return result; -} - export class Cargo { constructor(readonly rootFolder: string, readonly output: OutputChannel) { } + // Made public for testing purposes + static artifactSpec(args: readonly string[]): ArtifactSpec { + const cargoArgs = [...args, "--message-format=json"]; + + // arguments for a runnable from the quick pick should be updated. + // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_code_lens + switch (cargoArgs[0]) { + case "run": cargoArgs[0] = "build"; break; + case "test": { + if (!cargoArgs.includes("--no-run")) { + cargoArgs.push("--no-run"); + } + break; + } + } + + const result: ArtifactSpec = { cargoArgs: cargoArgs }; + if (cargoArgs[0] === "test") { + // for instance, `crates\rust-analyzer\tests\heavy_tests\main.rs` tests + // produce 2 artifacts: {"kind": "bin"} and {"kind": "test"} + result.filter = (artifacts) => artifacts.filter(it => it.isTest); + } + + return result; + } + private async getArtifacts(spec: ArtifactSpec): Promise { const artifacts: CompilationArtifact[] = []; @@ -77,7 +79,7 @@ export class Cargo { } async executableFromArgs(args: readonly string[]): Promise { - const artifacts = await this.getArtifacts(artifactSpec(args)); + const artifacts = await this.getArtifacts(Cargo.artifactSpec(args)); if (artifacts.length === 0) { throw new Error('No compilation artifacts'); @@ -94,14 +96,7 @@ export class Cargo { onStderrString: (data: string) => void ): Promise { return new Promise((resolve, reject) => { - let cargoPath; - try { - cargoPath = getCargoPathOrFail(); - } catch (err) { - return reject(err); - } - - const cargo = cp.spawn(cargoPath, cargoArgs, { + const cargo = cp.spawn(cargoPath(), cargoArgs, { stdio: ['ignore', 'pipe', 'pipe'], cwd: this.rootFolder }); @@ -126,26 +121,54 @@ export class Cargo { } } -// Mirrors `ra_env::get_path_for_executable` implementation -function getCargoPathOrFail(): string { - const envVar = process.env.CARGO; - const executableName = "cargo"; - - if (envVar) { - if (isValidExecutable(envVar)) return envVar; - - throw new Error(`\`${envVar}\` environment variable points to something that's not a valid executable`); - } - - if (isValidExecutable(executableName)) return executableName; - - const standardLocation = path.join(os.homedir(), '.cargo', 'bin', executableName); - - if (isValidExecutable(standardLocation)) return standardLocation; - - throw new Error( - `Failed to find \`${executableName}\` executable. ` + - `Make sure \`${executableName}\` is in \`$PATH\`, ` + - `or set \`${envVar}\` to point to a valid executable.` - ); +/** Mirrors `ra_toolchain::cargo()` implementation */ +export function cargoPath(): string { + return getPathForExecutable("cargo"); +} + +/** Mirrors `ra_toolchain::get_path_for_executable()` implementation */ +export const getPathForExecutable = memoize( + // We apply caching to decrease file-system interactions + (executableName: "cargo" | "rustc" | "rustup"): string => { + { + const envVar = process.env[executableName.toUpperCase()]; + if (envVar) return envVar; + } + + if (lookupInPath(executableName)) return executableName; + + try { + // hmm, `os.homedir()` seems to be infallible + // it is not mentioned in docs and cannot be infered by the type signature... + const standardPath = path.join(os.homedir(), ".cargo", "bin", executableName); + + if (isFile(standardPath)) return standardPath; + } catch (err) { + log.error("Failed to read the fs info", err); + } + return executableName; + } +); + +function lookupInPath(exec: string): boolean { + const paths = process.env.PATH ?? "";; + + const candidates = paths.split(path.delimiter).flatMap(dirInPath => { + const candidate = path.join(dirInPath, exec); + return os.type() === "Windows_NT" + ? [candidate, `${candidate}.exe`] + : [candidate]; + }); + + return candidates.some(isFile); +} + +function isFile(suspectPath: string): boolean { + // It is not mentionned in docs, but `statSync()` throws an error when + // the path doesn't exist + try { + return fs.statSync(suspectPath).isFile(); + } catch { + return false; + } } diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts index 352ef9162f3d..fe3fb71cd7dc 100644 --- a/editors/code/src/util.ts +++ b/editors/code/src/util.ts @@ -99,3 +99,21 @@ export function isValidExecutable(path: string): boolean { export function setContextValue(key: string, value: any): Thenable { return vscode.commands.executeCommand('setContext', key, value); } + +/** + * Returns a higher-order function that caches the results of invoking the + * underlying function. + */ +export function memoize(func: (this: TThis, arg: Param) => Ret) { + const cache = new Map(); + + return function(this: TThis, arg: Param) { + const cached = cache.get(arg); + if (cached) return cached; + + const result = func.call(this, arg); + cache.set(arg, result); + + return result; + }; +} diff --git a/editors/code/tests/unit/launch_config.test.ts b/editors/code/tests/unit/launch_config.test.ts index d5cf1b74eadd..68794d53ede1 100644 --- a/editors/code/tests/unit/launch_config.test.ts +++ b/editors/code/tests/unit/launch_config.test.ts @@ -1,25 +1,25 @@ import * as assert from 'assert'; -import * as cargo from '../../src/cargo'; +import { Cargo } from '../../src/toolchain'; suite('Launch configuration', () => { suite('Lens', () => { test('A binary', async () => { - const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]); + const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "pkg_name"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]); assert.deepEqual(args.filter, undefined); }); test('One of Multiple Binaries', async () => { - const args = cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]); + const args = Cargo.artifactSpec(["build", "--package", "pkg_name", "--bin", "bin1"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin1", "--message-format=json"]); assert.deepEqual(args.filter, undefined); }); test('A test', async () => { - const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]); + const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib", "--no-run"]); assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--no-run", "--message-format=json"]); assert.notDeepEqual(args.filter, undefined); @@ -28,7 +28,7 @@ suite('Launch configuration', () => { suite('QuickPick', () => { test('A binary', async () => { - const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]); + const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "pkg_name"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "pkg_name", "--message-format=json"]); assert.deepEqual(args.filter, undefined); @@ -36,14 +36,14 @@ suite('Launch configuration', () => { test('One of Multiple Binaries', async () => { - const args = cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]); + const args = Cargo.artifactSpec(["run", "--package", "pkg_name", "--bin", "bin2"]); assert.deepEqual(args.cargoArgs, ["build", "--package", "pkg_name", "--bin", "bin2", "--message-format=json"]); assert.deepEqual(args.filter, undefined); }); test('A test', async () => { - const args = cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]); + const args = Cargo.artifactSpec(["test", "--package", "pkg_name", "--lib"]); assert.deepEqual(args.cargoArgs, ["test", "--package", "pkg_name", "--lib", "--message-format=json", "--no-run"]); assert.notDeepEqual(args.filter, undefined); diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs index f60f0fb16ce1..392648d7133d 100644 --- a/xtask/src/ast_src.rs +++ b/xtask/src/ast_src.rs @@ -1707,7 +1707,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc { /// ``` /// /// [Reference](https://doc.rust-lang.org/reference/items/generics.html#where-clauses) - struct WherePred: TypeBoundsOwner { T![lifetime], TypeRef } + struct WherePred: TypeBoundsOwner { T![for], TypeParamList, T![lifetime], TypeRef } /// Where clause. /// diff --git a/xtask/src/codegen.rs b/xtask/src/codegen.rs index 5511c01d5487..f5f4b964a4c1 100644 --- a/xtask/src/codegen.rs +++ b/xtask/src/codegen.rs @@ -18,8 +18,10 @@ use std::{ use crate::{not_bash::fs2, project_root, Result}; pub use self::{ - gen_assists_docs::generate_assists_docs, gen_feature_docs::generate_feature_docs, - gen_parser_tests::generate_parser_tests, gen_syntax::generate_syntax, + gen_assists_docs::{generate_assists_docs, generate_assists_tests}, + gen_feature_docs::generate_feature_docs, + gen_parser_tests::generate_parser_tests, + gen_syntax::generate_syntax, }; const GRAMMAR_DIR: &str = "crates/ra_parser/src/grammar"; diff --git a/xtask/src/codegen/gen_assists_docs.rs b/xtask/src/codegen/gen_assists_docs.rs index 6c1be53503dc..526941f73ace 100644 --- a/xtask/src/codegen/gen_assists_docs.rs +++ b/xtask/src/codegen/gen_assists_docs.rs @@ -7,16 +7,17 @@ use crate::{ project_root, rust_files, Result, }; +pub fn generate_assists_tests(mode: Mode) -> Result<()> { + let assists = Assist::collect()?; + generate_tests(&assists, mode) +} + pub fn generate_assists_docs(mode: Mode) -> Result<()> { let assists = Assist::collect()?; - generate_tests(&assists, mode)?; - let contents = assists.into_iter().map(|it| it.to_string()).collect::>().join("\n\n"); let contents = contents.trim().to_string() + "\n"; let dst = project_root().join("docs/user/generated_assists.adoc"); - codegen::update(&dst, &contents, mode)?; - - Ok(()) + codegen::update(&dst, &contents, mode) } #[derive(Debug)] diff --git a/xtask/src/codegen/gen_syntax.rs b/xtask/src/codegen/gen_syntax.rs index 19d5594f5b06..745a25862b68 100644 --- a/xtask/src/codegen/gen_syntax.rs +++ b/xtask/src/codegen/gen_syntax.rs @@ -40,7 +40,7 @@ fn generate_tokens(grammar: AstSrc<'_>) -> Result { pub(crate) syntax: SyntaxToken, } impl std::fmt::Display for #name { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(&self.syntax, f) } } @@ -68,7 +68,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result { .iter() .map(|node| { let name = format_ident!("{}", node.name); - let kind = format_ident!("{}", to_upper_snake_case(&name.to_string())); + let kind = format_ident!("{}", to_upper_snake_case(node.name)); let traits = node.traits.iter().map(|trait_name| { let trait_name = format_ident!("{}", trait_name); quote!(impl ast::#trait_name for #name {}) @@ -199,7 +199,7 @@ fn generate_nodes(kinds: KindsSrc<'_>, grammar: AstSrc<'_>) -> Result { enum_names.chain(node_names.clone()).map(|it| format_ident!("{}", it)).map(|name| { quote! { impl std::fmt::Display for #name { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { std::fmt::Display::fmt(self.syntax(), f) } } diff --git a/xtask/src/lib.rs b/xtask/src/lib.rs index 874957885e84..747654c1fcb2 100644 --- a/xtask/src/lib.rs +++ b/xtask/src/lib.rs @@ -4,6 +4,7 @@ pub mod not_bash; pub mod install; +pub mod release; pub mod dist; pub mod pre_commit; @@ -19,7 +20,7 @@ use walkdir::{DirEntry, WalkDir}; use crate::{ codegen::Mode, - not_bash::{date_iso, fs2, pushd, pushenv, rm_rf, run}, + not_bash::{fs2, pushd, pushenv, rm_rf, run}, }; pub use anyhow::{bail, Context as _, Result}; @@ -153,58 +154,6 @@ pub fn run_pre_cache() -> Result<()> { Ok(()) } -pub fn run_release(dry_run: bool) -> Result<()> { - if !dry_run { - run!("git switch release")?; - run!("git fetch upstream --tags --force")?; - run!("git reset --hard tags/nightly")?; - run!("git push")?; - } - - let website_root = project_root().join("../rust-analyzer.github.io"); - let changelog_dir = website_root.join("./thisweek/_posts"); - - let today = date_iso()?; - let commit = run!("git rev-parse HEAD")?; - let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count(); - - let contents = format!( - "\ -= Changelog #{} -:sectanchors: -:page-layout: post - -Commit: commit:{}[] + -Release: release:{}[] - -== New Features - -* pr:[] . - -== Fixes - -== Internal Improvements -", - changelog_n, commit, today - ); - - let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); - fs2::write(&path, &contents)?; - - for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { - let src = project_root().join("./docs/user/").join(adoc); - let dst = website_root.join(adoc); - fs2::copy(src, dst)?; - } - - let tags = run!("git tag --list"; echo = false)?; - let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); - - println!("\n git log {}..HEAD --merges --reverse", prev_tag); - - Ok(()) -} - fn is_release_tag(tag: &str) -> bool { tag.len() == "2020-02-24".len() && tag.starts_with(|c: char| c.is_ascii_digit()) } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index 9d7cdd1145ae..f7a79362d95f 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -16,8 +16,9 @@ use xtask::{ dist::run_dist, install::{ClientOpt, InstallCmd, ServerOpt}, not_bash::pushd, - pre_commit, project_root, run_clippy, run_fuzzer, run_pre_cache, run_release, run_rustfmt, - Result, + pre_commit, project_root, + release::ReleaseCmd, + run_clippy, run_fuzzer, run_pre_cache, run_rustfmt, Result, }; fn main() -> Result<()> { @@ -74,6 +75,7 @@ FLAGS: args.finish()?; codegen::generate_syntax(Mode::Overwrite)?; codegen::generate_parser_tests(Mode::Overwrite)?; + codegen::generate_assists_tests(Mode::Overwrite)?; codegen::generate_assists_docs(Mode::Overwrite)?; codegen::generate_feature_docs(Mode::Overwrite)?; Ok(()) @@ -101,7 +103,7 @@ FLAGS: "release" => { let dry_run = args.contains("--dry-run"); args.finish()?; - run_release(dry_run) + ReleaseCmd { dry_run }.run() } "dist" => { let nightly = args.contains("--nightly"); diff --git a/xtask/src/release.rs b/xtask/src/release.rs new file mode 100644 index 000000000000..36c912184f0c --- /dev/null +++ b/xtask/src/release.rs @@ -0,0 +1,67 @@ +use crate::{ + codegen, is_release_tag, + not_bash::{date_iso, fs2, run}, + project_root, Mode, Result, +}; + +pub struct ReleaseCmd { + pub dry_run: bool, +} + +impl ReleaseCmd { + pub fn run(self) -> Result<()> { + if !self.dry_run { + run!("git switch release")?; + run!("git fetch upstream --tags --force")?; + run!("git reset --hard tags/nightly")?; + run!("git push")?; + } + codegen::generate_assists_docs(Mode::Overwrite)?; + codegen::generate_feature_docs(Mode::Overwrite)?; + + let website_root = project_root().join("../rust-analyzer.github.io"); + let changelog_dir = website_root.join("./thisweek/_posts"); + + let today = date_iso()?; + let commit = run!("git rev-parse HEAD")?; + let changelog_n = fs2::read_dir(changelog_dir.as_path())?.count(); + + let contents = format!( + "\ + = Changelog #{} + :sectanchors: + :page-layout: post + + Commit: commit:{}[] + + Release: release:{}[] + + == New Features + + * pr:[] . + + == Fixes + + == Internal Improvements + ", + changelog_n, commit, today + ); + + let path = changelog_dir.join(format!("{}-changelog-{}.adoc", today, changelog_n)); + fs2::write(&path, &contents)?; + + for &adoc in ["manual.adoc", "generated_features.adoc", "generated_assists.adoc"].iter() { + let src = project_root().join("./docs/user/").join(adoc); + let dst = website_root.join(adoc); + fs2::copy(src, dst)?; + } + + let tags = run!("git tag --list"; echo = false)?; + let prev_tag = tags.lines().filter(|line| is_release_tag(line)).last().unwrap(); + + let git_log = run!("git log {}..HEAD --merges --reverse", prev_tag; echo = false)?; + let git_log_dst = website_root.join("git.log"); + fs2::write(git_log_dst, &git_log)?; + + Ok(()) + } +} diff --git a/xtask/tests/tidy.rs b/xtask/tests/tidy.rs index 4ac5d929fc6b..d38ac7f17e78 100644 --- a/xtask/tests/tidy.rs +++ b/xtask/tests/tidy.rs @@ -25,18 +25,11 @@ fn generated_tests_are_fresh() { #[test] fn generated_assists_are_fresh() { - if let Err(error) = codegen::generate_assists_docs(Mode::Verify) { + if let Err(error) = codegen::generate_assists_tests(Mode::Verify) { panic!("{}. Please update assists by running `cargo xtask codegen`", error); } } -#[test] -fn generated_features_are_fresh() { - if let Err(error) = codegen::generate_feature_docs(Mode::Verify) { - panic!("{}. Please update features by running `cargo xtask codegen`", error); - } -} - #[test] fn check_code_formatting() { if let Err(error) = run_rustfmt(Mode::Verify) { @@ -180,13 +173,11 @@ impl TidyDocs { } fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool { - let mut cur_path = p; - while let Some(path) = cur_path.parent() { - if dirs_to_exclude.iter().any(|dir| path.ends_with(dir)) { - return true; - } - cur_path = path; - } - - false + p.strip_prefix(project_root()) + .unwrap() + .components() + .rev() + .skip(1) + .filter_map(|it| it.as_os_str().to_str()) + .any(|it| dirs_to_exclude.contains(&it)) }