Merge branch 'master' of github.com:rust-analyzer/rust-analyzer

This commit is contained in:
Benjamin Coenen 2020-05-01 16:26:30 +02:00
commit dc34162450
68 changed files with 1926 additions and 590 deletions

View file

@ -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
View file

@ -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
View file

@ -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
View file

@ -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",

View file

@ -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 @@ impl From<{0}> for {1} {{
)
}
fn already_has_from_impl(
fn existing_from_impl(
sema: &'_ hir::Semantics<'_, RootDatabase>,
variant: &ast::EnumVariant,
) -> bool {
let scope = sema.scope(&variant.syntax());
) -> Option<()> {
let variant = sema.to_def(variant)?;
let enum_ = variant.parent_enum(sema.db);
let krate = enum_.module(sema.db).krate();
let from_path = ast::make::path_from_text("From");
let from_hir_path = match hir::Path::from_ast(from_path) {
Some(p) => p,
None => return false,
};
let from_trait = match scope.resolve_hir_path(&from_hir_path) {
Some(hir::PathResolution::Def(hir::ModuleDef::Trait(t))) => t,
_ => return false,
};
let from_trait = FamousDefs(sema, krate).core_convert_From()?;
let e: hir::Enum = match sema.to_def(&variant.parent_enum()) {
Some(e) => e,
None => return false,
};
let e_ty = e.ty(sema.db);
let enum_type = enum_.ty(sema.db);
let hir_enum_var: hir::EnumVariant = match sema.to_def(variant) {
Some(ev) => ev,
None => return false,
};
let var_ty = hir_enum_var.fields(sema.db)[0].signature_ty(sema.db);
let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
e_ty.impls_trait(sema.db, from_trait, &[var_ty])
if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
Some(())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::helpers::{check_assist, check_assist_not_applicable};
use test_utils::covers;
#[test]
fn test_add_from_impl_for_enum() {
@ -136,36 +130,40 @@ mod tests {
);
}
fn check_not_applicable(ra_fixture: &str) {
let fixture =
format!("//- main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
check_assist_not_applicable(add_from_impl_for_enum, &fixture)
}
#[test]
fn test_add_from_impl_no_element() {
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One }");
check_not_applicable("enum A { <|>One }");
}
#[test]
fn test_add_from_impl_more_than_one_element_in_tuple() {
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One(u32, String) }");
check_not_applicable("enum A { <|>One(u32, String) }");
}
#[test]
fn test_add_from_impl_struct_variant() {
check_assist_not_applicable(add_from_impl_for_enum, "enum A { <|>One { x: u32 } }");
check_not_applicable("enum A { <|>One { x: u32 } }");
}
#[test]
fn test_add_from_impl_already_exists() {
check_assist_not_applicable(
add_from_impl_for_enum,
r#"enum A { <|>One(u32), }
covers!(test_add_from_impl_already_exists);
check_not_applicable(
r#"
enum A { <|>One(u32), }
impl From<u32> for A {
fn from(v: u32) -> Self {
A::One(v)
}
}
pub trait From<T> {
fn from(T) -> Self;
}"#,
"#,
);
}

View file

@ -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 @@ impl VariantData {
}",
);
}
#[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"),
}
}
"#,
);
}
}

View file

@ -1,6 +1,5 @@
use std::iter::once;
use hir::Adt;
use ra_syntax::{
ast::{
self,
@ -12,6 +11,7 @@ use ra_syntax::{
use crate::{
assist_ctx::{Assist, AssistCtx},
utils::TryEnum,
AssistId,
};
@ -45,20 +45,10 @@ pub(crate) fn replace_let_with_if_let(ctx: AssistCtx) -> Option<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))),

View file

@ -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,17 +37,10 @@ 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();
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)));
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();
@ -56,24 +48,16 @@ pub(crate) fn replace_unwrap_with_match(ctx: AssistCtx) -> Option<Assist> {
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 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_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
})
}
#[cfg(test)]

View file

@ -8,4 +8,5 @@ test_utils::marks![
test_not_inline_mut_variable
test_not_applicable_if_variable_unused
change_visibility_field_false_positive
test_add_from_impl_already_exists
];

View file

@ -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,
}
}
}

View file

@ -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"

View file

@ -57,7 +57,7 @@ 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 {
@ -70,7 +70,6 @@ pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
_ => (),
}
}
}
None
}

View file

@ -953,6 +953,16 @@ impl TypeParam {
pub fn module(self, db: &dyn HirDatabase) -> Module {
self.id.parent.module(db.upcast()).into()
}
pub fn ty(self, db: &dyn HirDatabase) -> Type {
let resolver = self.id.parent.resolver(db.upcast());
let environment = TraitEnvironment::lower(db, &resolver);
let ty = Ty::Placeholder(self.id);
Type {
krate: self.id.parent.module(db.upcast()).krate,
ty: InEnvironment { value: ty, environment },
}
}
}
// FIXME: rename from `ImplDef` to `Impl`
@ -1157,18 +1167,21 @@ impl Type {
pub fn fields(&self, db: &dyn HirDatabase) -> Vec<(Field, Type)> {
if let Ty::Apply(a_ty) = &self.ty.value {
if let TypeCtor::Adt(AdtId::StructId(s)) = a_ty.ctor {
let var_def = s.into();
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(var_def)
.field_types(variant_id)
.iter()
.map(|(local_id, ty)| {
let def = Field { parent: var_def.into(), id: local_id };
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()
}

View file

@ -9,6 +9,7 @@ use hir_def::{
AsMacroCall, TraitId,
};
use hir_expand::ExpansionInfo;
use hir_ty::associated_type_shorthand_candidates;
use itertools::Itertools;
use ra_db::{FileId, FileRange};
use ra_prof::profile;
@ -24,8 +25,9 @@ use crate::{
semantics::source_to_def::{ChildContainer, SourceToDefCache, SourceToDefCtx},
source_analyzer::{resolve_hir_path, SourceAnalyzer},
AssocItem, Field, Function, HirFileId, ImplDef, InFile, Local, MacroDef, Module, ModuleDef,
Name, Origin, Path, ScopeDef, Trait, Type, TypeParam,
Name, Origin, Path, ScopeDef, Trait, Type, TypeAlias, TypeParam,
};
use resolver::TypeNs;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PathResolution {
@ -40,6 +42,44 @@ pub enum PathResolution {
AssocItem(AssocItem),
}
impl PathResolution {
fn in_type_ns(&self) -> Option<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,

View file

@ -182,10 +182,6 @@ impl ExprCollector<'_> {
self.alloc_expr(Expr::If { condition, then_branch, else_branch }, syntax_ptr)
}
ast::Expr::TryBlockExpr(e) => {
let body = self.collect_block_opt(e.body());
self.alloc_expr(Expr::TryBlock { body }, syntax_ptr)
}
ast::Expr::BlockExpr(e) => self.collect_block(e),
ast::Expr::LoopExpr(e) => {
let body = self.collect_block_opt(e.loop_body());

View file

@ -101,9 +101,6 @@ pub enum Expr {
Try {
expr: ExprId,
},
TryBlock {
body: ExprId,
},
Cast {
expr: ExprId,
type_ref: TypeRef,
@ -239,7 +236,6 @@ impl Expr {
f(*expr);
}
}
Expr::TryBlock { body } => f(*body),
Expr::Loop { body } => f(*body),
Expr::While { condition, body } => {
f(*condition);

View file

@ -73,11 +73,6 @@ impl<'a> InferenceContext<'a> {
self.coerce_merge_branch(&then_ty, &else_ty)
}
Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected),
Expr::TryBlock { body } => {
let _inner = self.infer_expr(*body, expected);
// FIXME should be std::result::Result<{inner}, _>
Ty::Unknown
}
Expr::Loop { body } => {
self.infer_expr(*body, &Expectation::has_type(Ty::unit()));
// FIXME handle break with value

View file

@ -66,7 +66,8 @@ pub use autoderef::autoderef;
pub use infer::{InferTy, InferenceResult};
pub use lower::CallableDef;
pub use lower::{
callable_item_sig, ImplTraitLoweringMode, TyDefId, TyLoweringContext, ValueTyDefId,
associated_type_shorthand_candidates, callable_item_sig, ImplTraitLoweringMode, TyDefId,
TyLoweringContext, ValueTyDefId,
};
pub use traits::{InEnvironment, Obligation, ProjectionPredicate, TraitEnvironment};

View file

@ -17,9 +17,9 @@ use hir_def::{
path::{GenericArg, Path, PathSegment, PathSegments},
resolver::{HasResolver, Resolver, TypeNs},
type_ref::{TypeBound, TypeRef},
AdtId, AssocContainerId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId, HasModule,
ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId, UnionId,
VariantId,
AdtId, AssocContainerId, AssocItemId, ConstId, EnumId, EnumVariantId, FunctionId, GenericDefId,
HasModule, ImplId, LocalFieldId, Lookup, StaticId, StructId, TraitId, TypeAliasId, TypeParamId,
UnionId, VariantId,
};
use ra_arena::map::ArenaMap;
use ra_db::CrateId;
@ -34,6 +34,7 @@ use crate::{
Binders, BoundVar, DebruijnIndex, FnSig, GenericPredicate, PolyFnSig, ProjectionPredicate,
ProjectionTy, Substs, TraitEnvironment, TraitRef, Ty, TypeCtor, TypeWalk,
};
use hir_expand::name::Name;
#[derive(Debug)]
pub struct TyLoweringContext<'a> {
@ -383,62 +384,39 @@ impl Ty {
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),
};
traits_.push(trait_ref);
}
}
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)
{
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"),
ctx.resolver.generic_def().expect(
"there should be generics if there's a generic param",
),
);
t.substs.subst_bound_vars(&s)
t.substs.clone().subst_bound_vars(&s)
}
TypeParamLoweringMode::Variable => t.substs,
TypeParamLoweringMode::Variable => t.substs.clone(),
};
// FIXME handle (forbid) type parameters on the segment
return Ty::Projection(ProjectionTy { associated_ty, parameters: substs });
}
// FIXME handle type parameters on the segment
return Some(Ty::Projection(ProjectionTy {
associated_ty,
parameters: substs,
}));
}
None
});
ty.unwrap_or(Ty::Unknown)
} else {
Ty::Unknown
}
}
fn from_hir_path_inner(
ctx: &TyLoweringContext<'_>,
@ -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,

View file

@ -249,6 +249,44 @@ mod tests {
);
}
#[test]
fn test_union_field_completion() {
assert_debug_snapshot!(
do_ref_completion(
r"
union Un {
field: u8,
other: u16,
}
fn foo(u: Un) {
u.<|>
}
",
),
@r###"
[
CompletionItem {
label: "field",
source_range: 140..140,
delete: 140..140,
insert: "field",
kind: Field,
detail: "u8",
},
CompletionItem {
label: "other",
source_range: 140..140,
delete: 140..140,
insert: "other",
kind: Field,
detail: "u16",
},
]
"###
);
}
#[test]
fn test_method_completion() {
assert_debug_snapshot!(

View file

@ -5,19 +5,29 @@ use ra_syntax::AstNode;
use test_utils::tested_by;
use crate::completion::{CompletionContext, Completions};
use rustc_hash::FxHashSet;
pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionContext) {
let path = match &ctx.path_prefix {
Some(path) => path.clone(),
_ => return,
};
let def = match ctx.scope().resolve_hir_path(&path) {
Some(PathResolution::Def(def)) => def,
_ => return,
let scope = ctx.scope();
let context_module = scope.module();
let res = match scope.resolve_hir_path(&path) {
Some(res) => res,
None => return,
};
let context_module = ctx.scope().module();
match def {
hir::ModuleDef::Module(module) => {
// Add associated types on type parameters and `Self`.
res.assoc_type_shorthand_candidates(ctx.db, |alias| {
acc.add_type_alias(ctx, alias);
None::<()>
});
match res {
PathResolution::Def(hir::ModuleDef::Module(module)) => {
let module_scope = module.scope(ctx.db, context_module);
for (name, def) in module_scope {
if ctx.use_item_syntax.is_some() {
@ -35,7 +45,8 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
acc.add_resolution(ctx, name.to_string(), &def);
}
}
hir::ModuleDef::Adt(_) | hir::ModuleDef::TypeAlias(_) => {
PathResolution::Def(def @ hir::ModuleDef::Adt(_))
| PathResolution::Def(def @ hir::ModuleDef::TypeAlias(_)) => {
if let hir::ModuleDef::Adt(Adt::Enum(e)) = def {
for variant in e.variants(ctx.db) {
acc.add_enum_variant(ctx, variant, None);
@ -46,8 +57,10 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
hir::ModuleDef::TypeAlias(a) => a.ty(ctx.db),
_ => unreachable!(),
};
// Iterate assoc types separately
// FIXME: complete T::AssocType
// XXX: For parity with Rust bug #22519, this does not complete Ty::AssocType.
// (where AssocType is defined on a trait, not an inherent impl)
let krate = ctx.krate;
if let Some(krate) = krate {
let traits_in_scope = ctx.scope().traits_in_scope();
@ -65,6 +78,7 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
None::<()>
});
// Iterate assoc types separately
ty.iterate_impl_items(ctx.db, krate, |item| {
if context_module.map_or(false, |m| !item.is_visible_from(ctx.db, m)) {
return None;
@ -77,7 +91,8 @@ pub(super) fn complete_qualified_path(acc: &mut Completions, ctx: &CompletionCon
});
}
}
hir::ModuleDef::Trait(t) => {
PathResolution::Def(hir::ModuleDef::Trait(t)) => {
// Handles `Trait::assoc` as well as `<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 @@ mod tests {
);
}
#[test]
fn completes_ty_param_assoc_ty() {
assert_debug_snapshot!(
do_reference_completion(
"
//- /lib.rs
trait Super {
type Ty;
const CONST: u8;
fn func() {}
fn method(&self) {}
}
trait Sub: Super {
type SubTy;
const C2: ();
fn subfunc() {}
fn submethod(&self) {}
}
fn foo<T: 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!(

View file

@ -53,7 +53,7 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
// Variants with trivial paths are already added by the existing completion logic,
// so we should avoid adding these twice
if path.segments.len() > 1 {
acc.add_enum_variant(ctx, variant, Some(path.to_string()));
acc.add_qualified_enum_variant(ctx, variant, path);
}
}
}
@ -1173,6 +1173,7 @@ mod tests {
delete: 248..250,
insert: "Foo::Bar",
kind: EnumVariant,
lookup: "Bar",
detail: "()",
},
CompletionItem {
@ -1181,6 +1182,7 @@ mod tests {
delete: 248..250,
insert: "Foo::Baz",
kind: EnumVariant,
lookup: "Baz",
detail: "()",
},
CompletionItem {
@ -1189,6 +1191,7 @@ mod tests {
delete: 248..250,
insert: "Foo::Quux",
kind: EnumVariant,
lookup: "Quux",
detail: "()",
},
]
@ -1231,6 +1234,7 @@ mod tests {
delete: 219..221,
insert: "Foo::Bar",
kind: EnumVariant,
lookup: "Bar",
detail: "()",
},
CompletionItem {
@ -1239,6 +1243,7 @@ mod tests {
delete: 219..221,
insert: "Foo::Baz",
kind: EnumVariant,
lookup: "Baz",
detail: "()",
},
CompletionItem {
@ -1247,6 +1252,7 @@ mod tests {
delete: 219..221,
insert: "Foo::Quux",
kind: EnumVariant,
lookup: "Quux",
detail: "()",
},
]
@ -1285,6 +1291,7 @@ mod tests {
delete: 185..186,
insert: "Foo::Bar",
kind: EnumVariant,
lookup: "Bar",
detail: "()",
},
CompletionItem {
@ -1293,6 +1300,7 @@ mod tests {
delete: 185..186,
insert: "Foo::Baz",
kind: EnumVariant,
lookup: "Baz",
detail: "()",
},
CompletionItem {
@ -1301,6 +1309,7 @@ mod tests {
delete: 185..186,
insert: "Foo::Quux",
kind: EnumVariant,
lookup: "Quux",
detail: "()",
},
CompletionItem {
@ -1353,6 +1362,7 @@ mod tests {
delete: 98..99,
insert: "m::E::V",
kind: EnumVariant,
lookup: "V",
detail: "()",
},
]

View file

@ -1,6 +1,6 @@
//! This modules takes care of rendering various definitions as completion items.
use hir::{Docs, HasAttrs, HasSource, HirDisplay, ScopeDef, StructKind, Type};
use hir::{Docs, HasAttrs, HasSource, HirDisplay, ModPath, ScopeDef, StructKind, Type};
use ra_syntax::ast::NameOwner;
use stdx::SepBy;
use test_utils::tested_by;
@ -246,14 +246,37 @@ impl Completions {
.add_to(self);
}
pub(crate) fn add_qualified_enum_variant(
&mut self,
ctx: &CompletionContext,
variant: hir::EnumVariant,
path: ModPath,
) {
self.add_enum_variant_impl(ctx, variant, None, Some(path))
}
pub(crate) fn add_enum_variant(
&mut self,
ctx: &CompletionContext,
variant: hir::EnumVariant,
local_name: Option<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 @@ impl Completions {
.surround_with("{ ", " }")
.to_string(),
};
let mut res =
CompletionItem::new(CompletionKind::Reference, ctx.source_range(), name.clone())
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);

View file

@ -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 @@ impl FunctionSignature {
FunctionSignature {
kind: CallableKind::StructConstructor,
visibility: node.visibility().map(|n| n.syntax().text().to_string()),
// Do we need `const`?
qualifier: Default::default(),
name: node.name().map(|n| n.text().to_string()),
ret_type: node.name().map(|n| n.text().to_string()),
parameters: params,
@ -128,6 +142,8 @@ impl FunctionSignature {
FunctionSignature {
kind: CallableKind::VariantConstructor,
visibility: None,
// Do we need `const`?
qualifier: Default::default(),
name: Some(name),
ret_type: None,
parameters: params,
@ -151,6 +167,7 @@ impl FunctionSignature {
FunctionSignature {
kind: CallableKind::Macro,
visibility: None,
qualifier: Default::default(),
name: node.name().map(|n| n.text().to_string()),
ret_type: None,
parameters: params,
@ -223,6 +240,12 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
FunctionSignature {
kind: CallableKind::Function,
visibility: node.visibility().map(|n| n.syntax().text().to_string()),
qualifier: FunctionQualifier {
is_async: node.async_token().is_some(),
is_const: node.const_token().is_some(),
is_unsafe: node.unsafe_token().is_some(),
extern_abi: node.abi().map(|n| n.to_string()),
},
name: node.name().map(|n| n.text().to_string()),
ret_type: node
.ret_type()
@ -246,6 +269,23 @@ impl Display for FunctionSignature {
write!(f, "{} ", t)?;
}
if self.qualifier.is_async {
write!(f, "async ")?;
}
if self.qualifier.is_const {
write!(f, "const ")?;
}
if self.qualifier.is_unsafe {
write!(f, "unsafe ")?;
}
if let Some(extern_abi) = &self.qualifier.extern_abi {
// Keyword `extern` is included in the string.
write!(f, "{} ", extern_abi)?;
}
if let Some(name) = &self.name {
match self.kind {
CallableKind::Function => write!(f, "fn {}", name)?,

View file

@ -844,4 +844,29 @@ fn func(foo: i32) { if true { <|>foo; }; }
&["fn foo()\n```\n\n<- `\u{3000}` here"],
);
}
#[test]
fn test_hover_function_show_qualifiers() {
check_hover_result(
"
//- /lib.rs
async fn foo<|>() {}
",
&["async fn foo()"],
);
check_hover_result(
"
//- /lib.rs
pub const unsafe fn foo<|>() {}
",
&["pub const unsafe fn foo()"],
);
check_hover_result(
r#"
//- /lib.rs
pub(crate) async unsafe extern "C" fn foo<|>() {}
"#,
&[r#"pub(crate) async unsafe extern "C" fn foo()"#],
);
}
}

View file

@ -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
}
}
",
);
}
}

View file

@ -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)

View file

@ -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);

View file

@ -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);
}

View file

@ -191,7 +191,6 @@ pub enum SyntaxKind {
RECORD_LIT,
RECORD_FIELD_LIST,
RECORD_FIELD,
TRY_BLOCK_EXPR,
BOX_EXPR,
CALL_EXPR,
INDEX_EXPR,

View file

@ -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)
}
};

View file

@ -2,13 +2,12 @@
use crate::{proc_macro::bridge, rustc_server::TokenStream};
use std::fs::File;
use std::path::Path;
use std::path::{Path, PathBuf};
use goblin::{mach::Mach, Object};
use libloading::Library;
use memmap::Mmap;
use ra_proc_macro::ProcMacroKind;
use std::io;
const NEW_REGISTRAR_SYMBOL: &str = "_rustc_proc_macro_decls_";
@ -109,23 +108,21 @@ impl ProcMacroLibraryLibloading {
}
}
type ProcMacroLibraryImpl = ProcMacroLibraryLibloading;
pub struct Expander {
libs: Vec<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,8 +138,7 @@ impl Expander {
TokenStream::with_subtree(attr.clone())
});
for lib in &self.libs {
for proc_macro in &lib.exported_macros {
for proc_macro in &self.inner.exported_macros {
match proc_macro {
bridge::client::ProcMacro::CustomDerive { trait_name, client, .. }
if *trait_name == macro_name =>
@ -174,15 +170,14 @@ impl Expander {
_ => continue,
}
}
}
Err(bridge::PanicMessage::String("Nothing to expand".to_string()))
}
pub fn list_macros(&self) -> Vec<(String, ProcMacroKind)> {
self.libs
self.inner
.exported_macros
.iter()
.flat_map(|it| &it.exported_macros)
.map(|proc_macro| match proc_macro {
bridge::client::ProcMacro::CustomDerive { trait_name, .. } => {
(trait_name.to_string(), ProcMacroKind::CustomDerive)
@ -197,3 +192,33 @@ impl Expander {
.collect()
}
}
/// Copy the dylib to temp directory to prevent locking in Windows
#[cfg(windows)]
fn ensure_file_with_lock_free_access(path: &Path) -> io::Result<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())
}

View file

@ -21,11 +21,21 @@ mod dylib;
use proc_macro::bridge::client::TokenStream;
use ra_proc_macro::{ExpansionResult, ExpansionTask, ListMacrosResult, ListMacrosTask};
use std::path::Path;
use std::{
collections::{hash_map::Entry, HashMap},
fs,
path::{Path, PathBuf},
time::SystemTime,
};
pub(crate) fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, String> {
let expander = create_expander(&task.lib);
#[derive(Default)]
pub(crate) struct ProcMacroSrv {
expanders: HashMap<(PathBuf, SystemTime), dylib::Expander>,
}
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) => {
@ -34,15 +44,23 @@ pub(crate) fn expand_task(task: &ExpansionTask) -> Result<ExpansionResult, Strin
}
}
pub(crate) fn list_macros(task: &ListMacrosTask) -> ListMacrosResult {
let expander = create_expander(&task.lib);
ListMacrosResult { macros: expander.list_macros() }
pub fn list_macros(&mut self, task: &ListMacrosTask) -> Result<ListMacrosResult, String> {
let expander = self.expander(&task.lib)?;
Ok(ListMacrosResult { macros: expander.list_macros() })
}
fn create_expander(lib: &Path) -> dylib::Expander {
dylib::Expander::new(lib)
.unwrap_or_else(|err| panic!("Cannot create expander for {}: {:?}", lib.display(), err))
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)
})?;
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;

View file

@ -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()
}

View file

@ -30,8 +30,9 @@ pub fn init_from(spec: &str) {
pub type Label = &'static str;
/// This function starts a profiling scope in the current execution stack with a given description.
/// It returns a Profile structure and measure elapsed time between this method invocation and Profile structure drop.
/// It supports nested profiling scopes in case when this function invoked multiple times at the execution stack. In this case the profiling information will be nested at the output.
/// It returns a `Profile` struct that measures elapsed time between this method invocation and `Profile` struct drop.
/// It supports nested profiling scopes in case when this function is invoked multiple times at the execution stack.
/// In this case the profiling information will be nested at the output.
/// Profiling information is being printed in the stderr.
///
/// # Example
@ -58,36 +59,35 @@ pub type Label = &'static str;
/// ```
pub fn profile(label: Label) -> Profiler {
assert!(!label.is_empty());
let enabled = PROFILING_ENABLED.load(Ordering::Relaxed)
&& PROFILE_STACK.with(|stack| stack.borrow_mut().push(label));
let label = if enabled { Some(label) } else { None };
Profiler { label, detail: None }
if PROFILING_ENABLED.load(Ordering::Relaxed)
&& PROFILE_STACK.with(|stack| stack.borrow_mut().push(label))
{
Profiler(Some(ProfilerImpl { label, detail: None }))
} else {
Profiler(None)
}
}
pub struct Profiler {
label: Option<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 @@ impl ProfileStack {
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())
}
}
}

View file

@ -16,7 +16,9 @@ use crate::{
};
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>) {

View file

@ -16,7 +16,7 @@ impl ast::Expr {
| ast::Expr::WhileExpr(_)
| ast::Expr::BlockExpr(_)
| ast::Expr::MatchExpr(_)
| ast::Expr::TryBlockExpr(_) => true,
| ast::Expr::TryExpr(_) => true,
_ => false,
}
}
@ -359,7 +359,22 @@ impl ast::Literal {
}
}
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,
}
}

View file

@ -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 @@ impl AstNode for LoopExpr {
}
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 @@ impl From<AwaitExpr> for Expr {
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 @@ impl AstNode for Expr {
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 @@ impl AstNode for Expr {
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 @@ impl AstNode for Expr {
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 @@ impl std::fmt::Display for LoopExpr {
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)

View file

@ -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)
}

View file

@ -13,7 +13,12 @@ impl Comment {
}
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();

View file

@ -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,
}
};
}
}

View file

@ -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

View file

@ -0,0 +1,4 @@
use ::crate;
use {crate, foo::{crate::foo::bar::baz}};
use hello::crate;
use hello::crate::there;

View file

@ -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"
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 "}"
R_CURLY@204..205 "}"
SEMICOLON@205..206 ";"
WHITESPACE@206..207 " "
COMMENT@207..249 "// Nonsensical but pe ..."
WHITESPACE@249..250 "\n"
SEMICOLON@204..205 ";"
WHITESPACE@205..206 " "
COMMENT@206..248 "// Nonsensical but pe ..."
WHITESPACE@248..249 "\n"

View file

@ -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

View file

@ -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"

View file

@ -0,0 +1 @@
fn foo() { try!(Ok(())); }

View file

@ -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"

View file

@ -0,0 +1 @@
macro_rules! try { () => {} }

View file

@ -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" }

View file

@ -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()),

View file

@ -150,7 +150,7 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
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()),

View file

@ -6,9 +6,12 @@ mod subscriptions;
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 crossbeam_channel::{never, select, unbounded, RecvError, Sender};
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 threadpool::ThreadPool;
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 @@ where
{
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");
}
}

View file

@ -326,10 +326,10 @@ pub fn handle_workspace_symbol(
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,
};

View file

@ -8,14 +8,15 @@ pub use lsp_types::{
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;

View file

@ -4,20 +4,9 @@ use std::ops;
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");
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");
macro_rules! define_semantic_token_types {
($(($ident:ident, $string:literal)),*$(,)?) => {
$(pub(crate) const $ident: SemanticTokenType = SemanticTokenType::new($string);)*
pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[
SemanticTokenType::COMMENT,
@ -40,15 +29,25 @@ pub(crate) const SUPPORTED_TYPES: &[SemanticTokenType] = &[
SemanticTokenType::VARIABLE,
SemanticTokenType::PARAMETER,
SemanticTokenType::LABEL,
ATTRIBUTE,
BUILTIN_TYPE,
ENUM_MEMBER,
LIFETIME,
TYPE_ALIAS,
UNION,
UNRESOLVED_REFERENCE,
FORMAT_SPECIFIER,
$($ident),*
];
};
}
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"),
];
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,
@ -58,10 +57,16 @@ pub(crate) const SUPPORTED_MODIFIERS: &[SemanticTokenModifier] = &[
SemanticTokenModifier::ABSTRACT,
SemanticTokenModifier::DEPRECATED,
SemanticTokenModifier::READONLY,
CONSTANT,
MUTABLE,
UNSAFE,
CONTROL_FLOW,
$($ident),*
];
};
}
define_semantic_token_modifiers![
(CONSTANT, "constant"),
(CONTROL_FLOW, "controlFlow"),
(MUTABLE, "mutable"),
(UNSAFE, "unsafe"),
];
#[derive(Default)]

View file

@ -4,8 +4,8 @@ use std::{collections::HashMap, path::PathBuf, time::Instant};
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() { message(); }
})
.server();
server.wait_until_workspace_is_loaded();
let res = server.send_request::<GotoDefinition>(TextDocumentPositionParams::new(
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(
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```""#)

View file

@ -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.

View file

@ -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 {}`

View file

@ -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.

View file

@ -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
View 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}.`));
});
});
}
}

View file

@ -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,16 +64,8 @@ export function runSingle(ctx: Ctx): Cmd {
};
}
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");
return;
}
const debugConfig = {
function getLldbDebugConfig(config: ra.Runnable, sourceFileMap: Record<string, string>): vscode.DebugConfiguration {
return {
type: "lldb",
request: "launch",
name: config.label,
@ -79,8 +73,60 @@ export function debugSingle(ctx: Ctx): Cmd {
args: config.args,
},
args: config.extraArgs,
cwd: config.cwd
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;
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 = lldbId === debugEngine.id
? getLldbDebugConfig(config, ctx.config.debug.sourceFileMap)
: await getCppvsDebugConfig(config, ctx.config.debug.sourceFileMap);
return vscode.debug.startDebugging(undefined, debugConfig);
};

View file

@ -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"),
};
}
}

View file

@ -162,7 +162,6 @@ pub(crate) const KINDS_SRC: KindsSrc = KindsSrc {
"RECORD_LIT",
"RECORD_FIELD_LIST",
"RECORD_FIELD",
"TRY_BLOCK_EXPR",
"BOX_EXPR",
// postfix
"CALL_EXPR",
@ -440,7 +439,6 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
}
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 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
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 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
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 @@ pub(crate) const AST_SRC: AstSrc = AstSrc {
FieldExpr,
AwaitExpr,
TryExpr,
TryBlockExpr,
CastExpr,
RefExpr,
PrefixExpr,

View file

@ -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") {

View file

@ -10,23 +10,19 @@ pub mod pre_commit;
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 @@ pub fn rust_files(path: &Path) -> impl Iterator<Item = PathBuf> {
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(())
}

View file

@ -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 @@ impl Drop for Pushd {
}
}
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 @@ impl Env {
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()
}