diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 2c1192f072..3f52f31f80 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -39,7 +39,6 @@ jobs: with: toolchain: stable profile: minimal - target: x86_64-unknown-linux-musl override: true - name: Install Nodejs diff --git a/.vscode/launch.json b/.vscode/launch.json index 3f74d75666..6a2fff9065 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -41,7 +41,7 @@ "outFiles": [ "${workspaceFolder}/editors/code/out/**/*.js" ], - "preLaunchTask": "Build Extension", + "preLaunchTask": "Build Server and Extension", "skipFiles": [ "/**/*.js" ], @@ -62,7 +62,7 @@ "outFiles": [ "${workspaceFolder}/editors/code/out/**/*.js" ], - "preLaunchTask": "Build Extension", + "preLaunchTask": "Build Server (Release) and Extension", "skipFiles": [ "/**/*.js" ], diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 4037e7cce1..0969ce89a1 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -4,7 +4,7 @@ "version": "2.0.0", "tasks": [ { - "label": "Build Extension", + "label": "Build Extension in Background", "group": "build", "type": "npm", "script": "watch", @@ -15,6 +15,17 @@ }, "isBackground": true, }, + { + "label": "Build Extension", + "group": "build", + "type": "npm", + "script": "build", + "path": "editors/code/", + "problemMatcher": { + "base": "$tsc", + "fileLocation": ["relative", "${workspaceFolder}/editors/code/"] + }, + }, { "label": "Build Server", "group": "build", @@ -22,5 +33,23 @@ "command": "cargo build --package rust-analyzer", "problemMatcher": "$rustc" }, + { + "label": "Build Server (Release)", + "group": "build", + "type": "shell", + "command": "cargo build --release --package rust-analyzer", + "problemMatcher": "$rustc" + }, + + { + "label": "Build Server and Extension", + "dependsOn": ["Build Server", "Build Extension"], + "problemMatcher": "$rustc" + }, + { + "label": "Build Server (Release) and Extension", + "dependsOn": ["Build Server (Release)", "Build Extension"], + "problemMatcher": "$rustc" + } ] } diff --git a/Cargo.lock b/Cargo.lock index 367ff3f828..522ecf2ee8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -68,9 +68,9 @@ dependencies = [ [[package]] name = "base64" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" [[package]] name = "bitflags" @@ -645,9 +645,9 @@ dependencies = [ [[package]] name = "lsp-types" -version = "0.73.0" +version = "0.74.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93d0cf64ea141b43d9e055f6b9df13f0bce32b103d84237509ce0a571ab9b159" +checksum = "820f746e5716ab9a2d664794636188bd003023b72e55404ee27105dc22869922" dependencies = [ "base64", "bitflags", @@ -1193,9 +1193,9 @@ dependencies = [ [[package]] name = "ra_vfs" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58a265769d5e5655345a9fcbd870a1a7c3658558c0d8efaed79e0669358f46b8" +checksum = "fcaa5615f420134aea7667253db101d03a5c5f300eac607872dc2a36407b2ac9" dependencies = [ "crossbeam-channel", "jod-thread", diff --git a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs index 03806724a3..49deb67017 100644 --- a/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs +++ b/crates/ra_assists/src/handlers/add_from_impl_for_enum.rs @@ -1,11 +1,12 @@ +use ra_ide_db::RootDatabase; use ra_syntax::{ ast::{self, AstNode, NameOwner}, TextSize, }; use stdx::format_to; -use crate::{Assist, AssistCtx, AssistId}; -use ra_ide_db::RootDatabase; +use crate::{utils::FamousDefs, Assist, AssistCtx, AssistId}; +use test_utils::tested_by; // Assist add_from_impl_for_enum // @@ -41,7 +42,8 @@ pub(crate) fn add_from_impl_for_enum(ctx: AssistCtx) -> Option { _ => return None, }; - if already_has_from_impl(ctx.sema, &variant) { + if existing_from_impl(ctx.sema, &variant).is_some() { + tested_by!(test_add_from_impl_already_exists); return None; } @@ -70,41 +72,33 @@ impl From<{0}> for {1} {{ ) } -fn already_has_from_impl( +fn existing_from_impl( sema: &'_ hir::Semantics<'_, RootDatabase>, variant: &ast::EnumVariant, -) -> bool { - let scope = sema.scope(&variant.syntax()); +) -> Option<()> { + let variant = sema.to_def(variant)?; + let enum_ = variant.parent_enum(sema.db); + let krate = enum_.module(sema.db).krate(); - let from_path = ast::make::path_from_text("From"); - let from_hir_path = match hir::Path::from_ast(from_path) { - Some(p) => p, - None => return false, - }; - let from_trait = match scope.resolve_hir_path(&from_hir_path) { - Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t, - _ => return false, - }; + let from_trait = FamousDefs(sema, krate).core_convert_From()?; - let e: hir::Enum = match sema.to_def(&variant.parent_enum()) { - Some(e) => e, - None => return false, - }; - let e_ty = e.ty(sema.db); + let enum_type = enum_.ty(sema.db); - let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) { - Some(ev) => ev, - None => return false, - }; - let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db); + let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db); - e_ty.impls_trait(sema.db, from_trait, &[var_ty]) + if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) { + Some(()) + } else { + None + } } #[cfg(test)] mod tests { use super::*; + use crate::helpers::{check_assist, check_assist_not_applicable}; + use test_utils::covers; #[test] fn test_add_from_impl_for_enum() { @@ -136,36 +130,40 @@ mod tests { ); } + 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(add_from_impl_for_enum, &fixture) + } + #[test] fn test_add_from_impl_no_element() { - check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }"); + check_not_applicable("enum A { <|>One }"); } #[test] fn test_add_from_impl_more_than_one_element_in_tuple() { - check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }"); + check_not_applicable("enum A { <|>One(u32, String) }"); } #[test] fn test_add_from_impl_struct_variant() { - check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }"); + check_not_applicable("enum A { <|>One { x: u32 } }"); } #[test] fn test_add_from_impl_already_exists() { - check_assist_not_applicable( - add_from_impl_for_enum, - r#"enum A { <|>One(u32), } + covers!(test_add_from_impl_already_exists); + check_not_applicable( + r#" +enum A { <|>One(u32), } impl From for A { fn from(v: u32) -> Self { A::One(v) } } - -pub trait From { - fn from(T) -> Self; -}"#, +"#, ); } 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 0a0a88f3dc..9841f6980b 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 @@ -1,11 +1,10 @@ use ra_fmt::unwrap_trivial_block; use ra_syntax::{ - ast::{self, make}, + ast::{self, edit::IndentLevel, make}, AstNode, }; -use crate::{Assist, AssistCtx, AssistId}; -use ast::edit::IndentLevel; +use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; // Assist: replace_if_let_with_match // @@ -44,15 +43,21 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { ast::ElseBranch::IfExpr(_) => return None, }; - ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", |edit| { + let sema = ctx.sema; + ctx.add_assist(AssistId("replace_if_let_with_match"), "Replace with match", move |edit| { let match_expr = { let then_arm = { let then_expr = unwrap_trivial_block(then_block); - make::match_arm(vec![pat], then_expr) + make::match_arm(vec![pat.clone()], then_expr) }; let else_arm = { + let pattern = sema + .type_of_pat(&pat) + .and_then(|ty| TryEnum::from_ty(sema, &ty)) + .map(|it| it.sad_pattern()) + .unwrap_or_else(|| make::placeholder_pat().into()); let else_expr = unwrap_trivial_block(else_block); - make::match_arm(vec![make::placeholder_pat().into()], else_expr) + make::match_arm(vec![pattern], else_expr) }; make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm])) }; @@ -68,6 +73,7 @@ pub(crate) fn replace_if_let_with_match(ctx: AssistCtx) -> Option { #[cfg(test)] mod tests { use super::*; + use crate::helpers::{check_assist, check_assist_target}; #[test] @@ -145,4 +151,64 @@ impl VariantData { }", ); } + + #[test] + fn special_case_option() { + check_assist( + replace_if_let_with_match, + r#" +enum Option { Some(T), None } +use Option::*; + +fn foo(x: Option) { + <|>if let Some(x) = x { + println!("{}", x) + } else { + println!("none") + } +} + "#, + r#" +enum Option { Some(T), None } +use Option::*; + +fn foo(x: Option) { + <|>match x { + Some(x) => println!("{}", x), + None => println!("none"), + } +} + "#, + ); + } + + #[test] + fn special_case_result() { + check_assist( + replace_if_let_with_match, + r#" +enum Result { Ok(T), Err(E) } +use Result::*; + +fn foo(x: Result) { + <|>if let Ok(x) = x { + println!("{}", x) + } else { + println!("none") + } +} + "#, + r#" +enum Result { Ok(T), Err(E) } +use Result::*; + +fn foo(x: Result) { + <|>match x { + Ok(x) => println!("{}", x), + Err(_) => println!("none"), + } +} + "#, + ); + } } diff --git a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs index bdbaae3898..0cf23b754e 100644 --- a/crates/ra_assists/src/handlers/replace_let_with_if_let.rs +++ b/crates/ra_assists/src/handlers/replace_let_with_if_let.rs @@ -1,6 +1,5 @@ use std::iter::once; -use hir::Adt; use ra_syntax::{ ast::{ self, @@ -12,6 +11,7 @@ use ra_syntax::{ use crate::{ assist_ctx::{Assist, AssistCtx}, + utils::TryEnum, AssistId, }; @@ -45,20 +45,10 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option { let init = let_stmt.initializer()?; let original_pat = let_stmt.pat()?; let ty = ctx.sema.type_of_expr(&init)?; - let enum_ = match ty.as_adt() { - Some(Adt::Enum(it)) => it, - _ => return None, - }; - let happy_case = - [("Result", "Ok"), ("Option", "Some")].iter().find_map(|(known_type, happy_case)| { - if &enum_.name(ctx.db).to_string() == known_type { - return Some(happy_case); - } - None - }); + let happy_variant = TryEnum::from_ty(ctx.sema, &ty).map(|it| it.happy_case()); ctx.add_assist(AssistId("replace_let_with_if_let"), "Replace with if-let", |edit| { - let with_placeholder: ast::Pat = match happy_case { + let with_placeholder: ast::Pat = match happy_variant { None => make::placeholder_pat().into(), Some(var_name) => make::tuple_struct_pat( make::path_unqualified(make::path_segment(make::name_ref(var_name))), diff --git a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs index 62cb7a7631..62d4ea5220 100644 --- a/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs +++ b/crates/ra_assists/src/handlers/replace_unwrap_with_match.rs @@ -1,12 +1,11 @@ use std::iter; use ra_syntax::{ - ast::{self, make}, + ast::{self, edit::IndentLevel, make}, AstNode, }; -use crate::{Assist, AssistCtx, AssistId}; -use ast::edit::IndentLevel; +use crate::{utils::TryEnum, Assist, AssistCtx, AssistId}; // Assist: replace_unwrap_with_match // @@ -38,42 +37,27 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option { } let caller = method_call.expr()?; let ty = ctx.sema.type_of_expr(&caller)?; + let happy_variant = TryEnum::from_ty(ctx.sema, &ty)?.happy_case(); - let type_name = ty.as_adt()?.name(ctx.sema.db).to_string(); + ctx.add_assist(AssistId("replace_unwrap_with_match"), "Replace unwrap with match", |edit| { + let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant))); + let it = make::bind_pat(make::name("a")).into(); + let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); - for (unwrap_type, variant_name) in [("Result", "Ok"), ("Option", "Some")].iter() { - if &type_name == unwrap_type { - return ctx.add_assist( - AssistId("replace_unwrap_with_match"), - "Replace unwrap with match", - |edit| { - let ok_path = - make::path_unqualified(make::path_segment(make::name_ref(variant_name))); - let it = make::bind_pat(make::name("a")).into(); - let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into(); + let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); + let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); - let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a"))); - let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path)); + let unreachable_call = make::unreachable_macro_call().into(); + let err_arm = make::match_arm(iter::once(make::placeholder_pat().into()), unreachable_call); - let unreachable_call = make::unreachable_macro_call().into(); - let err_arm = make::match_arm( - iter::once(make::placeholder_pat().into()), - unreachable_call, - ); + let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); + let match_expr = make::expr_match(caller.clone(), match_arm_list); + let match_expr = IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); - let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]); - let match_expr = make::expr_match(caller.clone(), match_arm_list); - let match_expr = - IndentLevel::from_node(method_call.syntax()).increase_indent(match_expr); - - edit.target(method_call.syntax().text_range()); - edit.set_cursor(caller.syntax().text_range().start()); - edit.replace_ast::(method_call.into(), match_expr); - }, - ); - } - } - None + edit.target(method_call.syntax().text_range()); + edit.set_cursor(caller.syntax().text_range().start()); + edit.replace_ast::(method_call.into(), match_expr); + }) } #[cfg(test)] diff --git a/crates/ra_assists/src/marks.rs b/crates/ra_assists/src/marks.rs index 6c2a2b8b6e..8d910205f0 100644 --- a/crates/ra_assists/src/marks.rs +++ b/crates/ra_assists/src/marks.rs @@ -8,4 +8,5 @@ test_utils::marks![ test_not_inline_mut_variable test_not_applicable_if_variable_unused change_visibility_field_false_positive + test_add_from_impl_already_exists ]; diff --git a/crates/ra_assists/src/utils.rs b/crates/ra_assists/src/utils.rs index 3d6c59bda9..efd9886978 100644 --- a/crates/ra_assists/src/utils.rs +++ b/crates/ra_assists/src/utils.rs @@ -1,7 +1,9 @@ //! Assorted functions shared by several assists. pub(crate) mod insert_use; -use hir::Semantics; +use std::iter; + +use hir::{Adt, Crate, Semantics, Trait, Type}; use ra_ide_db::RootDatabase; use ra_syntax::{ ast::{self, make, NameOwner}, @@ -99,3 +101,109 @@ fn invert_special_case(expr: &ast::Expr) -> Option { _ => None, } } + +#[derive(Clone, Copy)] +pub(crate) enum TryEnum { + Result, + Option, +} + +impl TryEnum { + const ALL: [TryEnum; 2] = [TryEnum::Option, TryEnum::Result]; + + pub(crate) fn from_ty(sema: &Semantics, ty: &Type) -> Option { + let enum_ = match ty.as_adt() { + Some(Adt::Enum(it)) => it, + _ => return None, + }; + TryEnum::ALL.iter().find_map(|&var| { + if &enum_.name(sema.db).to_string() == var.type_name() { + return Some(var); + } + None + }) + } + + pub(crate) fn happy_case(self) -> &'static str { + match self { + TryEnum::Result => "Ok", + TryEnum::Option => "Some", + } + } + + pub(crate) fn sad_pattern(self) -> ast::Pat { + match self { + TryEnum::Result => make::tuple_struct_pat( + make::path_unqualified(make::path_segment(make::name_ref("Err"))), + iter::once(make::placeholder_pat().into()), + ) + .into(), + TryEnum::Option => make::bind_pat(make::name("None")).into(), + } + } + + fn type_name(self) -> &'static str { + match self { + TryEnum::Result => "Result", + TryEnum::Option => "Option", + } + } +} + +/// Helps with finding well-know things inside the standard library. This is +/// somewhat similar to the known paths infra inside hir, but it different; We +/// want to make sure that IDE specific paths don't become interesting inside +/// the compiler itself as well. +pub(crate) struct FamousDefs<'a, 'b>(pub(crate) &'a Semantics<'b, RootDatabase>, pub(crate) Crate); + +#[allow(non_snake_case)] +impl FamousDefs<'_, '_> { + #[cfg(test)] + pub(crate) const FIXTURE: &'static str = r#" +//- /libcore.rs crate:core +pub mod convert{ + pub trait From { + fn from(T) -> Self; + } +} + +pub mod prelude { pub use crate::convert::From } +#[prelude_import] +pub use prelude::*; +"#; + + pub(crate) fn core_convert_From(&self) -> Option { + self.find_trait("core:convert:From") + } + + fn find_trait(&self, path: &str) -> Option { + let db = self.0.db; + let mut path = path.split(':'); + let trait_ = path.next_back()?; + let std_crate = path.next()?; + let std_crate = self + .1 + .dependencies(db) + .into_iter() + .find(|dep| &dep.name.to_string() == std_crate)? + .krate; + + let mut module = std_crate.root_module(db)?; + for segment in path { + module = module.children(db).find_map(|child| { + let name = child.name(db)?; + if &name.to_string() == segment { + Some(child) + } else { + None + } + })?; + } + let def = + module.scope(db, None).into_iter().find(|(name, _def)| &name.to_string() == trait_)?.1; + match def { + hir::ScopeDef::ModuleDef(hir::ModuleDef::Trait(it)) => Some(it), + _ => None, + } + } +} diff --git a/crates/ra_flycheck/Cargo.toml b/crates/ra_flycheck/Cargo.toml index 76e5cada4f..324c33d9dd 100644 --- a/crates/ra_flycheck/Cargo.toml +++ b/crates/ra_flycheck/Cargo.toml @@ -6,7 +6,7 @@ authors = ["rust-analyzer developers"] [dependencies] crossbeam-channel = "0.4.0" -lsp-types = { version = "0.73.0", features = ["proposed"] } +lsp-types = { version = "0.74.0", features = ["proposed"] } log = "0.4.8" cargo_metadata = "0.9.1" serde_json = "1.0.48" diff --git a/crates/ra_fmt/src/lib.rs b/crates/ra_fmt/src/lib.rs index 0b4ba1bbe6..1a30b2b3ab 100644 --- a/crates/ra_fmt/src/lib.rs +++ b/crates/ra_fmt/src/lib.rs @@ -57,18 +57,17 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option { return None; } return Some(expr); - } else { - // Unwrap `{ continue; }` - let (stmt,) = block.statements().next_tuple()?; - if let ast::Stmt::ExprStmt(expr_stmt) = stmt { - if has_anything_else(expr_stmt.syntax()) { - return None; - } - let expr = expr_stmt.expr()?; - match expr.syntax().kind() { - CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr), - _ => (), - } + } + // Unwrap `{ continue; }` + let (stmt,) = block.statements().next_tuple()?; + if let ast::Stmt::ExprStmt(expr_stmt) = stmt { + if has_anything_else(expr_stmt.syntax()) { + return None; + } + let expr = expr_stmt.expr()?; + match expr.syntax().kind() { + CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr), + _ => (), } } None diff --git a/crates/ra_hir/src/code_model.rs b/crates/ra_hir/src/code_model.rs index fb788736d8..af59aa1b6c 100644 --- a/crates/ra_hir/src/code_model.rs +++ b/crates/ra_hir/src/code_model.rs @@ -953,6 +953,16 @@ impl TypeParam { pub fn module(self, db: &dyn HirDatabase) -> Module { self.id.parent.module(db.upcast()).into() } + + pub fn ty(self, db: &dyn HirDatabase) -> Type { + let resolver = self.id.parent.resolver(db.upcast()); + let environment = TraitEnvironment::lower(db, &resolver); + let ty = Ty::Placeholder(self.id); + Type { + krate: self.id.parent.module(db.upcast()).krate, + ty: InEnvironment { value: ty, environment }, + } + } } // FIXME: rename from `ImplDef` to `Impl` @@ -1157,18 +1167,21 @@ impl Type { pub fn fields(&self, db: &dyn HirDatabase) -> Vec<(Field, Type)> { if let Ty::Apply(a_ty) = &self.ty.value { - if let TypeCtor::Adt(AdtId::StructId(s)) = a_ty.ctor { - let var_def = s.into(); - return db - .field_types(var_def) - .iter() - .map(|(local_id, ty)| { - let def = Field { parent: var_def.into(), id: local_id }; - let ty = ty.clone().subst(&a_ty.parameters); - (def, self.derived(ty)) - }) - .collect(); - } + let variant_id = match a_ty.ctor { + TypeCtor::Adt(AdtId::StructId(s)) => s.into(), + TypeCtor::Adt(AdtId::UnionId(u)) => u.into(), + _ => return Vec::new(), + }; + + return db + .field_types(variant_id) + .iter() + .map(|(local_id, ty)| { + let def = Field { parent: variant_id.into(), id: local_id }; + let ty = ty.clone().subst(&a_ty.parameters); + (def, self.derived(ty)) + }) + .collect(); }; Vec::new() } diff --git a/crates/ra_hir/src/semantics.rs b/crates/ra_hir/src/semantics.rs index 86bfb416c8..a0a0f234bf 100644 --- a/crates/ra_hir/src/semantics.rs +++ b/crates/ra_hir/src/semantics.rs @@ -9,6 +9,7 @@ use hir_def::{ AsMacroCall, TraitId, }; use hir_expand::ExpansionInfo; +use hir_ty::associated_type_shorthand_candidates; use itertools::Itertools; use ra_db::{FileId, FileRange}; use ra_prof::profile; @@ -24,8 +25,9 @@ use crate::{ semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx}, source_analyzer::{resolve_hir_path, SourceAnalyzer}, AssocItem, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef, - Name, Origin, Path, ScopeDef, Trait, Type, TypeParam, + Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam, }; +use resolver::TypeNs; #[derive(Debug, Clone, PartialEq, Eq)] pub enum PathResolution { @@ -40,6 +42,44 @@ pub enum PathResolution { AssocItem(AssocItem), } +impl PathResolution { + fn in_type_ns(&self) -> Option { + match self { + PathResolution::Def(ModuleDef::Adt(adt)) => Some(TypeNs::AdtId((*adt).into())), + PathResolution::Def(ModuleDef::BuiltinType(builtin)) => { + Some(TypeNs::BuiltinType(*builtin)) + } + PathResolution::Def(ModuleDef::Const(_)) + | PathResolution::Def(ModuleDef::EnumVariant(_)) + | PathResolution::Def(ModuleDef::Function(_)) + | PathResolution::Def(ModuleDef::Module(_)) + | PathResolution::Def(ModuleDef::Static(_)) + | PathResolution::Def(ModuleDef::Trait(_)) => None, + PathResolution::Def(ModuleDef::TypeAlias(alias)) => { + Some(TypeNs::TypeAliasId((*alias).into())) + } + PathResolution::Local(_) | PathResolution::Macro(_) => None, + PathResolution::TypeParam(param) => Some(TypeNs::GenericParam((*param).into())), + PathResolution::SelfType(impl_def) => Some(TypeNs::SelfType((*impl_def).into())), + PathResolution::AssocItem(AssocItem::Const(_)) + | PathResolution::AssocItem(AssocItem::Function(_)) => None, + PathResolution::AssocItem(AssocItem::TypeAlias(alias)) => { + Some(TypeNs::TypeAliasId((*alias).into())) + } + } + } + + /// Returns an iterator over associated types that may be specified after this path (using + /// `Ty::Assoc` syntax). + pub fn assoc_type_shorthand_candidates( + &self, + db: &dyn HirDatabase, + mut cb: impl FnMut(TypeAlias) -> Option, + ) -> Option { + associated_type_shorthand_candidates(db, self.in_type_ns()?, |_, _, id| cb(id.into())) + } +} + /// Primary API to get semantic information, like types, from syntax trees. pub struct Semantics<'db, DB> { pub db: &'db DB, diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index 5716038543..f467ed3fe2 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs @@ -182,10 +182,6 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr) } - ast::Expr::TryBlockExpr(e) => { - let body = self.collect_block_opt(e.body()); - self.alloc_expr(Expr::TryBlock { body }, syntax_ptr) - } ast::Expr::BlockExpr(e) => self.collect_block(e), ast::Expr::LoopExpr(e) => { let body = self.collect_block_opt(e.loop_body()); diff --git a/crates/ra_hir_def/src/expr.rs b/crates/ra_hir_def/src/expr.rs index a0cdad529b..aad12e1235 100644 --- a/crates/ra_hir_def/src/expr.rs +++ b/crates/ra_hir_def/src/expr.rs @@ -101,9 +101,6 @@ pub enum Expr { Try { expr: ExprId, }, - TryBlock { - body: ExprId, - }, Cast { expr: ExprId, type_ref: TypeRef, @@ -239,7 +236,6 @@ impl Expr { f(*expr); } } - Expr::TryBlock { body } => f(*body), Expr::Loop { body } => f(*body), Expr::While { condition, body } => { f(*condition); diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 83f946eeea..efc60986bd 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -73,11 +73,6 @@ impl<'a> InferenceContext<'a> { self.coerce_merge_branch(&then_ty, &else_ty) } Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), - Expr::TryBlock { body } => { - let _inner = self.infer_expr(*body, expected); - // FIXME should be std::result::Result<{inner}, _> - Ty::Unknown - } Expr::Loop { body } => { self.infer_expr(*body, &Expectation::has_type(Ty::unit())); // FIXME handle break with value diff --git a/crates/ra_hir_ty/src/lib.rs b/crates/ra_hir_ty/src/lib.rs index a8ef32ec59..a6f56c661e 100644 --- a/crates/ra_hir_ty/src/lib.rs +++ b/crates/ra_hir_ty/src/lib.rs @@ -66,7 +66,8 @@ pub use autoderef::autoderef; pub use infer::{InferTy, InferenceResult}; pub use lower::CallableDef; pub use lower::{ - callable_item_sig, ImplTraitLoweringMode, TyDefId, TyLoweringContext, ValueTyDefId, + associated_type_shorthand_candidates, callable_item_sig, ImplTraitLoweringMode, TyDefId, + TyLoweringContext, ValueTyDefId, }; pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment}; diff --git a/crates/ra_hir_ty/src/lower.rs b/crates/ra_hir_ty/src/lower.rs index a6f893037f..9ad6dbe075 100644 --- a/crates/ra_hir_ty/src/lower.rs +++ b/crates/ra_hir_ty/src/lower.rs @@ -17,9 +17,9 @@ use hir_def::{ path::{GenericArg, Path, PathSegment, PathSegments}, resolver::{HasResolver, Resolver, TypeNs}, type_ref::{TypeBound, TypeRef}, - AdtId, AssocContainerId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule, - ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId, - VariantId, + AdtId, AssocContainerId, AssocItemId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, + HasModule, ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, + UnionId, VariantId, }; use ra_arena::map::ArenaMap; use ra_db::CrateId; @@ -34,6 +34,7 @@ use crate::{ Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, PolyFnSig, ProjectionPredicate, ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk, }; +use hir_expand::name::Name; #[derive(Debug)] pub struct TyLoweringContext<'a> { @@ -383,61 +384,38 @@ impl Ty { res: Option, segment: PathSegment<'_>, ) -> Ty { - let traits_from_env: Vec<_> = match res { - Some(TypeNs::SelfType(impl_id)) => match ctx.db.impl_trait(impl_id) { - None => return Ty::Unknown, - Some(trait_ref) => vec![trait_ref.value], - }, - Some(TypeNs::GenericParam(param_id)) => { - let predicates = ctx.db.generic_predicates_for_param(param_id); - let mut traits_: Vec<_> = predicates - .iter() - .filter_map(|pred| match &pred.value { - GenericPredicate::Implemented(tr) => Some(tr.clone()), - _ => None, - }) - .collect(); - // Handle `Self::Type` referring to own associated type in trait definitions - if let GenericDefId::TraitId(trait_id) = param_id.parent { - let generics = generics(ctx.db.upcast(), trait_id.into()); - if generics.params.types[param_id.local_id].provenance - == TypeParamProvenance::TraitSelf - { - let trait_ref = TraitRef { - trait_: trait_id, - substs: Substs::bound_vars(&generics, DebruijnIndex::INNERMOST), + if let Some(res) = res { + let ty = + associated_type_shorthand_candidates(ctx.db, res, move |name, t, associated_ty| { + if name == segment.name { + let substs = match ctx.type_param_mode { + TypeParamLoweringMode::Placeholder => { + // if we're lowering to placeholders, we have to put + // them in now + let s = Substs::type_params( + ctx.db, + ctx.resolver.generic_def().expect( + "there should be generics if there's a generic param", + ), + ); + t.substs.clone().subst_bound_vars(&s) + } + TypeParamLoweringMode::Variable => t.substs.clone(), }; - traits_.push(trait_ref); + // FIXME handle type parameters on the segment + return Some(Ty::Projection(ProjectionTy { + associated_ty, + parameters: substs, + })); } - } - traits_ - } - _ => return Ty::Unknown, - }; - let traits = traits_from_env.into_iter().flat_map(|t| all_super_trait_refs(ctx.db, t)); - for t in traits { - if let Some(associated_ty) = - ctx.db.trait_data(t.trait_).associated_type_by_name(&segment.name) - { - let substs = match ctx.type_param_mode { - TypeParamLoweringMode::Placeholder => { - // if we're lowering to placeholders, we have to put - // them in now - let s = Substs::type_params( - ctx.db, - ctx.resolver - .generic_def() - .expect("there should be generics if there's a generic param"), - ); - t.substs.subst_bound_vars(&s) - } - TypeParamLoweringMode::Variable => t.substs, - }; - // FIXME handle (forbid) type parameters on the segment - return Ty::Projection(ProjectionTy { associated_ty, parameters: substs }); - } + + None + }); + + ty.unwrap_or(Ty::Unknown) + } else { + Ty::Unknown } - Ty::Unknown } fn from_hir_path_inner( @@ -694,6 +672,61 @@ pub fn callable_item_sig(db: &dyn HirDatabase, def: CallableDef) -> PolyFnSig { } } +pub fn associated_type_shorthand_candidates( + db: &dyn HirDatabase, + res: TypeNs, + mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option, +) -> Option { + let traits_from_env: Vec<_> = match res { + TypeNs::SelfType(impl_id) => match db.impl_trait(impl_id) { + None => vec![], + Some(trait_ref) => vec![trait_ref.value], + }, + TypeNs::GenericParam(param_id) => { + let predicates = db.generic_predicates_for_param(param_id); + let mut traits_: Vec<_> = predicates + .iter() + .filter_map(|pred| match &pred.value { + GenericPredicate::Implemented(tr) => Some(tr.clone()), + _ => None, + }) + .collect(); + // Handle `Self::Type` referring to own associated type in trait definitions + if let GenericDefId::TraitId(trait_id) = param_id.parent { + let generics = generics(db.upcast(), trait_id.into()); + if generics.params.types[param_id.local_id].provenance + == TypeParamProvenance::TraitSelf + { + let trait_ref = TraitRef { + trait_: trait_id, + substs: Substs::bound_vars(&generics, DebruijnIndex::INNERMOST), + }; + traits_.push(trait_ref); + } + } + traits_ + } + _ => vec![], + }; + + for t in traits_from_env.into_iter().flat_map(move |t| all_super_trait_refs(db, t)) { + let data = db.trait_data(t.trait_); + + for (name, assoc_id) in &data.items { + match assoc_id { + AssocItemId::TypeAliasId(alias) => { + if let Some(result) = cb(name, &t, *alias) { + return Some(result); + } + } + AssocItemId::FunctionId(_) | AssocItemId::ConstId(_) => {} + } + } + } + + None +} + /// Build the type of all specific fields of a struct or enum variant. pub(crate) fn field_types_query( db: &dyn HirDatabase, diff --git a/crates/ra_ide/src/completion/complete_dot.rs b/crates/ra_ide/src/completion/complete_dot.rs index 814354ffa6..05f825c6fe 100644 --- a/crates/ra_ide/src/completion/complete_dot.rs +++ b/crates/ra_ide/src/completion/complete_dot.rs @@ -249,6 +249,44 @@ mod tests { ); } + #[test] + fn test_union_field_completion() { + assert_debug_snapshot!( + do_ref_completion( + r" + union Un { + field: u8, + other: u16, + } + + fn foo(u: Un) { + u.<|> + } + ", + ), + @r###" + [ + CompletionItem { + label: "field", + source_range: 140..140, + delete: 140..140, + insert: "field", + kind: Field, + detail: "u8", + }, + CompletionItem { + label: "other", + source_range: 140..140, + delete: 140..140, + insert: "other", + kind: Field, + detail: "u16", + }, + ] + "### + ); + } + #[test] fn test_method_completion() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/complete_qualified_path.rs b/crates/ra_ide/src/completion/complete_qualified_path.rs index dd10f74e6d..aa56a5cd87 100644 --- a/crates/ra_ide/src/completion/complete_qualified_path.rs +++ b/crates/ra_ide/src/completion/complete_qualified_path.rs @@ -5,19 +5,29 @@ use ra_syntax::AstNode; use test_utils::tested_by; use crate::completion::{CompletionContext, Completions}; +use rustc_hash::FxHashSet; pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) { let path = match &ctx.path_prefix { Some(path) => path.clone(), _ => return, }; - let def = match ctx.scope().resolve_hir_path(&path) { - Some(PathResolution::Def(def)) => def, - _ => return, + let scope = ctx.scope(); + let context_module = scope.module(); + + let res = match scope.resolve_hir_path(&path) { + Some(res) => res, + None => return, }; - let context_module = ctx.scope().module(); - match def { - hir::ModuleDef::Module(module) => { + + // Add associated types on type parameters and `Self`. + res.assoc_type_shorthand_candidates(ctx.db, |alias| { + acc.add_type_alias(ctx, alias); + None::<()> + }); + + match res { + PathResolution::Def(hir::ModuleDef::Module(module)) => { let module_scope = module.scope(ctx.db, context_module); for (name, def) in module_scope { if ctx.use_item_syntax.is_some() { @@ -35,7 +45,8 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon acc.add_resolution(ctx, name.to_string(), &def); } } - hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => { + PathResolution::Def(def @ hir::ModuleDef::Adt(_)) + | PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) => { if let hir::ModuleDef::Adt(Adt::Enum(e)) = def { for variant in e.variants(ctx.db) { acc.add_enum_variant(ctx, variant, None); @@ -46,8 +57,10 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db), _ => unreachable!(), }; - // Iterate assoc types separately - // FIXME: complete T::AssocType + + // XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType. + // (where AssocType is defined on a trait, not an inherent impl) + let krate = ctx.krate; if let Some(krate) = krate { let traits_in_scope = ctx.scope().traits_in_scope(); @@ -65,6 +78,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon None::<()> }); + // Iterate assoc types separately ty.iterate_impl_items(ctx.db, krate, |item| { if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { return None; @@ -77,7 +91,8 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon }); } } - hir::ModuleDef::Trait(t) => { + PathResolution::Def(hir::ModuleDef::Trait(t)) => { + // Handles `Trait::assoc` as well as `::assoc`. for item in t.items(ctx.db) { if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { continue; @@ -91,8 +106,38 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon } } } + PathResolution::TypeParam(_) | PathResolution::SelfType(_) => { + if let Some(krate) = ctx.krate { + let ty = match res { + PathResolution::TypeParam(param) => param.ty(ctx.db), + PathResolution::SelfType(impl_def) => impl_def.target_ty(ctx.db), + _ => return, + }; + + let traits_in_scope = ctx.scope().traits_in_scope(); + let mut seen = FxHashSet::default(); + ty.iterate_path_candidates(ctx.db, krate, &traits_in_scope, None, |_ty, item| { + if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) { + return None; + } + + // We might iterate candidates of a trait multiple times here, so deduplicate + // them. + if seen.insert(item) { + match item { + hir::AssocItem::Function(func) => { + acc.add_function(ctx, func, None); + } + hir::AssocItem::Const(ct) => acc.add_const(ctx, ct), + hir::AssocItem::TypeAlias(ty) => acc.add_type_alias(ctx, ty), + } + } + None::<()> + }); + } + } _ => {} - }; + } } #[cfg(test)] @@ -843,6 +888,211 @@ mod tests { ); } + #[test] + fn completes_ty_param_assoc_ty() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + trait Super { + type Ty; + const CONST: u8; + fn func() {} + fn method(&self) {} + } + + trait Sub: Super { + type SubTy; + const C2: (); + fn subfunc() {} + fn submethod(&self) {} + } + + fn foo() { + T::<|> + } + " + ), + @r###" + [ + CompletionItem { + label: "C2", + source_range: 219..219, + delete: 219..219, + insert: "C2", + kind: Const, + detail: "const C2: ();", + }, + CompletionItem { + label: "CONST", + source_range: 219..219, + delete: 219..219, + insert: "CONST", + kind: Const, + detail: "const CONST: u8;", + }, + CompletionItem { + label: "SubTy", + source_range: 219..219, + delete: 219..219, + insert: "SubTy", + kind: TypeAlias, + detail: "type SubTy;", + }, + CompletionItem { + label: "Ty", + source_range: 219..219, + delete: 219..219, + insert: "Ty", + kind: TypeAlias, + detail: "type Ty;", + }, + CompletionItem { + label: "func()", + source_range: 219..219, + delete: 219..219, + insert: "func()$0", + kind: Function, + lookup: "func", + detail: "fn func()", + }, + CompletionItem { + label: "method()", + source_range: 219..219, + delete: 219..219, + insert: "method()$0", + kind: Method, + lookup: "method", + detail: "fn method(&self)", + }, + CompletionItem { + label: "subfunc()", + source_range: 219..219, + delete: 219..219, + insert: "subfunc()$0", + kind: Function, + lookup: "subfunc", + detail: "fn subfunc()", + }, + CompletionItem { + label: "submethod()", + source_range: 219..219, + delete: 219..219, + insert: "submethod()$0", + kind: Method, + lookup: "submethod", + detail: "fn submethod(&self)", + }, + ] + "### + ); + } + + #[test] + fn completes_self_param_assoc_ty() { + assert_debug_snapshot!( + do_reference_completion( + " + //- /lib.rs + trait Super { + type Ty; + const CONST: u8 = 0; + fn func() {} + fn method(&self) {} + } + + trait Sub: Super { + type SubTy; + const C2: () = (); + fn subfunc() {} + fn submethod(&self) {} + } + + struct Wrap(T); + impl Super for Wrap {} + impl Sub for Wrap { + fn subfunc() { + // Should be able to assume `Self: Sub + Super` + Self::<|> + } + } + " + ), + @r###" + [ + CompletionItem { + label: "C2", + source_range: 365..365, + delete: 365..365, + insert: "C2", + kind: Const, + detail: "const C2: () = ();", + }, + CompletionItem { + label: "CONST", + source_range: 365..365, + delete: 365..365, + insert: "CONST", + kind: Const, + detail: "const CONST: u8 = 0;", + }, + CompletionItem { + label: "SubTy", + source_range: 365..365, + delete: 365..365, + insert: "SubTy", + kind: TypeAlias, + detail: "type SubTy;", + }, + CompletionItem { + label: "Ty", + source_range: 365..365, + delete: 365..365, + insert: "Ty", + kind: TypeAlias, + detail: "type Ty;", + }, + CompletionItem { + label: "func()", + source_range: 365..365, + delete: 365..365, + insert: "func()$0", + kind: Function, + lookup: "func", + detail: "fn func()", + }, + CompletionItem { + label: "method()", + source_range: 365..365, + delete: 365..365, + insert: "method()$0", + kind: Method, + lookup: "method", + detail: "fn method(&self)", + }, + CompletionItem { + label: "subfunc()", + source_range: 365..365, + delete: 365..365, + insert: "subfunc()$0", + kind: Function, + lookup: "subfunc", + detail: "fn subfunc()", + }, + CompletionItem { + label: "submethod()", + source_range: 365..365, + delete: 365..365, + insert: "submethod()$0", + kind: Method, + lookup: "submethod", + detail: "fn submethod(&self)", + }, + ] + "### + ); + } + #[test] fn completes_type_alias() { assert_debug_snapshot!( diff --git a/crates/ra_ide/src/completion/complete_unqualified_path.rs b/crates/ra_ide/src/completion/complete_unqualified_path.rs index f559f2b970..a6a5568de0 100644 --- a/crates/ra_ide/src/completion/complete_unqualified_path.rs +++ b/crates/ra_ide/src/completion/complete_unqualified_path.rs @@ -53,7 +53,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T // Variants with trivial paths are already added by the existing completion logic, // so we should avoid adding these twice if path.segments.len() > 1 { - acc.add_enum_variant(ctx, variant, Some(path.to_string())); + acc.add_qualified_enum_variant(ctx, variant, path); } } } @@ -1173,6 +1173,7 @@ mod tests { delete: 248..250, insert: "Foo::Bar", kind: EnumVariant, + lookup: "Bar", detail: "()", }, CompletionItem { @@ -1181,6 +1182,7 @@ mod tests { delete: 248..250, insert: "Foo::Baz", kind: EnumVariant, + lookup: "Baz", detail: "()", }, CompletionItem { @@ -1189,6 +1191,7 @@ mod tests { delete: 248..250, insert: "Foo::Quux", kind: EnumVariant, + lookup: "Quux", detail: "()", }, ] @@ -1231,6 +1234,7 @@ mod tests { delete: 219..221, insert: "Foo::Bar", kind: EnumVariant, + lookup: "Bar", detail: "()", }, CompletionItem { @@ -1239,6 +1243,7 @@ mod tests { delete: 219..221, insert: "Foo::Baz", kind: EnumVariant, + lookup: "Baz", detail: "()", }, CompletionItem { @@ -1247,6 +1252,7 @@ mod tests { delete: 219..221, insert: "Foo::Quux", kind: EnumVariant, + lookup: "Quux", detail: "()", }, ] @@ -1285,6 +1291,7 @@ mod tests { delete: 185..186, insert: "Foo::Bar", kind: EnumVariant, + lookup: "Bar", detail: "()", }, CompletionItem { @@ -1293,6 +1300,7 @@ mod tests { delete: 185..186, insert: "Foo::Baz", kind: EnumVariant, + lookup: "Baz", detail: "()", }, CompletionItem { @@ -1301,6 +1309,7 @@ mod tests { delete: 185..186, insert: "Foo::Quux", kind: EnumVariant, + lookup: "Quux", detail: "()", }, CompletionItem { @@ -1353,6 +1362,7 @@ mod tests { delete: 98..99, insert: "m::E::V", kind: EnumVariant, + lookup: "V", detail: "()", }, ] diff --git a/crates/ra_ide/src/completion/presentation.rs b/crates/ra_ide/src/completion/presentation.rs index 77d3543760..2edb130cf7 100644 --- a/crates/ra_ide/src/completion/presentation.rs +++ b/crates/ra_ide/src/completion/presentation.rs @@ -1,6 +1,6 @@ //! This modules takes care of rendering various definitions as completion items. -use hir::{Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type}; +use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type}; use ra_syntax::ast::NameOwner; use stdx::SepBy; use test_utils::tested_by; @@ -246,14 +246,37 @@ impl Completions { .add_to(self); } + pub(crate) fn add_qualified_enum_variant( + &mut self, + ctx: &CompletionContext, + variant: hir::EnumVariant, + path: ModPath, + ) { + self.add_enum_variant_impl(ctx, variant, None, Some(path)) + } + pub(crate) fn add_enum_variant( &mut self, ctx: &CompletionContext, variant: hir::EnumVariant, local_name: Option, + ) { + self.add_enum_variant_impl(ctx, variant, local_name, None) + } + + fn add_enum_variant_impl( + &mut self, + ctx: &CompletionContext, + variant: hir::EnumVariant, + local_name: Option, + path: Option, ) { let is_deprecated = is_deprecated(variant, ctx.db); let name = local_name.unwrap_or_else(|| variant.name(ctx.db).to_string()); + let qualified_name = match &path { + Some(it) => it.to_string(), + None => name.to_string(), + }; let detail_types = variant .fields(ctx.db) .into_iter() @@ -271,16 +294,23 @@ impl Completions { .surround_with("{ ", " }") .to_string(), }; - let mut res = - CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone()) - .kind(CompletionItemKind::EnumVariant) - .set_documentation(variant.docs(ctx.db)) - .set_deprecated(is_deprecated) - .detail(detail); + let mut res = CompletionItem::new( + CompletionKind::Reference, + ctx.source_range(), + qualified_name.clone(), + ) + .kind(CompletionItemKind::EnumVariant) + .set_documentation(variant.docs(ctx.db)) + .set_deprecated(is_deprecated) + .detail(detail); + + if path.is_some() { + res = res.lookup_by(name); + } if variant_kind == StructKind::Tuple { let params = Params::Anonymous(variant.fields(ctx.db).len()); - res = res.add_call_parens(ctx, name, params) + res = res.add_call_parens(ctx, qualified_name, params) } res.add_to(self); diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs index b5e2785fe0..db3907fe64 100644 --- a/crates/ra_ide/src/display/function_signature.rs +++ b/crates/ra_ide/src/display/function_signature.rs @@ -26,6 +26,8 @@ pub struct FunctionSignature { pub kind: CallableKind, /// Optional visibility pub visibility: Option, + /// Qualifiers like `async`, `unsafe`, ... + pub qualifier: FunctionQualifier, /// Name of the function pub name: Option, /// Documentation for the function @@ -46,6 +48,16 @@ pub struct FunctionSignature { pub has_self_param: bool, } +#[derive(Debug, Default)] +pub struct FunctionQualifier { + // `async` and `const` are mutually exclusive. Do we need to enforcing it here? + pub is_async: bool, + pub is_const: bool, + pub is_unsafe: bool, + /// The string `extern ".."` + pub extern_abi: Option, +} + impl FunctionSignature { pub(crate) fn with_doc_opt(mut self, doc: Option) -> Self { self.doc = doc; @@ -83,6 +95,8 @@ impl FunctionSignature { FunctionSignature { kind: CallableKind::StructConstructor, visibility: node.visibility().map(|n| n.syntax().text().to_string()), + // Do we need `const`? + qualifier: Default::default(), name: node.name().map(|n| n.text().to_string()), ret_type: node.name().map(|n| n.text().to_string()), parameters: params, @@ -128,6 +142,8 @@ impl FunctionSignature { FunctionSignature { kind: CallableKind::VariantConstructor, visibility: None, + // Do we need `const`? + qualifier: Default::default(), name: Some(name), ret_type: None, parameters: params, @@ -151,6 +167,7 @@ impl FunctionSignature { FunctionSignature { kind: CallableKind::Macro, visibility: None, + qualifier: Default::default(), name: node.name().map(|n| n.text().to_string()), ret_type: None, parameters: params, @@ -223,6 +240,12 @@ impl From<&'_ ast::FnDef> for FunctionSignature { FunctionSignature { kind: CallableKind::Function, visibility: node.visibility().map(|n| n.syntax().text().to_string()), + qualifier: FunctionQualifier { + is_async: node.async_token().is_some(), + is_const: node.const_token().is_some(), + is_unsafe: node.unsafe_token().is_some(), + extern_abi: node.abi().map(|n| n.to_string()), + }, name: node.name().map(|n| n.text().to_string()), ret_type: node .ret_type() @@ -246,6 +269,23 @@ impl Display for FunctionSignature { write!(f, "{} ", t)?; } + if self.qualifier.is_async { + write!(f, "async ")?; + } + + if self.qualifier.is_const { + write!(f, "const ")?; + } + + if self.qualifier.is_unsafe { + write!(f, "unsafe ")?; + } + + if let Some(extern_abi) = &self.qualifier.extern_abi { + // Keyword `extern` is included in the string. + write!(f, "{} ", extern_abi)?; + } + if let Some(name) = &self.name { match self.kind { CallableKind::Function => write!(f, "fn {}", name)?, diff --git a/crates/ra_ide/src/hover.rs b/crates/ra_ide/src/hover.rs index 58c799eca7..a62f598f0d 100644 --- a/crates/ra_ide/src/hover.rs +++ b/crates/ra_ide/src/hover.rs @@ -844,4 +844,29 @@ fn func(foo: i32) { if true { <|>foo; }; } &["fn foo()\n```\n\n<- `\u{3000}` here"], ); } + + #[test] + fn test_hover_function_show_qualifiers() { + check_hover_result( + " + //- /lib.rs + async fn foo<|>() {} + ", + &["async fn foo()"], + ); + check_hover_result( + " + //- /lib.rs + pub const unsafe fn foo<|>() {} + ", + &["pub const unsafe fn foo()"], + ); + check_hover_result( + r#" + //- /lib.rs + pub(crate) async unsafe extern "C" fn foo<|>() {} + "#, + &[r#"pub(crate) async unsafe extern "C" fn foo()"#], + ); + } } diff --git a/crates/ra_ide/src/join_lines.rs b/crates/ra_ide/src/join_lines.rs index fde0bfa98b..d0def7eaaf 100644 --- a/crates/ra_ide/src/join_lines.rs +++ b/crates/ra_ide/src/join_lines.rs @@ -131,6 +131,9 @@ fn has_comma_after(node: &SyntaxNode) -> bool { fn join_single_expr_block(edit: &mut TextEditBuilder, token: &SyntaxToken) -> Option<()> { let block = ast::Block::cast(token.parent())?; let block_expr = ast::BlockExpr::cast(block.syntax().parent()?)?; + if !block_expr.is_standalone() { + return None; + } let expr = extract_trivial_expression(&block_expr)?; let block_range = block_expr.syntax().text_range(); @@ -662,4 +665,67 @@ fn main() { ", ) } + + #[test] + fn join_lines_mandatory_blocks_block() { + check_join_lines( + r" +<|>fn foo() { + 92 +} + ", + r" +<|>fn foo() { 92 +} + ", + ); + + check_join_lines( + r" +fn foo() { + <|>if true { + 92 + } +} + ", + r" +fn foo() { + <|>if true { 92 + } +} + ", + ); + + check_join_lines( + r" +fn foo() { + <|>loop { + 92 + } +} + ", + r" +fn foo() { + <|>loop { 92 + } +} + ", + ); + + check_join_lines( + r" +fn foo() { + <|>unsafe { + 92 + } +} + ", + r" +fn foo() { + <|>unsafe { 92 + } +} + ", + ); + } } diff --git a/crates/ra_parser/src/grammar/expressions/atom.rs b/crates/ra_parser/src/grammar/expressions/atom.rs index 0d277a5866..76aa601cb5 100644 --- a/crates/ra_parser/src/grammar/expressions/atom.rs +++ b/crates/ra_parser/src/grammar/expressions/atom.rs @@ -84,7 +84,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar T![box] => box_expr(p, None), T![for] => for_expr(p, None), T![while] => while_expr(p, None), - T![try] => try_block_expr(p, None), + T![try] => try_expr(p, None), LIFETIME if la == T![:] => { let m = p.start(); label(p); @@ -134,7 +134,7 @@ pub(super) fn atom_expr(p: &mut Parser, r: Restrictions) -> Option<(CompletedMar } }; let blocklike = match done.kind() { - IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR | BLOCK_EXPR | TRY_BLOCK_EXPR => { + IF_EXPR | WHILE_EXPR | FOR_EXPR | LOOP_EXPR | MATCH_EXPR | BLOCK_EXPR | TRY_EXPR => { BlockLike::Block } _ => BlockLike::NotBlock, @@ -532,9 +532,25 @@ fn break_expr(p: &mut Parser, r: Restrictions) -> CompletedMarker { // fn foo() { // let _ = try {}; // } -fn try_block_expr(p: &mut Parser, m: Option) -> CompletedMarker { +fn try_expr(p: &mut Parser, m: Option) -> CompletedMarker { assert!(p.at(T![try])); let m = m.unwrap_or_else(|| p.start()); + // Special-case `try!` as macro. + // This is a hack until we do proper edition support + if p.nth_at(1, T![!]) { + // test try_macro_fallback + // fn foo() { try!(Ok(())); } + let path = p.start(); + let path_segment = p.start(); + let name_ref = p.start(); + p.bump_remap(IDENT); + name_ref.complete(p, NAME_REF); + path_segment.complete(p, PATH_SEGMENT); + path.complete(p, PATH); + let _block_like = items::macro_call_after_excl(p); + return m.complete(p, MACRO_CALL); + } + p.bump(T![try]); block(p); m.complete(p, TRY_EXPR) diff --git a/crates/ra_parser/src/grammar/items.rs b/crates/ra_parser/src/grammar/items.rs index 433ed6812f..1503a87300 100644 --- a/crates/ra_parser/src/grammar/items.rs +++ b/crates/ra_parser/src/grammar/items.rs @@ -415,6 +415,17 @@ pub(super) fn macro_call_after_excl(p: &mut Parser) -> BlockLike { if p.at(IDENT) { name(p); } + // Special-case `macro_rules! try`. + // This is a hack until we do proper edition support + + // test try_macro_rules + // macro_rules! try { () => {} } + if p.at(T![try]) { + let m = p.start(); + p.bump_remap(IDENT); + m.complete(p, NAME); + } + match p.current() { T!['{'] => { token_tree(p); diff --git a/crates/ra_parser/src/grammar/items/use_item.rs b/crates/ra_parser/src/grammar/items/use_item.rs index e3b991c8c6..3a0c7a31a8 100644 --- a/crates/ra_parser/src/grammar/items/use_item.rs +++ b/crates/ra_parser/src/grammar/items/use_item.rs @@ -47,7 +47,7 @@ fn use_tree(p: &mut Parser, top_level: bool) { // use {crate::path::from::root, or::path::from::crate_name}; // Rust 2018 (with a crate named `or`) // use {path::from::root}; // Rust 2015 // use ::{some::arbritrary::path}; // Rust 2015 - // use ::{{{crate::export}}}; // Nonsensical but perfectly legal nestnig + // use ::{{{root::export}}}; // Nonsensical but perfectly legal nesting T!['{'] => { use_tree_list(p); } diff --git a/crates/ra_parser/src/syntax_kind/generated.rs b/crates/ra_parser/src/syntax_kind/generated.rs index 524e7d784e..ab727ed7e1 100644 --- a/crates/ra_parser/src/syntax_kind/generated.rs +++ b/crates/ra_parser/src/syntax_kind/generated.rs @@ -191,7 +191,6 @@ pub enum SyntaxKind { RECORD_LIT, RECORD_FIELD_LIST, RECORD_FIELD, - TRY_BLOCK_EXPR, BOX_EXPR, CALL_EXPR, INDEX_EXPR, diff --git a/crates/ra_proc_macro_srv/src/cli.rs b/crates/ra_proc_macro_srv/src/cli.rs index 7282e5b9b7..1437794c9e 100644 --- a/crates/ra_proc_macro_srv/src/cli.rs +++ b/crates/ra_proc_macro_srv/src/cli.rs @@ -1,15 +1,17 @@ //! Driver for proc macro server -use crate::{expand_task, list_macros}; +use crate::ProcMacroSrv; use ra_proc_macro::msg::{self, Message}; use std::io; pub fn run() -> io::Result<()> { + let mut srv = ProcMacroSrv::default(); + while let Some(req) = read_request()? { let res = match req { - msg::Request::ListMacro(task) => Ok(msg::Response::ListMacro(list_macros(&task))), + msg::Request::ListMacro(task) => srv.list_macros(&task).map(msg::Response::ListMacro), msg::Request::ExpansionMacro(task) => { - expand_task(&task).map(msg::Response::ExpansionMacro) + srv.expand(&task).map(msg::Response::ExpansionMacro) } }; diff --git a/crates/ra_proc_macro_srv/src/dylib.rs b/crates/ra_proc_macro_srv/src/dylib.rs index d202eb0fde..aa84e951cd 100644 --- a/crates/ra_proc_macro_srv/src/dylib.rs +++ b/crates/ra_proc_macro_srv/src/dylib.rs @@ -2,13 +2,12 @@ use crate::{proc_macro::bridge, rustc_server::TokenStream}; use std::fs::File; -use std::path::Path; +use std::path::{Path, PathBuf}; use goblin::{mach::Mach, Object}; use libloading::Library; use memmap::Mmap; use ra_proc_macro::ProcMacroKind; - use std::io; const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_"; @@ -109,23 +108,21 @@ impl ProcMacroLibraryLibloading { } } -type ProcMacroLibraryImpl = ProcMacroLibraryLibloading; - pub struct Expander { - libs: Vec, + inner: ProcMacroLibraryLibloading, } impl Expander { - pub fn new(lib: &Path) -> Result { + pub fn new(lib: &Path) -> io::Result { // Some libraries for dynamic loading require canonicalized path even when it is // already absolute - let lib = lib - .canonicalize() - .unwrap_or_else(|err| panic!("Cannot canonicalize {}: {:?}", lib.display(), err)); + let lib = lib.canonicalize()?; - let library = ProcMacroLibraryImpl::open(&lib).map_err(|e| e.to_string())?; + let lib = ensure_file_with_lock_free_access(&lib)?; - Ok(Expander { libs: vec![library] }) + let library = ProcMacroLibraryLibloading::open(&lib)?; + + Ok(Expander { inner: library }) } pub fn expand( @@ -141,38 +138,36 @@ impl Expander { TokenStream::with_subtree(attr.clone()) }); - for lib in &self.libs { - for proc_macro in &lib.exported_macros { - match proc_macro { - bridge::client::ProcMacro::CustomDerive { trait_name, client, .. } - if *trait_name == macro_name => - { - let res = client.run( - &crate::proc_macro::bridge::server::SameThread, - crate::rustc_server::Rustc::default(), - parsed_body, - ); - return res.map(|it| it.subtree); - } - bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { - let res = client.run( - &crate::proc_macro::bridge::server::SameThread, - crate::rustc_server::Rustc::default(), - parsed_body, - ); - return res.map(|it| it.subtree); - } - bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { - let res = client.run( - &crate::proc_macro::bridge::server::SameThread, - crate::rustc_server::Rustc::default(), - parsed_attributes, - parsed_body, - ); - return res.map(|it| it.subtree); - } - _ => continue, + for proc_macro in &self.inner.exported_macros { + match proc_macro { + bridge::client::ProcMacro::CustomDerive { trait_name, client, .. } + if *trait_name == macro_name => + { + let res = client.run( + &crate::proc_macro::bridge::server::SameThread, + crate::rustc_server::Rustc::default(), + parsed_body, + ); + return res.map(|it| it.subtree); } + bridge::client::ProcMacro::Bang { name, client } if *name == macro_name => { + let res = client.run( + &crate::proc_macro::bridge::server::SameThread, + crate::rustc_server::Rustc::default(), + parsed_body, + ); + return res.map(|it| it.subtree); + } + bridge::client::ProcMacro::Attr { name, client } if *name == macro_name => { + let res = client.run( + &crate::proc_macro::bridge::server::SameThread, + crate::rustc_server::Rustc::default(), + parsed_attributes, + parsed_body, + ); + return res.map(|it| it.subtree); + } + _ => continue, } } @@ -180,9 +175,9 @@ impl Expander { } pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> { - self.libs + self.inner + .exported_macros .iter() - .flat_map(|it| &it.exported_macros) .map(|proc_macro| match proc_macro { bridge::client::ProcMacro::CustomDerive { trait_name, .. } => { (trait_name.to_string(), ProcMacroKind::CustomDerive) @@ -197,3 +192,33 @@ impl Expander { .collect() } } + +/// Copy the dylib to temp directory to prevent locking in Windows +#[cfg(windows)] +fn ensure_file_with_lock_free_access(path: &Path) -> io::Result { + use std::{ffi::OsString, time::SystemTime}; + + let mut to = std::env::temp_dir(); + + let file_name = path.file_name().ok_or_else(|| { + io::Error::new( + io::ErrorKind::InvalidInput, + format!("File path is invalid: {}", path.display()), + ) + })?; + + // generate a time deps unique number + let t = SystemTime::now().duration_since(std::time::UNIX_EPOCH).expect("Time went backwards"); + + let mut unique_name = OsString::from(t.as_millis().to_string()); + unique_name.push(file_name); + + to.push(unique_name); + std::fs::copy(path, &to).unwrap(); + Ok(to) +} + +#[cfg(unix)] +fn ensure_file_with_lock_free_access(path: &Path) -> io::Result { + Ok(path.to_path_buf()) +} diff --git a/crates/ra_proc_macro_srv/src/lib.rs b/crates/ra_proc_macro_srv/src/lib.rs index 3aca859db3..922bb84bbf 100644 --- a/crates/ra_proc_macro_srv/src/lib.rs +++ b/crates/ra_proc_macro_srv/src/lib.rs @@ -21,28 +21,46 @@ mod dylib; use proc_macro::bridge::client::TokenStream; use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask}; -use std::path::Path; +use std::{ + collections::{hash_map::Entry, HashMap}, + fs, + path::{Path, PathBuf}, + time::SystemTime, +}; -pub(crate) fn expand_task(task: &ExpansionTask) -> Result { - let expander = create_expander(&task.lib); +#[derive(Default)] +pub(crate) struct ProcMacroSrv { + expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>, +} - match expander.expand(&task.macro_name, &task.macro_body, task.attributes.as_ref()) { - Ok(expansion) => Ok(ExpansionResult { expansion }), - Err(msg) => { - Err(format!("Cannot perform expansion for {}: error {:?}", &task.macro_name, msg)) +impl ProcMacroSrv { + pub fn expand(&mut self, task: &ExpansionTask) -> Result { + let expander = self.expander(&task.lib)?; + match expander.expand(&task.macro_name, &task.macro_body, task.attributes.as_ref()) { + Ok(expansion) => Ok(ExpansionResult { expansion }), + Err(msg) => { + Err(format!("Cannot perform expansion for {}: error {:?}", &task.macro_name, msg)) + } } } -} -pub(crate) fn list_macros(task: &ListMacrosTask) -> ListMacrosResult { - let expander = create_expander(&task.lib); + pub fn list_macros(&mut self, task: &ListMacrosTask) -> Result { + let expander = self.expander(&task.lib)?; + Ok(ListMacrosResult { macros: expander.list_macros() }) + } - ListMacrosResult { macros: expander.list_macros() } -} + fn expander(&mut self, path: &Path) -> Result<&dylib::Expander, String> { + let time = fs::metadata(path).and_then(|it| it.modified()).map_err(|err| { + format!("Failed to get file metadata for {}: {:?}", path.display(), err) + })?; -fn create_expander(lib: &Path) -> dylib::Expander { - dylib::Expander::new(lib) - .unwrap_or_else(|err| panic!("Cannot create expander for {}: {:?}", lib.display(), err)) + Ok(match self.expanders.entry((path.to_path_buf(), time)) { + Entry::Vacant(v) => v.insert(dylib::Expander::new(path).map_err(|err| { + format!("Cannot create expander for {}: {:?}", path.display(), err) + })?), + Entry::Occupied(e) => e.into_mut(), + }) + } } pub mod cli; diff --git a/crates/ra_proc_macro_srv/src/tests/utils.rs b/crates/ra_proc_macro_srv/src/tests/utils.rs index 2139ec7a4d..646a427c56 100644 --- a/crates/ra_proc_macro_srv/src/tests/utils.rs +++ b/crates/ra_proc_macro_srv/src/tests/utils.rs @@ -1,7 +1,7 @@ //! utils used in proc-macro tests use crate::dylib; -use crate::list_macros; +use crate::ProcMacroSrv; pub use difference::Changeset as __Changeset; use ra_proc_macro::ListMacrosTask; use std::str::FromStr; @@ -59,7 +59,7 @@ pub fn assert_expand( pub fn list(crate_name: &str, version: &str) -> Vec { let path = fixtures::dylib_path(crate_name, version); let task = ListMacrosTask { lib: path }; - - let res = list_macros(&task); + let mut srv = ProcMacroSrv::default(); + let res = srv.list_macros(&task).unwrap(); res.macros.into_iter().map(|(name, kind)| format!("{} [{:?}]", name, kind)).collect() } diff --git a/crates/ra_prof/src/hprof.rs b/crates/ra_prof/src/hprof.rs index 2b8a903636..a3f5321fb3 100644 --- a/crates/ra_prof/src/hprof.rs +++ b/crates/ra_prof/src/hprof.rs @@ -30,8 +30,9 @@ pub fn init_from(spec: &str) { pub type Label = &'static str; /// This function starts a profiling scope in the current execution stack with a given description. -/// It returns a Profile structure and measure elapsed time between this method invocation and Profile structure drop. -/// It supports nested profiling scopes in case when this function invoked multiple times at the execution stack. In this case the profiling information will be nested at the output. +/// It returns a `Profile` struct that measures elapsed time between this method invocation and `Profile` struct drop. +/// It supports nested profiling scopes in case when this function is invoked multiple times at the execution stack. +/// In this case the profiling information will be nested at the output. /// Profiling information is being printed in the stderr. /// /// # Example @@ -58,36 +59,35 @@ pub type Label = &'static str; /// ``` pub fn profile(label: Label) -> Profiler { assert!(!label.is_empty()); - let enabled = PROFILING_ENABLED.load(Ordering::Relaxed) - && PROFILE_STACK.with(|stack| stack.borrow_mut().push(label)); - let label = if enabled { Some(label) } else { None }; - Profiler { label, detail: None } + + if PROFILING_ENABLED.load(Ordering::Relaxed) + && PROFILE_STACK.with(|stack| stack.borrow_mut().push(label)) + { + Profiler(Some(ProfilerImpl { label, detail: None })) + } else { + Profiler(None) + } } -pub struct Profiler { - label: Option