Merge branch 'master' of github.com:rust-analyzer/rust-analyzer
This commit is contained in:
commit
dc34162450
1
.github/workflows/release.yaml
vendored
1
.github/workflows/release.yaml
vendored
@ -39,7 +39,6 @@ jobs:
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
target: x86_64-unknown-linux-musl
|
||||
override: true
|
||||
|
||||
- name: Install Nodejs
|
||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@ -41,7 +41,7 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/editors/code/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build Extension",
|
||||
"preLaunchTask": "Build Server and Extension",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
@ -62,7 +62,7 @@
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/editors/code/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "Build Extension",
|
||||
"preLaunchTask": "Build Server (Release) and Extension",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**/*.js"
|
||||
],
|
||||
|
31
.vscode/tasks.json
vendored
31
.vscode/tasks.json
vendored
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
12
Cargo.lock
generated
12
Cargo.lock
generated
@ -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",
|
||||
|
@ -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<Assist> {
|
||||
_ => 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 @@ fn from(v: {0}) -> Self {{
|
||||
)
|
||||
}
|
||||
|
||||
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 @@ fn from(v: foo::bar::baz::Boo) -> Self {
|
||||
);
|
||||
}
|
||||
|
||||
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<u32> for A {
|
||||
fn from(v: u32) -> Self {
|
||||
A::One(v)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait From<T> {
|
||||
fn from(T) -> Self;
|
||||
}"#,
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<Assist> {
|
||||
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<Assist> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::helpers::{check_assist, check_assist_target};
|
||||
|
||||
#[test]
|
||||
@ -145,4 +151,64 @@ pub fn is_struct(&self) -> bool {
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_case_option() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
use Option::*;
|
||||
|
||||
fn foo(x: Option<i32>) {
|
||||
<|>if let Some(x) = x {
|
||||
println!("{}", x)
|
||||
} else {
|
||||
println!("none")
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Option<T> { Some(T), None }
|
||||
use Option::*;
|
||||
|
||||
fn foo(x: Option<i32>) {
|
||||
<|>match x {
|
||||
Some(x) => println!("{}", x),
|
||||
None => println!("none"),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn special_case_result() {
|
||||
check_assist(
|
||||
replace_if_let_with_match,
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
use Result::*;
|
||||
|
||||
fn foo(x: Result<i32, ()>) {
|
||||
<|>if let Ok(x) = x {
|
||||
println!("{}", x)
|
||||
} else {
|
||||
println!("none")
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
use Result::*;
|
||||
|
||||
fn foo(x: Result<i32, ()>) {
|
||||
<|>match x {
|
||||
Ok(x) => println!("{}", x),
|
||||
Err(_) => println!("none"),
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
use std::iter::once;
|
||||
|
||||
use hir::Adt;
|
||||
use ra_syntax::{
|
||||
ast::{
|
||||
self,
|
||||
@ -12,6 +11,7 @@
|
||||
|
||||
use crate::{
|
||||
assist_ctx::{Assist, AssistCtx},
|
||||
utils::TryEnum,
|
||||
AssistId,
|
||||
};
|
||||
|
||||
@ -45,20 +45,10 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<Assist> {
|
||||
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))),
|
||||
|
@ -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<Assist> {
|
||||
}
|
||||
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::<ast::Expr>(method_call.into(), match_expr);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
None
|
||||
edit.target(method_call.syntax().text_range());
|
||||
edit.set_cursor(caller.syntax().text_range().start());
|
||||
edit.replace_ast::<ast::Expr>(method_call.into(), match_expr);
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -8,4 +8,5 @@
|
||||
test_not_inline_mut_variable
|
||||
test_not_applicable_if_variable_unused
|
||||
change_visibility_field_false_positive
|
||||
test_add_from_impl_already_exists
|
||||
];
|
||||
|
@ -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<ast::Expr> {
|
||||
_ => 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<RootDatabase>, ty: &Type) -> Option<TryEnum> {
|
||||
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<T> {
|
||||
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<Trait> {
|
||||
self.find_trait("core:convert:From")
|
||||
}
|
||||
|
||||
fn find_trait(&self, path: &str) -> Option<Trait> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -57,18 +57,17 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
|
||||
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
|
||||
|
@ -953,6 +953,16 @@ pub fn name(self, db: &dyn HirDatabase) -> Name {
|
||||
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 @@ fn go(ty: &Ty) -> bool {
|
||||
|
||||
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()
|
||||
}
|
||||
|
@ -9,6 +9,7 @@
|
||||
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 @@
|
||||
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<TypeNs> {
|
||||
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<R>(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
mut cb: impl FnMut(TypeAlias) -> Option<R>,
|
||||
) -> Option<R> {
|
||||
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,
|
||||
|
@ -182,10 +182,6 @@ fn collect_expr(&mut self, expr: ast::Expr) -> ExprId {
|
||||
|
||||
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());
|
||||
|
@ -101,9 +101,6 @@ pub enum Expr {
|
||||
Try {
|
||||
expr: ExprId,
|
||||
},
|
||||
TryBlock {
|
||||
body: ExprId,
|
||||
},
|
||||
Cast {
|
||||
expr: ExprId,
|
||||
type_ref: TypeRef,
|
||||
@ -239,7 +236,6 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
||||
f(*expr);
|
||||
}
|
||||
}
|
||||
Expr::TryBlock { body } => f(*body),
|
||||
Expr::Loop { body } => f(*body),
|
||||
Expr::While { condition, body } => {
|
||||
f(*condition);
|
||||
|
@ -73,11 +73,6 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
||||
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
|
||||
|
@ -66,7 +66,8 @@ fn from(it: $sv) -> $e {
|
||||
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};
|
||||
|
||||
|
@ -17,9 +17,9 @@
|
||||
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 @@
|
||||
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 @@ fn select_associated_type(
|
||||
res: Option<TypeNs>,
|
||||
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<R>(
|
||||
db: &dyn HirDatabase,
|
||||
res: TypeNs,
|
||||
mut cb: impl FnMut(&Name, &TraitRef, TypeAliasId) -> Option<R>,
|
||||
) -> Option<R> {
|
||||
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,
|
||||
|
@ -249,6 +249,44 @@ fn foo(a: inner::A) {
|
||||
);
|
||||
}
|
||||
|
||||
#[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!(
|
||||
|
@ -5,19 +5,29 @@
|
||||
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 `<Ty as Trait>::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 @@ fn foo() { let _ = <S as Trait>::<|> }
|
||||
);
|
||||
}
|
||||
|
||||
#[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: Sub>() {
|
||||
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>(T);
|
||||
impl<T> Super for Wrap<T> {}
|
||||
impl<T> Sub for Wrap<T> {
|
||||
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!(
|
||||
|
@ -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 @@ fn main() {
|
||||
delete: 248..250,
|
||||
insert: "Foo::Bar",
|
||||
kind: EnumVariant,
|
||||
lookup: "Bar",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1181,6 +1182,7 @@ fn main() {
|
||||
delete: 248..250,
|
||||
insert: "Foo::Baz",
|
||||
kind: EnumVariant,
|
||||
lookup: "Baz",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1189,6 +1191,7 @@ fn main() {
|
||||
delete: 248..250,
|
||||
insert: "Foo::Quux",
|
||||
kind: EnumVariant,
|
||||
lookup: "Quux",
|
||||
detail: "()",
|
||||
},
|
||||
]
|
||||
@ -1231,6 +1234,7 @@ fn main() {
|
||||
delete: 219..221,
|
||||
insert: "Foo::Bar",
|
||||
kind: EnumVariant,
|
||||
lookup: "Bar",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1239,6 +1243,7 @@ fn main() {
|
||||
delete: 219..221,
|
||||
insert: "Foo::Baz",
|
||||
kind: EnumVariant,
|
||||
lookup: "Baz",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1247,6 +1252,7 @@ fn main() {
|
||||
delete: 219..221,
|
||||
insert: "Foo::Quux",
|
||||
kind: EnumVariant,
|
||||
lookup: "Quux",
|
||||
detail: "()",
|
||||
},
|
||||
]
|
||||
@ -1285,6 +1291,7 @@ fn main() {
|
||||
delete: 185..186,
|
||||
insert: "Foo::Bar",
|
||||
kind: EnumVariant,
|
||||
lookup: "Bar",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1293,6 +1300,7 @@ fn main() {
|
||||
delete: 185..186,
|
||||
insert: "Foo::Baz",
|
||||
kind: EnumVariant,
|
||||
lookup: "Baz",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1301,6 +1309,7 @@ fn main() {
|
||||
delete: 185..186,
|
||||
insert: "Foo::Quux",
|
||||
kind: EnumVariant,
|
||||
lookup: "Quux",
|
||||
detail: "()",
|
||||
},
|
||||
CompletionItem {
|
||||
@ -1353,6 +1362,7 @@ fn f() -> m::E {
|
||||
delete: 98..99,
|
||||
insert: "m::E::V",
|
||||
kind: EnumVariant,
|
||||
lookup: "V",
|
||||
detail: "()",
|
||||
},
|
||||
]
|
||||
|
@ -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 @@ pub(crate) fn add_type_alias(&mut self, ctx: &CompletionContext, type_alias: hir
|
||||
.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<String>,
|
||||
) {
|
||||
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<String>,
|
||||
path: Option<ModPath>,
|
||||
) {
|
||||
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 @@ pub(crate) fn add_enum_variant(
|
||||
.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);
|
||||
|
@ -26,6 +26,8 @@ pub struct FunctionSignature {
|
||||
pub kind: CallableKind,
|
||||
/// Optional visibility
|
||||
pub visibility: Option<String>,
|
||||
/// Qualifiers like `async`, `unsafe`, ...
|
||||
pub qualifier: FunctionQualifier,
|
||||
/// Name of the function
|
||||
pub name: Option<String>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
impl FunctionSignature {
|
||||
pub(crate) fn with_doc_opt(mut self, doc: Option<Documentation>) -> Self {
|
||||
self.doc = doc;
|
||||
@ -83,6 +95,8 @@ pub(crate) fn from_struct(db: &RootDatabase, st: hir::Struct) -> Option<Self> {
|
||||
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 @@ pub(crate) fn from_enum_variant(db: &RootDatabase, variant: hir::EnumVariant) ->
|
||||
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 @@ pub(crate) fn from_macro(db: &RootDatabase, macro_def: hir::MacroDef) -> Option<
|
||||
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 @@ fn param_name_list(node: &ast::FnDef) -> Vec<String> {
|
||||
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 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
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)?,
|
||||
|
@ -844,4 +844,29 @@ fn bar() {
|
||||
&["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()"#],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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<Marker>) -> CompletedMarker {
|
||||
fn try_expr(p: &mut Parser, m: Option<Marker>) -> 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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -191,7 +191,6 @@ pub enum SyntaxKind {
|
||||
RECORD_LIT,
|
||||
RECORD_FIELD_LIST,
|
||||
RECORD_FIELD,
|
||||
TRY_BLOCK_EXPR,
|
||||
BOX_EXPR,
|
||||
CALL_EXPR,
|
||||
INDEX_EXPR,
|
||||
|
@ -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)
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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 @@ fn open(file: &Path) -> io::Result<Self> {
|
||||
}
|
||||
}
|
||||
|
||||
type ProcMacroLibraryImpl = ProcMacroLibraryLibloading;
|
||||
|
||||
pub struct Expander {
|
||||
libs: Vec<ProcMacroLibraryImpl>,
|
||||
inner: ProcMacroLibraryLibloading,
|
||||
}
|
||||
|
||||
impl Expander {
|
||||
pub fn new(lib: &Path) -> Result<Expander, String> {
|
||||
pub fn new(lib: &Path) -> io::Result<Expander> {
|
||||
// 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 @@ pub fn expand(
|
||||
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 @@ pub fn expand(
|
||||
}
|
||||
|
||||
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 @@ pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
|
||||
.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<PathBuf> {
|
||||
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<PathBuf> {
|
||||
Ok(path.to_path_buf())
|
||||
}
|
||||
|
@ -21,28 +21,46 @@
|
||||
|
||||
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<ExpansionResult, String> {
|
||||
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<ExpansionResult, String> {
|
||||
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<ListMacrosResult, String> {
|
||||
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;
|
||||
|
@ -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<String> {
|
||||
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()
|
||||
}
|
||||
|
@ -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 fn init_from(spec: &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<Label>,
|
||||
pub struct Profiler(Option<ProfilerImpl>);
|
||||
|
||||
struct ProfilerImpl {
|
||||
label: Label,
|
||||
detail: Option<String>,
|
||||
}
|
||||
|
||||
impl Profiler {
|
||||
pub fn detail(mut self, detail: impl FnOnce() -> String) -> Profiler {
|
||||
if self.label.is_some() {
|
||||
self.detail = Some(detail())
|
||||
if let Some(profiler) = &mut self.0 {
|
||||
profiler.detail = Some(detail())
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Profiler {
|
||||
impl Drop for ProfilerImpl {
|
||||
fn drop(&mut self) {
|
||||
match self {
|
||||
Profiler { label: Some(label), detail } => {
|
||||
PROFILE_STACK.with(|stack| {
|
||||
stack.borrow_mut().pop(label, detail.take());
|
||||
});
|
||||
}
|
||||
Profiler { label: None, .. } => (),
|
||||
}
|
||||
PROFILE_STACK.with(|it| it.borrow_mut().pop(self.label, self.detail.take()));
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,21 +179,18 @@ fn push(&mut self, label: Label) -> bool {
|
||||
pub fn pop(&mut self, label: Label, detail: Option<String>) {
|
||||
let start = self.starts.pop().unwrap();
|
||||
let duration = start.elapsed();
|
||||
let level = self.starts.len();
|
||||
self.messages.finish(Message { duration, label, detail });
|
||||
if level == 0 {
|
||||
if self.starts.is_empty() {
|
||||
let longer_than = self.filter.longer_than;
|
||||
// Convert to millis for comparison to avoid problems with rounding
|
||||
// (otherwise we could print `0ms` despite user's `>0` filter when
|
||||
// `duration` is just a few nanos).
|
||||
if duration.as_millis() > longer_than.as_millis() {
|
||||
let stderr = stderr();
|
||||
if let Some(root) = self.messages.root() {
|
||||
print(&self.messages, root, 0, longer_than, &mut stderr.lock());
|
||||
print(&self.messages, root, 0, longer_than, &mut stderr().lock());
|
||||
}
|
||||
}
|
||||
self.messages.clear();
|
||||
assert!(self.starts.is_empty())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,7 +16,9 @@
|
||||
};
|
||||
|
||||
pub use self::{
|
||||
expr_extensions::{ArrayExprKind, BinOp, ElseBranch, LiteralKind, PrefixOp, RangeOp},
|
||||
expr_extensions::{
|
||||
ArrayExprKind, BinOp, BlockModifier, ElseBranch, LiteralKind, PrefixOp, RangeOp,
|
||||
},
|
||||
extensions::{
|
||||
AttrKind, FieldKind, NameOrNameRef, PathSegmentKind, SelfParamKind, SlicePatComponents,
|
||||
StructKind, TypeBoundKind, VisibilityKind,
|
||||
@ -242,6 +244,21 @@ fn test_comments_preserve_trailing_whitespace() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_four_slash_line_comment() {
|
||||
let file = SourceFile::parse(
|
||||
r#"
|
||||
//// too many slashes to be a doc comment
|
||||
/// doc comment
|
||||
mod foo {}
|
||||
"#,
|
||||
)
|
||||
.ok()
|
||||
.unwrap();
|
||||
let module = file.syntax().descendants().find_map(Module::cast).unwrap();
|
||||
assert_eq!("doc comment", module.doc_comment_text().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_where_predicates() {
|
||||
fn assert_bound(text: &str, bound: Option<TypeBound>) {
|
||||
|
@ -16,7 +16,7 @@ pub fn is_block_like(&self) -> bool {
|
||||
| ast::Expr::WhileExpr(_)
|
||||
| ast::Expr::BlockExpr(_)
|
||||
| ast::Expr::MatchExpr(_)
|
||||
| ast::Expr::TryBlockExpr(_) => true,
|
||||
| ast::Expr::TryExpr(_) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -359,7 +359,22 @@ pub fn kind(&self) -> LiteralKind {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum BlockModifier {
|
||||
Async(SyntaxToken),
|
||||
Unsafe(SyntaxToken),
|
||||
}
|
||||
|
||||
impl ast::BlockExpr {
|
||||
pub fn modifier(&self) -> Option<BlockModifier> {
|
||||
if let Some(token) = self.async_token() {
|
||||
return Some(BlockModifier::Async(token));
|
||||
}
|
||||
if let Some(token) = self.unsafe_token() {
|
||||
return Some(BlockModifier::Unsafe(token));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// false if the block is an intrinsic part of the syntax and can't be
|
||||
/// replaced with arbitrary expression.
|
||||
///
|
||||
@ -368,12 +383,15 @@ impl ast::BlockExpr {
|
||||
/// const FOO: () = { stand_alone };
|
||||
/// ```
|
||||
pub fn is_standalone(&self) -> bool {
|
||||
let kind = match self.syntax().parent() {
|
||||
if self.modifier().is_some() {
|
||||
return false;
|
||||
}
|
||||
let parent = match self.syntax().parent() {
|
||||
Some(it) => it,
|
||||
None => return true,
|
||||
Some(it) => it.kind(),
|
||||
};
|
||||
match kind {
|
||||
FN_DEF | MATCH_ARM | IF_EXPR | WHILE_EXPR | LOOP_EXPR | TRY_BLOCK_EXPR => false,
|
||||
match parent.kind() {
|
||||
FN_DEF | IF_EXPR | WHILE_EXPR | LOOP_EXPR => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
@ -475,16 +475,6 @@ impl LoopExpr {
|
||||
pub fn loop_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![loop]) }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct TryBlockExpr {
|
||||
pub(crate) syntax: SyntaxNode,
|
||||
}
|
||||
impl ast::AttrsOwner for TryBlockExpr {}
|
||||
impl TryBlockExpr {
|
||||
pub fn try_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![try]) }
|
||||
pub fn body(&self) -> Option<BlockExpr> { support::child(&self.syntax) }
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct ForExpr {
|
||||
pub(crate) syntax: SyntaxNode,
|
||||
@ -554,6 +544,7 @@ impl ast::AttrsOwner for BlockExpr {}
|
||||
impl BlockExpr {
|
||||
pub fn label(&self) -> Option<Label> { support::child(&self.syntax) }
|
||||
pub fn unsafe_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![unsafe]) }
|
||||
pub fn async_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![async]) }
|
||||
pub fn block(&self) -> Option<Block> { support::child(&self.syntax) }
|
||||
}
|
||||
|
||||
@ -1249,6 +1240,7 @@ pub struct PathSegment {
|
||||
}
|
||||
impl PathSegment {
|
||||
pub fn coloncolon_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![::]) }
|
||||
pub fn crate_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![crate]) }
|
||||
pub fn l_angle_token(&self) -> Option<SyntaxToken> { support::token(&self.syntax, T![<]) }
|
||||
pub fn name_ref(&self) -> Option<NameRef> { support::child(&self.syntax) }
|
||||
pub fn type_arg_list(&self) -> Option<TypeArgList> { support::child(&self.syntax) }
|
||||
@ -1473,7 +1465,6 @@ pub enum Expr {
|
||||
FieldExpr(FieldExpr),
|
||||
AwaitExpr(AwaitExpr),
|
||||
TryExpr(TryExpr),
|
||||
TryBlockExpr(TryBlockExpr),
|
||||
CastExpr(CastExpr),
|
||||
RefExpr(RefExpr),
|
||||
PrefixExpr(PrefixExpr),
|
||||
@ -1956,17 +1947,6 @@ fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
}
|
||||
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
||||
}
|
||||
impl AstNode for TryBlockExpr {
|
||||
fn can_cast(kind: SyntaxKind) -> bool { kind == TRY_BLOCK_EXPR }
|
||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
if Self::can_cast(syntax.kind()) {
|
||||
Some(Self { syntax })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
fn syntax(&self) -> &SyntaxNode { &self.syntax }
|
||||
}
|
||||
impl AstNode for ForExpr {
|
||||
fn can_cast(kind: SyntaxKind) -> bool { kind == FOR_EXPR }
|
||||
fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
@ -3308,9 +3288,6 @@ fn from(node: AwaitExpr) -> Expr { Expr::AwaitExpr(node) }
|
||||
impl From<TryExpr> for Expr {
|
||||
fn from(node: TryExpr) -> Expr { Expr::TryExpr(node) }
|
||||
}
|
||||
impl From<TryBlockExpr> for Expr {
|
||||
fn from(node: TryBlockExpr) -> Expr { Expr::TryBlockExpr(node) }
|
||||
}
|
||||
impl From<CastExpr> for Expr {
|
||||
fn from(node: CastExpr) -> Expr { Expr::CastExpr(node) }
|
||||
}
|
||||
@ -3341,9 +3318,8 @@ fn can_cast(kind: SyntaxKind) -> bool {
|
||||
TUPLE_EXPR | ARRAY_EXPR | PAREN_EXPR | PATH_EXPR | LAMBDA_EXPR | IF_EXPR
|
||||
| LOOP_EXPR | FOR_EXPR | WHILE_EXPR | CONTINUE_EXPR | BREAK_EXPR | LABEL
|
||||
| BLOCK_EXPR | RETURN_EXPR | MATCH_EXPR | RECORD_LIT | CALL_EXPR | INDEX_EXPR
|
||||
| METHOD_CALL_EXPR | FIELD_EXPR | AWAIT_EXPR | TRY_EXPR | TRY_BLOCK_EXPR
|
||||
| CAST_EXPR | REF_EXPR | PREFIX_EXPR | RANGE_EXPR | BIN_EXPR | LITERAL | MACRO_CALL
|
||||
| BOX_EXPR => true,
|
||||
| METHOD_CALL_EXPR | FIELD_EXPR | AWAIT_EXPR | TRY_EXPR | CAST_EXPR | REF_EXPR
|
||||
| PREFIX_EXPR | RANGE_EXPR | BIN_EXPR | LITERAL | MACRO_CALL | BOX_EXPR => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -3371,7 +3347,6 @@ fn cast(syntax: SyntaxNode) -> Option<Self> {
|
||||
FIELD_EXPR => Expr::FieldExpr(FieldExpr { syntax }),
|
||||
AWAIT_EXPR => Expr::AwaitExpr(AwaitExpr { syntax }),
|
||||
TRY_EXPR => Expr::TryExpr(TryExpr { syntax }),
|
||||
TRY_BLOCK_EXPR => Expr::TryBlockExpr(TryBlockExpr { syntax }),
|
||||
CAST_EXPR => Expr::CastExpr(CastExpr { syntax }),
|
||||
REF_EXPR => Expr::RefExpr(RefExpr { syntax }),
|
||||
PREFIX_EXPR => Expr::PrefixExpr(PrefixExpr { syntax }),
|
||||
@ -3408,7 +3383,6 @@ fn syntax(&self) -> &SyntaxNode {
|
||||
Expr::FieldExpr(it) => &it.syntax,
|
||||
Expr::AwaitExpr(it) => &it.syntax,
|
||||
Expr::TryExpr(it) => &it.syntax,
|
||||
Expr::TryBlockExpr(it) => &it.syntax,
|
||||
Expr::CastExpr(it) => &it.syntax,
|
||||
Expr::RefExpr(it) => &it.syntax,
|
||||
Expr::PrefixExpr(it) => &it.syntax,
|
||||
@ -3889,11 +3863,6 @@ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(self.syntax(), f)
|
||||
}
|
||||
}
|
||||
impl std::fmt::Display for TryBlockExpr {
|
||||
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 {
|
||||
std::fmt::Display::fmt(self.syntax(), f)
|
||||
|
@ -22,8 +22,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path {
|
||||
pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path {
|
||||
path_from_text(&format!("{}::{}", qual, segment))
|
||||
}
|
||||
|
||||
pub fn path_from_text(text: &str) -> ast::Path {
|
||||
fn path_from_text(text: &str) -> ast::Path {
|
||||
ast_from_text(text)
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,12 @@ pub fn kind(&self) -> CommentKind {
|
||||
}
|
||||
|
||||
pub fn prefix(&self) -> &'static str {
|
||||
prefix_by_kind(self.kind())
|
||||
for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() {
|
||||
if *k == self.kind() && self.text().starts_with(prefix) {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,6 +53,7 @@ pub enum CommentPlacement {
|
||||
const COMMENT_PREFIX_TO_KIND: &[(&str, CommentKind)] = {
|
||||
use {CommentPlacement::*, CommentShape::*};
|
||||
&[
|
||||
("////", CommentKind { shape: Line, doc: None }),
|
||||
("///", CommentKind { shape: Line, doc: Some(Outer) }),
|
||||
("//!", CommentKind { shape: Line, doc: Some(Inner) }),
|
||||
("/**", CommentKind { shape: Block, doc: Some(Outer) }),
|
||||
@ -69,15 +75,6 @@ fn kind_by_prefix(text: &str) -> CommentKind {
|
||||
panic!("bad comment text: {:?}", text)
|
||||
}
|
||||
|
||||
fn prefix_by_kind(kind: CommentKind) -> &'static str {
|
||||
for (prefix, k) in COMMENT_PREFIX_TO_KIND.iter() {
|
||||
if *k == kind {
|
||||
return prefix;
|
||||
}
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
impl Whitespace {
|
||||
pub fn spans_multiple_lines(&self) -> bool {
|
||||
let text = self.text();
|
||||
|
@ -96,6 +96,7 @@ pub(crate) fn validate(root: &SyntaxNode) -> Vec<SyntaxError> {
|
||||
ast::RecordField(it) => validate_numeric_name(it.name_ref(), &mut errors),
|
||||
ast::Visibility(it) => validate_visibility(it, &mut errors),
|
||||
ast::RangeExpr(it) => validate_range_expr(it, &mut errors),
|
||||
ast::PathSegment(it) => validate_crate_keyword_in_path_segment(it, &mut errors),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@ -222,3 +223,60 @@ fn validate_range_expr(expr: ast::RangeExpr, errors: &mut Vec<SyntaxError>) {
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_crate_keyword_in_path_segment(
|
||||
segment: ast::PathSegment,
|
||||
errors: &mut Vec<SyntaxError>,
|
||||
) {
|
||||
const ERR_MSG: &str = "The `crate` keyword is only allowed as the first segment of a path";
|
||||
|
||||
let crate_token = match segment.crate_token() {
|
||||
None => return,
|
||||
Some(it) => it,
|
||||
};
|
||||
|
||||
// Disallow both ::crate and foo::crate
|
||||
let mut path = segment.parent_path();
|
||||
if segment.coloncolon_token().is_some() || path.qualifier().is_some() {
|
||||
errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range()));
|
||||
return;
|
||||
}
|
||||
|
||||
// For expressions and types, validation is complete, but we still have
|
||||
// to handle invalid UseItems like this:
|
||||
//
|
||||
// use foo:{crate::bar::baz};
|
||||
//
|
||||
// To handle this we must inspect the parent `UseItem`s and `UseTree`s
|
||||
// but right now we're looking deep inside the nested `Path` nodes because
|
||||
// `Path`s are left-associative:
|
||||
//
|
||||
// ((crate)::bar)::baz)
|
||||
// ^ current value of path
|
||||
//
|
||||
// So we need to climb to the top
|
||||
while let Some(parent) = path.parent_path() {
|
||||
path = parent;
|
||||
}
|
||||
|
||||
// Now that we've found the whole path we need to see if there's a prefix
|
||||
// somewhere in the UseTree hierarchy. This check is arbitrarily deep
|
||||
// because rust allows arbitrary nesting like so:
|
||||
//
|
||||
// use {foo::{{{{crate::bar::baz}}}}};
|
||||
for node in path.syntax().ancestors().skip(1) {
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::UseTree(it) => if let Some(tree_path) = it.path() {
|
||||
// Even a top-level path exists within a `UseTree` so we must explicitly
|
||||
// allow our path but disallow anything else
|
||||
if tree_path != path {
|
||||
errors.push(SyntaxError::new(ERR_MSG, crate_token.text_range()));
|
||||
}
|
||||
},
|
||||
ast::UseTreeList(_it) => continue,
|
||||
_ => return,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,91 @@
|
||||
SOURCE_FILE@0..98
|
||||
USE_ITEM@0..12
|
||||
USE_KW@0..3 "use"
|
||||
WHITESPACE@3..4 " "
|
||||
USE_TREE@4..11
|
||||
PATH@4..11
|
||||
PATH_SEGMENT@4..11
|
||||
COLON2@4..6 "::"
|
||||
CRATE_KW@6..11 "crate"
|
||||
SEMICOLON@11..12 ";"
|
||||
WHITESPACE@12..13 "\n"
|
||||
USE_ITEM@13..54
|
||||
USE_KW@13..16 "use"
|
||||
WHITESPACE@16..17 " "
|
||||
USE_TREE@17..53
|
||||
USE_TREE_LIST@17..53
|
||||
L_CURLY@17..18 "{"
|
||||
USE_TREE@18..23
|
||||
PATH@18..23
|
||||
PATH_SEGMENT@18..23
|
||||
CRATE_KW@18..23 "crate"
|
||||
COMMA@23..24 ","
|
||||
WHITESPACE@24..25 " "
|
||||
USE_TREE@25..52
|
||||
PATH@25..28
|
||||
PATH_SEGMENT@25..28
|
||||
NAME_REF@25..28
|
||||
IDENT@25..28 "foo"
|
||||
COLON2@28..30 "::"
|
||||
USE_TREE_LIST@30..52
|
||||
L_CURLY@30..31 "{"
|
||||
USE_TREE@31..51
|
||||
PATH@31..51
|
||||
PATH@31..46
|
||||
PATH@31..41
|
||||
PATH@31..36
|
||||
PATH_SEGMENT@31..36
|
||||
CRATE_KW@31..36 "crate"
|
||||
COLON2@36..38 "::"
|
||||
PATH_SEGMENT@38..41
|
||||
NAME_REF@38..41
|
||||
IDENT@38..41 "foo"
|
||||
COLON2@41..43 "::"
|
||||
PATH_SEGMENT@43..46
|
||||
NAME_REF@43..46
|
||||
IDENT@43..46 "bar"
|
||||
COLON2@46..48 "::"
|
||||
PATH_SEGMENT@48..51
|
||||
NAME_REF@48..51
|
||||
IDENT@48..51 "baz"
|
||||
R_CURLY@51..52 "}"
|
||||
R_CURLY@52..53 "}"
|
||||
SEMICOLON@53..54 ";"
|
||||
WHITESPACE@54..55 "\n"
|
||||
USE_ITEM@55..72
|
||||
USE_KW@55..58 "use"
|
||||
WHITESPACE@58..59 " "
|
||||
USE_TREE@59..71
|
||||
PATH@59..71
|
||||
PATH@59..64
|
||||
PATH_SEGMENT@59..64
|
||||
NAME_REF@59..64
|
||||
IDENT@59..64 "hello"
|
||||
COLON2@64..66 "::"
|
||||
PATH_SEGMENT@66..71
|
||||
CRATE_KW@66..71 "crate"
|
||||
SEMICOLON@71..72 ";"
|
||||
WHITESPACE@72..73 "\n"
|
||||
USE_ITEM@73..97
|
||||
USE_KW@73..76 "use"
|
||||
WHITESPACE@76..77 " "
|
||||
USE_TREE@77..96
|
||||
PATH@77..96
|
||||
PATH@77..89
|
||||
PATH@77..82
|
||||
PATH_SEGMENT@77..82
|
||||
NAME_REF@77..82
|
||||
IDENT@77..82 "hello"
|
||||
COLON2@82..84 "::"
|
||||
PATH_SEGMENT@84..89
|
||||
CRATE_KW@84..89 "crate"
|
||||
COLON2@89..91 "::"
|
||||
PATH_SEGMENT@91..96
|
||||
NAME_REF@91..96
|
||||
IDENT@91..96 "there"
|
||||
SEMICOLON@96..97 ";"
|
||||
WHITESPACE@97..98 "\n"
|
||||
error 6..11: The `crate` keyword is only allowed as the first segment of a path
|
||||
error 31..36: The `crate` keyword is only allowed as the first segment of a path
|
||||
error 66..71: The `crate` keyword is only allowed as the first segment of a path
|
||||
error 84..89: The `crate` keyword is only allowed as the first segment of a path
|
@ -0,0 +1,4 @@
|
||||
use ::crate;
|
||||
use {crate, foo::{crate::foo::bar::baz}};
|
||||
use hello::crate;
|
||||
use hello::crate::there;
|
@ -1,4 +1,4 @@
|
||||
SOURCE_FILE@0..250
|
||||
SOURCE_FILE@0..249
|
||||
USE_ITEM@0..58
|
||||
USE_KW@0..3 "use"
|
||||
WHITESPACE@3..4 " "
|
||||
@ -104,32 +104,33 @@ SOURCE_FILE@0..250
|
||||
WHITESPACE@166..167 " "
|
||||
COMMENT@167..179 "// Rust 2015"
|
||||
WHITESPACE@179..180 "\n"
|
||||
USE_ITEM@180..206
|
||||
USE_ITEM@180..205
|
||||
USE_KW@180..183 "use"
|
||||
WHITESPACE@183..184 " "
|
||||
USE_TREE@184..205
|
||||
USE_TREE@184..204
|
||||
COLON2@184..186 "::"
|
||||
USE_TREE_LIST@186..205
|
||||
USE_TREE_LIST@186..204
|
||||
L_CURLY@186..187 "{"
|
||||
USE_TREE@187..204
|
||||
USE_TREE_LIST@187..204
|
||||
USE_TREE@187..203
|
||||
USE_TREE_LIST@187..203
|
||||
L_CURLY@187..188 "{"
|
||||
USE_TREE@188..203
|
||||
USE_TREE_LIST@188..203
|
||||
USE_TREE@188..202
|
||||
USE_TREE_LIST@188..202
|
||||
L_CURLY@188..189 "{"
|
||||
USE_TREE@189..202
|
||||
PATH@189..202
|
||||
PATH@189..194
|
||||
PATH_SEGMENT@189..194
|
||||
CRATE_KW@189..194 "crate"
|
||||
COLON2@194..196 "::"
|
||||
PATH_SEGMENT@196..202
|
||||
NAME_REF@196..202
|
||||
IDENT@196..202 "export"
|
||||
R_CURLY@202..203 "}"
|
||||
R_CURLY@203..204 "}"
|
||||
R_CURLY@204..205 "}"
|
||||
SEMICOLON@205..206 ";"
|
||||
WHITESPACE@206..207 " "
|
||||
COMMENT@207..249 "// Nonsensical but pe ..."
|
||||
WHITESPACE@249..250 "\n"
|
||||
USE_TREE@189..201
|
||||
PATH@189..201
|
||||
PATH@189..193
|
||||
PATH_SEGMENT@189..193
|
||||
NAME_REF@189..193
|
||||
IDENT@189..193 "root"
|
||||
COLON2@193..195 "::"
|
||||
PATH_SEGMENT@195..201
|
||||
NAME_REF@195..201
|
||||
IDENT@195..201 "export"
|
||||
R_CURLY@201..202 "}"
|
||||
R_CURLY@202..203 "}"
|
||||
R_CURLY@203..204 "}"
|
||||
SEMICOLON@204..205 ";"
|
||||
WHITESPACE@205..206 " "
|
||||
COMMENT@206..248 "// Nonsensical but pe ..."
|
||||
WHITESPACE@248..249 "\n"
|
||||
|
@ -1,4 +1,4 @@
|
||||
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
|
||||
|
@ -0,0 +1,35 @@
|
||||
SOURCE_FILE@0..27
|
||||
FN_DEF@0..26
|
||||
FN_KW@0..2 "fn"
|
||||
WHITESPACE@2..3 " "
|
||||
NAME@3..6
|
||||
IDENT@3..6 "foo"
|
||||
PARAM_LIST@6..8
|
||||
L_PAREN@6..7 "("
|
||||
R_PAREN@7..8 ")"
|
||||
WHITESPACE@8..9 " "
|
||||
BLOCK_EXPR@9..26
|
||||
BLOCK@9..26
|
||||
L_CURLY@9..10 "{"
|
||||
WHITESPACE@10..11 " "
|
||||
EXPR_STMT@11..24
|
||||
MACRO_CALL@11..23
|
||||
PATH@11..14
|
||||
PATH_SEGMENT@11..14
|
||||
NAME_REF@11..14
|
||||
IDENT@11..14 "try"
|
||||
BANG@14..15 "!"
|
||||
TOKEN_TREE@15..23
|
||||
L_PAREN@15..16 "("
|
||||
IDENT@16..18 "Ok"
|
||||
TOKEN_TREE@18..22
|
||||
L_PAREN@18..19 "("
|
||||
TOKEN_TREE@19..21
|
||||
L_PAREN@19..20 "("
|
||||
R_PAREN@20..21 ")"
|
||||
R_PAREN@21..22 ")"
|
||||
R_PAREN@22..23 ")"
|
||||
SEMICOLON@23..24 ";"
|
||||
WHITESPACE@24..25 " "
|
||||
R_CURLY@25..26 "}"
|
||||
WHITESPACE@26..27 "\n"
|
@ -0,0 +1 @@
|
||||
fn foo() { try!(Ok(())); }
|
@ -0,0 +1,27 @@
|
||||
SOURCE_FILE@0..30
|
||||
MACRO_CALL@0..29
|
||||
PATH@0..11
|
||||
PATH_SEGMENT@0..11
|
||||
NAME_REF@0..11
|
||||
IDENT@0..11 "macro_rules"
|
||||
BANG@11..12 "!"
|
||||
WHITESPACE@12..13 " "
|
||||
NAME@13..16
|
||||
IDENT@13..16 "try"
|
||||
WHITESPACE@16..17 " "
|
||||
TOKEN_TREE@17..29
|
||||
L_CURLY@17..18 "{"
|
||||
WHITESPACE@18..19 " "
|
||||
TOKEN_TREE@19..21
|
||||
L_PAREN@19..20 "("
|
||||
R_PAREN@20..21 ")"
|
||||
WHITESPACE@21..22 " "
|
||||
EQ@22..23 "="
|
||||
R_ANGLE@23..24 ">"
|
||||
WHITESPACE@24..25 " "
|
||||
TOKEN_TREE@25..27
|
||||
L_CURLY@25..26 "{"
|
||||
R_CURLY@26..27 "}"
|
||||
WHITESPACE@27..28 " "
|
||||
R_CURLY@28..29 "}"
|
||||
WHITESPACE@29..30 "\n"
|
@ -0,0 +1 @@
|
||||
macro_rules! try { () => {} }
|
@ -20,7 +20,7 @@ globset = "0.4.4"
|
||||
itertools = "0.9.0"
|
||||
jod-thread = "0.1.0"
|
||||
log = "0.4.8"
|
||||
lsp-types = { version = "0.73.0", features = ["proposed"] }
|
||||
lsp-types = { version = "0.74.0", features = ["proposed"] }
|
||||
parking_lot = "0.10.0"
|
||||
pico-args = "0.3.1"
|
||||
rand = { version = "0.7.3", features = ["small_rng"] }
|
||||
@ -39,7 +39,7 @@ ra_prof = { path = "../ra_prof" }
|
||||
ra_project_model = { path = "../ra_project_model" }
|
||||
ra_syntax = { path = "../ra_syntax" }
|
||||
ra_text_edit = { path = "../ra_text_edit" }
|
||||
ra_vfs = "0.5.2"
|
||||
ra_vfs = "0.6.0"
|
||||
|
||||
# This should only be used in CLI
|
||||
ra_db = { path = "../ra_db" }
|
||||
|
@ -16,7 +16,7 @@ pub fn server_capabilities() -> ServerCapabilities {
|
||||
ServerCapabilities {
|
||||
text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
|
||||
open_close: Some(true),
|
||||
change: Some(TextDocumentSyncKind::Full),
|
||||
change: Some(TextDocumentSyncKind::Incremental),
|
||||
will_save: None,
|
||||
will_save_wait_until: None,
|
||||
save: Some(SaveOptions::default()),
|
||||
|
@ -150,7 +150,7 @@ fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> ::lsp_types::CompletionIte
|
||||
detail: self.detail().map(|it| it.to_string()),
|
||||
filter_text: Some(self.lookup().to_string()),
|
||||
kind: self.kind().map(|it| it.conv()),
|
||||
text_edit: Some(text_edit),
|
||||
text_edit: Some(text_edit.into()),
|
||||
additional_text_edits: Some(additional_text_edits),
|
||||
documentation: self.documentation().map(|it| it.conv()),
|
||||
deprecated: Some(self.deprecated()),
|
||||
|
@ -6,9 +6,12 @@
|
||||
pub(crate) mod pending_requests;
|
||||
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
env,
|
||||
error::Error,
|
||||
fmt, panic,
|
||||
fmt,
|
||||
ops::Range,
|
||||
panic,
|
||||
path::PathBuf,
|
||||
sync::Arc,
|
||||
time::{Duration, Instant},
|
||||
@ -18,11 +21,12 @@
|
||||
use itertools::Itertools;
|
||||
use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||
use lsp_types::{
|
||||
NumberOrString, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams,
|
||||
WorkDoneProgressEnd, WorkDoneProgressReport,
|
||||
DidChangeTextDocumentParams, NumberOrString, TextDocumentContentChangeEvent, WorkDoneProgress,
|
||||
WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd,
|
||||
WorkDoneProgressReport,
|
||||
};
|
||||
use ra_flycheck::{url_from_path_with_drive_lowercasing, CheckTask};
|
||||
use ra_ide::{Canceled, FileId, LibraryData, SourceRootId};
|
||||
use ra_ide::{Canceled, FileId, LibraryData, LineIndex, SourceRootId};
|
||||
use ra_prof::profile;
|
||||
use ra_project_model::{PackageRoot, ProjectWorkspace};
|
||||
use ra_vfs::{VfsFile, VfsTask, Watch};
|
||||
@ -33,6 +37,7 @@
|
||||
|
||||
use crate::{
|
||||
config::{Config, FilesWatcher},
|
||||
conv::{ConvWith, TryConvWith},
|
||||
diagnostics::DiagnosticTask,
|
||||
main_loop::{
|
||||
pending_requests::{PendingRequest, PendingRequests},
|
||||
@ -579,12 +584,16 @@ fn on_notification(
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match notification_cast::<req::DidChangeTextDocument>(not) {
|
||||
Ok(mut params) => {
|
||||
let uri = params.text_document.uri;
|
||||
Ok(params) => {
|
||||
let DidChangeTextDocumentParams { text_document, content_changes } = params;
|
||||
let world = state.snapshot();
|
||||
let file_id = text_document.try_conv_with(&world)?;
|
||||
let line_index = world.analysis().file_line_index(file_id)?;
|
||||
let uri = text_document.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
let text =
|
||||
params.content_changes.pop().ok_or_else(|| "empty changes".to_string())?.text;
|
||||
state.vfs.write().change_file_overlay(path.as_path(), text);
|
||||
state.vfs.write().change_file_overlay(&path, |old_text| {
|
||||
apply_document_changes(old_text, Cow::Borrowed(&line_index), content_changes);
|
||||
});
|
||||
return Ok(());
|
||||
}
|
||||
Err(not) => not,
|
||||
@ -653,6 +662,48 @@ fn on_notification(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn apply_document_changes(
|
||||
old_text: &mut String,
|
||||
mut line_index: Cow<'_, LineIndex>,
|
||||
content_changes: Vec<TextDocumentContentChangeEvent>,
|
||||
) {
|
||||
// The changes we got must be applied sequentially, but can cross lines so we
|
||||
// have to keep our line index updated.
|
||||
// Some clients (e.g. Code) sort the ranges in reverse. As an optimization, we
|
||||
// remember the last valid line in the index and only rebuild it if needed.
|
||||
enum IndexValid {
|
||||
All,
|
||||
UpToLine(u64),
|
||||
}
|
||||
|
||||
impl IndexValid {
|
||||
fn covers(&self, line: u64) -> bool {
|
||||
match *self {
|
||||
IndexValid::UpToLine(to) => to >= line,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut index_valid = IndexValid::All;
|
||||
for change in content_changes {
|
||||
match change.range {
|
||||
Some(range) => {
|
||||
if !index_valid.covers(range.start.line) {
|
||||
line_index = Cow::Owned(LineIndex::new(&old_text));
|
||||
}
|
||||
index_valid = IndexValid::UpToLine(range.start.line);
|
||||
let range = range.conv_with(&line_index);
|
||||
old_text.replace_range(Range::<usize>::from(range), &change.text);
|
||||
}
|
||||
None => {
|
||||
*old_text = change.text;
|
||||
index_valid = IndexValid::UpToLine(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_check_task(
|
||||
task: CheckTask,
|
||||
world_state: &mut WorldState,
|
||||
@ -958,3 +1009,64 @@ fn request_new<R>(id: RequestId, params: R::Params) -> Request
|
||||
{
|
||||
Request::new(id, R::METHOD.to_string(), params)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::borrow::Cow;
|
||||
|
||||
use lsp_types::{Position, Range, TextDocumentContentChangeEvent};
|
||||
use ra_ide::LineIndex;
|
||||
|
||||
#[test]
|
||||
fn apply_document_changes() {
|
||||
fn run(text: &mut String, changes: Vec<TextDocumentContentChangeEvent>) {
|
||||
let line_index = Cow::Owned(LineIndex::new(&text));
|
||||
super::apply_document_changes(text, line_index, changes);
|
||||
}
|
||||
|
||||
macro_rules! c {
|
||||
[$($sl:expr, $sc:expr; $el:expr, $ec:expr => $text:expr),+] => {
|
||||
vec![$(TextDocumentContentChangeEvent {
|
||||
range: Some(Range {
|
||||
start: Position { line: $sl, character: $sc },
|
||||
end: Position { line: $el, character: $ec },
|
||||
}),
|
||||
range_length: None,
|
||||
text: String::from($text),
|
||||
}),+]
|
||||
};
|
||||
}
|
||||
|
||||
let mut text = String::new();
|
||||
run(&mut text, vec![]);
|
||||
assert_eq!(text, "");
|
||||
run(
|
||||
&mut text,
|
||||
vec![TextDocumentContentChangeEvent {
|
||||
range: None,
|
||||
range_length: None,
|
||||
text: String::from("the"),
|
||||
}],
|
||||
);
|
||||
assert_eq!(text, "the");
|
||||
run(&mut text, c![0, 3; 0, 3 => " quick"]);
|
||||
assert_eq!(text, "the quick");
|
||||
run(&mut text, c![0, 0; 0, 4 => "", 0, 5; 0, 5 => " foxes"]);
|
||||
assert_eq!(text, "quick foxes");
|
||||
run(&mut text, c![0, 11; 0, 11 => "\ndream"]);
|
||||
assert_eq!(text, "quick foxes\ndream");
|
||||
run(&mut text, c![1, 0; 1, 0 => "have "]);
|
||||
assert_eq!(text, "quick foxes\nhave dream");
|
||||
run(&mut text, c![0, 0; 0, 0 => "the ", 1, 4; 1, 4 => " quiet", 1, 16; 1, 16 => "s\n"]);
|
||||
assert_eq!(text, "the quick foxes\nhave quiet dreams\n");
|
||||
run(&mut text, c![0, 15; 0, 15 => "\n", 2, 17; 2, 17 => "\n"]);
|
||||
assert_eq!(text, "the quick foxes\n\nhave quiet dreams\n\n");
|
||||
run(
|
||||
&mut text,
|
||||
c![1, 0; 1, 0 => "DREAM", 2, 0; 2, 0 => "they ", 3, 0; 3, 0 => "DON'T THEY?"],
|
||||
);
|
||||
assert_eq!(text, "the quick foxes\nDREAM\nthey have quiet dreams\nDON'T THEY?\n");
|
||||
run(&mut text, c![0, 10; 1, 5 => "", 2, 0; 2, 12 => ""]);
|
||||
assert_eq!(text, "the quick \nthey have quiet dreams\n");
|
||||
}
|
||||
}
|
||||
|
@ -326,10 +326,10 @@ fn exec_query(world: &WorldSnapshot, query: Query) -> Result<Vec<SymbolInformati
|
||||
|
||||
pub fn handle_goto_definition(
|
||||
world: WorldSnapshot,
|
||||
params: req::TextDocumentPositionParams,
|
||||
params: req::GotoDefinitionParams,
|
||||
) -> Result<Option<req::GotoDefinitionResponse>> {
|
||||
let _p = profile("handle_goto_definition");
|
||||
let position = params.try_conv_with(&world)?;
|
||||
let position = params.text_document_position_params.try_conv_with(&world)?;
|
||||
let nav_info = match world.analysis().goto_definition(position)? {
|
||||
None => return Ok(None),
|
||||
Some(it) => it,
|
||||
@ -340,10 +340,10 @@ pub fn handle_goto_definition(
|
||||
|
||||
pub fn handle_goto_implementation(
|
||||
world: WorldSnapshot,
|
||||
params: req::TextDocumentPositionParams,
|
||||
params: req::GotoImplementationParams,
|
||||
) -> Result<Option<req::GotoImplementationResponse>> {
|
||||
let _p = profile("handle_goto_implementation");
|
||||
let position = params.try_conv_with(&world)?;
|
||||
let position = params.text_document_position_params.try_conv_with(&world)?;
|
||||
let nav_info = match world.analysis().goto_implementation(position)? {
|
||||
None => return Ok(None),
|
||||
Some(it) => it,
|
||||
@ -354,10 +354,10 @@ pub fn handle_goto_implementation(
|
||||
|
||||
pub fn handle_goto_type_definition(
|
||||
world: WorldSnapshot,
|
||||
params: req::TextDocumentPositionParams,
|
||||
params: req::GotoTypeDefinitionParams,
|
||||
) -> Result<Option<req::GotoTypeDefinitionResponse>> {
|
||||
let _p = profile("handle_goto_type_definition");
|
||||
let position = params.try_conv_with(&world)?;
|
||||
let position = params.text_document_position_params.try_conv_with(&world)?;
|
||||
let nav_info = match world.analysis().goto_type_definition(position)? {
|
||||
None => return Ok(None),
|
||||
Some(it) => it,
|
||||
@ -487,10 +487,10 @@ pub fn handle_folding_range(
|
||||
|
||||
pub fn handle_signature_help(
|
||||
world: WorldSnapshot,
|
||||
params: req::TextDocumentPositionParams,
|
||||
params: req::SignatureHelpParams,
|
||||
) -> Result<Option<req::SignatureHelp>> {
|
||||
let _p = profile("handle_signature_help");
|
||||
let position = params.try_conv_with(&world)?;
|
||||
let position = params.text_document_position_params.try_conv_with(&world)?;
|
||||
if let Some(call_info) = world.analysis().call_info(position)? {
|
||||
let concise = !world.config.call_info_full;
|
||||
let mut active_parameter = call_info.active_parameter.map(|it| it as i64);
|
||||
@ -509,12 +509,9 @@ pub fn handle_signature_help(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_hover(
|
||||
world: WorldSnapshot,
|
||||
params: req::TextDocumentPositionParams,
|
||||
) -> Result<Option<Hover>> {
|
||||
pub fn handle_hover(world: WorldSnapshot, params: req::HoverParams) -> Result<Option<Hover>> {
|
||||
let _p = profile("handle_hover");
|
||||
let position = params.try_conv_with(&world)?;
|
||||
let position = params.text_document_position_params.try_conv_with(&world)?;
|
||||
let info = match world.analysis().hover(position)? {
|
||||
None => return Ok(None),
|
||||
Some(info) => info,
|
||||
@ -878,8 +875,14 @@ pub fn handle_code_lens(
|
||||
.map(|it| {
|
||||
let range = it.node_range.conv_with(&line_index);
|
||||
let pos = range.start;
|
||||
let lens_params =
|
||||
req::TextDocumentPositionParams::new(params.text_document.clone(), pos);
|
||||
let lens_params = req::GotoImplementationParams {
|
||||
text_document_position_params: req::TextDocumentPositionParams::new(
|
||||
params.text_document.clone(),
|
||||
pos,
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
};
|
||||
CodeLens {
|
||||
range,
|
||||
command: None,
|
||||
@ -894,7 +897,7 @@ pub fn handle_code_lens(
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
enum CodeLensResolveData {
|
||||
Impls(req::TextDocumentPositionParams),
|
||||
Impls(req::GotoImplementationParams),
|
||||
}
|
||||
|
||||
pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Result<CodeLens> {
|
||||
@ -927,7 +930,7 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re
|
||||
title,
|
||||
command: "rust-analyzer.showReferences".into(),
|
||||
arguments: Some(vec![
|
||||
to_value(&lens_params.text_document.uri).unwrap(),
|
||||
to_value(&lens_params.text_document_position_params.text_document.uri).unwrap(),
|
||||
to_value(code_lens.range.start).unwrap(),
|
||||
to_value(locations).unwrap(),
|
||||
]),
|
||||
@ -944,16 +947,16 @@ pub fn handle_code_lens_resolve(world: WorldSnapshot, code_lens: CodeLens) -> Re
|
||||
|
||||
pub fn handle_document_highlight(
|
||||
world: WorldSnapshot,
|
||||
params: req::TextDocumentPositionParams,
|
||||
params: req::DocumentHighlightParams,
|
||||
) -> Result<Option<Vec<DocumentHighlight>>> {
|
||||
let _p = profile("handle_document_highlight");
|
||||
let file_id = params.text_document.try_conv_with(&world)?;
|
||||
let file_id = params.text_document_position_params.text_document.try_conv_with(&world)?;
|
||||
let line_index = world.analysis().file_line_index(file_id)?;
|
||||
|
||||
let refs = match world
|
||||
.analysis()
|
||||
.find_all_refs(params.try_conv_with(&world)?, Some(SearchScope::single_file(file_id)))?
|
||||
{
|
||||
let refs = match world.analysis().find_all_refs(
|
||||
params.text_document_position_params.try_conv_with(&world)?,
|
||||
Some(SearchScope::single_file(file_id)),
|
||||
)? {
|
||||
None => return Ok(None),
|
||||
Some(refs) => refs,
|
||||
};
|
||||
|
@ -8,14 +8,15 @@
|
||||
notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens,
|
||||
CodeLensParams, CompletionParams, CompletionResponse, ConfigurationItem, ConfigurationParams,
|
||||
DiagnosticTag, DidChangeConfigurationParams, DidChangeWatchedFilesParams,
|
||||
DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams,
|
||||
DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType,
|
||||
PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken,
|
||||
PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, SelectionRange,
|
||||
SelectionRangeParams, SemanticTokensParams, SemanticTokensRangeParams,
|
||||
DidChangeWatchedFilesRegistrationOptions, DocumentHighlightParams,
|
||||
DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
|
||||
FileSystemWatcher, GotoDefinitionParams, GotoDefinitionResponse, Hover, HoverParams,
|
||||
InitializeResult, MessageType, PartialResultParams, ProgressParams, ProgressParamsValue,
|
||||
ProgressToken, PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams,
|
||||
SelectionRange, SelectionRangeParams, SemanticTokensParams, SemanticTokensRangeParams,
|
||||
SemanticTokensRangeResult, SemanticTokensResult, ServerCapabilities, ShowMessageParams,
|
||||
SignatureHelp, SymbolKind, TextDocumentEdit, TextDocumentPositionParams, TextEdit,
|
||||
WorkDoneProgressParams, WorkspaceEdit, WorkspaceSymbolParams,
|
||||
SignatureHelp, SignatureHelpParams, SymbolKind, TextDocumentEdit, TextDocumentPositionParams,
|
||||
TextEdit, WorkDoneProgressParams, WorkspaceEdit, WorkspaceSymbolParams,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -4,64 +4,69 @@
|
||||
|
||||
use lsp_types::{Range, SemanticToken, SemanticTokenModifier, SemanticTokenType, SemanticTokens};
|
||||
|
||||
pub(crate) const ATTRIBUTE: SemanticTokenType = SemanticTokenType::new("attribute");
|
||||
pub(crate) const BUILTIN_TYPE: SemanticTokenType = SemanticTokenType::new("builtinType");
|
||||
pub(crate) const ENUM_MEMBER: SemanticTokenType = SemanticTokenType::new("enumMember");
|
||||
pub(crate) const LIFETIME: SemanticTokenType = SemanticTokenType::new("lifetime");
|
||||
pub(crate) const TYPE_ALIAS: SemanticTokenType = SemanticTokenType::new("typeAlias");
|
||||
pub(crate) const UNION: SemanticTokenType = SemanticTokenType::new("union");
|
||||
pub(crate) const UNRESOLVED_REFERENCE: SemanticTokenType =
|
||||
SemanticTokenType::new("unresolvedReference");
|
||||
pub(crate) const FORMAT_SPECIFIER: SemanticTokenType = SemanticTokenType::new("formatSpecifier");
|
||||
macro_rules! define_semantic_token_types {
|
||||
($(($ident:ident, $string:literal)),*$(,)?) => {
|
||||
$(pub(crate) const $ident: SemanticTokenType = SemanticTokenType::new($string);)*
|
||||
|
||||
pub(crate) const CONSTANT: SemanticTokenModifier = SemanticTokenModifier::new("constant");
|
||||
pub(crate) const CONTROL_FLOW: SemanticTokenModifier = SemanticTokenModifier::new("controlFlow");
|
||||
pub(crate) const MUTABLE: SemanticTokenModifier = SemanticTokenModifier::new("mutable");
|
||||
pub(crate) const UNSAFE: SemanticTokenModifier = SemanticTokenModifier::new("unsafe");
|
||||
pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[
|
||||
SemanticTokenType::COMMENT,
|
||||
SemanticTokenType::KEYWORD,
|
||||
SemanticTokenType::STRING,
|
||||
SemanticTokenType::NUMBER,
|
||||
SemanticTokenType::REGEXP,
|
||||
SemanticTokenType::OPERATOR,
|
||||
SemanticTokenType::NAMESPACE,
|
||||
SemanticTokenType::TYPE,
|
||||
SemanticTokenType::STRUCT,
|
||||
SemanticTokenType::CLASS,
|
||||
SemanticTokenType::INTERFACE,
|
||||
SemanticTokenType::ENUM,
|
||||
SemanticTokenType::TYPE_PARAMETER,
|
||||
SemanticTokenType::FUNCTION,
|
||||
SemanticTokenType::MEMBER,
|
||||
SemanticTokenType::PROPERTY,
|
||||
SemanticTokenType::MACRO,
|
||||
SemanticTokenType::VARIABLE,
|
||||
SemanticTokenType::PARAMETER,
|
||||
SemanticTokenType::LABEL,
|
||||
$($ident),*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[
|
||||
SemanticTokenType::COMMENT,
|
||||
SemanticTokenType::KEYWORD,
|
||||
SemanticTokenType::STRING,
|
||||
SemanticTokenType::NUMBER,
|
||||
SemanticTokenType::REGEXP,
|
||||
SemanticTokenType::OPERATOR,
|
||||
SemanticTokenType::NAMESPACE,
|
||||
SemanticTokenType::TYPE,
|
||||
SemanticTokenType::STRUCT,
|
||||
SemanticTokenType::CLASS,
|
||||
SemanticTokenType::INTERFACE,
|
||||
SemanticTokenType::ENUM,
|
||||
SemanticTokenType::TYPE_PARAMETER,
|
||||
SemanticTokenType::FUNCTION,
|
||||
SemanticTokenType::MEMBER,
|
||||
SemanticTokenType::PROPERTY,
|
||||
SemanticTokenType::MACRO,
|
||||
SemanticTokenType::VARIABLE,
|
||||
SemanticTokenType::PARAMETER,
|
||||
SemanticTokenType::LABEL,
|
||||
ATTRIBUTE,
|
||||
BUILTIN_TYPE,
|
||||
ENUM_MEMBER,
|
||||
LIFETIME,
|
||||
TYPE_ALIAS,
|
||||
UNION,
|
||||
UNRESOLVED_REFERENCE,
|
||||
FORMAT_SPECIFIER,
|
||||
define_semantic_token_types![
|
||||
(ATTRIBUTE, "attribute"),
|
||||
(BUILTIN_TYPE, "builtinType"),
|
||||
(ENUM_MEMBER, "enumMember"),
|
||||
(LIFETIME, "lifetime"),
|
||||
(TYPE_ALIAS, "typeAlias"),
|
||||
(UNION, "union"),
|
||||
(UNRESOLVED_REFERENCE, "unresolvedReference"),
|
||||
(FORMAT_SPECIFIER, "formatSpecifier"),
|
||||
];
|
||||
|
||||
pub(crate) const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
|
||||
SemanticTokenModifier::DOCUMENTATION,
|
||||
SemanticTokenModifier::DECLARATION,
|
||||
SemanticTokenModifier::DEFINITION,
|
||||
SemanticTokenModifier::STATIC,
|
||||
SemanticTokenModifier::ABSTRACT,
|
||||
SemanticTokenModifier::DEPRECATED,
|
||||
SemanticTokenModifier::READONLY,
|
||||
CONSTANT,
|
||||
MUTABLE,
|
||||
UNSAFE,
|
||||
CONTROL_FLOW,
|
||||
macro_rules! define_semantic_token_modifiers {
|
||||
($(($ident:ident, $string:literal)),*$(,)?) => {
|
||||
$(pub(crate) const $ident: SemanticTokenModifier = SemanticTokenModifier::new($string);)*
|
||||
|
||||
pub(crate) const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
|
||||
SemanticTokenModifier::DOCUMENTATION,
|
||||
SemanticTokenModifier::DECLARATION,
|
||||
SemanticTokenModifier::DEFINITION,
|
||||
SemanticTokenModifier::STATIC,
|
||||
SemanticTokenModifier::ABSTRACT,
|
||||
SemanticTokenModifier::DEPRECATED,
|
||||
SemanticTokenModifier::READONLY,
|
||||
$($ident),*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
define_semantic_token_modifiers![
|
||||
(CONSTANT, "constant"),
|
||||
(CONTROL_FLOW, "controlFlow"),
|
||||
(MUTABLE, "mutable"),
|
||||
(UNSAFE, "unsafe"),
|
||||
];
|
||||
|
||||
#[derive(Default)]
|
||||
|
@ -4,8 +4,8 @@
|
||||
|
||||
use lsp_types::{
|
||||
CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions,
|
||||
PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams,
|
||||
WorkDoneProgressParams,
|
||||
GotoDefinitionParams, HoverParams, PartialResultParams, Position, Range, TextDocumentItem,
|
||||
TextDocumentPositionParams, WorkDoneProgressParams,
|
||||
};
|
||||
use rust_analyzer::req::{
|
||||
CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
|
||||
@ -610,10 +610,14 @@ fn main() {
|
||||
})
|
||||
.server();
|
||||
server.wait_until_workspace_is_loaded();
|
||||
let res = server.send_request::<GotoDefinition>(TextDocumentPositionParams::new(
|
||||
server.doc_id("src/main.rs"),
|
||||
Position::new(2, 15),
|
||||
));
|
||||
let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("src/main.rs"),
|
||||
Position::new(2, 15),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
partial_result_params: Default::default(),
|
||||
});
|
||||
assert!(format!("{}", res).contains("hello.rs"));
|
||||
}
|
||||
|
||||
@ -692,10 +696,13 @@ pub fn foo(_input: TokenStream) -> TokenStream {
|
||||
.root("bar")
|
||||
.server();
|
||||
server.wait_until_workspace_is_loaded();
|
||||
let res = server.send_request::<HoverRequest>(TextDocumentPositionParams::new(
|
||||
server.doc_id("foo/src/main.rs"),
|
||||
Position::new(7, 9),
|
||||
));
|
||||
let res = server.send_request::<HoverRequest>(HoverParams {
|
||||
text_document_position_params: TextDocumentPositionParams::new(
|
||||
server.doc_id("foo/src/main.rs"),
|
||||
Position::new(7, 9),
|
||||
),
|
||||
work_done_progress_params: Default::default(),
|
||||
});
|
||||
|
||||
let value = res.get("contents").unwrap().get("value").unwrap().to_string();
|
||||
assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#)
|
||||
|
@ -35,7 +35,7 @@ The syntax tree consists of three layers:
|
||||
* AST
|
||||
|
||||
Of these, only GreenNodes store the actual data, the other two layers are (non-trivial) views into green tree.
|
||||
Red-green terminology comes from Roslyn ([link](https://docs.microsoft.com/en-ie/archive/blogs/ericlippert/persistence-facades-and-roslyns-red-green-trees)) and gives the name to the `rowan` library. Green and syntax nodes are defined in rowan, ast is defined in rust-analyzer.
|
||||
Red-green terminology comes from Roslyn ([link](https://ericlippert.com/2012/06/08/red-green-trees/)) and gives the name to the `rowan` library. Green and syntax nodes are defined in rowan, ast is defined in rust-analyzer.
|
||||
|
||||
Syntax trees are a semi-transient data structure.
|
||||
In general, frontend does not keep syntax trees for all files in memory.
|
||||
|
@ -140,8 +140,8 @@ space or `;` depending on the return type of the function.
|
||||
When completing a function call, `()` are automatically inserted. If a function
|
||||
takes arguments, the cursor is positioned inside the parenthesis.
|
||||
|
||||
There are postifx completions, which can be triggerd by typing something like
|
||||
`foo().if`. The word after `.` determines postifx completion. Possible variants are:
|
||||
There are postfix completions, which can be triggered by typing something like
|
||||
`foo().if`. The word after `.` determines postfix completion. Possible variants are:
|
||||
|
||||
- `expr.if` -> `if expr {}`
|
||||
- `expr.match` -> `match expr {}`
|
||||
|
@ -111,7 +111,7 @@ Here are some useful self-diagnostic commands:
|
||||
=== rust-analyzer Language Server Binary
|
||||
|
||||
Other editors generally require the `rust-analyzer` binary to be in `$PATH`.
|
||||
You can download the pre-built binary from the https://github.com/rust-analyzer/rust-analyzer/releases[releases] page. Typically, you then need to rename the binary for your platform, e.g. `rust-analyzer-mac` if you're on Mac OS, to `rust-analzyer` and make it executable in addition to moving it into a directory in your `$PATH`.
|
||||
You can download the pre-built binary from the https://github.com/rust-analyzer/rust-analyzer/releases[releases] page. Typically, you then need to rename the binary for your platform, e.g. `rust-analyzer-mac` if you're on Mac OS, to `rust-analyzer` and make it executable in addition to moving it into a directory in your `$PATH`.
|
||||
|
||||
On Linux to install the `rust-analyzer` binary into `~/.local/bin`, this commands could be used
|
||||
|
||||
@ -169,13 +169,15 @@ The are several LSP client implementations for vim:
|
||||
|
||||
1. Install coc.nvim by following the instructions at
|
||||
https://github.com/neoclide/coc.nvim[coc.nvim]
|
||||
(nodejs required)
|
||||
(Node.js required)
|
||||
2. Run `:CocInstall coc-rust-analyzer` to install
|
||||
https://github.com/fannheyward/coc-rust-analyzer[coc-rust-analyzer],
|
||||
this extension implements _most_ of the features supported in the VSCode extension:
|
||||
* automatically install and upgrade stable/nightly releases
|
||||
* same configurations as VSCode extension, `rust-analyzer.serverPath`, `rust-analyzer.cargo.features` etc.
|
||||
* same commands too, `rust-analyzer.analyzerStatus`, `rust-analyzer.ssr` etc.
|
||||
* highlighting and inlay_hints are not implemented yet
|
||||
* inlay hints for method chaining support, _Neovim Only_
|
||||
* semantic highlighting is not implemented yet
|
||||
|
||||
==== LanguageClient-neovim
|
||||
|
||||
@ -195,7 +197,7 @@ let g:LanguageClient_serverCommands = {
|
||||
==== YouCompleteMe
|
||||
|
||||
1. Install YouCompleteMe by following the instructions
|
||||
https://ycm-core.github.io/YouCompleteMe/#rust-semantic-completion[here]
|
||||
https://github.com/ycm-core/lsp-examples#rust-rust-analyzer[here]
|
||||
|
||||
2. Configure by adding this to your vim/neovim config file (replacing the existing Rust-specific line if it exists):
|
||||
+
|
||||
@ -212,6 +214,21 @@ let g:ycm_language_server =
|
||||
\ ]
|
||||
----
|
||||
|
||||
==== ALE
|
||||
|
||||
To add the LSP server to https://github.com/dense-analysis/ale[ale]:
|
||||
|
||||
[source,vim]
|
||||
----
|
||||
call ale#linter#Define('rust', {
|
||||
\ 'name': 'rust-analyzer',
|
||||
\ 'lsp': 'stdio',
|
||||
\ 'executable': 'rust-analyzer',
|
||||
\ 'command': '%e',
|
||||
\ 'project_root': '.',
|
||||
\})
|
||||
----
|
||||
|
||||
==== nvim-lsp
|
||||
|
||||
NeoVim 0.5 (not yet released) has built-in language server support.
|
||||
@ -229,9 +246,9 @@ You also need the `LSP` package. To install it:
|
||||
* Type `Install Package Control`, press enter
|
||||
2. In the command palette, run `Package control: Install package`, and in the list that pops up, type `LSP` and press enter.
|
||||
|
||||
Finally, with your Rust project open, in the command palette, run `LSP: Enable Language Server In Project` or `LSP: Enable Language Server Globally`, then select `rust-analyzer` in the list that pops up to enable the rust-analyzer LSP. The latter means that rust-analzyer is enabled by default in Rust projects.
|
||||
Finally, with your Rust project open, in the command palette, run `LSP: Enable Language Server In Project` or `LSP: Enable Language Server Globally`, then select `rust-analyzer` in the list that pops up to enable the rust-analyzer LSP. The latter means that rust-analyzer is enabled by default in Rust projects.
|
||||
|
||||
If it worked, you should see "rust-analzyer, Line X, Column Y" on the left side of the bottom bar, and after waiting a bit, functionality like tooltips on hovering over variables should become available.
|
||||
If it worked, you should see "rust-analyzer, Line X, Column Y" on the left side of the bottom bar, and after waiting a bit, functionality like tooltips on hovering over variables should become available.
|
||||
|
||||
If you get an error saying `No such file or directory: 'rust-analyzer'`, see the <<rust-analyzer-language-server-binary,`rust-analyzer` binary>> section on installing the language server binary.
|
||||
|
||||
|
@ -27,6 +27,7 @@
|
||||
"scripts": {
|
||||
"vscode:prepublish": "tsc && rollup -c",
|
||||
"package": "vsce package -o rust-analyzer.vsix",
|
||||
"build": "tsc",
|
||||
"watch": "tsc --watch",
|
||||
"lint": "tsfmt --verify && eslint -c .eslintrc.js --ext ts ./src",
|
||||
"fix": " tsfmt -r && eslint -c .eslintrc.js --ext ts ./src --fix"
|
||||
@ -388,6 +389,28 @@
|
||||
"description": "Enable Proc macro support, cargo.loadOutDirsFromCheck must be enabled.",
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"rust-analyzer.debug.engine": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"auto",
|
||||
"vadimcn.vscode-lldb",
|
||||
"ms-vscode.cpptools"
|
||||
],
|
||||
"default": "auto",
|
||||
"description": "Preffered debug engine.",
|
||||
"markdownEnumDescriptions": [
|
||||
"First try to use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb), if it's not installed try to use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools).",
|
||||
"Use [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)",
|
||||
"Use [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools)"
|
||||
]
|
||||
},
|
||||
"rust-analyzer.debug.sourceFileMap": {
|
||||
"type": "object",
|
||||
"description": "Optional source file mappings passed to the debug engine.",
|
||||
"default": {
|
||||
"/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
106
editors/code/src/cargo.ts
Normal file
106
editors/code/src/cargo.ts
Normal file
@ -0,0 +1,106 @@
|
||||
import * as cp from 'child_process';
|
||||
import * as readline from 'readline';
|
||||
import { OutputChannel } from 'vscode';
|
||||
|
||||
interface CompilationArtifact {
|
||||
fileName: string;
|
||||
name: string;
|
||||
kind: string;
|
||||
isTest: boolean;
|
||||
}
|
||||
|
||||
export class Cargo {
|
||||
rootFolder: string;
|
||||
env?: Record<string, string>;
|
||||
output: OutputChannel;
|
||||
|
||||
public constructor(cargoTomlFolder: string, output: OutputChannel, env: Record<string, string> | undefined = undefined) {
|
||||
this.rootFolder = cargoTomlFolder;
|
||||
this.output = output;
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
public async artifactsFromArgs(cargoArgs: string[]): Promise<CompilationArtifact[]> {
|
||||
const artifacts: CompilationArtifact[] = [];
|
||||
|
||||
try {
|
||||
await this.runCargo(cargoArgs,
|
||||
message => {
|
||||
if (message.reason === 'compiler-artifact' && message.executable) {
|
||||
const isBinary = message.target.crate_types.includes('bin');
|
||||
const isBuildScript = message.target.kind.includes('custom-build');
|
||||
if ((isBinary && !isBuildScript) || message.profile.test) {
|
||||
artifacts.push({
|
||||
fileName: message.executable,
|
||||
name: message.target.name,
|
||||
kind: message.target.kind[0],
|
||||
isTest: message.profile.test
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (message.reason === 'compiler-message') {
|
||||
this.output.append(message.message.rendered);
|
||||
}
|
||||
},
|
||||
stderr => {
|
||||
this.output.append(stderr);
|
||||
}
|
||||
);
|
||||
}
|
||||
catch (err) {
|
||||
this.output.show(true);
|
||||
throw new Error(`Cargo invocation has failed: ${err}`);
|
||||
}
|
||||
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
public async executableFromArgs(args: string[]): Promise<string> {
|
||||
const cargoArgs = [...args]; // to remain args unchanged
|
||||
cargoArgs.push("--message-format=json");
|
||||
|
||||
const artifacts = await this.artifactsFromArgs(cargoArgs);
|
||||
|
||||
if (artifacts.length === 0) {
|
||||
throw new Error('No compilation artifacts');
|
||||
} else if (artifacts.length > 1) {
|
||||
throw new Error('Multiple compilation artifacts are not supported.');
|
||||
}
|
||||
|
||||
return artifacts[0].fileName;
|
||||
}
|
||||
|
||||
runCargo(
|
||||
cargoArgs: string[],
|
||||
onStdoutJson: (obj: any) => void,
|
||||
onStderrString: (data: string) => void
|
||||
): Promise<number> {
|
||||
return new Promise<number>((resolve, reject) => {
|
||||
const cargo = cp.spawn('cargo', cargoArgs, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
cwd: this.rootFolder,
|
||||
env: this.env,
|
||||
});
|
||||
|
||||
cargo.on('error', err => {
|
||||
reject(new Error(`could not launch cargo: ${err}`));
|
||||
});
|
||||
cargo.stderr.on('data', chunk => {
|
||||
onStderrString(chunk.toString());
|
||||
});
|
||||
|
||||
const rl = readline.createInterface({ input: cargo.stdout });
|
||||
rl.on('line', line => {
|
||||
const message = JSON.parse(line);
|
||||
onStdoutJson(message);
|
||||
});
|
||||
|
||||
cargo.on('exit', (exitCode, _) => {
|
||||
if (exitCode === 0)
|
||||
resolve(exitCode);
|
||||
else
|
||||
reject(new Error(`exit code: ${exitCode}.`));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as lc from 'vscode-languageclient';
|
||||
import * as ra from '../rust-analyzer-api';
|
||||
import * as os from "os";
|
||||
|
||||
import { Ctx, Cmd } from '../ctx';
|
||||
import { Cargo } from '../cargo';
|
||||
|
||||
export function run(ctx: Ctx): Cmd {
|
||||
let prevRunnable: RunnableQuickPick | undefined;
|
||||
@ -62,25 +64,69 @@ export function runSingle(ctx: Ctx): Cmd {
|
||||
};
|
||||
}
|
||||
|
||||
function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration {
|
||||
return {
|
||||
type: "lldb",
|
||||
request: "launch",
|
||||
name: config.label,
|
||||
cargo: {
|
||||
args: config.args,
|
||||
},
|
||||
args: config.extraArgs,
|
||||
cwd: config.cwd,
|
||||
sourceMap: sourceFileMap
|
||||
};
|
||||
}
|
||||
|
||||
const debugOutput = vscode.window.createOutputChannel("Debug");
|
||||
|
||||
async function getCppvsDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): Promise<vscode.DebugConfiguration> {
|
||||
debugOutput.clear();
|
||||
|
||||
const cargo = new Cargo(config.cwd || '.', debugOutput);
|
||||
const executable = await cargo.executableFromArgs(config.args);
|
||||
|
||||
// if we are here, there were no compilation errors.
|
||||
return {
|
||||
type: (os.platform() === "win32") ? "cppvsdbg" : 'cppdbg',
|
||||
request: "launch",
|
||||
name: config.label,
|
||||
program: executable,
|
||||
args: config.extraArgs,
|
||||
cwd: config.cwd,
|
||||
sourceFileMap: sourceFileMap,
|
||||
};
|
||||
}
|
||||
|
||||
export function debugSingle(ctx: Ctx): Cmd {
|
||||
return async (config: ra.Runnable) => {
|
||||
const editor = ctx.activeRustEditor;
|
||||
if (!editor) return;
|
||||
if (!vscode.extensions.getExtension("vadimcn.vscode-lldb")) {
|
||||
vscode.window.showErrorMessage("Install `vadimcn.vscode-lldb` extension for debugging");
|
||||
|
||||
const lldbId = "vadimcn.vscode-lldb";
|
||||
const cpptoolsId = "ms-vscode.cpptools";
|
||||
|
||||
const debugEngineId = ctx.config.debug.engine;
|
||||
let debugEngine = null;
|
||||
if (debugEngineId === "auto") {
|
||||
debugEngine = vscode.extensions.getExtension(lldbId);
|
||||
if (!debugEngine) {
|
||||
debugEngine = vscode.extensions.getExtension(cpptoolsId);
|
||||
}
|
||||
}
|
||||
else {
|
||||
debugEngine = vscode.extensions.getExtension(debugEngineId);
|
||||
}
|
||||
|
||||
if (!debugEngine) {
|
||||
vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=${lldbId})`
|
||||
+ ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=${cpptoolsId}) extension for debugging.`);
|
||||
return;
|
||||
}
|
||||
|
||||
const debugConfig = {
|
||||
type: "lldb",
|
||||
request: "launch",
|
||||
name: config.label,
|
||||
cargo: {
|
||||
args: config.args,
|
||||
},
|
||||
args: config.extraArgs,
|
||||
cwd: config.cwd
|
||||
};
|
||||
const debugConfig = lldbId === debugEngine.id
|
||||
? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap)
|
||||
: await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap);
|
||||
|
||||
return vscode.debug.startDebugging(undefined, debugConfig);
|
||||
};
|
||||
|
@ -92,7 +92,6 @@ export class Config {
|
||||
get askBeforeDownload() { return this.get<boolean>("updates.askBeforeDownload"); }
|
||||
get traceExtension() { return this.get<boolean>("trace.extension"); }
|
||||
|
||||
|
||||
get inlayHints() {
|
||||
return {
|
||||
typeHints: this.get<boolean>("inlayHints.typeHints"),
|
||||
@ -107,4 +106,12 @@ export class Config {
|
||||
command: this.get<string>("checkOnSave.command"),
|
||||
};
|
||||
}
|
||||
|
||||
get debug() {
|
||||
return {
|
||||
engine: this.get<string>("debug.engine"),
|
||||
sourceFileMap: this.get<Record<string, string>>("debug.sourceFileMap"),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -162,7 +162,6 @@ pub(crate) struct KindsSrc<'a> {
|
||||
"RECORD_LIT",
|
||||
"RECORD_FIELD_LIST",
|
||||
"RECORD_FIELD",
|
||||
"TRY_BLOCK_EXPR",
|
||||
"BOX_EXPR",
|
||||
// postfix
|
||||
"CALL_EXPR",
|
||||
@ -440,7 +439,6 @@ struct LambdaExpr: AttrsOwner {
|
||||
}
|
||||
struct IfExpr: AttrsOwner { T![if], Condition }
|
||||
struct LoopExpr: AttrsOwner, LoopBodyOwner { T![loop] }
|
||||
struct TryBlockExpr: AttrsOwner { T![try], body: BlockExpr }
|
||||
struct ForExpr: AttrsOwner, LoopBodyOwner {
|
||||
T![for],
|
||||
Pat,
|
||||
@ -451,7 +449,7 @@ struct WhileExpr: AttrsOwner, LoopBodyOwner { T![while], Condition }
|
||||
struct ContinueExpr: AttrsOwner { T![continue], T![lifetime] }
|
||||
struct BreakExpr: AttrsOwner { T![break], T![lifetime], Expr }
|
||||
struct Label { T![lifetime] }
|
||||
struct BlockExpr: AttrsOwner { Label, T![unsafe], Block }
|
||||
struct BlockExpr: AttrsOwner { Label, T![unsafe], T![async], Block }
|
||||
struct ReturnExpr: AttrsOwner { Expr }
|
||||
struct CallExpr: ArgListOwner { Expr }
|
||||
struct MethodCallExpr: AttrsOwner, ArgListOwner {
|
||||
@ -595,7 +593,7 @@ struct Path {
|
||||
qualifier: Path,
|
||||
}
|
||||
struct PathSegment {
|
||||
T![::], T![<], NameRef, TypeArgList, ParamList, RetType, PathType, T![>]
|
||||
T![::], T![crate], T![<], NameRef, TypeArgList, ParamList, RetType, PathType, T![>]
|
||||
}
|
||||
struct TypeArgList {
|
||||
T![::],
|
||||
@ -722,7 +720,6 @@ enum Expr: AttrsOwner {
|
||||
FieldExpr,
|
||||
AwaitExpr,
|
||||
TryExpr,
|
||||
TryBlockExpr,
|
||||
CastExpr,
|
||||
RefExpr,
|
||||
PrefixExpr,
|
||||
|
@ -50,21 +50,19 @@ fn dist_server(nightly: bool) -> Result<()> {
|
||||
if cfg!(target_os = "linux") {
|
||||
std::env::set_var("CC", "clang");
|
||||
run!(
|
||||
"cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release
|
||||
--target x86_64-unknown-linux-musl
|
||||
"
|
||||
"cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release"
|
||||
// We'd want to add, but that requires setting the right linker somehow
|
||||
// --features=jemalloc
|
||||
)?;
|
||||
if !nightly {
|
||||
run!("strip ./target/x86_64-unknown-linux-musl/release/rust-analyzer")?;
|
||||
run!("strip ./target/release/rust-analyzer")?;
|
||||
}
|
||||
} else {
|
||||
run!("cargo build --manifest-path ./crates/rust-analyzer/Cargo.toml --bin rust-analyzer --release")?;
|
||||
}
|
||||
|
||||
let (src, dst) = if cfg!(target_os = "linux") {
|
||||
("./target/x86_64-unknown-linux-musl/release/rust-analyzer", "./dist/rust-analyzer-linux")
|
||||
("./target/release/rust-analyzer", "./dist/rust-analyzer-linux")
|
||||
} else if cfg!(target_os = "windows") {
|
||||
("./target/release/rust-analyzer.exe", "./dist/rust-analyzer-windows.exe")
|
||||
} else if cfg!(target_os = "macos") {
|
||||
|
@ -10,23 +10,19 @@
|
||||
pub mod codegen;
|
||||
mod ast_src;
|
||||
|
||||
use anyhow::Context;
|
||||
use std::{
|
||||
env,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::{
|
||||
codegen::Mode,
|
||||
not_bash::{date_iso, fs2, pushd, rm_rf, run},
|
||||
not_bash::{date_iso, fs2, pushd, pushenv, rm_rf, run},
|
||||
};
|
||||
|
||||
pub use anyhow::Result;
|
||||
|
||||
const TOOLCHAIN: &str = "stable";
|
||||
pub use anyhow::{bail, Context as _, Result};
|
||||
|
||||
pub fn project_root() -> PathBuf {
|
||||
Path::new(
|
||||
@ -55,54 +51,44 @@ fn is_hidden(entry: &DirEntry) -> bool {
|
||||
|
||||
pub fn run_rustfmt(mode: Mode) -> Result<()> {
|
||||
let _dir = pushd(project_root());
|
||||
let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
|
||||
ensure_rustfmt()?;
|
||||
|
||||
let check = if mode == Mode::Verify { "--check" } else { "" };
|
||||
run!("rustup run {} -- cargo fmt -- {}", TOOLCHAIN, check)?;
|
||||
match mode {
|
||||
Mode::Overwrite => run!("cargo fmt"),
|
||||
Mode::Verify => run!("cargo fmt -- --check"),
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn reformat(text: impl std::fmt::Display) -> Result<String> {
|
||||
let _e = pushenv("RUSTUP_TOOLCHAIN", "stable");
|
||||
ensure_rustfmt()?;
|
||||
let mut rustfmt = Command::new("rustup")
|
||||
.args(&["run", TOOLCHAIN, "--", "rustfmt", "--config-path"])
|
||||
.arg(project_root().join("rustfmt.toml"))
|
||||
.args(&["--config", "fn_single_line=true"])
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.spawn()?;
|
||||
write!(rustfmt.stdin.take().unwrap(), "{}", text)?;
|
||||
let output = rustfmt.wait_with_output()?;
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let stdout = run!(
|
||||
"rustfmt --config-path {} --config fn_single_line=true", project_root().join("rustfmt.toml").display();
|
||||
<text.to_string().as_bytes()
|
||||
)?;
|
||||
let preamble = "Generated file, do not edit by hand, see `xtask/src/codegen`";
|
||||
Ok(format!("//! {}\n\n{}", preamble, stdout))
|
||||
Ok(format!("//! {}\n\n{}\n", preamble, stdout))
|
||||
}
|
||||
|
||||
fn ensure_rustfmt() -> Result<()> {
|
||||
match Command::new("rustup")
|
||||
.args(&["run", TOOLCHAIN, "--", "cargo", "fmt", "--version"])
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
{
|
||||
Ok(status) if status.success() => return Ok(()),
|
||||
_ => (),
|
||||
};
|
||||
run!("rustup toolchain install {}", TOOLCHAIN)?;
|
||||
run!("rustup component add rustfmt --toolchain {}", TOOLCHAIN)?;
|
||||
let out = run!("rustfmt --version")?;
|
||||
if !out.contains("stable") {
|
||||
bail!(
|
||||
"Failed to run rustfmt from toolchain 'stable'. \
|
||||
Please run `rustup component add rustfmt --toolchain stable` to install it.",
|
||||
)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_clippy() -> Result<()> {
|
||||
match Command::new("rustup")
|
||||
.args(&["run", TOOLCHAIN, "--", "cargo", "clippy", "--version"])
|
||||
.stderr(Stdio::null())
|
||||
.stdout(Stdio::null())
|
||||
.status()
|
||||
{
|
||||
Ok(status) if status.success() => (),
|
||||
_ => install_clippy().context("install clippy")?,
|
||||
};
|
||||
if run!("cargo clippy --version").is_err() {
|
||||
bail!(
|
||||
"Failed run cargo clippy. \
|
||||
Please run `rustup component add clippy` to install it.",
|
||||
)
|
||||
}
|
||||
|
||||
let allowed_lints = [
|
||||
"clippy::collapsible_if",
|
||||
@ -110,27 +96,24 @@ pub fn run_clippy() -> Result<()> {
|
||||
"clippy::nonminimal_bool",
|
||||
"clippy::redundant_pattern_matching",
|
||||
];
|
||||
run!(
|
||||
"rustup run {} -- cargo clippy --all-features --all-targets -- -A {}",
|
||||
TOOLCHAIN,
|
||||
allowed_lints.join(" -A ")
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn install_clippy() -> Result<()> {
|
||||
run!("rustup toolchain install {}", TOOLCHAIN)?;
|
||||
run!("rustup component add clippy --toolchain {}", TOOLCHAIN)?;
|
||||
run!("cargo clippy --all-features --all-targets -- -A {}", allowed_lints.join(" -A "))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn run_fuzzer() -> Result<()> {
|
||||
let _d = pushd("./crates/ra_syntax");
|
||||
let _e = pushenv("RUSTUP_TOOLCHAIN", "nightly");
|
||||
if run!("cargo fuzz --help").is_err() {
|
||||
run!("cargo install cargo-fuzz")?;
|
||||
};
|
||||
|
||||
run!("rustup run nightly -- cargo fuzz run parser")?;
|
||||
// Expecting nightly rustc
|
||||
let out = run!("rustc --version")?;
|
||||
if !out.contains("nightly") {
|
||||
bail!("fuzz tests require nightly rustc")
|
||||
}
|
||||
|
||||
run!("cargo fuzz run parser")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,8 @@
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
env,
|
||||
ffi::OsString,
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
process::{Command, Stdio},
|
||||
};
|
||||
@ -57,7 +59,10 @@ macro_rules! _run {
|
||||
run!($($expr),*; echo = true)
|
||||
};
|
||||
($($expr:expr),* ; echo = $echo:expr) => {
|
||||
$crate::not_bash::run_process(format!($($expr),*), $echo)
|
||||
$crate::not_bash::run_process(format!($($expr),*), $echo, None)
|
||||
};
|
||||
($($expr:expr),* ; <$stdin:expr) => {
|
||||
$crate::not_bash::run_process(format!($($expr),*), false, Some($stdin))
|
||||
};
|
||||
}
|
||||
pub(crate) use _run as run;
|
||||
@ -77,6 +82,21 @@ fn drop(&mut self) {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Pushenv {
|
||||
_p: (),
|
||||
}
|
||||
|
||||
pub fn pushenv(var: &str, value: &str) -> Pushenv {
|
||||
Env::with(|env| env.pushenv(var.into(), value.into()));
|
||||
Pushenv { _p: () }
|
||||
}
|
||||
|
||||
impl Drop for Pushenv {
|
||||
fn drop(&mut self) {
|
||||
Env::with(|env| env.popenv())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rm_rf(path: impl AsRef<Path>) -> Result<()> {
|
||||
let path = path.as_ref();
|
||||
if !path.exists() {
|
||||
@ -90,15 +110,15 @@ pub fn rm_rf(path: impl AsRef<Path>) -> Result<()> {
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn run_process(cmd: String, echo: bool) -> Result<String> {
|
||||
run_process_inner(&cmd, echo).with_context(|| format!("process `{}` failed", cmd))
|
||||
pub fn run_process(cmd: String, echo: bool, stdin: Option<&[u8]>) -> Result<String> {
|
||||
run_process_inner(&cmd, echo, stdin).with_context(|| format!("process `{}` failed", cmd))
|
||||
}
|
||||
|
||||
pub fn date_iso() -> Result<String> {
|
||||
run!("date --iso --utc")
|
||||
}
|
||||
|
||||
fn run_process_inner(cmd: &str, echo: bool) -> Result<String> {
|
||||
fn run_process_inner(cmd: &str, echo: bool, stdin: Option<&[u8]>) -> Result<String> {
|
||||
let mut args = shelx(cmd);
|
||||
let binary = args.remove(0);
|
||||
let current_dir = Env::with(|it| it.cwd().to_path_buf());
|
||||
@ -107,12 +127,17 @@ fn run_process_inner(cmd: &str, echo: bool) -> Result<String> {
|
||||
println!("> {}", cmd)
|
||||
}
|
||||
|
||||
let output = Command::new(binary)
|
||||
.args(args)
|
||||
.current_dir(current_dir)
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::inherit())
|
||||
.output()?;
|
||||
let mut command = Command::new(binary);
|
||||
command.args(args).current_dir(current_dir).stderr(Stdio::inherit());
|
||||
let output = match stdin {
|
||||
None => command.stdin(Stdio::null()).output(),
|
||||
Some(stdin) => {
|
||||
command.stdin(Stdio::piped()).stdout(Stdio::piped());
|
||||
let mut process = command.spawn()?;
|
||||
process.stdin.take().unwrap().write_all(stdin)?;
|
||||
process.wait_with_output()
|
||||
}
|
||||
}?;
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
|
||||
if echo {
|
||||
@ -133,13 +158,15 @@ fn shelx(cmd: &str) -> Vec<String> {
|
||||
|
||||
struct Env {
|
||||
pushd_stack: Vec<PathBuf>,
|
||||
pushenv_stack: Vec<(OsString, Option<OsString>)>,
|
||||
}
|
||||
|
||||
impl Env {
|
||||
fn with<F: FnOnce(&mut Env) -> T, T>(f: F) -> T {
|
||||
thread_local! {
|
||||
static ENV: RefCell<Env> = RefCell::new(Env {
|
||||
pushd_stack: vec![env::current_dir().unwrap()]
|
||||
pushd_stack: vec![env::current_dir().unwrap()],
|
||||
pushenv_stack: vec![],
|
||||
});
|
||||
}
|
||||
ENV.with(|it| f(&mut *it.borrow_mut()))
|
||||
@ -154,6 +181,17 @@ fn popd(&mut self) {
|
||||
self.pushd_stack.pop().unwrap();
|
||||
env::set_current_dir(self.cwd()).unwrap();
|
||||
}
|
||||
fn pushenv(&mut self, var: OsString, value: OsString) {
|
||||
self.pushenv_stack.push((var.clone(), env::var_os(&var)));
|
||||
env::set_var(var, value)
|
||||
}
|
||||
fn popenv(&mut self) {
|
||||
let (var, value) = self.pushenv_stack.pop().unwrap();
|
||||
match value {
|
||||
None => env::remove_var(var),
|
||||
Some(value) => env::set_var(var, value),
|
||||
}
|
||||
}
|
||||
fn cwd(&self) -> &Path {
|
||||
self.pushd_stack.last().unwrap()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user