mirror of
https://github.com/rust-lang/rust.git
synced 2026-05-30 04:56:25 +03:00
Rollup merge of #154241 - lnicola:sync-from-ra, r=lnicola
`rust-analyzer` subtree update Subtree update of `rust-analyzer` to https://github.com/rust-lang/rust-analyzer/commit/b42b63f390a4dab14e6efa34a70e67f5b087cc62. Created using https://github.com/rust-lang/josh-sync. r? @ghost
This commit is contained in:
@@ -20,6 +20,8 @@ analyzing Rust code. See
|
||||
[Architecture](https://rust-analyzer.github.io/book/contributing/architecture.html)
|
||||
in the manual.
|
||||
|
||||
[](https://app.codecov.io/github/rust-lang/rust-analyzer/tree/master)
|
||||
|
||||
## Quick Start
|
||||
|
||||
https://rust-analyzer.github.io/book/installation.html
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
//! Things related to IR printing in the next-trait-solver.
|
||||
|
||||
use std::any::type_name_of_val;
|
||||
|
||||
use rustc_type_ir::{self as ty, ir_print::IrPrint};
|
||||
|
||||
use super::SolverDefId;
|
||||
@@ -82,7 +80,10 @@ fn print_debug(
|
||||
t: &ty::TraitPredicate<Self>,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
match t.polarity {
|
||||
ty::PredicatePolarity::Positive => write!(fmt, "{:?}", t.trait_ref),
|
||||
ty::PredicatePolarity::Negative => write!(fmt, "!{:?}", t.trait_ref),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'db> IrPrint<rustc_type_ir::HostEffectPredicate<Self>> for DbInterner<'db> {
|
||||
@@ -97,7 +98,11 @@ fn print_debug(
|
||||
t: &rustc_type_ir::HostEffectPredicate<Self>,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
let prefix = match t.constness {
|
||||
ty::BoundConstness::Const => "const",
|
||||
ty::BoundConstness::Maybe => "[const]",
|
||||
};
|
||||
write!(fmt, "{prefix} {:?}", t.trait_ref)
|
||||
}
|
||||
}
|
||||
impl<'db> IrPrint<ty::ExistentialTraitRef<Self>> for DbInterner<'db> {
|
||||
@@ -183,7 +188,7 @@ fn print_debug(
|
||||
t: &ty::NormalizesTo<Self>,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
write!(fmt, "NormalizesTo({} -> {:?})", t.alias, t.term)
|
||||
}
|
||||
}
|
||||
impl<'db> IrPrint<ty::SubtypePredicate<Self>> for DbInterner<'db> {
|
||||
@@ -198,7 +203,7 @@ fn print_debug(
|
||||
t: &ty::SubtypePredicate<Self>,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
write!(fmt, "{:?} <: {:?}", t.a, t.b)
|
||||
}
|
||||
}
|
||||
impl<'db> IrPrint<ty::CoercePredicate<Self>> for DbInterner<'db> {
|
||||
@@ -210,7 +215,7 @@ fn print_debug(
|
||||
t: &ty::CoercePredicate<Self>,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
write!(fmt, "CoercePredicate({:?} -> {:?})", t.a, t.b)
|
||||
}
|
||||
}
|
||||
impl<'db> IrPrint<ty::FnSig<Self>> for DbInterner<'db> {
|
||||
@@ -219,7 +224,9 @@ fn print(t: &ty::FnSig<Self>, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Re
|
||||
}
|
||||
|
||||
fn print_debug(t: &ty::FnSig<Self>, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
let tys = t.inputs_and_output.as_slice();
|
||||
let (output, inputs) = tys.split_last().unwrap();
|
||||
write!(fmt, "fn({:?}) -> {:?}", inputs, output)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,6 +242,10 @@ fn print_debug(
|
||||
t: &rustc_type_ir::PatternKind<DbInterner<'db>>,
|
||||
fmt: &mut std::fmt::Formatter<'_>,
|
||||
) -> std::fmt::Result {
|
||||
fmt.write_str(&format!("TODO: {:?}", type_name_of_val(t)))
|
||||
match t {
|
||||
ty::PatternKind::Range { start, end } => write!(fmt, "{:?}..={:?}", start, end),
|
||||
ty::PatternKind::Or(list) => write!(fmt, "or({:?})", list),
|
||||
ty::PatternKind::NotNull => fmt.write_str("!null"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,20 +51,6 @@ fn from(d: $diag $(<$lt>)?) -> $AnyDiagnostic<$db> {
|
||||
)*
|
||||
};
|
||||
}
|
||||
// FIXME Accept something like the following in the macro call instead
|
||||
// diagnostics![
|
||||
// pub struct BreakOutsideOfLoop {
|
||||
// pub expr: InFile<AstPtr<ast::Expr>>,
|
||||
// pub is_break: bool,
|
||||
// pub bad_value_break: bool,
|
||||
// }, ...
|
||||
// or more concisely
|
||||
// BreakOutsideOfLoop {
|
||||
// expr: InFile<AstPtr<ast::Expr>>,
|
||||
// is_break: bool,
|
||||
// bad_value_break: bool,
|
||||
// }, ...
|
||||
// ]
|
||||
|
||||
diagnostics![AnyDiagnostic<'db> ->
|
||||
AwaitOutsideOfAsync,
|
||||
|
||||
@@ -84,6 +84,7 @@ fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Exp
|
||||
match parent {
|
||||
ast::LetStmt(it) => it.initializer()?,
|
||||
ast::LetExpr(it) => it.expr()?,
|
||||
ast::BinExpr(it) => it.rhs()?,
|
||||
ast::Static(it) => it.body()?,
|
||||
ast::Const(it) => it.body()?,
|
||||
_ => return None,
|
||||
@@ -174,6 +175,70 @@ fn foo() {
|
||||
n + 100
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
add_braces,
|
||||
r#"
|
||||
fn foo() {
|
||||
let x;
|
||||
x =$0 n + 100;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let x;
|
||||
x = {
|
||||
n + 100
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
add_braces,
|
||||
r#"
|
||||
fn foo() {
|
||||
if let x =$0 n + 100 {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
if let x = {
|
||||
n + 100
|
||||
} {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_add_braces_for_const_initializer() {
|
||||
check_assist(
|
||||
add_braces,
|
||||
r#"
|
||||
const X: i32 =$0 1 + 2;
|
||||
"#,
|
||||
r#"
|
||||
const X: i32 = {
|
||||
1 + 2
|
||||
};
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn suggest_add_braces_for_static_initializer() {
|
||||
check_assist(
|
||||
add_braces,
|
||||
r#"
|
||||
static X: i32 $0= 1 + 2;
|
||||
"#,
|
||||
r#"
|
||||
static X: i32 = {
|
||||
1 + 2
|
||||
};
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@
|
||||
};
|
||||
use syntax::{
|
||||
SyntaxToken, T,
|
||||
ast::{
|
||||
self, AstNode, HasLoopBody,
|
||||
make::{self, tokens},
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
ast::{self, AstNode, HasLoopBody, syntax_factory::SyntaxFactory},
|
||||
syntax_editor::{Position, SyntaxEditor},
|
||||
};
|
||||
|
||||
@@ -35,9 +31,9 @@
|
||||
// }
|
||||
// ```
|
||||
pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let loop_kw = ctx.find_token_syntax_at_offset(T![loop])?;
|
||||
let loop_expr = loop_kw.parent().and_then(ast::LoopExpr::cast)?;
|
||||
if loop_expr.label().is_some() {
|
||||
let loop_expr = ctx.find_node_at_offset::<ast::AnyHasLoopBody>()?;
|
||||
let loop_kw = loop_token(&loop_expr)?;
|
||||
if loop_expr.label().is_some() || !loop_kw.text_range().contains_inclusive(ctx.offset()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -52,8 +48,8 @@ pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||
let label = make.lifetime("'l");
|
||||
let elements = vec![
|
||||
label.syntax().clone().into(),
|
||||
make::token(T![:]).into(),
|
||||
tokens::single_space().into(),
|
||||
make.token(T![:]).into(),
|
||||
make.whitespace(" ").into(),
|
||||
];
|
||||
editor.insert_all(Position::before(&loop_kw), elements);
|
||||
|
||||
@@ -80,6 +76,14 @@ pub(crate) fn add_label_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>) -> O
|
||||
)
|
||||
}
|
||||
|
||||
fn loop_token(loop_expr: &ast::AnyHasLoopBody) -> Option<syntax::SyntaxToken> {
|
||||
loop_expr
|
||||
.syntax()
|
||||
.children_with_tokens()
|
||||
.filter_map(|it| it.into_token())
|
||||
.find(|it| matches!(it.kind(), T![for] | T![loop] | T![while]))
|
||||
}
|
||||
|
||||
fn insert_label_after_token(
|
||||
editor: &mut SyntaxEditor,
|
||||
make: &SyntaxFactory,
|
||||
@@ -88,7 +92,7 @@ fn insert_label_after_token(
|
||||
builder: &mut SourceChangeBuilder,
|
||||
) {
|
||||
let label = make.lifetime("'l");
|
||||
let elements = vec![tokens::single_space().into(), label.syntax().clone().into()];
|
||||
let elements = vec![make.whitespace(" ").into(), label.syntax().clone().into()];
|
||||
editor.insert_all(Position::after(token), elements);
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
@@ -123,6 +127,48 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_label_to_while_expr() {
|
||||
check_assist(
|
||||
add_label_to_loop,
|
||||
r#"
|
||||
fn main() {
|
||||
while$0 true {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}"#,
|
||||
r#"
|
||||
fn main() {
|
||||
${1:'l}: while true {
|
||||
break ${2:'l};
|
||||
continue ${0:'l};
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_label_to_for_expr() {
|
||||
check_assist(
|
||||
add_label_to_loop,
|
||||
r#"
|
||||
fn main() {
|
||||
for$0 _ in 0..5 {
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
}"#,
|
||||
r#"
|
||||
fn main() {
|
||||
${1:'l}: for _ in 0..5 {
|
||||
break ${2:'l};
|
||||
continue ${0:'l};
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_label_to_outer_loop() {
|
||||
check_assist(
|
||||
@@ -191,6 +237,31 @@ fn main() {
|
||||
break 'l;
|
||||
continue 'l;
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn do_not_add_label_if_outside_keyword() {
|
||||
check_assist_not_applicable(
|
||||
add_label_to_loop,
|
||||
r#"
|
||||
fn main() {
|
||||
'l: loop {$0
|
||||
break 'l;
|
||||
continue 'l;
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist_not_applicable(
|
||||
add_label_to_loop,
|
||||
r#"
|
||||
fn main() {
|
||||
'l: while true {$0
|
||||
break 'l;
|
||||
continue 'l;
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use syntax::ast::{self, AstNode, HasGenericParams, HasName};
|
||||
use syntax::{
|
||||
SyntaxKind, SyntaxNode, SyntaxToken,
|
||||
ast::{self, AstNode, HasGenericParams, HasName},
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists};
|
||||
|
||||
@@ -21,7 +24,7 @@
|
||||
// ```
|
||||
pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let ref_type_focused = ctx.find_node_at_offset::<ast::RefType>()?;
|
||||
if ref_type_focused.lifetime().is_some() {
|
||||
if ref_type_focused.lifetime().is_some_and(|lifetime| lifetime.text() != "'_") {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -34,10 +37,10 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) -
|
||||
return None;
|
||||
}
|
||||
|
||||
let ref_types = fetch_borrowed_types(&node)?;
|
||||
let changes = fetch_borrowed_types(&node)?;
|
||||
let target = node.syntax().text_range();
|
||||
|
||||
acc.add(AssistId::generate("add_lifetime_to_type"), "Add lifetime", target, |builder| {
|
||||
acc.add(AssistId::quick_fix("add_lifetime_to_type"), "Add lifetime", target, |builder| {
|
||||
match node.generic_param_list() {
|
||||
Some(gen_param) => {
|
||||
if let Some(left_angle) = gen_param.l_angle_token() {
|
||||
@@ -51,16 +54,21 @@ pub(crate) fn add_lifetime_to_type(acc: &mut Assists, ctx: &AssistContext<'_>) -
|
||||
}
|
||||
}
|
||||
|
||||
for ref_type in ref_types {
|
||||
if let Some(amp_token) = ref_type.amp_token() {
|
||||
builder.insert(amp_token.text_range().end(), "'a ");
|
||||
for change in changes {
|
||||
match change {
|
||||
Change::Replace(it) => {
|
||||
builder.replace(it.text_range(), "'a");
|
||||
}
|
||||
Change::Insert(it) => {
|
||||
builder.insert(it.text_range().end(), "'a ");
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<ast::RefType>> {
|
||||
let ref_types: Vec<ast::RefType> = match node {
|
||||
fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<Change>> {
|
||||
let ref_types: Vec<_> = match node {
|
||||
ast::Adt::Enum(enum_) => {
|
||||
let variant_list = enum_.variant_list()?;
|
||||
variant_list
|
||||
@@ -79,55 +87,50 @@ fn fetch_borrowed_types(node: &ast::Adt) -> Option<Vec<ast::RefType>> {
|
||||
}
|
||||
ast::Adt::Union(un) => {
|
||||
let record_field_list = un.record_field_list()?;
|
||||
record_field_list
|
||||
.fields()
|
||||
.filter_map(|r_field| {
|
||||
if let ast::Type::RefType(ref_type) = r_field.ty()?
|
||||
&& ref_type.lifetime().is_none()
|
||||
{
|
||||
return Some(ref_type);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect()
|
||||
find_ref_types_from_field_list(&record_field_list.into())?
|
||||
}
|
||||
};
|
||||
|
||||
if ref_types.is_empty() { None } else { Some(ref_types) }
|
||||
}
|
||||
|
||||
fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option<Vec<ast::RefType>> {
|
||||
let ref_types: Vec<ast::RefType> = match field_list {
|
||||
ast::FieldList::RecordFieldList(record_list) => record_list
|
||||
.fields()
|
||||
.filter_map(|f| {
|
||||
if let ast::Type::RefType(ref_type) = f.ty()?
|
||||
&& ref_type.lifetime().is_none()
|
||||
{
|
||||
return Some(ref_type);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect(),
|
||||
ast::FieldList::TupleFieldList(tuple_field_list) => tuple_field_list
|
||||
.fields()
|
||||
.filter_map(|f| {
|
||||
if let ast::Type::RefType(ref_type) = f.ty()?
|
||||
&& ref_type.lifetime().is_none()
|
||||
{
|
||||
return Some(ref_type);
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
.collect(),
|
||||
fn find_ref_types_from_field_list(field_list: &ast::FieldList) -> Option<Vec<Change>> {
|
||||
let ref_types: Vec<_> = match field_list {
|
||||
ast::FieldList::RecordFieldList(record_list) => {
|
||||
record_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect()
|
||||
}
|
||||
ast::FieldList::TupleFieldList(tuple_field_list) => {
|
||||
tuple_field_list.fields().flat_map(|f| infer_lifetimes(f.syntax())).collect()
|
||||
}
|
||||
};
|
||||
|
||||
if ref_types.is_empty() { None } else { Some(ref_types) }
|
||||
}
|
||||
|
||||
enum Change {
|
||||
Replace(SyntaxToken),
|
||||
Insert(SyntaxToken),
|
||||
}
|
||||
|
||||
fn infer_lifetimes(node: &SyntaxNode) -> Vec<Change> {
|
||||
node.children()
|
||||
.filter(|it| !matches!(it.kind(), SyntaxKind::FN_PTR_TYPE | SyntaxKind::TYPE_BOUND_LIST))
|
||||
.flat_map(|it| {
|
||||
infer_lifetimes(&it)
|
||||
.into_iter()
|
||||
.chain(ast::Lifetime::cast(it.clone()).and_then(|lt| {
|
||||
lt.lifetime_ident_token().filter(|lt| lt.text() == "'_").map(Change::Replace)
|
||||
}))
|
||||
.chain(
|
||||
ast::RefType::cast(it)
|
||||
.filter(|ty| ty.lifetime().is_none())
|
||||
.and_then(|ty| ty.amp_token())
|
||||
.map(Change::Insert),
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
@@ -164,6 +167,24 @@ fn add_lifetime_to_struct() {
|
||||
check_assist_not_applicable(add_lifetime_to_type, r#"struct Foo { a: &'a$0 i32 }"#);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_lifetime_to_nested_types() {
|
||||
check_assist(
|
||||
add_lifetime_to_type,
|
||||
r#"struct Foo { a: &$0i32, b: &(&i32, fn(&str) -> &str) }"#,
|
||||
r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_lifetime_to_explicit_infer_lifetime() {
|
||||
check_assist(
|
||||
add_lifetime_to_type,
|
||||
r#"struct Foo { a: &'_ $0i32, b: &'_ (&'_ i32, fn(&str) -> &str) }"#,
|
||||
r#"struct Foo<'a> { a: &'a i32, b: &'a (&'a i32, fn(&str) -> &str) }"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_lifetime_to_enum() {
|
||||
check_assist(
|
||||
|
||||
+121
-11
@@ -245,14 +245,7 @@ pub(crate) fn add_missing_match_arms(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
.arms()
|
||||
.filter(|arm| {
|
||||
if matches!(arm.pat(), Some(ast::Pat::WildcardPat(_))) {
|
||||
let is_empty_expr = arm.expr().is_none_or(|e| match e {
|
||||
ast::Expr::BlockExpr(b) => {
|
||||
b.statements().next().is_none() && b.tail_expr().is_none()
|
||||
}
|
||||
ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
|
||||
_ => false,
|
||||
});
|
||||
if is_empty_expr {
|
||||
if arm.expr().is_none_or(is_empty_expr) {
|
||||
false
|
||||
} else {
|
||||
cov_mark::hit!(add_missing_match_arms_empty_expr);
|
||||
@@ -347,12 +340,24 @@ fn cursor_at_trivial_match_arm_list(
|
||||
// $0
|
||||
// }
|
||||
if let Some(last_arm) = match_arm_list.arms().last() {
|
||||
let last_arm_range = ctx.sema.original_range_opt(last_arm.syntax())?.range;
|
||||
let last_node = match last_arm.expr() {
|
||||
Some(expr) => expr.syntax().clone(),
|
||||
None => last_arm.syntax().clone(),
|
||||
};
|
||||
let last_node_range = ctx.sema.original_range_opt(&last_node)?.range;
|
||||
let match_expr_range = ctx.sema.original_range_opt(match_expr.syntax())?.range;
|
||||
if last_arm_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() {
|
||||
if last_node_range.end() <= ctx.offset() && ctx.offset() < match_expr_range.end() {
|
||||
cov_mark::hit!(add_missing_match_arms_end_of_last_arm);
|
||||
return Some(());
|
||||
}
|
||||
|
||||
if ast::Expr::cast(last_node.clone()).is_some_and(is_empty_expr)
|
||||
&& last_node_range.contains(ctx.offset())
|
||||
&& !last_node.text().contains_char('\n')
|
||||
{
|
||||
cov_mark::hit!(add_missing_match_arms_end_of_last_empty_arm);
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
|
||||
// match { _$0 => {...} }
|
||||
@@ -371,6 +376,14 @@ fn is_variant_missing(existing_pats: &[Pat], var: &Pat) -> bool {
|
||||
!existing_pats.iter().any(|pat| does_pat_match_variant(pat, var))
|
||||
}
|
||||
|
||||
fn is_empty_expr(e: ast::Expr) -> bool {
|
||||
match e {
|
||||
ast::Expr::BlockExpr(b) => b.statements().next().is_none() && b.tail_expr().is_none(),
|
||||
ast::Expr::TupleExpr(t) => t.fields().next().is_none(),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Fixme: this is still somewhat limited, use hir_ty::diagnostics::match_check?
|
||||
fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
|
||||
match (pat, var) {
|
||||
@@ -1066,7 +1079,7 @@ fn main() {
|
||||
|
||||
#[test]
|
||||
fn add_missing_match_arms_end_of_last_arm() {
|
||||
cov_mark::check!(add_missing_match_arms_end_of_last_arm);
|
||||
cov_mark::check_count!(add_missing_match_arms_end_of_last_arm, 2);
|
||||
check_assist(
|
||||
add_missing_match_arms,
|
||||
r#"
|
||||
@@ -1095,6 +1108,103 @@ fn main() {
|
||||
(A::Two, B::Two) => ${3:todo!()},$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
add_missing_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
(A::Two, B::One) => 2$0,
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
(A::Two, B::One) => 2,
|
||||
(A::One, B::One) => ${1:todo!()},
|
||||
(A::One, B::Two) => ${2:todo!()},
|
||||
(A::Two, B::Two) => ${3:todo!()},$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_missing_match_arms_end_of_last_empty_arm() {
|
||||
cov_mark::check_count!(add_missing_match_arms_end_of_last_empty_arm, 2);
|
||||
check_assist(
|
||||
add_missing_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
(A::Two, B::One) => {$0}
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
(A::Two, B::One) => {}
|
||||
(A::One, B::One) => ${1:todo!()},
|
||||
(A::One, B::Two) => ${2:todo!()},
|
||||
(A::Two, B::Two) => ${3:todo!()},$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
add_missing_match_arms,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
(A::Two, B::One) => ($0)
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
enum A { One, Two }
|
||||
enum B { One, Two }
|
||||
|
||||
fn main() {
|
||||
let a = A::One;
|
||||
let b = B::One;
|
||||
match (a, b) {
|
||||
(A::Two, B::One) => (),
|
||||
(A::One, B::One) => ${1:todo!()},
|
||||
(A::One, B::Two) => ${2:todo!()},
|
||||
(A::Two, B::Two) => ${3:todo!()},$0
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -197,7 +197,7 @@ pub(crate) fn apply_demorgan_iterator(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||
let (name, arg_expr) = validate_method_call_expr(ctx, &method_call)?;
|
||||
|
||||
let ast::Expr::ClosureExpr(closure_expr) = arg_expr else { return None };
|
||||
let closure_body = closure_expr.body()?.clone_for_update();
|
||||
let closure_body = closure_expr.body()?;
|
||||
|
||||
let op_range = method_call.syntax().text_range();
|
||||
let label = format!("Apply De Morgan's law to `Iterator::{}`", name.text().as_str());
|
||||
|
||||
@@ -533,7 +533,7 @@ fn make_bool_enum(make_pub: bool, make: &SyntaxFactory) -> ast::Enum {
|
||||
],
|
||||
),
|
||||
));
|
||||
make.enum_(
|
||||
make.item_enum(
|
||||
[derive_eq],
|
||||
if make_pub { Some(make.visibility_pub()) } else { None },
|
||||
make.name("Bool"),
|
||||
|
||||
@@ -220,7 +220,7 @@ pub(crate) fn convert_closure_to_fn(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
}
|
||||
|
||||
let body = if wrap_body_in_block {
|
||||
make::block_expr([], Some(body))
|
||||
make::block_expr([], Some(body.reset_indent().indent(1.into())))
|
||||
} else {
|
||||
ast::BlockExpr::cast(body.syntax().clone()).unwrap()
|
||||
};
|
||||
@@ -969,6 +969,32 @@ fn closure() {
|
||||
}
|
||||
closure();
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
convert_closure_to_fn,
|
||||
r#"
|
||||
//- minicore: copy
|
||||
fn foo() {
|
||||
{
|
||||
let closure = |$0| match () {
|
||||
() => {},
|
||||
};
|
||||
closure();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
{
|
||||
fn closure() {
|
||||
match () {
|
||||
() => {},
|
||||
}
|
||||
}
|
||||
closure();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use ide_db::{famous_defs::FamousDefs, syntax_helpers::suggest_name};
|
||||
use syntax::{
|
||||
AstNode,
|
||||
ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, make, syntax_factory::SyntaxFactory},
|
||||
ast::{self, HasAttrs, HasLoopBody, edit::IndentLevel, syntax_factory::SyntaxFactory},
|
||||
syntax_editor::Position,
|
||||
};
|
||||
|
||||
@@ -57,13 +57,13 @@ pub(crate) fn convert_for_loop_to_while_let(
|
||||
{
|
||||
(expr, Some(make.name_ref(method.as_str())))
|
||||
} else if let ast::Expr::RefExpr(_) = iterable {
|
||||
(make::expr_paren(iterable).into(), Some(make.name_ref("into_iter")))
|
||||
(make.expr_paren(iterable).into(), Some(make.name_ref("into_iter")))
|
||||
} else {
|
||||
(iterable, Some(make.name_ref("into_iter")))
|
||||
};
|
||||
|
||||
let iterable = if let Some(method) = method {
|
||||
make::expr_method_call(iterable, method, make::arg_list([])).into()
|
||||
make.expr_method_call(iterable, method, make.arg_list([])).into()
|
||||
} else {
|
||||
iterable
|
||||
};
|
||||
@@ -89,17 +89,18 @@ pub(crate) fn convert_for_loop_to_while_let(
|
||||
for_loop.syntax(),
|
||||
&mut editor,
|
||||
for_loop.attrs().map(|it| it.clone_for_update()),
|
||||
&make,
|
||||
);
|
||||
|
||||
editor.insert(
|
||||
Position::before(for_loop.syntax()),
|
||||
make::tokens::whitespace(format!("\n{indent}").as_str()),
|
||||
make.whitespace(format!("\n{indent}").as_str()),
|
||||
);
|
||||
editor.insert(Position::before(for_loop.syntax()), mut_expr.syntax());
|
||||
|
||||
let opt_pat = make.tuple_struct_pat(make::ext::ident_path("Some"), [pat]);
|
||||
let opt_pat = make.tuple_struct_pat(make.ident_path("Some"), [pat]);
|
||||
let iter_next_expr = make.expr_method_call(
|
||||
make.expr_path(make::ext::ident_path(&tmp_var)),
|
||||
make.expr_path(make.ident_path(&tmp_var)),
|
||||
make.name_ref("next"),
|
||||
make.arg_list([]),
|
||||
);
|
||||
|
||||
+17
-31
@@ -1,6 +1,6 @@
|
||||
use ide_db::{famous_defs::FamousDefs, traits::resolve_target_trait};
|
||||
use syntax::ast::edit::IndentLevel;
|
||||
use syntax::ast::{self, AstNode, HasGenericArgs, HasName, make};
|
||||
use syntax::ast::{self, AstNode, HasGenericArgs, HasName, syntax_factory::SyntaxFactory};
|
||||
use syntax::syntax_editor::{Element, Position};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists};
|
||||
@@ -74,36 +74,25 @@ pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||
"Convert From to TryFrom",
|
||||
impl_.syntax().text_range(),
|
||||
|builder| {
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let mut editor = builder.make_editor(impl_.syntax());
|
||||
editor.replace(
|
||||
trait_ty.syntax(),
|
||||
make::ty(&format!("TryFrom<{from_type}>")).syntax().clone_for_update(),
|
||||
);
|
||||
|
||||
editor.replace(trait_ty.syntax(), make.ty(&format!("TryFrom<{from_type}>")).syntax());
|
||||
editor.replace(
|
||||
from_fn_return_type.syntax(),
|
||||
make::ty("Result<Self, Self::Error>").syntax().clone_for_update(),
|
||||
);
|
||||
editor
|
||||
.replace(from_fn_name.syntax(), make::name("try_from").syntax().clone_for_update());
|
||||
editor.replace(
|
||||
tail_expr.syntax(),
|
||||
wrap_ok(tail_expr.clone()).syntax().clone_for_update(),
|
||||
make.ty("Result<Self, Self::Error>").syntax(),
|
||||
);
|
||||
editor.replace(from_fn_name.syntax(), make.name("try_from").syntax());
|
||||
editor.replace(tail_expr.syntax(), wrap_ok(&make, tail_expr.clone()).syntax());
|
||||
|
||||
for r in return_exprs {
|
||||
let t = r.expr().unwrap_or_else(make::ext::expr_unit);
|
||||
editor.replace(t.syntax(), wrap_ok(t.clone()).syntax().clone_for_update());
|
||||
let t = r.expr().unwrap_or_else(|| make.expr_unit());
|
||||
editor.replace(t.syntax(), wrap_ok(&make, t.clone()).syntax());
|
||||
}
|
||||
|
||||
let error_type = ast::AssocItem::TypeAlias(make::ty_alias(
|
||||
None,
|
||||
"Error",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
Some((make::ty_unit(), None)),
|
||||
))
|
||||
.clone_for_update();
|
||||
let error_type_alias =
|
||||
make.ty_alias(None, "Error", None, None, None, Some((make.ty("()"), None)));
|
||||
let error_type = ast::AssocItem::TypeAlias(error_type_alias);
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap
|
||||
&& let ast::AssocItem::TypeAlias(type_alias) = &error_type
|
||||
@@ -117,22 +106,19 @@ pub(crate) fn convert_from_to_tryfrom(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||
editor.insert_all(
|
||||
Position::after(associated_l_curly),
|
||||
vec![
|
||||
make::tokens::whitespace(&format!("\n{indent}")).syntax_element(),
|
||||
make.whitespace(&format!("\n{indent}")).syntax_element(),
|
||||
error_type.syntax().syntax_element(),
|
||||
make::tokens::whitespace("\n").syntax_element(),
|
||||
make.whitespace("\n").syntax_element(),
|
||||
],
|
||||
);
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.vfs_file_id(), editor);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn wrap_ok(expr: ast::Expr) -> ast::Expr {
|
||||
make::expr_call(
|
||||
make::expr_path(make::ext::ident_path("Ok")),
|
||||
make::arg_list(std::iter::once(expr)),
|
||||
)
|
||||
.into()
|
||||
fn wrap_ok(make: &SyntaxFactory, expr: ast::Expr) -> ast::Expr {
|
||||
make.expr_call(make.expr_path(make.path_from_text("Ok")), make.arg_list([expr])).into()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+239
-59
@@ -1,14 +1,18 @@
|
||||
use either::Either;
|
||||
use ide_db::{defs::Definition, search::FileReference};
|
||||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
SyntaxKind,
|
||||
ast::{self, AstNode, HasAttrs, HasGenericParams, HasVisibility},
|
||||
NodeOrToken, SyntaxKind, SyntaxNode, T,
|
||||
algo::next_non_trivia_token,
|
||||
ast::{
|
||||
self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory,
|
||||
},
|
||||
match_ast,
|
||||
syntax_editor::{Position, SyntaxEditor},
|
||||
syntax_editor::{Element, Position, SyntaxEditor},
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
|
||||
use crate::{
|
||||
AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder, utils::cover_edit_range,
|
||||
};
|
||||
|
||||
// Assist: convert_named_struct_to_tuple_struct
|
||||
//
|
||||
@@ -81,17 +85,17 @@ pub(crate) fn convert_named_struct_to_tuple_struct(
|
||||
AssistId::refactor_rewrite("convert_named_struct_to_tuple_struct"),
|
||||
"Convert to tuple struct",
|
||||
strukt_or_variant.syntax().text_range(),
|
||||
|edit| {
|
||||
edit_field_references(ctx, edit, record_fields.fields());
|
||||
edit_struct_references(ctx, edit, strukt_def);
|
||||
edit_struct_def(ctx, edit, &strukt_or_variant, record_fields);
|
||||
|builder| {
|
||||
edit_field_references(ctx, builder, record_fields.fields());
|
||||
edit_struct_references(ctx, builder, strukt_def);
|
||||
edit_struct_def(ctx, builder, &strukt_or_variant, record_fields);
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn edit_struct_def(
|
||||
ctx: &AssistContext<'_>,
|
||||
edit: &mut SourceChangeBuilder,
|
||||
builder: &mut SourceChangeBuilder,
|
||||
strukt: &Either<ast::Struct, ast::Variant>,
|
||||
record_fields: ast::RecordFieldList,
|
||||
) {
|
||||
@@ -108,24 +112,23 @@ fn edit_struct_def(
|
||||
let field = ast::TupleField::cast(field_syntax)?;
|
||||
Some(field)
|
||||
});
|
||||
let tuple_fields = ast::make::tuple_field_list(tuple_fields);
|
||||
let record_fields_text_range = record_fields.syntax().text_range();
|
||||
|
||||
edit.edit_file(ctx.vfs_file_id());
|
||||
edit.replace(record_fields_text_range, tuple_fields.syntax().text());
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let mut edit = builder.make_editor(strukt.syntax());
|
||||
|
||||
let tuple_fields = make.tuple_field_list(tuple_fields);
|
||||
|
||||
let mut elements = vec![tuple_fields.syntax().clone().into()];
|
||||
if let Either::Left(strukt) = strukt {
|
||||
if let Some(w) = strukt.where_clause() {
|
||||
let mut where_clause = w.to_string();
|
||||
if where_clause.ends_with(',') {
|
||||
where_clause.pop();
|
||||
}
|
||||
where_clause.push(';');
|
||||
edit.delete(w.syntax());
|
||||
|
||||
edit.delete(w.syntax().text_range());
|
||||
edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text());
|
||||
edit.insert(record_fields_text_range.end(), where_clause);
|
||||
edit.insert(record_fields_text_range.end(), ast::make::tokens::single_newline().text());
|
||||
elements.extend([
|
||||
make.whitespace("\n").into(),
|
||||
remove_trailing_comma(w).into(),
|
||||
make.token(T![;]).into(),
|
||||
make.whitespace("\n").into(),
|
||||
]);
|
||||
|
||||
if let Some(tok) = strukt
|
||||
.generic_param_list()
|
||||
@@ -133,25 +136,28 @@ fn edit_struct_def(
|
||||
.and_then(|tok| tok.next_token())
|
||||
.filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
|
||||
{
|
||||
edit.delete(tok.text_range());
|
||||
edit.delete(tok);
|
||||
}
|
||||
} else {
|
||||
edit.insert(record_fields_text_range.end(), ";");
|
||||
elements.push(make.token(T![;]).into());
|
||||
}
|
||||
}
|
||||
edit.replace_with_many(record_fields.syntax(), elements);
|
||||
|
||||
if let Some(tok) = record_fields
|
||||
.l_curly_token()
|
||||
.and_then(|tok| tok.prev_token())
|
||||
.filter(|tok| tok.kind() == SyntaxKind::WHITESPACE)
|
||||
{
|
||||
edit.delete(tok.text_range())
|
||||
edit.delete(tok)
|
||||
}
|
||||
|
||||
builder.add_file_edits(ctx.vfs_file_id(), edit);
|
||||
}
|
||||
|
||||
fn edit_struct_references(
|
||||
ctx: &AssistContext<'_>,
|
||||
edit: &mut SourceChangeBuilder,
|
||||
builder: &mut SourceChangeBuilder,
|
||||
strukt: Either<hir::Struct, hir::Variant>,
|
||||
) {
|
||||
let strukt_def = match strukt {
|
||||
@@ -161,17 +167,20 @@ fn edit_struct_references(
|
||||
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
|
||||
|
||||
for (file_id, refs) in usages {
|
||||
edit.edit_file(file_id.file_id(ctx.db()));
|
||||
let source = ctx.sema.parse(file_id);
|
||||
let mut edit = builder.make_editor(source.syntax());
|
||||
for r in refs {
|
||||
process_struct_name_reference(ctx, r, edit);
|
||||
process_struct_name_reference(ctx, r, &mut edit, &source);
|
||||
}
|
||||
builder.add_file_edits(file_id.file_id(ctx.db()), edit);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_struct_name_reference(
|
||||
ctx: &AssistContext<'_>,
|
||||
r: FileReference,
|
||||
edit: &mut SourceChangeBuilder,
|
||||
edit: &mut SyntaxEditor,
|
||||
source: &ast::SourceFile,
|
||||
) -> Option<()> {
|
||||
// First check if it's the last semgnet of a path that directly belongs to a record
|
||||
// expression/pattern.
|
||||
@@ -192,36 +201,26 @@ fn process_struct_name_reference(
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::RecordPat(record_struct_pat) => {
|
||||
// When we failed to get the original range for the whole struct expression node,
|
||||
// When we failed to get the original range for the whole struct pattern node,
|
||||
// we can't provide any reasonable edit. Leave it untouched.
|
||||
let file_range = ctx.sema.original_range_opt(record_struct_pat.syntax())?;
|
||||
edit.replace(
|
||||
file_range.range,
|
||||
ast::make::tuple_struct_pat(
|
||||
record_struct_pat.path()?,
|
||||
record_struct_pat
|
||||
.record_pat_field_list()?
|
||||
.fields()
|
||||
.filter_map(|pat| pat.pat())
|
||||
.chain(record_struct_pat.record_pat_field_list()?
|
||||
.rest_pat()
|
||||
.map(Into::into))
|
||||
)
|
||||
.to_string()
|
||||
record_to_tuple_struct_like(
|
||||
ctx,
|
||||
source,
|
||||
edit,
|
||||
record_struct_pat.record_pat_field_list()?,
|
||||
|it| it.fields().filter_map(|it| it.name_ref()),
|
||||
);
|
||||
},
|
||||
ast::RecordExpr(record_expr) => {
|
||||
// When we failed to get the original range for the whole struct pattern node,
|
||||
// When we failed to get the original range for the whole struct expression node,
|
||||
// we can't provide any reasonable edit. Leave it untouched.
|
||||
let file_range = ctx.sema.original_range_opt(record_expr.syntax())?;
|
||||
let path = record_expr.path()?;
|
||||
let args = record_expr
|
||||
.record_expr_field_list()?
|
||||
.fields()
|
||||
.filter_map(|f| f.expr())
|
||||
.join(", ");
|
||||
|
||||
edit.replace(file_range.range, format!("{path}({args})"));
|
||||
record_to_tuple_struct_like(
|
||||
ctx,
|
||||
source,
|
||||
edit,
|
||||
record_expr.record_expr_field_list()?,
|
||||
|it| it.fields().filter_map(|it| it.name_ref()),
|
||||
);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
@@ -230,11 +229,67 @@ fn process_struct_name_reference(
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn record_to_tuple_struct_like<T, I>(
|
||||
ctx: &AssistContext<'_>,
|
||||
source: &ast::SourceFile,
|
||||
edit: &mut SyntaxEditor,
|
||||
field_list: T,
|
||||
fields: impl FnOnce(&T) -> I,
|
||||
) -> Option<()>
|
||||
where
|
||||
T: AstNode,
|
||||
I: IntoIterator<Item = ast::NameRef>,
|
||||
{
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let orig = ctx.sema.original_range_opt(field_list.syntax())?;
|
||||
let list_range = cover_edit_range(source, orig.range);
|
||||
|
||||
let l_curly = match list_range.start() {
|
||||
NodeOrToken::Node(node) => node.first_token()?,
|
||||
NodeOrToken::Token(t) => t.clone(),
|
||||
};
|
||||
let r_curly = match list_range.end() {
|
||||
NodeOrToken::Node(node) => node.last_token()?,
|
||||
NodeOrToken::Token(t) => t.clone(),
|
||||
};
|
||||
|
||||
if l_curly.kind() == T!['{'] {
|
||||
delete_whitespace(edit, l_curly.prev_token());
|
||||
delete_whitespace(edit, l_curly.next_token());
|
||||
edit.replace(l_curly, make.token(T!['(']));
|
||||
}
|
||||
if r_curly.kind() == T!['}'] {
|
||||
delete_whitespace(edit, r_curly.prev_token());
|
||||
edit.replace(r_curly, make.token(T![')']));
|
||||
}
|
||||
|
||||
for name_ref in fields(&field_list) {
|
||||
let Some(orig) = ctx.sema.original_range_opt(name_ref.syntax()) else { continue };
|
||||
let name_range = cover_edit_range(source, orig.range);
|
||||
|
||||
if let Some(colon) = next_non_trivia_token(name_range.end().clone())
|
||||
&& colon.kind() == T![:]
|
||||
{
|
||||
edit.delete(&colon);
|
||||
edit.delete_all(name_range);
|
||||
|
||||
if let Some(next) = next_non_trivia_token(colon.clone())
|
||||
&& next.kind() != T!['}']
|
||||
{
|
||||
// Avoid overlapping delete whitespace on `{ field: }`
|
||||
delete_whitespace(edit, colon.next_token());
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn edit_field_references(
|
||||
ctx: &AssistContext<'_>,
|
||||
edit: &mut SourceChangeBuilder,
|
||||
builder: &mut SourceChangeBuilder,
|
||||
fields: impl Iterator<Item = ast::RecordField>,
|
||||
) {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
for (index, field) in fields.enumerate() {
|
||||
let field = match ctx.sema.to_def(&field) {
|
||||
Some(it) => it,
|
||||
@@ -243,19 +298,46 @@ fn edit_field_references(
|
||||
let def = Definition::Field(field);
|
||||
let usages = def.usages(&ctx.sema).all();
|
||||
for (file_id, refs) in usages {
|
||||
edit.edit_file(file_id.file_id(ctx.db()));
|
||||
let source = ctx.sema.parse(file_id);
|
||||
let mut edit = builder.make_editor(source.syntax());
|
||||
|
||||
for r in refs {
|
||||
if let Some(name_ref) = r.name.as_name_ref() {
|
||||
// Only edit the field reference if it's part of a `.field` access
|
||||
if name_ref.syntax().parent().and_then(ast::FieldExpr::cast).is_some() {
|
||||
edit.replace(r.range, index.to_string());
|
||||
edit.replace_all(
|
||||
cover_edit_range(&source, r.range),
|
||||
vec![make.name_ref(&index.to_string()).syntax().clone().into()],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
builder.add_file_edits(file_id.file_id(ctx.db()), edit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete_whitespace(edit: &mut SyntaxEditor, whitespace: Option<impl Element>) {
|
||||
let Some(whitespace) = whitespace else { return };
|
||||
let NodeOrToken::Token(token) = whitespace.syntax_element() else { return };
|
||||
|
||||
if token.kind() == SyntaxKind::WHITESPACE && !token.text().contains('\n') {
|
||||
edit.delete(token);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_trailing_comma(w: ast::WhereClause) -> SyntaxNode {
|
||||
let w = w.syntax().clone_subtree();
|
||||
let mut editor = SyntaxEditor::new(w.clone());
|
||||
if let Some(last) = w.last_child_or_token()
|
||||
&& last.kind() == T![,]
|
||||
{
|
||||
editor.delete(last);
|
||||
}
|
||||
editor.finish().new_root().clone()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
@@ -677,6 +759,102 @@ struct Wrap<T>(T)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_constructor_expr_uses_self() {
|
||||
// regression test for #21595
|
||||
check_assist(
|
||||
convert_named_struct_to_tuple_struct,
|
||||
r#"
|
||||
struct $0Foo { field1: u32 }
|
||||
impl Foo {
|
||||
fn clone(&self) -> Self {
|
||||
Self { field1: self.field1 }
|
||||
}
|
||||
}"#,
|
||||
r#"
|
||||
struct Foo(u32);
|
||||
impl Foo {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0)
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
convert_named_struct_to_tuple_struct,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
struct $0Foo { field1: u32 }
|
||||
impl Foo {
|
||||
fn clone(&self) -> Self {
|
||||
id!(Self { field1: self.field1 })
|
||||
}
|
||||
}"#,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
struct Foo(u32);
|
||||
impl Foo {
|
||||
fn clone(&self) -> Self {
|
||||
id!(Self(self.0))
|
||||
}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_pat_uses_self() {
|
||||
// regression test for #21595
|
||||
check_assist(
|
||||
convert_named_struct_to_tuple_struct,
|
||||
r#"
|
||||
enum Foo {
|
||||
$0Value { field: &'static Foo },
|
||||
Nil,
|
||||
}
|
||||
fn foo(foo: &Foo) {
|
||||
if let Foo::Value { field: Foo::Value { field } } = foo {}
|
||||
}"#,
|
||||
r#"
|
||||
enum Foo {
|
||||
Value(&'static Foo),
|
||||
Nil,
|
||||
}
|
||||
fn foo(foo: &Foo) {
|
||||
if let Foo::Value(Foo::Value(field)) = foo {}
|
||||
}"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
convert_named_struct_to_tuple_struct,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
enum Foo {
|
||||
$0Value { field: &'static Foo },
|
||||
Nil,
|
||||
}
|
||||
fn foo(foo: &Foo) {
|
||||
if let id!(Foo::Value { field: Foo::Value { field } }) = foo {}
|
||||
}"#,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
enum Foo {
|
||||
Value(&'static Foo),
|
||||
Nil,
|
||||
}
|
||||
fn foo(foo: &Foo) {
|
||||
if let id!(Foo::Value(Foo::Value(field))) = foo {}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_applicable_other_than_record_variant() {
|
||||
check_assist_not_applicable(
|
||||
@@ -1042,7 +1220,9 @@ macro_rules! id {
|
||||
|
||||
fn test() {
|
||||
id! {
|
||||
let s = Struct(42);
|
||||
let s = Struct(
|
||||
42,
|
||||
);
|
||||
let Struct(value) = s;
|
||||
let Struct(inner) = s;
|
||||
}
|
||||
|
||||
+28
-21
@@ -10,14 +10,14 @@
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AssistId,
|
||||
assist_context::{AssistContext, Assists},
|
||||
utils::{invert_boolean_expression_legacy, is_never_block},
|
||||
utils::{invert_boolean_expression, is_never_block},
|
||||
};
|
||||
|
||||
// Assist: convert_to_guarded_return
|
||||
@@ -69,6 +69,7 @@ fn if_expr_to_guarded_return(
|
||||
acc: &mut Assists,
|
||||
ctx: &AssistContext<'_>,
|
||||
) -> Option<()> {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let else_block = match if_expr.else_branch() {
|
||||
Some(ast::ElseBranch::Block(block_expr)) if is_never_block(&ctx.sema, &block_expr) => {
|
||||
Some(block_expr)
|
||||
@@ -88,7 +89,7 @@ fn if_expr_to_guarded_return(
|
||||
return None;
|
||||
}
|
||||
|
||||
let let_chains = flat_let_chain(cond);
|
||||
let let_chains = flat_let_chain(cond, &make);
|
||||
|
||||
let then_branch = if_expr.then_branch()?;
|
||||
let then_block = then_branch.stmt_list()?;
|
||||
@@ -110,7 +111,8 @@ fn if_expr_to_guarded_return(
|
||||
|
||||
let early_expression = else_block
|
||||
.or_else(|| {
|
||||
early_expression(parent_container, &ctx.sema).map(ast::make::tail_only_block_expr)
|
||||
early_expression(parent_container, &ctx.sema, &make)
|
||||
.map(ast::make::tail_only_block_expr)
|
||||
})?
|
||||
.reset_indent();
|
||||
|
||||
@@ -133,6 +135,7 @@ fn if_expr_to_guarded_return(
|
||||
"Convert to guarded return",
|
||||
target,
|
||||
|edit| {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let if_indent_level = IndentLevel::from_node(if_expr.syntax());
|
||||
let replacement = let_chains.into_iter().map(|expr| {
|
||||
if let ast::Expr::LetExpr(let_expr) = &expr
|
||||
@@ -140,15 +143,15 @@ fn if_expr_to_guarded_return(
|
||||
{
|
||||
// If-let.
|
||||
let let_else_stmt =
|
||||
make::let_else_stmt(pat, None, expr, early_expression.clone());
|
||||
make.let_else_stmt(pat, None, expr, early_expression.clone());
|
||||
let let_else_stmt = let_else_stmt.indent(if_indent_level);
|
||||
let_else_stmt.syntax().clone()
|
||||
} else {
|
||||
// If.
|
||||
let new_expr = {
|
||||
let then_branch = clean_stmt_block(&early_expression);
|
||||
let cond = invert_boolean_expression_legacy(expr);
|
||||
make::expr_if(cond, then_branch, None).indent(if_indent_level)
|
||||
let then_branch = clean_stmt_block(&early_expression, &make);
|
||||
let cond = invert_boolean_expression(&make, expr);
|
||||
make.expr_if(cond, then_branch, None).indent(if_indent_level)
|
||||
};
|
||||
new_expr.syntax().clone()
|
||||
}
|
||||
@@ -159,7 +162,7 @@ fn if_expr_to_guarded_return(
|
||||
.enumerate()
|
||||
.flat_map(|(i, node)| {
|
||||
(i != 0)
|
||||
.then(|| make::tokens::whitespace(newline).into())
|
||||
.then(|| make.whitespace(newline).into())
|
||||
.into_iter()
|
||||
.chain(node.children_with_tokens())
|
||||
})
|
||||
@@ -201,12 +204,13 @@ fn let_stmt_to_guarded_return(
|
||||
let happy_pattern = try_enum.happy_pattern(pat);
|
||||
let target = let_stmt.syntax().text_range();
|
||||
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let early_expression: ast::Expr = {
|
||||
let parent_block =
|
||||
let_stmt.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
|
||||
let parent_container = parent_block.syntax().parent()?;
|
||||
|
||||
early_expression(parent_container, &ctx.sema)?
|
||||
early_expression(parent_container, &ctx.sema, &make)?
|
||||
};
|
||||
|
||||
acc.add(
|
||||
@@ -215,9 +219,10 @@ fn let_stmt_to_guarded_return(
|
||||
target,
|
||||
|edit| {
|
||||
let let_indent_level = IndentLevel::from_node(let_stmt.syntax());
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
|
||||
let replacement = {
|
||||
let let_else_stmt = make::let_else_stmt(
|
||||
let let_else_stmt = make.let_else_stmt(
|
||||
happy_pattern,
|
||||
let_stmt.ty(),
|
||||
expr.reset_indent(),
|
||||
@@ -228,6 +233,7 @@ fn let_stmt_to_guarded_return(
|
||||
};
|
||||
let mut editor = edit.make_editor(let_stmt.syntax());
|
||||
editor.replace(let_stmt.syntax(), replacement);
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
edit.add_file_edits(ctx.vfs_file_id(), editor);
|
||||
},
|
||||
)
|
||||
@@ -236,38 +242,39 @@ fn let_stmt_to_guarded_return(
|
||||
fn early_expression(
|
||||
parent_container: SyntaxNode,
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
make: &SyntaxFactory,
|
||||
) -> Option<ast::Expr> {
|
||||
let return_none_expr = || {
|
||||
let none_expr = make::expr_path(make::ext::ident_path("None"));
|
||||
make::expr_return(Some(none_expr))
|
||||
let none_expr = make.expr_path(make.ident_path("None"));
|
||||
make.expr_return(Some(none_expr))
|
||||
};
|
||||
if let Some(fn_) = ast::Fn::cast(parent_container.clone())
|
||||
&& let Some(fn_def) = sema.to_def(&fn_)
|
||||
&& let Some(TryEnum::Option) = TryEnum::from_ty(sema, &fn_def.ret_type(sema.db))
|
||||
{
|
||||
return Some(return_none_expr());
|
||||
return Some(return_none_expr().into());
|
||||
}
|
||||
if let Some(body) = ast::ClosureExpr::cast(parent_container.clone()).and_then(|it| it.body())
|
||||
&& let Some(ret_ty) = sema.type_of_expr(&body).map(TypeInfo::original)
|
||||
&& let Some(TryEnum::Option) = TryEnum::from_ty(sema, &ret_ty)
|
||||
{
|
||||
return Some(return_none_expr());
|
||||
return Some(return_none_expr().into());
|
||||
}
|
||||
|
||||
Some(match parent_container.kind() {
|
||||
WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make::expr_continue(None),
|
||||
FN | CLOSURE_EXPR => make::expr_return(None),
|
||||
WHILE_EXPR | LOOP_EXPR | FOR_EXPR => make.expr_continue(None).into(),
|
||||
FN | CLOSURE_EXPR => make.expr_return(None).into(),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn flat_let_chain(mut expr: ast::Expr) -> Vec<ast::Expr> {
|
||||
fn flat_let_chain(mut expr: ast::Expr, make: &SyntaxFactory) -> Vec<ast::Expr> {
|
||||
let mut chains = vec![];
|
||||
let mut reduce_cond = |rhs| {
|
||||
if !matches!(rhs, ast::Expr::LetExpr(_))
|
||||
&& let Some(last) = chains.pop_if(|last| !matches!(last, ast::Expr::LetExpr(_)))
|
||||
{
|
||||
chains.push(make::expr_bin_op(rhs, ast::BinaryOp::LogicOp(ast::LogicOp::And), last));
|
||||
chains.push(make.expr_bin_op(rhs, ast::BinaryOp::LogicOp(ast::LogicOp::And), last));
|
||||
} else {
|
||||
chains.push(rhs);
|
||||
}
|
||||
@@ -286,12 +293,12 @@ fn flat_let_chain(mut expr: ast::Expr) -> Vec<ast::Expr> {
|
||||
chains
|
||||
}
|
||||
|
||||
fn clean_stmt_block(block: &ast::BlockExpr) -> ast::BlockExpr {
|
||||
fn clean_stmt_block(block: &ast::BlockExpr, make: &SyntaxFactory) -> ast::BlockExpr {
|
||||
if block.statements().next().is_none()
|
||||
&& let Some(tail_expr) = block.tail_expr()
|
||||
&& block.modifier().is_none()
|
||||
{
|
||||
make::block_expr(once(make::expr_stmt(tail_expr).into()), None)
|
||||
make.block_expr(once(make.expr_stmt(tail_expr).into()), None)
|
||||
} else {
|
||||
block.clone()
|
||||
}
|
||||
|
||||
+183
-91
@@ -1,17 +1,21 @@
|
||||
use either::Either;
|
||||
use hir::FileRangeWrapper;
|
||||
use ide_db::defs::{Definition, NameRefClass};
|
||||
use std::ops::RangeInclusive;
|
||||
use ide_db::{
|
||||
defs::{Definition, NameRefClass},
|
||||
search::FileReference,
|
||||
};
|
||||
use syntax::{
|
||||
SyntaxElement, SyntaxKind, SyntaxNode, T, TextSize,
|
||||
SyntaxKind, T,
|
||||
ast::{
|
||||
self, AstNode, HasAttrs, HasGenericParams, HasVisibility, syntax_factory::SyntaxFactory,
|
||||
self, AstNode, HasArgList, HasAttrs, HasGenericParams, HasVisibility,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
match_ast,
|
||||
syntax_editor::{Element, Position, SyntaxEditor},
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder};
|
||||
use crate::{
|
||||
AssistContext, AssistId, Assists, assist_context::SourceChangeBuilder, utils::cover_edit_range,
|
||||
};
|
||||
|
||||
// Assist: convert_tuple_struct_to_named_struct
|
||||
//
|
||||
@@ -147,93 +151,121 @@ fn edit_struct_references(
|
||||
};
|
||||
let usages = strukt_def.usages(&ctx.sema).include_self_refs().all();
|
||||
|
||||
let edit_node = |node: SyntaxNode| -> Option<SyntaxNode> {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::TupleStructPat(tuple_struct_pat) => {
|
||||
Some(make.record_pat_with_fields(
|
||||
tuple_struct_pat.path()?,
|
||||
generate_record_pat_list(&tuple_struct_pat, names),
|
||||
).syntax().clone())
|
||||
},
|
||||
// for tuple struct creations like Foo(42)
|
||||
ast::CallExpr(call_expr) => {
|
||||
let path = call_expr.syntax().descendants().find_map(ast::PathExpr::cast).and_then(|expr| expr.path())?;
|
||||
|
||||
// this also includes method calls like Foo::new(42), we should skip them
|
||||
if let Some(name_ref) = path.segment().and_then(|s| s.name_ref()) {
|
||||
match NameRefClass::classify(&ctx.sema, &name_ref) {
|
||||
Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {},
|
||||
Some(NameRefClass::Definition(def, _)) if def == strukt_def => {},
|
||||
_ => return None,
|
||||
};
|
||||
}
|
||||
|
||||
let arg_list = call_expr.syntax().descendants().find_map(ast::ArgList::cast)?;
|
||||
Some(
|
||||
make.record_expr(
|
||||
path,
|
||||
ast::make::record_expr_field_list(arg_list.args().zip(names).map(
|
||||
|(expr, name)| {
|
||||
ast::make::record_expr_field(
|
||||
ast::make::name_ref(&name.to_string()),
|
||||
Some(expr),
|
||||
)
|
||||
},
|
||||
)),
|
||||
).syntax().clone()
|
||||
)
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (file_id, refs) in usages {
|
||||
let source = ctx.sema.parse(file_id);
|
||||
let source = source.syntax();
|
||||
let mut editor = edit.make_editor(source.syntax());
|
||||
|
||||
let mut editor = edit.make_editor(source);
|
||||
for r in refs.iter().rev() {
|
||||
if let Some((old_node, new_node)) = r
|
||||
.name
|
||||
.syntax()
|
||||
.ancestors()
|
||||
.find_map(|node| Some((node.clone(), edit_node(node.clone())?)))
|
||||
{
|
||||
if let Some(old_node) = ctx.sema.original_syntax_node_rooted(&old_node) {
|
||||
editor.replace(old_node, new_node);
|
||||
} else {
|
||||
let FileRangeWrapper { file_id: _, range } = ctx.sema.original_range(&old_node);
|
||||
let parent = source.covering_element(range);
|
||||
match parent {
|
||||
SyntaxElement::Token(token) => {
|
||||
editor.replace(token, new_node.syntax_element());
|
||||
}
|
||||
SyntaxElement::Node(parent_node) => {
|
||||
// replace the part of macro
|
||||
// ```
|
||||
// foo!(a, Test::A(0));
|
||||
// ^^^^^^^^^^^^^^^ // parent_node
|
||||
// ^^^^^^^^^^ // replace_range
|
||||
// ```
|
||||
let start = parent_node
|
||||
.children_with_tokens()
|
||||
.find(|t| t.text_range().contains(range.start()));
|
||||
let end = parent_node
|
||||
.children_with_tokens()
|
||||
.find(|t| t.text_range().contains(range.end() - TextSize::new(1)));
|
||||
if let (Some(start), Some(end)) = (start, end) {
|
||||
let replace_range = RangeInclusive::new(start, end);
|
||||
editor.replace_all(replace_range, vec![new_node.into()]);
|
||||
}
|
||||
}
|
||||
for r in refs {
|
||||
process_struct_name_reference(ctx, r, &mut editor, &source, &strukt_def, names);
|
||||
}
|
||||
|
||||
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_struct_name_reference(
|
||||
ctx: &AssistContext<'_>,
|
||||
r: FileReference,
|
||||
editor: &mut SyntaxEditor,
|
||||
source: &ast::SourceFile,
|
||||
strukt_def: &Definition,
|
||||
names: &[ast::Name],
|
||||
) -> Option<()> {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let name_ref = r.name.as_name_ref()?;
|
||||
let path_segment = name_ref.syntax().parent().and_then(ast::PathSegment::cast)?;
|
||||
let full_path = path_segment.syntax().parent().and_then(ast::Path::cast)?.top_path();
|
||||
|
||||
if full_path.segment()?.name_ref()? != *name_ref {
|
||||
// `name_ref` isn't the last segment of the path, so `full_path` doesn't point to the
|
||||
// struct we want to edit.
|
||||
return None;
|
||||
}
|
||||
|
||||
let parent = full_path.syntax().parent()?;
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::TupleStructPat(tuple_struct_pat) => {
|
||||
let range = ctx.sema.original_range_opt(tuple_struct_pat.syntax())?.range;
|
||||
let new = make.record_pat_with_fields(
|
||||
full_path,
|
||||
generate_record_pat_list(&tuple_struct_pat, names),
|
||||
);
|
||||
editor.replace_all(cover_edit_range(source, range), vec![new.syntax().clone().into()]);
|
||||
},
|
||||
ast::PathExpr(path_expr) => {
|
||||
let call_expr = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
|
||||
|
||||
// this also includes method calls like Foo::new(42), we should skip them
|
||||
match NameRefClass::classify(&ctx.sema, name_ref) {
|
||||
Some(NameRefClass::Definition(Definition::SelfType(_), _)) => {},
|
||||
Some(NameRefClass::Definition(def, _)) if def == *strukt_def => {},
|
||||
_ => return None,
|
||||
}
|
||||
|
||||
let arg_list = call_expr.arg_list()?;
|
||||
let mut first_insert = vec![];
|
||||
for (expr, name) in arg_list.args().zip(names) {
|
||||
let range = ctx.sema.original_range_opt(expr.syntax())?.range;
|
||||
let place = cover_edit_range(source, range);
|
||||
let elements = vec![
|
||||
make.name_ref(&name.text()).syntax().clone().into(),
|
||||
make.token(T![:]).into(),
|
||||
make.whitespace(" ").into(),
|
||||
];
|
||||
if first_insert.is_empty() {
|
||||
// XXX: SyntaxEditor cannot insert after deleted element
|
||||
first_insert = elements;
|
||||
} else {
|
||||
editor.insert_all(Position::before(place.start()), elements);
|
||||
}
|
||||
}
|
||||
}
|
||||
process_delimiter(ctx, source, editor, &arg_list, first_insert);
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn process_delimiter(
|
||||
ctx: &AssistContext<'_>,
|
||||
source: &ast::SourceFile,
|
||||
editor: &mut SyntaxEditor,
|
||||
list: &impl AstNode,
|
||||
first_insert: Vec<syntax::SyntaxElement>,
|
||||
) {
|
||||
let Some(range) = ctx.sema.original_range_opt(list.syntax()) else { return };
|
||||
let place = cover_edit_range(source, range.range);
|
||||
|
||||
let l_paren = match place.start() {
|
||||
syntax::NodeOrToken::Node(node) => node.first_token(),
|
||||
syntax::NodeOrToken::Token(t) => Some(t.clone()),
|
||||
};
|
||||
let r_paren = match place.end() {
|
||||
syntax::NodeOrToken::Node(node) => node.last_token(),
|
||||
syntax::NodeOrToken::Token(t) => Some(t.clone()),
|
||||
};
|
||||
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
if let Some(l_paren) = l_paren
|
||||
&& l_paren.kind() == T!['(']
|
||||
{
|
||||
let mut open_delim = vec![
|
||||
make.whitespace(" ").into(),
|
||||
make.token(T!['{']).into(),
|
||||
make.whitespace(" ").into(),
|
||||
];
|
||||
open_delim.extend(first_insert);
|
||||
editor.replace_with_many(l_paren, open_delim);
|
||||
}
|
||||
if let Some(r_paren) = r_paren
|
||||
&& r_paren.kind() == T![')']
|
||||
{
|
||||
editor.replace_with_many(
|
||||
r_paren,
|
||||
vec![make.whitespace(" ").into(), make.token(T!['}']).into()],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,13 +284,15 @@ fn edit_field_references(
|
||||
let usages = def.usages(&ctx.sema).all();
|
||||
for (file_id, refs) in usages {
|
||||
let source = ctx.sema.parse(file_id);
|
||||
let source = source.syntax();
|
||||
let mut editor = edit.make_editor(source);
|
||||
let mut editor = edit.make_editor(source.syntax());
|
||||
for r in refs {
|
||||
if let Some(name_ref) = r.name.as_name_ref()
|
||||
&& let Some(original) = ctx.sema.original_ast_node(name_ref.clone())
|
||||
&& let Some(original) = ctx.sema.original_range_opt(name_ref.syntax())
|
||||
{
|
||||
editor.replace(original.syntax(), name.syntax());
|
||||
editor.replace_all(
|
||||
cover_edit_range(&source, original.range),
|
||||
vec![name.syntax().clone().into()],
|
||||
);
|
||||
}
|
||||
}
|
||||
edit.add_file_edits(file_id.file_id(ctx.db()), editor);
|
||||
@@ -739,6 +773,64 @@ struct Wrap<T>
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn convert_expr_uses_self() {
|
||||
check_assist(
|
||||
convert_tuple_struct_to_named_struct,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
struct T$0(u8);
|
||||
fn test(t: T) {
|
||||
T(t.0);
|
||||
id!(T(t.0));
|
||||
}"#,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
struct T { field1: u8 }
|
||||
fn test(t: T) {
|
||||
T { field1: t.field1 };
|
||||
id!(T { field1: t.field1 });
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "FIXME overlap edits in nested uses self"]
|
||||
fn convert_pat_uses_self() {
|
||||
check_assist(
|
||||
convert_tuple_struct_to_named_struct,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
enum T {
|
||||
$0Value(&'static T),
|
||||
Nil,
|
||||
}
|
||||
fn test(t: T) {
|
||||
if let T::Value(T::Value(t)) = t {}
|
||||
if let id!(T::Value(T::Value(t))) = t {}
|
||||
}"#,
|
||||
r#"
|
||||
macro_rules! id {
|
||||
($($t:tt)*) => { $($t)* }
|
||||
}
|
||||
enum T {
|
||||
Value { field1: &'static T },
|
||||
Nil,
|
||||
}
|
||||
fn test(t: T) {
|
||||
if let T::Value { field1: T::Value { field1: t } } = t {}
|
||||
if let id!(T::Value { field1: T::Value { field1: t } }) = t {}
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_applicable_other_than_tuple_variant() {
|
||||
check_assist_not_applicable(
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
ast::{
|
||||
self, HasLoopBody,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
syntax_editor::{Element, Position},
|
||||
};
|
||||
@@ -14,7 +14,7 @@
|
||||
use crate::{
|
||||
AssistId,
|
||||
assist_context::{AssistContext, Assists},
|
||||
utils::invert_boolean_expression_legacy,
|
||||
utils::invert_boolean_expression,
|
||||
};
|
||||
|
||||
// Assist: convert_while_to_loop
|
||||
@@ -52,44 +52,47 @@ pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
"Convert while to loop",
|
||||
target,
|
||||
|builder| {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let mut edit = builder.make_editor(while_expr.syntax());
|
||||
let while_indent_level = IndentLevel::from_node(while_expr.syntax());
|
||||
|
||||
let break_block = make::block_expr(
|
||||
iter::once(make::expr_stmt(make::expr_break(None, None)).into()),
|
||||
None,
|
||||
)
|
||||
.indent(IndentLevel(1));
|
||||
let break_block = make
|
||||
.block_expr(
|
||||
iter::once(make.expr_stmt(make.expr_break(None, None).into()).into()),
|
||||
None,
|
||||
)
|
||||
.indent(IndentLevel(1));
|
||||
|
||||
edit.replace_all(
|
||||
while_kw.syntax_element()..=while_cond.syntax().syntax_element(),
|
||||
vec![make::token(T![loop]).syntax_element()],
|
||||
vec![make.token(T![loop]).syntax_element()],
|
||||
);
|
||||
|
||||
if is_pattern_cond(while_cond.clone()) {
|
||||
let then_branch = while_body.reset_indent().indent(IndentLevel(1));
|
||||
let if_expr = make::expr_if(while_cond, then_branch, Some(break_block.into()));
|
||||
let stmts = iter::once(make::expr_stmt(if_expr.into()).into());
|
||||
let block_expr = make::block_expr(stmts, None);
|
||||
let if_expr = make.expr_if(while_cond, then_branch, Some(break_block.into()));
|
||||
let stmts = iter::once(make.expr_stmt(if_expr.into()).into());
|
||||
let block_expr = make.block_expr(stmts, None);
|
||||
edit.replace(while_body.syntax(), block_expr.indent(while_indent_level).syntax());
|
||||
} else {
|
||||
let if_cond = invert_boolean_expression_legacy(while_cond);
|
||||
let if_expr = make::expr_if(if_cond, break_block, None).indent(while_indent_level);
|
||||
let if_cond = invert_boolean_expression(&make, while_cond);
|
||||
let if_expr = make.expr_if(if_cond, break_block, None).indent(while_indent_level);
|
||||
if !while_body.syntax().text().contains_char('\n') {
|
||||
edit.insert(
|
||||
Position::after(&l_curly),
|
||||
make::tokens::whitespace(&format!("\n{while_indent_level}")),
|
||||
make.whitespace(&format!("\n{while_indent_level}")),
|
||||
);
|
||||
}
|
||||
edit.insert_all(
|
||||
Position::after(&l_curly),
|
||||
vec![
|
||||
make::tokens::whitespace(&format!("\n{}", while_indent_level + 1)).into(),
|
||||
make.whitespace(&format!("\n{}", while_indent_level + 1)).into(),
|
||||
if_expr.syntax().syntax_element(),
|
||||
],
|
||||
);
|
||||
};
|
||||
|
||||
edit.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.vfs_file_id(), edit);
|
||||
},
|
||||
)
|
||||
|
||||
+4
-7
@@ -381,23 +381,20 @@ fn build_usage_edit(
|
||||
Some(field_expr) => Some({
|
||||
let field_name: SmolStr = field_expr.name_ref()?.to_string().into();
|
||||
let new_field_name = field_names.get(&field_name)?;
|
||||
let new_expr = ast::make::expr_path(ast::make::ext::ident_path(new_field_name));
|
||||
let new_expr = make.expr_path(make.ident_path(new_field_name));
|
||||
|
||||
// If struct binding is a reference, we might need to deref field usages
|
||||
if data.is_ref {
|
||||
let (replace_expr, ref_data) = determine_ref_and_parens(ctx, &field_expr);
|
||||
(
|
||||
replace_expr.syntax().clone_for_update(),
|
||||
ref_data.wrap_expr(new_expr).syntax().clone_for_update(),
|
||||
)
|
||||
(replace_expr.syntax().clone(), ref_data.wrap_expr(new_expr, make).syntax().clone())
|
||||
} else {
|
||||
(field_expr.syntax().clone(), new_expr.syntax().clone_for_update())
|
||||
(field_expr.syntax().clone(), new_expr.syntax().clone())
|
||||
}
|
||||
}),
|
||||
None => Some((
|
||||
usage.name.syntax().as_node().unwrap().clone(),
|
||||
make.expr_macro(
|
||||
ast::make::ext::ident_path("todo"),
|
||||
make.ident_path("todo"),
|
||||
make.token_tree(syntax::SyntaxKind::L_PAREN, []),
|
||||
)
|
||||
.syntax()
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
ast::{
|
||||
self,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
};
|
||||
@@ -68,41 +67,46 @@ pub(crate) fn desugar_try_expr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||
AssistId::refactor_rewrite("desugar_try_expr_match"),
|
||||
"Replace try expression with match",
|
||||
target,
|
||||
|edit| {
|
||||
|builder| {
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let mut editor = builder.make_editor(try_expr.syntax());
|
||||
|
||||
let sad_pat = match try_enum {
|
||||
TryEnum::Option => make::path_pat(make::ext::ident_path("None")),
|
||||
TryEnum::Result => make::tuple_struct_pat(
|
||||
make::ext::ident_path("Err"),
|
||||
iter::once(make::path_pat(make::ext::ident_path("err"))),
|
||||
)
|
||||
.into(),
|
||||
TryEnum::Option => make.path_pat(make.ident_path("None")),
|
||||
TryEnum::Result => make
|
||||
.tuple_struct_pat(
|
||||
make.ident_path("Err"),
|
||||
iter::once(make.path_pat(make.ident_path("err"))),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
let sad_expr = match try_enum {
|
||||
TryEnum::Option => {
|
||||
make::expr_return(Some(make::expr_path(make::ext::ident_path("None"))))
|
||||
}
|
||||
TryEnum::Result => make::expr_return(Some(
|
||||
make::expr_call(
|
||||
make::expr_path(make::ext::ident_path("Err")),
|
||||
make::arg_list(iter::once(make::expr_path(make::ext::ident_path("err")))),
|
||||
TryEnum::Option => make.expr_return(Some(make.expr_path(make.ident_path("None")))),
|
||||
TryEnum::Result => make.expr_return(Some(
|
||||
make.expr_call(
|
||||
make.expr_path(make.ident_path("Err")),
|
||||
make.arg_list(iter::once(make.expr_path(make.ident_path("err")))),
|
||||
)
|
||||
.into(),
|
||||
)),
|
||||
};
|
||||
|
||||
let happy_arm = make::match_arm(
|
||||
try_enum.happy_pattern(make::ident_pat(false, false, make::name("it")).into()),
|
||||
let happy_arm = make.match_arm(
|
||||
try_enum.happy_pattern(make.ident_pat(false, false, make.name("it")).into()),
|
||||
None,
|
||||
make::expr_path(make::ext::ident_path("it")),
|
||||
make.expr_path(make.ident_path("it")),
|
||||
);
|
||||
let sad_arm = make::match_arm(sad_pat, None, sad_expr);
|
||||
let sad_arm = make.match_arm(sad_pat, None, sad_expr.into());
|
||||
|
||||
let match_arm_list = make::match_arm_list([happy_arm, sad_arm]);
|
||||
let match_arm_list = make.match_arm_list([happy_arm, sad_arm]);
|
||||
|
||||
let expr_match = make::expr_match(expr.clone(), match_arm_list)
|
||||
let expr_match = make
|
||||
.expr_match(expr.clone(), match_arm_list)
|
||||
.indent(IndentLevel::from_node(try_expr.syntax()));
|
||||
|
||||
edit.replace_ast::<ast::Expr>(try_expr.clone().into(), expr_match.into());
|
||||
editor.replace(try_expr.syntax(), expr_match.syntax());
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.vfs_file_id(), editor);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
+7
-7
@@ -8,7 +8,7 @@
|
||||
AstNode, AstToken, NodeOrToken,
|
||||
SyntaxKind::WHITESPACE,
|
||||
SyntaxToken, T,
|
||||
ast::{self, TokenTree, make, syntax_factory::SyntaxFactory},
|
||||
ast::{self, TokenTree, syntax_factory::SyntaxFactory},
|
||||
};
|
||||
|
||||
// Assist: extract_expressions_from_format_string
|
||||
@@ -57,6 +57,7 @@ pub(crate) fn extract_expressions_from_format_string(
|
||||
"Extract format expressions",
|
||||
tt.syntax().text_range(),
|
||||
|edit| {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
// Extract existing arguments in macro
|
||||
let mut raw_tokens = tt.token_trees_and_tokens().skip(1).collect_vec();
|
||||
let format_string_index = format_str_index(&raw_tokens, &fmt_string);
|
||||
@@ -94,14 +95,14 @@ pub(crate) fn extract_expressions_from_format_string(
|
||||
let mut new_tt_bits = raw_tokens;
|
||||
let mut placeholder_indexes = vec![];
|
||||
|
||||
new_tt_bits.push(NodeOrToken::Token(make::tokens::literal(&new_fmt)));
|
||||
new_tt_bits.push(NodeOrToken::Token(make.expr_literal(&new_fmt).token().clone()));
|
||||
|
||||
for arg in extracted_args {
|
||||
if matches!(arg, Arg::Expr(_) | Arg::Placeholder) {
|
||||
// insert ", " before each arg
|
||||
new_tt_bits.extend_from_slice(&[
|
||||
NodeOrToken::Token(make::token(T![,])),
|
||||
NodeOrToken::Token(make::tokens::single_space()),
|
||||
NodeOrToken::Token(make.token(T![,])),
|
||||
NodeOrToken::Token(make.whitespace(" ")),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -109,7 +110,7 @@ pub(crate) fn extract_expressions_from_format_string(
|
||||
Arg::Expr(s) => {
|
||||
// insert arg
|
||||
let expr = ast::Expr::parse(&s, ctx.edition()).syntax_node();
|
||||
let mut expr_tt = utils::tt_from_syntax(expr);
|
||||
let mut expr_tt = utils::tt_from_syntax(expr, &make);
|
||||
new_tt_bits.append(&mut expr_tt);
|
||||
}
|
||||
Arg::Placeholder => {
|
||||
@@ -120,7 +121,7 @@ pub(crate) fn extract_expressions_from_format_string(
|
||||
}
|
||||
None => {
|
||||
placeholder_indexes.push(new_tt_bits.len());
|
||||
new_tt_bits.push(NodeOrToken::Token(make::token(T![_])));
|
||||
new_tt_bits.push(NodeOrToken::Token(make.token(T![_])));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,7 +130,6 @@ pub(crate) fn extract_expressions_from_format_string(
|
||||
}
|
||||
|
||||
// Insert new args
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let new_tt = make.token_tree(tt_delimiter, new_tt_bits);
|
||||
let mut editor = edit.make_editor(tt.syntax());
|
||||
editor.replace(tt.syntax(), new_tt.syntax());
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
ast::{
|
||||
self, AstNode,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
syntax_editor::Position,
|
||||
@@ -75,7 +74,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||
.next()
|
||||
.and_then(ast::Expr::cast)
|
||||
{
|
||||
expr.syntax().ancestors().find_map(valid_target_expr)?.syntax().clone()
|
||||
expr.syntax().ancestors().find_map(valid_target_expr(ctx))?.syntax().clone()
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
@@ -96,7 +95,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||
let to_extract = node
|
||||
.descendants()
|
||||
.take_while(|it| range.contains_range(it.text_range()))
|
||||
.find_map(valid_target_expr)?;
|
||||
.find_map(valid_target_expr(ctx))?;
|
||||
|
||||
let ty = ctx.sema.type_of_expr(&to_extract).map(TypeInfo::adjusted);
|
||||
if matches!(&ty, Some(ty_info) if ty_info.is_unit()) {
|
||||
@@ -176,7 +175,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||
let mut editor = edit.make_editor(&expr_replace);
|
||||
|
||||
let pat_name = make.name(&var_name);
|
||||
let name_expr = make.expr_path(make::ext::ident_path(&var_name));
|
||||
let name_expr = make.expr_path(make.ident_path(&var_name));
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap {
|
||||
let tabstop = edit.make_tabstop_before(cap);
|
||||
@@ -233,7 +232,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext<'_>) -> Op
|
||||
Position::before(place),
|
||||
vec![
|
||||
new_stmt.syntax().clone().into(),
|
||||
make::tokens::whitespace(&trailing_ws).into(),
|
||||
make.whitespace(&trailing_ws).into(),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -283,14 +282,19 @@ fn peel_parens(mut expr: ast::Expr) -> ast::Expr {
|
||||
|
||||
/// Check whether the node is a valid expression which can be extracted to a variable.
|
||||
/// In general that's true for any expression, but in some cases that would produce invalid code.
|
||||
fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
|
||||
match node.kind() {
|
||||
SyntaxKind::PATH_EXPR | SyntaxKind::LOOP_EXPR | SyntaxKind::LET_EXPR => None,
|
||||
fn valid_target_expr(ctx: &AssistContext<'_>) -> impl Fn(SyntaxNode) -> Option<ast::Expr> {
|
||||
|node| match node.kind() {
|
||||
SyntaxKind::LOOP_EXPR | SyntaxKind::LET_EXPR => None,
|
||||
SyntaxKind::BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
|
||||
SyntaxKind::RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
|
||||
SyntaxKind::BLOCK_EXPR => {
|
||||
ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
|
||||
}
|
||||
SyntaxKind::PATH_EXPR => {
|
||||
let path_expr = ast::PathExpr::cast(node)?;
|
||||
let path_resolution = ctx.sema.resolve_path(&path_expr.path()?)?;
|
||||
like_const_value(ctx, path_resolution).then_some(path_expr.into())
|
||||
}
|
||||
_ => ast::Expr::cast(node),
|
||||
}
|
||||
}
|
||||
@@ -455,6 +459,31 @@ fn from(to_extract: &ast::Expr, kind: &ExtractionKind) -> Option<Anchor> {
|
||||
}
|
||||
}
|
||||
|
||||
fn like_const_value(ctx: &AssistContext<'_>, path_resolution: hir::PathResolution) -> bool {
|
||||
let db = ctx.db();
|
||||
let adt_like_const_value = |adt: Option<hir::Adt>| matches!(adt, Some(hir::Adt::Struct(s)) if s.kind(db) == hir::StructKind::Unit);
|
||||
match path_resolution {
|
||||
hir::PathResolution::Def(def) => match def {
|
||||
hir::ModuleDef::Adt(adt) => adt_like_const_value(Some(adt)),
|
||||
hir::ModuleDef::Variant(variant) => variant.kind(db) == hir::StructKind::Unit,
|
||||
hir::ModuleDef::TypeAlias(ty) => adt_like_const_value(ty.ty(db).as_adt()),
|
||||
hir::ModuleDef::Const(_) | hir::ModuleDef::Static(_) => true,
|
||||
hir::ModuleDef::Trait(_)
|
||||
| hir::ModuleDef::BuiltinType(_)
|
||||
| hir::ModuleDef::Macro(_)
|
||||
| hir::ModuleDef::Module(_) => false,
|
||||
hir::ModuleDef::Function(_) => false, // no extract named function
|
||||
},
|
||||
hir::PathResolution::SelfType(ty) => adt_like_const_value(ty.self_ty(db).as_adt()),
|
||||
hir::PathResolution::ConstParam(_) => true,
|
||||
hir::PathResolution::Local(_)
|
||||
| hir::PathResolution::TypeParam(_)
|
||||
| hir::PathResolution::BuiltinAttr(_)
|
||||
| hir::PathResolution::ToolModule(_)
|
||||
| hir::PathResolution::DeriveHelper(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
// NOTE: We use check_assist_by_label, but not check_assist_not_applicable_by_label
|
||||
@@ -1747,6 +1776,27 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_non_local_path_expr() {
|
||||
check_assist_by_label(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct Foo;
|
||||
fn foo() -> Foo {
|
||||
$0Foo$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Foo;
|
||||
fn foo() -> Foo {
|
||||
let $0foo = Foo;
|
||||
foo
|
||||
}
|
||||
"#,
|
||||
"Extract into variable",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_for_return_not_applicable() {
|
||||
check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
|
||||
|
||||
+36
-33
@@ -4,7 +4,7 @@
|
||||
ast::{
|
||||
self, AstNode, HasGenericParams, HasName, HasVisibility as _,
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
make,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
syntax_editor::Position,
|
||||
};
|
||||
@@ -100,7 +100,6 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
let Some(impl_def) = find_struct_impl(ctx, &adt, std::slice::from_ref(&name)) else {
|
||||
continue;
|
||||
};
|
||||
let field = make::ext::field_from_idents(["self", &field_name])?;
|
||||
|
||||
acc.add_group(
|
||||
&GroupLabel("Generate delegate methods…".to_owned()),
|
||||
@@ -108,10 +107,14 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
format!("Generate delegate for `{field_name}.{name}()`",),
|
||||
target,
|
||||
|edit| {
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let field = make
|
||||
.field_from_idents(["self", &field_name])
|
||||
.expect("always be a valid expression");
|
||||
// Create the function
|
||||
let method_source = match ctx.sema.source(method) {
|
||||
Some(source) => {
|
||||
let v = source.value.clone_for_update();
|
||||
let v = source.value;
|
||||
let source_scope = ctx.sema.scope(v.syntax());
|
||||
let target_scope = ctx.sema.scope(strukt.syntax());
|
||||
if let (Some(s), Some(t)) = (source_scope, target_scope) {
|
||||
@@ -132,42 +135,42 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
let is_unsafe = method_source.unsafe_token().is_some();
|
||||
let is_gen = method_source.gen_token().is_some();
|
||||
|
||||
let fn_name = make::name(&name);
|
||||
let fn_name = make.name(&name);
|
||||
|
||||
let type_params = method_source.generic_param_list();
|
||||
let where_clause = method_source.where_clause();
|
||||
let params =
|
||||
method_source.param_list().unwrap_or_else(|| make::param_list(None, []));
|
||||
method_source.param_list().unwrap_or_else(|| make.param_list(None, []));
|
||||
|
||||
// compute the `body`
|
||||
let arg_list = method_source
|
||||
.param_list()
|
||||
.map(convert_param_list_to_arg_list)
|
||||
.unwrap_or_else(|| make::arg_list([]));
|
||||
.map(|v| convert_param_list_to_arg_list(v, &make))
|
||||
.unwrap_or_else(|| make.arg_list([]));
|
||||
|
||||
let tail_expr =
|
||||
make::expr_method_call(field, make::name_ref(&name), arg_list).into();
|
||||
let tail_expr = make.expr_method_call(field, make.name_ref(&name), arg_list).into();
|
||||
let tail_expr_finished =
|
||||
if is_async { make::expr_await(tail_expr) } else { tail_expr };
|
||||
let body = make::block_expr([], Some(tail_expr_finished));
|
||||
if is_async { make.expr_await(tail_expr).into() } else { tail_expr };
|
||||
let body = make.block_expr([], Some(tail_expr_finished));
|
||||
|
||||
let ret_type = method_source.ret_type();
|
||||
|
||||
let f = make::fn_(
|
||||
None,
|
||||
vis,
|
||||
fn_name,
|
||||
type_params,
|
||||
where_clause,
|
||||
params,
|
||||
body,
|
||||
ret_type,
|
||||
is_async,
|
||||
is_const,
|
||||
is_unsafe,
|
||||
is_gen,
|
||||
)
|
||||
.indent(IndentLevel(1));
|
||||
let f = make
|
||||
.fn_(
|
||||
None,
|
||||
vis,
|
||||
fn_name,
|
||||
type_params,
|
||||
where_clause,
|
||||
params,
|
||||
body,
|
||||
ret_type,
|
||||
is_async,
|
||||
is_const,
|
||||
is_unsafe,
|
||||
is_gen,
|
||||
)
|
||||
.indent(IndentLevel(1));
|
||||
let item = ast::AssocItem::Fn(f.clone());
|
||||
|
||||
let mut editor = edit.make_editor(strukt.syntax());
|
||||
@@ -179,7 +182,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
Some(item)
|
||||
}
|
||||
None => {
|
||||
let assoc_item_list = make::assoc_item_list(Some(vec![item]));
|
||||
let assoc_item_list = make.assoc_item_list(vec![item]);
|
||||
editor.insert(
|
||||
Position::last_child_of(impl_def.syntax()),
|
||||
assoc_item_list.syntax(),
|
||||
@@ -192,17 +195,16 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
let ty_params = strukt.generic_param_list();
|
||||
let ty_args = ty_params.as_ref().map(|it| it.to_generic_args());
|
||||
let where_clause = strukt.where_clause();
|
||||
let assoc_item_list = make::assoc_item_list(Some(vec![item]));
|
||||
let assoc_item_list = make.assoc_item_list(vec![item]);
|
||||
|
||||
let impl_def = make::impl_(
|
||||
let impl_def = make.impl_(
|
||||
None,
|
||||
ty_params,
|
||||
ty_args,
|
||||
make::ty_path(make::ext::ident_path(name)),
|
||||
syntax::ast::Type::PathType(make.ty_path(make.ident_path(name))),
|
||||
where_clause,
|
||||
Some(assoc_item_list),
|
||||
)
|
||||
.clone_for_update();
|
||||
);
|
||||
|
||||
// Fixup impl_def indentation
|
||||
let indent = strukt.indent_level();
|
||||
@@ -213,7 +215,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
editor.insert_all(
|
||||
Position::after(strukt.syntax()),
|
||||
vec![
|
||||
make::tokens::whitespace(&format!("\n\n{indent}")).into(),
|
||||
make.whitespace(&format!("\n\n{indent}")).into(),
|
||||
impl_def.syntax().clone().into(),
|
||||
],
|
||||
);
|
||||
@@ -227,6 +229,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
|
||||
let tabstop = edit.make_tabstop_before(cap);
|
||||
editor.add_annotation(fn_.syntax(), tabstop);
|
||||
}
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
edit.add_file_edits(ctx.vfs_file_id(), editor);
|
||||
},
|
||||
)?;
|
||||
|
||||
@@ -782,7 +782,7 @@ fn func_assoc_item(
|
||||
};
|
||||
|
||||
// Build argument list with self expression prepended
|
||||
let other_args = convert_param_list_to_arg_list(l);
|
||||
let other_args = convert_param_list_to_arg_list(l, &make);
|
||||
let all_args: Vec<ast::Expr> =
|
||||
std::iter::once(tail_expr_self).chain(other_args.args()).collect();
|
||||
let args = make.arg_list(all_args);
|
||||
@@ -790,13 +790,13 @@ fn func_assoc_item(
|
||||
make.expr_call(make.expr_path(qualified_path), args).into()
|
||||
}
|
||||
None => make
|
||||
.expr_call(make.expr_path(qualified_path), convert_param_list_to_arg_list(l))
|
||||
.expr_call(make.expr_path(qualified_path), convert_param_list_to_arg_list(l, &make))
|
||||
.into(),
|
||||
},
|
||||
None => make
|
||||
.expr_call(
|
||||
make.expr_path(qualified_path),
|
||||
convert_param_list_to_arg_list(make.param_list(None, Vec::new())),
|
||||
convert_param_list_to_arg_list(make.param_list(None, Vec::new()), &make),
|
||||
)
|
||||
.into(),
|
||||
};
|
||||
|
||||
+95
-11
@@ -1,8 +1,10 @@
|
||||
use crate::assist_context::{AssistContext, Assists};
|
||||
use ide_db::assists::AssistId;
|
||||
use syntax::{
|
||||
AstNode, SyntaxKind, T,
|
||||
ast::{self, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make},
|
||||
AstNode, AstToken, SyntaxKind, T,
|
||||
ast::{
|
||||
self, HasDocComments, HasGenericParams, HasName, HasVisibility, edit::AstNodeEdit, make,
|
||||
},
|
||||
syntax_editor::{Position, SyntaxEditor},
|
||||
};
|
||||
|
||||
@@ -45,7 +47,7 @@
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// trait ${0:NewTrait}<const N: usize> {
|
||||
// trait ${0:Create}<const N: usize> {
|
||||
// // Used as an associated constant.
|
||||
// const CONST_ASSOC: usize = N * 4;
|
||||
//
|
||||
@@ -54,7 +56,7 @@
|
||||
// const_maker! {i32, 7}
|
||||
// }
|
||||
//
|
||||
// impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||
// impl<const N: usize> ${0:Create}<N> for Foo<N> {
|
||||
// // Used as an associated constant.
|
||||
// const CONST_ASSOC: usize = N * 4;
|
||||
//
|
||||
@@ -107,7 +109,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
||||
};
|
||||
let trait_ast = make::trait_(
|
||||
false,
|
||||
"NewTrait",
|
||||
&trait_name(&impl_assoc_items).text(),
|
||||
impl_ast.generic_param_list(),
|
||||
impl_ast.where_clause(),
|
||||
trait_items,
|
||||
@@ -133,6 +135,7 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
||||
let mut editor = builder.make_editor(impl_ast.syntax());
|
||||
impl_assoc_items.assoc_items().for_each(|item| {
|
||||
remove_items_visibility(&mut editor, &item);
|
||||
remove_doc_comments(&mut editor, &item);
|
||||
});
|
||||
|
||||
editor.insert_all(Position::before(impl_name.syntax()), elements);
|
||||
@@ -160,6 +163,18 @@ pub(crate) fn generate_trait_from_impl(acc: &mut Assists, ctx: &AssistContext<'_
|
||||
Some(())
|
||||
}
|
||||
|
||||
fn trait_name(items: &ast::AssocItemList) -> ast::Name {
|
||||
let mut fn_names = items
|
||||
.assoc_items()
|
||||
.filter_map(|x| if let ast::AssocItem::Fn(f) = x { f.name() } else { None });
|
||||
fn_names
|
||||
.next()
|
||||
.and_then(|name| {
|
||||
fn_names.next().is_none().then(|| make::name(&stdx::to_camel_case(&name.text())))
|
||||
})
|
||||
.unwrap_or_else(|| make::name("NewTrait"))
|
||||
}
|
||||
|
||||
/// `E0449` Trait items always share the visibility of their trait
|
||||
fn remove_items_visibility(editor: &mut SyntaxEditor, item: &ast::AssocItem) {
|
||||
if let Some(has_vis) = ast::AnyHasVisibility::cast(item.syntax().clone()) {
|
||||
@@ -175,6 +190,17 @@ fn remove_items_visibility(editor: &mut SyntaxEditor, item: &ast::AssocItem) {
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_doc_comments(editor: &mut SyntaxEditor, item: &ast::AssocItem) {
|
||||
for doc in item.doc_comments() {
|
||||
if let Some(next) = doc.syntax().next_token()
|
||||
&& next.kind() == SyntaxKind::WHITESPACE
|
||||
{
|
||||
editor.delete(next);
|
||||
}
|
||||
editor.delete(doc.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
fn strip_body(editor: &mut SyntaxEditor, item: &ast::AssocItem) {
|
||||
if let ast::AssocItem::Fn(f) = item
|
||||
&& let Some(body) = f.body()
|
||||
@@ -226,11 +252,47 @@ fn add(&mut self, x: f64) {
|
||||
r#"
|
||||
struct Foo(f64);
|
||||
|
||||
trait NewTrait {
|
||||
trait Add {
|
||||
fn add(&mut self, x: f64);
|
||||
}
|
||||
|
||||
impl NewTrait for Foo {
|
||||
impl Add for Foo {
|
||||
fn add(&mut self, x: f64) {
|
||||
self.0 += x;
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_doc_comments() {
|
||||
check_assist_no_snippet_cap(
|
||||
generate_trait_from_impl,
|
||||
r#"
|
||||
struct Foo(f64);
|
||||
|
||||
impl F$0oo {
|
||||
/// Add `x`
|
||||
///
|
||||
/// # Examples
|
||||
#[cfg(true)]
|
||||
fn add(&mut self, x: f64) {
|
||||
self.0 += x;
|
||||
}
|
||||
}"#,
|
||||
r#"
|
||||
struct Foo(f64);
|
||||
|
||||
trait Add {
|
||||
/// Add `x`
|
||||
///
|
||||
/// # Examples
|
||||
#[cfg(true)]
|
||||
fn add(&mut self, x: f64);
|
||||
}
|
||||
|
||||
impl Add for Foo {
|
||||
#[cfg(true)]
|
||||
fn add(&mut self, x: f64) {
|
||||
self.0 += x;
|
||||
}
|
||||
@@ -339,11 +401,11 @@ pub fn a_func() -> Option<()> {
|
||||
r#"
|
||||
struct Foo;
|
||||
|
||||
trait NewTrait {
|
||||
trait AFunc {
|
||||
fn a_func() -> Option<()>;
|
||||
}
|
||||
|
||||
impl NewTrait for Foo {
|
||||
impl AFunc for Foo {
|
||||
fn a_func() -> Option<()> {
|
||||
Some(())
|
||||
}
|
||||
@@ -373,17 +435,39 @@ fn foo() {}
|
||||
}"#,
|
||||
r#"
|
||||
mod a {
|
||||
trait NewTrait {
|
||||
trait Foo {
|
||||
fn foo();
|
||||
}
|
||||
|
||||
impl NewTrait for S {
|
||||
impl Foo for S {
|
||||
fn foo() {}
|
||||
}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_fn_impl_not_suggest_trait_name() {
|
||||
check_assist_no_snippet_cap(
|
||||
generate_trait_from_impl,
|
||||
r#"
|
||||
impl S$0 {
|
||||
fn foo() {}
|
||||
fn bar() {}
|
||||
}"#,
|
||||
r#"
|
||||
trait NewTrait {
|
||||
fn foo();
|
||||
fn bar();
|
||||
}
|
||||
|
||||
impl NewTrait for S {
|
||||
fn foo() {}
|
||||
fn bar() {}
|
||||
}"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_cap_is_some() {
|
||||
check_assist(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use either::{Either, for_both};
|
||||
use hir::{PathResolution, Semantics};
|
||||
use ide_db::{
|
||||
EditionedFileId, RootDatabase,
|
||||
@@ -5,8 +6,9 @@
|
||||
search::{FileReference, FileReferenceNode, UsageSearchResult},
|
||||
};
|
||||
use syntax::{
|
||||
SyntaxElement, TextRange,
|
||||
Direction, TextRange,
|
||||
ast::{self, AstNode, AstToken, HasName, syntax_factory::SyntaxFactory},
|
||||
syntax_editor::{Element, SyntaxEditor},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -36,12 +38,15 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
let InlineData { let_stmt, delete_let, references, target } =
|
||||
if let Some(path_expr) = ctx.find_node_at_offset::<ast::PathExpr>() {
|
||||
inline_usage(&ctx.sema, path_expr, range, file_id)
|
||||
} else if let Some(let_stmt) = ctx.find_node_at_offset::<ast::LetStmt>() {
|
||||
} else if let Some(let_stmt) = ctx.find_node_at_offset() {
|
||||
inline_let(&ctx.sema, let_stmt, range, file_id)
|
||||
} else {
|
||||
None
|
||||
}?;
|
||||
let initializer_expr = let_stmt.initializer()?;
|
||||
let initializer_expr = match &let_stmt {
|
||||
either::Either::Left(it) => it.initializer()?,
|
||||
either::Either::Right(it) => it.expr()?,
|
||||
};
|
||||
|
||||
let wrap_in_parens = references
|
||||
.into_iter()
|
||||
@@ -81,13 +86,15 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
let mut editor = builder.make_editor(&target);
|
||||
if delete_let {
|
||||
editor.delete(let_stmt.syntax());
|
||||
if let Some(whitespace) = let_stmt
|
||||
.syntax()
|
||||
.next_sibling_or_token()
|
||||
.and_then(SyntaxElement::into_token)
|
||||
.and_then(ast::Whitespace::cast)
|
||||
|
||||
if let Some(bin_expr) = let_stmt.syntax().parent().and_then(ast::BinExpr::cast)
|
||||
&& let Some(op_token) = bin_expr.op_token()
|
||||
{
|
||||
editor.delete(whitespace.syntax());
|
||||
editor.delete(&op_token);
|
||||
remove_whitespace(op_token, Direction::Prev, &mut editor);
|
||||
remove_whitespace(let_stmt.syntax(), Direction::Prev, &mut editor);
|
||||
} else {
|
||||
remove_whitespace(let_stmt.syntax(), Direction::Next, &mut editor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +123,7 @@ pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
}
|
||||
|
||||
struct InlineData {
|
||||
let_stmt: ast::LetStmt,
|
||||
let_stmt: Either<ast::LetStmt, ast::LetExpr>,
|
||||
delete_let: bool,
|
||||
target: ast::NameOrNameRef,
|
||||
references: Vec<FileReference>,
|
||||
@@ -124,11 +131,11 @@ struct InlineData {
|
||||
|
||||
fn inline_let(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
let_stmt: ast::LetStmt,
|
||||
let_stmt: Either<ast::LetStmt, ast::LetExpr>,
|
||||
range: TextRange,
|
||||
file_id: EditionedFileId,
|
||||
) -> Option<InlineData> {
|
||||
let bind_pat = match let_stmt.pat()? {
|
||||
let bind_pat = match for_both!(&let_stmt, it => it.pat())? {
|
||||
ast::Pat::IdentPat(pat) => pat,
|
||||
_ => return None,
|
||||
};
|
||||
@@ -187,7 +194,7 @@ fn inline_usage(
|
||||
|
||||
let bind_pat = source.as_ident_pat()?;
|
||||
|
||||
let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
|
||||
let let_stmt = AstNode::cast(bind_pat.syntax().parent()?)?;
|
||||
|
||||
let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
|
||||
let mut references = references.remove(&file_id)?;
|
||||
@@ -197,6 +204,23 @@ fn inline_usage(
|
||||
Some(InlineData { let_stmt, delete_let, target: ast::NameOrNameRef::NameRef(name), references })
|
||||
}
|
||||
|
||||
fn remove_whitespace(elem: impl Element, dir: Direction, editor: &mut SyntaxEditor) {
|
||||
let token = match elem.syntax_element() {
|
||||
syntax::NodeOrToken::Node(node) => match dir {
|
||||
Direction::Next => node.last_token(),
|
||||
Direction::Prev => node.first_token(),
|
||||
},
|
||||
syntax::NodeOrToken::Token(t) => Some(t),
|
||||
};
|
||||
let next_token = match dir {
|
||||
Direction::Next => token.and_then(|it| it.next_token()),
|
||||
Direction::Prev => token.and_then(|it| it.prev_token()),
|
||||
};
|
||||
if let Some(whitespace) = next_token.and_then(ast::Whitespace::cast) {
|
||||
editor.delete(whitespace.syntax());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::{check_assist, check_assist_not_applicable};
|
||||
@@ -404,6 +428,38 @@ fn foo() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_inline_let_expr() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
if let a$0 = 1
|
||||
&& true
|
||||
{
|
||||
a + 1;
|
||||
if a > 10 {}
|
||||
while a > 10 {}
|
||||
let b = a * 10;
|
||||
bar(a);
|
||||
}
|
||||
}",
|
||||
r"
|
||||
fn bar(a: usize) {}
|
||||
fn foo() {
|
||||
if true
|
||||
{
|
||||
1 + 1;
|
||||
if 1 > 10 {}
|
||||
while 1 > 10 {}
|
||||
let b = 1 * 10;
|
||||
bar(1);
|
||||
}
|
||||
}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_not_inline_mut_variable() {
|
||||
cov_mark::check!(test_not_inline_mut_variable);
|
||||
@@ -816,6 +872,70 @@ fn f() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_expr_works_on_local_usage() {
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r#"
|
||||
fn f() {
|
||||
if let xyz = 0
|
||||
&& true
|
||||
{
|
||||
xyz$0;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f() {
|
||||
if true
|
||||
{
|
||||
0;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r#"
|
||||
fn f() {
|
||||
if let xyz = true
|
||||
&& xyz$0
|
||||
{
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f() {
|
||||
if true
|
||||
{
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
inline_local_variable,
|
||||
r#"
|
||||
fn f() {
|
||||
if true
|
||||
&& let xyz = 0
|
||||
{
|
||||
xyz$0;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn f() {
|
||||
if true
|
||||
{
|
||||
0;
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn does_not_remove_let_when_multiple_usages() {
|
||||
check_assist(
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
use syntax::ast::syntax_factory::SyntaxFactory;
|
||||
use syntax::syntax_editor::SyntaxEditor;
|
||||
use syntax::{
|
||||
AstNode, NodeOrToken, SyntaxNode,
|
||||
AstNode, NodeOrToken, SyntaxKind, SyntaxNode, T,
|
||||
ast::{self, HasGenericParams, HasName},
|
||||
};
|
||||
|
||||
@@ -322,12 +322,42 @@ fn create_replacement(
|
||||
if let Some(old_lifetime) = ast::Lifetime::cast(syntax.clone()) {
|
||||
if let Some(new_lifetime) = lifetime_map.0.get(&old_lifetime.to_string()) {
|
||||
if new_lifetime.text() == "'_" {
|
||||
removals.push(NodeOrToken::Node(syntax.clone()));
|
||||
// Check if this lifetime is inside a LifetimeArg (in angle brackets)
|
||||
if let Some(lifetime_arg) =
|
||||
old_lifetime.syntax().parent().and_then(ast::LifetimeArg::cast)
|
||||
{
|
||||
// Remove LifetimeArg and associated comma/whitespace
|
||||
let lifetime_arg_syntax = lifetime_arg.syntax();
|
||||
removals.push(NodeOrToken::Node(lifetime_arg_syntax.clone()));
|
||||
|
||||
if let Some(ws) = syntax.next_sibling_or_token() {
|
||||
removals.push(ws.clone());
|
||||
// Remove comma and whitespace (look forward then backward)
|
||||
let comma_and_ws: Vec<_> = lifetime_arg_syntax
|
||||
.siblings_with_tokens(syntax::Direction::Next)
|
||||
.skip(1)
|
||||
.take_while(|it| it.as_token().is_some())
|
||||
.take_while_inclusive(|it| it.kind() == T![,])
|
||||
.collect();
|
||||
|
||||
if comma_and_ws.iter().any(|it| it.kind() == T![,]) {
|
||||
removals.extend(comma_and_ws);
|
||||
} else {
|
||||
// No comma after, try before
|
||||
let comma_and_ws: Vec<_> = lifetime_arg_syntax
|
||||
.siblings_with_tokens(syntax::Direction::Prev)
|
||||
.skip(1)
|
||||
.take_while(|it| it.as_token().is_some())
|
||||
.take_while_inclusive(|it| it.kind() == T![,])
|
||||
.collect();
|
||||
removals.extend(comma_and_ws);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
removals.push(NodeOrToken::Node(syntax.clone()));
|
||||
if let Some(ws) = syntax.next_sibling_or_token()
|
||||
&& ws.kind() == SyntaxKind::WHITESPACE
|
||||
{
|
||||
removals.push(ws);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -349,6 +379,34 @@ fn create_replacement(
|
||||
}
|
||||
}
|
||||
|
||||
// Deduplicate removals to avoid intersecting changes
|
||||
removals.sort_by_key(|n| n.text_range().start());
|
||||
removals.dedup();
|
||||
|
||||
// Remove GenericArgList entirely if all its args are being removed (avoids empty angle brackets)
|
||||
let generic_arg_lists_to_check: Vec<_> =
|
||||
updated_concrete_type.descendants().filter_map(ast::GenericArgList::cast).collect();
|
||||
|
||||
for generic_arg_list in generic_arg_lists_to_check {
|
||||
let will_be_empty = generic_arg_list.generic_args().all(|arg| match arg {
|
||||
ast::GenericArg::LifetimeArg(lt_arg) => removals.iter().any(|removal| {
|
||||
if let NodeOrToken::Node(node) = removal { node == lt_arg.syntax() } else { false }
|
||||
}),
|
||||
_ => false,
|
||||
});
|
||||
|
||||
if will_be_empty && generic_arg_list.generic_args().next().is_some() {
|
||||
removals.retain(|removal| {
|
||||
if let NodeOrToken::Node(node) = removal {
|
||||
!node.ancestors().any(|anc| anc == *generic_arg_list.syntax())
|
||||
} else {
|
||||
true
|
||||
}
|
||||
});
|
||||
removals.push(NodeOrToken::Node(generic_arg_list.syntax().clone()));
|
||||
}
|
||||
}
|
||||
|
||||
for (old, new) in replacements {
|
||||
editor.replace(old, new);
|
||||
}
|
||||
@@ -946,6 +1004,48 @@ trait Tr {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inline_types_with_lifetime() {
|
||||
check_assist(
|
||||
inline_type_alias_uses,
|
||||
r#"
|
||||
struct A<'a, 'b>(pub &'a mut &'b mut ());
|
||||
|
||||
type $0T<'a, 'b> = A<'a, 'b>;
|
||||
|
||||
fn foo(_: T) {}
|
||||
"#,
|
||||
r#"
|
||||
struct A<'a, 'b>(pub &'a mut &'b mut ());
|
||||
|
||||
|
||||
|
||||
fn foo(_: A) {}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_lifetime_and_type_args() {
|
||||
check_assist(
|
||||
inline_type_alias,
|
||||
r#"
|
||||
type Foo<'a, T> = Bar<'a, T>;
|
||||
struct Bar<'a, T>(&'a T);
|
||||
fn main() {
|
||||
let a: $0Foo<u32>;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
type Foo<'a, T> = Bar<'a, T>;
|
||||
struct Bar<'a, T>(&'a T);
|
||||
fn main() {
|
||||
let a: Bar<u32>;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
mod inline_type_alias_uses {
|
||||
use crate::{handlers::inline_type_alias::inline_type_alias_uses, tests::check_assist};
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
use ide_db::syntax_helpers::node_ext::is_pattern_cond;
|
||||
use syntax::{
|
||||
T,
|
||||
ast::{self, AstNode},
|
||||
ast::{self, AstNode, syntax_factory::SyntaxFactory},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
AssistId,
|
||||
assist_context::{AssistContext, Assists},
|
||||
utils::invert_boolean_expression_legacy,
|
||||
utils::invert_boolean_expression,
|
||||
};
|
||||
|
||||
// Assist: invert_if
|
||||
@@ -50,7 +50,8 @@ pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()
|
||||
};
|
||||
|
||||
acc.add(AssistId::refactor_rewrite("invert_if"), "Invert if", if_range, |edit| {
|
||||
let flip_cond = invert_boolean_expression_legacy(cond.clone());
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let flip_cond = invert_boolean_expression(&make, cond.clone());
|
||||
edit.replace_ast(cond, flip_cond);
|
||||
|
||||
let else_node = else_block.syntax();
|
||||
|
||||
@@ -49,8 +49,9 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Optio
|
||||
SyntaxElement::Node(n) => n,
|
||||
SyntaxElement::Token(t) => t.parent()?,
|
||||
};
|
||||
let mut selected_nodes =
|
||||
parent_node.children().filter(|it| selection_range.contains_range(it.text_range()));
|
||||
let mut selected_nodes = parent_node.children().filter(|it| {
|
||||
selection_range.intersect(it.text_range()).is_some_and(|it| !it.is_empty())
|
||||
});
|
||||
|
||||
let first_selected = selected_nodes.next()?;
|
||||
let edits = match_ast! {
|
||||
@@ -677,6 +678,25 @@ fn merge_selection_uses() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_partial_selection_uses() {
|
||||
cov_mark::check!(merge_with_selected_use_item_neighbors);
|
||||
check_assist(
|
||||
merge_imports,
|
||||
r"
|
||||
use std::fmt::Error;
|
||||
$0use std::fmt::Display;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Write;
|
||||
use$0 std::fmt::Result;
|
||||
",
|
||||
r"
|
||||
use std::fmt::Error;
|
||||
use std::fmt::{Debug, Display, Result, Write};
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_selection_use_trees() {
|
||||
cov_mark::check!(merge_with_selected_use_tree_neighbors);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
SyntaxKind::WHITESPACE,
|
||||
TextRange,
|
||||
ast::{
|
||||
AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat, edit::AstNodeEdit, make,
|
||||
AstNode, BlockExpr, ElseBranch, Expr, IfExpr, MatchArm, Pat, edit::AstNodeEdit,
|
||||
prec::ExprPrecedence, syntax_factory::SyntaxFactory,
|
||||
},
|
||||
syntax_editor::Element,
|
||||
@@ -53,14 +53,15 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
let space_after_arrow = match_arm.fat_arrow_token()?.next_sibling_or_token();
|
||||
|
||||
let arm_expr = match_arm.expr()?;
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let if_branch = chain([&match_arm], &rest_arms)
|
||||
.rfold(None, |else_branch, arm| {
|
||||
if let Some(guard) = arm.guard() {
|
||||
let then_branch = crate::utils::wrap_block(&arm.expr()?);
|
||||
let then_branch = crate::utils::wrap_block(&arm.expr()?, &make);
|
||||
let guard_condition = guard.condition()?.reset_indent();
|
||||
Some(make::expr_if(guard_condition, then_branch, else_branch).into())
|
||||
Some(make.expr_if(guard_condition, then_branch, else_branch).into())
|
||||
} else {
|
||||
arm.expr().map(|it| crate::utils::wrap_block(&it).into())
|
||||
arm.expr().map(|it| crate::utils::wrap_block(&it, &make).into())
|
||||
}
|
||||
})?
|
||||
.indent(arm_expr.indent_level());
|
||||
@@ -84,7 +85,7 @@ pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext<'_>)
|
||||
if let Some(element) = space_after_arrow
|
||||
&& element.kind() == WHITESPACE
|
||||
{
|
||||
edit.replace(element, make::tokens::single_space());
|
||||
edit.replace(element, make.whitespace(" "));
|
||||
}
|
||||
|
||||
edit.delete(guard.syntax());
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use hir::{AsAssocItem, AssocItem, AssocItemContainer, ItemInNs, ModuleDef, db::HirDatabase};
|
||||
use ide_db::assists::AssistId;
|
||||
use syntax::{AstNode, ast};
|
||||
use syntax::{AstNode, ast, ast::syntax_factory::SyntaxFactory};
|
||||
|
||||
use crate::{
|
||||
assist_context::{AssistContext, Assists},
|
||||
@@ -52,19 +52,25 @@ pub(crate) fn qualify_method_call(acc: &mut Assists, ctx: &AssistContext<'_>) ->
|
||||
cfg,
|
||||
)?;
|
||||
|
||||
let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call, resolved_call);
|
||||
let qualify_candidate = QualifyCandidate::ImplMethod(ctx.sema.db, call.clone(), resolved_call);
|
||||
|
||||
acc.add(
|
||||
AssistId::refactor_rewrite("qualify_method_call"),
|
||||
format!("Qualify `{ident}` method call"),
|
||||
range,
|
||||
|builder| {
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let mut editor = builder.make_editor(call.syntax());
|
||||
qualify_candidate.qualify(
|
||||
|replace_with: String| builder.replace(range, replace_with),
|
||||
|_| {},
|
||||
&mut editor,
|
||||
&make,
|
||||
&receiver_path,
|
||||
item_in_ns,
|
||||
current_edition,
|
||||
)
|
||||
);
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.vfs_file_id(), editor);
|
||||
},
|
||||
);
|
||||
Some(())
|
||||
|
||||
@@ -11,7 +11,8 @@
|
||||
use syntax::ast::HasGenericArgs;
|
||||
use syntax::{
|
||||
AstNode, ast,
|
||||
ast::{HasArgList, make},
|
||||
ast::{HasArgList, syntax_factory::SyntaxFactory},
|
||||
syntax_editor::SyntaxEditor,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
@@ -54,25 +55,25 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
|
||||
let qualify_candidate = match candidate {
|
||||
ImportCandidate::Path(candidate) if !candidate.qualifier.is_empty() => {
|
||||
cov_mark::hit!(qualify_path_qualifier_start);
|
||||
let path = ast::Path::cast(syntax_under_caret)?;
|
||||
let path = ast::Path::cast(syntax_under_caret.clone())?;
|
||||
let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
|
||||
QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
|
||||
}
|
||||
ImportCandidate::Path(_) => {
|
||||
cov_mark::hit!(qualify_path_unqualified_name);
|
||||
let path = ast::Path::cast(syntax_under_caret)?;
|
||||
let path = ast::Path::cast(syntax_under_caret.clone())?;
|
||||
let generics = path.segment()?.generic_arg_list();
|
||||
QualifyCandidate::UnqualifiedName(generics)
|
||||
}
|
||||
ImportCandidate::TraitAssocItem(_) => {
|
||||
cov_mark::hit!(qualify_path_trait_assoc_item);
|
||||
let path = ast::Path::cast(syntax_under_caret)?;
|
||||
let path = ast::Path::cast(syntax_under_caret.clone())?;
|
||||
let (qualifier, segment) = (path.qualifier()?, path.segment()?);
|
||||
QualifyCandidate::TraitAssocItem(qualifier, segment)
|
||||
}
|
||||
ImportCandidate::TraitMethod(_) => {
|
||||
cov_mark::hit!(qualify_path_trait_method);
|
||||
let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret)?;
|
||||
let mcall_expr = ast::MethodCallExpr::cast(syntax_under_caret.clone())?;
|
||||
QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
|
||||
}
|
||||
};
|
||||
@@ -101,12 +102,18 @@ pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
|
||||
label(ctx.db(), candidate, &import, current_edition),
|
||||
range,
|
||||
|builder| {
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let mut editor = builder.make_editor(&syntax_under_caret);
|
||||
qualify_candidate.qualify(
|
||||
|replace_with: String| builder.replace(range, replace_with),
|
||||
&mut editor,
|
||||
&make,
|
||||
&import.import_path,
|
||||
import.item_to_import,
|
||||
current_edition,
|
||||
)
|
||||
);
|
||||
editor.add_mappings(make.finish_with_mappings());
|
||||
builder.add_file_edits(ctx.vfs_file_id(), editor);
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -124,6 +131,8 @@ impl QualifyCandidate<'_> {
|
||||
pub(crate) fn qualify(
|
||||
&self,
|
||||
mut replacer: impl FnMut(String),
|
||||
editor: &mut SyntaxEditor,
|
||||
make: &SyntaxFactory,
|
||||
import: &hir::ModPath,
|
||||
item: hir::ItemInNs,
|
||||
edition: Edition,
|
||||
@@ -142,10 +151,10 @@ pub(crate) fn qualify(
|
||||
replacer(format!("<{qualifier} as {import}>::{segment}"));
|
||||
}
|
||||
QualifyCandidate::TraitMethod(db, mcall_expr) => {
|
||||
Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
|
||||
Self::qualify_trait_method(db, mcall_expr, editor, make, import, item);
|
||||
}
|
||||
QualifyCandidate::ImplMethod(db, mcall_expr, hir_fn) => {
|
||||
Self::qualify_fn_call(db, mcall_expr, replacer, import, hir_fn);
|
||||
Self::qualify_fn_call(db, mcall_expr, editor, make, import, hir_fn);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,7 +162,8 @@ pub(crate) fn qualify(
|
||||
fn qualify_fn_call(
|
||||
db: &RootDatabase,
|
||||
mcall_expr: &ast::MethodCallExpr,
|
||||
mut replacer: impl FnMut(String),
|
||||
editor: &mut SyntaxEditor,
|
||||
make: &SyntaxFactory,
|
||||
import: ast::Path,
|
||||
hir_fn: &hir::Function,
|
||||
) -> Option<()> {
|
||||
@@ -165,15 +175,17 @@ fn qualify_fn_call(
|
||||
|
||||
if let Some(self_access) = hir_fn.self_param(db).map(|sp| sp.access(db)) {
|
||||
let receiver = match self_access {
|
||||
hir::Access::Shared => make::expr_ref(receiver, false),
|
||||
hir::Access::Exclusive => make::expr_ref(receiver, true),
|
||||
hir::Access::Shared => make.expr_ref(receiver, false),
|
||||
hir::Access::Exclusive => make.expr_ref(receiver, true),
|
||||
hir::Access::Owned => receiver,
|
||||
};
|
||||
let arg_list = match arg_list {
|
||||
Some(args) => make::arg_list(iter::once(receiver).chain(args)),
|
||||
None => make::arg_list(iter::once(receiver)),
|
||||
Some(args) => make.arg_list(iter::once(receiver).chain(args)),
|
||||
None => make.arg_list(iter::once(receiver)),
|
||||
};
|
||||
replacer(format!("{import}::{method_name}{generics}{arg_list}"));
|
||||
let call_path = make.path_from_text(&format!("{import}::{method_name}{generics}"));
|
||||
let call_expr = make.expr_call(make.expr_path(call_path), arg_list);
|
||||
editor.replace(mcall_expr.syntax(), call_expr.syntax());
|
||||
}
|
||||
Some(())
|
||||
}
|
||||
@@ -181,14 +193,15 @@ fn qualify_fn_call(
|
||||
fn qualify_trait_method(
|
||||
db: &RootDatabase,
|
||||
mcall_expr: &ast::MethodCallExpr,
|
||||
replacer: impl FnMut(String),
|
||||
editor: &mut SyntaxEditor,
|
||||
make: &SyntaxFactory,
|
||||
import: ast::Path,
|
||||
item: hir::ItemInNs,
|
||||
) -> Option<()> {
|
||||
let trait_method_name = mcall_expr.name_ref()?;
|
||||
let trait_ = item_as_trait(db, item)?;
|
||||
let method = find_trait_method(db, trait_, &trait_method_name)?;
|
||||
Self::qualify_fn_call(db, mcall_expr, replacer, import, &method)
|
||||
Self::qualify_fn_call(db, mcall_expr, editor, make, import, &method)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+74
-16
@@ -1,8 +1,11 @@
|
||||
use either::Either;
|
||||
use ide_db::syntax_helpers::suggest_name;
|
||||
use syntax::ast::{self, AstNode, HasArgList, syntax_factory::SyntaxFactory};
|
||||
use syntax::ast::{self, AstNode, HasArgList, prec::ExprPrecedence, syntax_factory::SyntaxFactory};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists, utils::cover_let_chain};
|
||||
use crate::{
|
||||
AssistContext, AssistId, Assists,
|
||||
utils::{cover_let_chain, wrap_paren, wrap_paren_in_call},
|
||||
};
|
||||
|
||||
// Assist: replace_is_some_with_if_let_some
|
||||
//
|
||||
@@ -39,6 +42,7 @@ pub(crate) fn replace_is_method_with_if_let_method(
|
||||
match method_kind {
|
||||
"is_some" | "is_ok" => {
|
||||
let receiver = call_expr.receiver()?;
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
|
||||
let mut name_generator = suggest_name::NameGenerator::new_from_scope_locals(
|
||||
ctx.sema.scope(has_cond.syntax()),
|
||||
@@ -48,7 +52,7 @@ pub(crate) fn replace_is_method_with_if_let_method(
|
||||
} else {
|
||||
name_generator.for_variable(&receiver, &ctx.sema)
|
||||
};
|
||||
let (pat, predicate) = method_predicate(&call_expr).unzip();
|
||||
let (pat, predicate) = method_predicate(&call_expr, &var_name, &make);
|
||||
|
||||
let (assist_id, message, text) = if method_kind == "is_some" {
|
||||
("replace_is_some_with_if_let_some", "Replace `is_some` with `let Some`", "Some")
|
||||
@@ -61,13 +65,9 @@ pub(crate) fn replace_is_method_with_if_let_method(
|
||||
message,
|
||||
call_expr.syntax().text_range(),
|
||||
|edit| {
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let mut editor = edit.make_editor(call_expr.syntax());
|
||||
|
||||
let var_pat = pat.unwrap_or_else(|| {
|
||||
make.ident_pat(false, false, make.name(&var_name)).into()
|
||||
});
|
||||
let pat = make.tuple_struct_pat(make.ident_path(text), [var_pat]).into();
|
||||
let pat = make.tuple_struct_pat(make.ident_path(text), [pat]).into();
|
||||
let let_expr = make.expr_let(pat, receiver);
|
||||
|
||||
if let Some(cap) = ctx.config.snippet_cap
|
||||
@@ -81,6 +81,7 @@ pub(crate) fn replace_is_method_with_if_let_method(
|
||||
|
||||
let new_expr = if let Some(predicate) = predicate {
|
||||
let op = ast::BinaryOp::LogicOp(ast::LogicOp::And);
|
||||
let predicate = wrap_paren(predicate, &make, ExprPrecedence::LAnd);
|
||||
make.expr_bin(let_expr.into(), op, predicate).into()
|
||||
} else {
|
||||
ast::Expr::from(let_expr)
|
||||
@@ -96,14 +97,23 @@ pub(crate) fn replace_is_method_with_if_let_method(
|
||||
}
|
||||
}
|
||||
|
||||
fn method_predicate(call_expr: &ast::MethodCallExpr) -> Option<(ast::Pat, ast::Expr)> {
|
||||
let argument = call_expr.arg_list()?.args().next()?;
|
||||
match argument {
|
||||
ast::Expr::ClosureExpr(it) => {
|
||||
let pat = it.param_list()?.params().next()?.pat()?;
|
||||
Some((pat, it.body()?))
|
||||
}
|
||||
_ => None,
|
||||
fn method_predicate(
|
||||
call_expr: &ast::MethodCallExpr,
|
||||
name: &str,
|
||||
make: &SyntaxFactory,
|
||||
) -> (ast::Pat, Option<ast::Expr>) {
|
||||
let argument = call_expr.arg_list().and_then(|it| it.args().next());
|
||||
if let Some(ast::Expr::ClosureExpr(it)) = argument.clone()
|
||||
&& let Some(pat) = it.param_list().and_then(|it| it.params().next()?.pat())
|
||||
{
|
||||
(pat, it.body())
|
||||
} else {
|
||||
let pat = make.ident_pat(false, false, make.name(name));
|
||||
let expr = argument.map(|expr| {
|
||||
let arg_list = make.arg_list([make.expr_path(make.ident_path(name))]);
|
||||
make.expr_call(wrap_paren_in_call(expr, make), arg_list).into()
|
||||
});
|
||||
(pat.into(), expr)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,6 +242,54 @@ fn main() {
|
||||
let x = Some(1);
|
||||
if let Some(it) = x && it != 3 {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
replace_is_method_with_if_let_method,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = Some(1);
|
||||
if x.is_som$0e_and(|it| it != 3 || it > 10) {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = Some(1);
|
||||
if let Some(it) = x && (it != 3 || it > 10) {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
replace_is_method_with_if_let_method,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = Some(1);
|
||||
if x.is_som$0e_and(predicate) {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = Some(1);
|
||||
if let Some(x1) = x && predicate(x1) {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_assist(
|
||||
replace_is_method_with_if_let_method,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = Some(1);
|
||||
if x.is_som$0e_and(func.f) {}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let x = Some(1);
|
||||
if let Some(x1) = x && (func.f)(x1) {}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -86,8 +86,8 @@ pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext<'_>
|
||||
}
|
||||
|
||||
fn let_expr_needs_paren(expr: &ast::Expr) -> bool {
|
||||
let fake_expr_let =
|
||||
ast::make::expr_let(ast::make::tuple_pat(None).into(), ast::make::ext::expr_unit());
|
||||
let make = SyntaxFactory::without_mappings();
|
||||
let fake_expr_let = make.expr_let(make.tuple_pat(None).into(), make.expr_unit());
|
||||
let Some(fake_expr) = fake_expr_let.expr() else {
|
||||
stdx::never!();
|
||||
return false;
|
||||
|
||||
+3
-13
@@ -2,10 +2,10 @@
|
||||
use ide_db::{RootDatabase, assists::AssistId, defs::Definition};
|
||||
use syntax::{
|
||||
AstNode,
|
||||
ast::{self, Expr, HasArgList, make},
|
||||
ast::{self, Expr, HasArgList, make, syntax_factory::SyntaxFactory},
|
||||
};
|
||||
|
||||
use crate::{AssistContext, Assists};
|
||||
use crate::{AssistContext, Assists, utils::wrap_paren_in_call};
|
||||
|
||||
// Assist: replace_with_lazy_method
|
||||
//
|
||||
@@ -177,11 +177,7 @@ fn into_call(param: &Expr, sema: &Semantics<'_, RootDatabase>) -> Expr {
|
||||
}
|
||||
})()
|
||||
.unwrap_or_else(|| {
|
||||
let callable = if needs_parens_in_call(param) {
|
||||
make::expr_paren(param.clone()).into()
|
||||
} else {
|
||||
param.clone()
|
||||
};
|
||||
let callable = wrap_paren_in_call(param.clone(), &SyntaxFactory::without_mappings());
|
||||
make::expr_call(callable, make::arg_list(Vec::new())).into()
|
||||
})
|
||||
}
|
||||
@@ -200,12 +196,6 @@ fn ends_is(name: &str, end: &str) -> bool {
|
||||
name.strip_suffix(end).is_some_and(|s| s.is_empty() || s.ends_with('_'))
|
||||
}
|
||||
|
||||
fn needs_parens_in_call(param: &Expr) -> bool {
|
||||
let call = make::expr_call(make::ext::expr_unit(), make::arg_list(Vec::new()));
|
||||
let callable = call.expr().expect("invalid make call");
|
||||
param.needs_parens_in_place_of(call.syntax(), callable.syntax())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::check_assist;
|
||||
|
||||
@@ -45,6 +45,7 @@ pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
|
||||
ast::LoopExpr(it) => it.syntax().clone(),
|
||||
ast::WhileExpr(it) => it.syntax().clone(),
|
||||
ast::MatchArm(it) => it.parent_match().syntax().clone(),
|
||||
ast::LetElse(it) => it.syntax().parent()?,
|
||||
ast::LetStmt(it) => {
|
||||
replacement = wrap_let(&it, replacement);
|
||||
prefer_container = Some(it.syntax().clone());
|
||||
@@ -556,6 +557,40 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_let_else() {
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
let Some(2) = None else {$0
|
||||
return;
|
||||
};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
return;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
unwrap_block,
|
||||
r#"
|
||||
fn main() {
|
||||
let Some(2) = None else {$0
|
||||
return
|
||||
};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
return
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_match_arm() {
|
||||
check_assist(
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
use std::iter;
|
||||
|
||||
use either::Either;
|
||||
use syntax::{
|
||||
AstNode, T,
|
||||
ast::{self, edit::AstNodeEdit},
|
||||
@@ -24,11 +27,16 @@
|
||||
// ```
|
||||
pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
|
||||
let let_stmt = let_kw.parent().and_then(ast::LetStmt::cast)?;
|
||||
let indent_level = let_stmt.indent_level().0 as usize;
|
||||
let pat = let_stmt.pat()?;
|
||||
let ty = let_stmt.ty();
|
||||
let init = let_stmt.initializer()?;
|
||||
let let_stmt = let_kw.parent().and_then(Either::<ast::LetStmt, ast::LetExpr>::cast)?;
|
||||
let mut indent_level = let_stmt.indent_level();
|
||||
let pat = either::for_both!(&let_stmt, it => it.pat())?;
|
||||
let (ty, init, prefix, suffix) = match &let_stmt {
|
||||
Either::Left(let_stmt) => (let_stmt.ty(), let_stmt.initializer()?, "", ";"),
|
||||
Either::Right(let_expr) => {
|
||||
indent_level += 1;
|
||||
(None, let_expr.expr()?, "&& ", "")
|
||||
}
|
||||
};
|
||||
|
||||
// This only applies for tuple patterns, types, and initializers.
|
||||
let tuple_pat = match pat {
|
||||
@@ -60,25 +68,19 @@ pub(crate) fn unwrap_tuple(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option
|
||||
"Unwrap tuple",
|
||||
let_kw.text_range(),
|
||||
|edit| {
|
||||
let indents = " ".repeat(indent_level);
|
||||
let mut decls = String::new();
|
||||
|
||||
// If there is an ascribed type, insert that type for each declaration,
|
||||
// otherwise, omit that type.
|
||||
if let Some(tys) = tuple_ty {
|
||||
let mut zipped_decls = String::new();
|
||||
for (pat, ty, expr) in
|
||||
itertools::izip!(tuple_pat.fields(), tys.fields(), tuple_init.fields())
|
||||
{
|
||||
zipped_decls.push_str(&format!("{indents}let {pat}: {ty} = {expr};\n"))
|
||||
}
|
||||
edit.replace(parent.text_range(), zipped_decls.trim());
|
||||
} else {
|
||||
let mut zipped_decls = String::new();
|
||||
for (pat, expr) in itertools::izip!(tuple_pat.fields(), tuple_init.fields()) {
|
||||
zipped_decls.push_str(&format!("{indents}let {pat} = {expr};\n"));
|
||||
}
|
||||
edit.replace(parent.text_range(), zipped_decls.trim());
|
||||
let tys =
|
||||
tuple_ty.into_iter().flat_map(|it| it.fields().map(Some)).chain(iter::repeat(None));
|
||||
for (pat, ty, expr) in itertools::izip!(tuple_pat.fields(), tys, tuple_init.fields()) {
|
||||
let ty = ty.map_or_else(String::new, |ty| format!(": {ty}"));
|
||||
decls.push_str(&format!("{prefix}let {pat}{ty} = {expr}{suffix}\n{indent_level}"))
|
||||
}
|
||||
|
||||
let s = decls.trim();
|
||||
edit.replace(parent.text_range(), s.strip_prefix(prefix).unwrap_or(s));
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -123,6 +125,28 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_tuples_in_let_expr() {
|
||||
check_assist(
|
||||
unwrap_tuple,
|
||||
r#"
|
||||
fn main() {
|
||||
if $0let (foo, bar) = ("Foo", "Bar") {
|
||||
code();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
if let foo = "Foo"
|
||||
&& let bar = "Bar" {
|
||||
code();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unwrap_tuple_with_types() {
|
||||
check_assist(
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
use itertools::Itertools;
|
||||
use syntax::{
|
||||
NodeOrToken, SyntaxToken, T, TextRange, algo,
|
||||
ast::{self, AstNode, make, syntax_factory::SyntaxFactory},
|
||||
ast::{self, AstNode, edit::AstNodeEdit, make, syntax_factory::SyntaxFactory},
|
||||
};
|
||||
|
||||
use crate::{AssistContext, AssistId, Assists};
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
enum WrapUnwrapOption {
|
||||
WrapDerive { derive: TextRange, attr: ast::Attr },
|
||||
WrapAttr(ast::Attr),
|
||||
WrapAttr(Vec<ast::Attr>),
|
||||
}
|
||||
|
||||
/// Attempts to get the derive attribute from a derive attribute list
|
||||
@@ -102,9 +102,9 @@ fn attempt_get_derive(attr: ast::Attr, ident: SyntaxToken) -> WrapUnwrapOption {
|
||||
if ident.parent().and_then(ast::TokenTree::cast).is_none()
|
||||
|| !attr.simple_name().map(|v| v.eq("derive")).unwrap_or_default()
|
||||
{
|
||||
WrapUnwrapOption::WrapAttr(attr)
|
||||
WrapUnwrapOption::WrapAttr(vec![attr])
|
||||
} else {
|
||||
attempt_attr().unwrap_or(WrapUnwrapOption::WrapAttr(attr))
|
||||
attempt_attr().unwrap_or_else(|| WrapUnwrapOption::WrapAttr(vec![attr]))
|
||||
}
|
||||
}
|
||||
pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
|
||||
@@ -118,13 +118,27 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -
|
||||
Some(attempt_get_derive(attr, ident))
|
||||
}
|
||||
|
||||
(Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(attr)),
|
||||
(Some(attr), _) => Some(WrapUnwrapOption::WrapAttr(vec![attr])),
|
||||
_ => None,
|
||||
}
|
||||
} else {
|
||||
let covering_element = ctx.covering_element();
|
||||
match covering_element {
|
||||
NodeOrToken::Node(node) => ast::Attr::cast(node).map(WrapUnwrapOption::WrapAttr),
|
||||
NodeOrToken::Node(node) => {
|
||||
if let Some(attr) = ast::Attr::cast(node.clone()) {
|
||||
Some(WrapUnwrapOption::WrapAttr(vec![attr]))
|
||||
} else {
|
||||
let attrs = node
|
||||
.children()
|
||||
.filter(|it| it.text_range().intersect(ctx.selection_trimmed()).is_some())
|
||||
.map(ast::Attr::cast)
|
||||
.collect::<Option<Vec<_>>>()?;
|
||||
if attrs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(WrapUnwrapOption::WrapAttr(attrs))
|
||||
}
|
||||
}
|
||||
NodeOrToken::Token(ident) if ident.kind() == syntax::T![ident] => {
|
||||
let attr = ident.parent_ancestors().find_map(ast::Attr::cast)?;
|
||||
Some(attempt_get_derive(attr, ident))
|
||||
@@ -133,10 +147,12 @@ pub(crate) fn wrap_unwrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -
|
||||
}
|
||||
}?;
|
||||
match option {
|
||||
WrapUnwrapOption::WrapAttr(attr) if attr.simple_name().as_deref() == Some("cfg_attr") => {
|
||||
unwrap_cfg_attr(acc, attr)
|
||||
}
|
||||
WrapUnwrapOption::WrapAttr(attr) => wrap_cfg_attr(acc, ctx, attr),
|
||||
WrapUnwrapOption::WrapAttr(attrs) => match &attrs[..] {
|
||||
[attr] if attr.simple_name().as_deref() == Some("cfg_attr") => {
|
||||
unwrap_cfg_attr(acc, attrs.into_iter().next().unwrap())
|
||||
}
|
||||
_ => wrap_cfg_attrs(acc, ctx, attrs),
|
||||
},
|
||||
WrapUnwrapOption::WrapDerive { derive, attr } => wrap_derive(acc, ctx, attr, derive),
|
||||
}
|
||||
}
|
||||
@@ -220,40 +236,51 @@ fn wrap_derive(
|
||||
);
|
||||
Some(())
|
||||
}
|
||||
fn wrap_cfg_attr(acc: &mut Assists, ctx: &AssistContext<'_>, attr: ast::Attr) -> Option<()> {
|
||||
let range = attr.syntax().text_range();
|
||||
let path = attr.path()?;
|
||||
fn wrap_cfg_attrs(acc: &mut Assists, ctx: &AssistContext<'_>, attrs: Vec<ast::Attr>) -> Option<()> {
|
||||
let (first_attr, last_attr) = (attrs.first()?, attrs.last()?);
|
||||
let range = first_attr.syntax().text_range().cover(last_attr.syntax().text_range());
|
||||
let path_attrs =
|
||||
attrs.iter().map(|attr| Some((attr.path()?, attr.clone()))).collect::<Option<Vec<_>>>()?;
|
||||
let handle_source_change = |edit: &mut SourceChangeBuilder| {
|
||||
let make = SyntaxFactory::with_mappings();
|
||||
let mut editor = edit.make_editor(attr.syntax());
|
||||
let mut raw_tokens =
|
||||
vec![NodeOrToken::Token(make.token(T![,])), NodeOrToken::Token(make.whitespace(" "))];
|
||||
path.syntax().descendants_with_tokens().for_each(|it| {
|
||||
if let NodeOrToken::Token(token) = it {
|
||||
raw_tokens.push(NodeOrToken::Token(token));
|
||||
}
|
||||
});
|
||||
if let Some(meta) = attr.meta() {
|
||||
if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) {
|
||||
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
|
||||
raw_tokens.push(NodeOrToken::Token(eq));
|
||||
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
|
||||
let mut editor = edit.make_editor(first_attr.syntax());
|
||||
let mut raw_tokens = vec![];
|
||||
for (path, attr) in path_attrs {
|
||||
raw_tokens.extend([
|
||||
NodeOrToken::Token(make.token(T![,])),
|
||||
NodeOrToken::Token(make.whitespace(" ")),
|
||||
]);
|
||||
path.syntax().descendants_with_tokens().for_each(|it| {
|
||||
if let NodeOrToken::Token(token) = it {
|
||||
raw_tokens.push(NodeOrToken::Token(token));
|
||||
}
|
||||
});
|
||||
if let Some(meta) = attr.meta() {
|
||||
if let (Some(eq), Some(expr)) = (meta.eq_token(), meta.expr()) {
|
||||
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
|
||||
raw_tokens.push(NodeOrToken::Token(eq));
|
||||
raw_tokens.push(NodeOrToken::Token(make.whitespace(" ")));
|
||||
|
||||
expr.syntax().descendants_with_tokens().for_each(|it| {
|
||||
if let NodeOrToken::Token(token) = it {
|
||||
raw_tokens.push(NodeOrToken::Token(token));
|
||||
}
|
||||
});
|
||||
} else if let Some(tt) = meta.token_tree() {
|
||||
raw_tokens.extend(tt.token_trees_and_tokens());
|
||||
expr.syntax().descendants_with_tokens().for_each(|it| {
|
||||
if let NodeOrToken::Token(token) = it {
|
||||
raw_tokens.push(NodeOrToken::Token(token));
|
||||
}
|
||||
});
|
||||
} else if let Some(tt) = meta.token_tree() {
|
||||
raw_tokens.extend(tt.token_trees_and_tokens());
|
||||
}
|
||||
}
|
||||
}
|
||||
let meta =
|
||||
make.meta_token_tree(make.ident_path("cfg_attr"), make.token_tree(T!['('], raw_tokens));
|
||||
let cfg_attr =
|
||||
if attr.excl_token().is_some() { make.attr_inner(meta) } else { make.attr_outer(meta) };
|
||||
let cfg_attr = if first_attr.excl_token().is_some() {
|
||||
make.attr_inner(meta)
|
||||
} else {
|
||||
make.attr_outer(meta)
|
||||
};
|
||||
|
||||
editor.replace(attr.syntax(), cfg_attr.syntax());
|
||||
let syntax_range = first_attr.syntax().clone().into()..=last_attr.syntax().clone().into();
|
||||
editor.replace_all(syntax_range, vec![cfg_attr.syntax().clone().into()]);
|
||||
|
||||
if let Some(snippet_cap) = ctx.config.snippet_cap
|
||||
&& let Some(first_meta) =
|
||||
@@ -332,7 +359,8 @@ fn unwrap_cfg_attr(acc: &mut Assists, attr: ast::Attr) -> Option<()> {
|
||||
return None;
|
||||
}
|
||||
let handle_source_change = |f: &mut SourceChangeBuilder| {
|
||||
let inner_attrs = inner_attrs.iter().map(|it| it.to_string()).join("\n");
|
||||
let inner_attrs =
|
||||
inner_attrs.iter().map(|it| it.to_string()).join(&format!("\n{}", attr.indent_level()));
|
||||
f.replace(range, inner_attrs);
|
||||
};
|
||||
acc.add(
|
||||
@@ -414,6 +442,42 @@ pub struct Test {
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
wrap_unwrap_cfg_attr,
|
||||
r#"
|
||||
pub struct Test {
|
||||
#[other_attr]
|
||||
$0#[foo]
|
||||
#[bar]$0
|
||||
#[other_attr]
|
||||
test: u32,
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
pub struct Test {
|
||||
#[other_attr]
|
||||
#[cfg_attr($0, foo, bar)]
|
||||
#[other_attr]
|
||||
test: u32,
|
||||
}
|
||||
"#,
|
||||
);
|
||||
check_assist(
|
||||
wrap_unwrap_cfg_attr,
|
||||
r#"
|
||||
pub struct Test {
|
||||
#[cfg_attr(debug_assertions$0, foo, bar)]
|
||||
test: u32,
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
pub struct Test {
|
||||
#[foo]
|
||||
#[bar]
|
||||
test: u32,
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn to_from_eq_attr() {
|
||||
|
||||
@@ -2282,7 +2282,7 @@ macro_rules! const_maker {
|
||||
};
|
||||
}
|
||||
|
||||
trait ${0:NewTrait}<const N: usize> {
|
||||
trait ${0:Create}<const N: usize> {
|
||||
// Used as an associated constant.
|
||||
const CONST_ASSOC: usize = N * 4;
|
||||
|
||||
@@ -2291,7 +2291,7 @@ trait ${0:NewTrait}<const N: usize> {
|
||||
const_maker! {i32, 7}
|
||||
}
|
||||
|
||||
impl<const N: usize> ${0:NewTrait}<N> for Foo<N> {
|
||||
impl<const N: usize> ${0:Create}<N> for Foo<N> {
|
||||
// Used as an associated constant.
|
||||
const CONST_ASSOC: usize = N * 4;
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
edit::{AstNodeEdit, IndentLevel},
|
||||
edit_in_place::AttrsOwnerEdit,
|
||||
make,
|
||||
prec::ExprPrecedence,
|
||||
syntax_factory::SyntaxFactory,
|
||||
},
|
||||
syntax_editor::{Element, Removable, SyntaxEditor},
|
||||
@@ -86,17 +87,31 @@ pub fn extract_trivial_expression(block_expr: &ast::BlockExpr) -> Option<ast::Ex
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn wrap_block(expr: &ast::Expr) -> ast::BlockExpr {
|
||||
pub(crate) fn wrap_block(expr: &ast::Expr, make: &SyntaxFactory) -> ast::BlockExpr {
|
||||
if let ast::Expr::BlockExpr(block) = expr
|
||||
&& let Some(first) = block.syntax().first_token()
|
||||
&& first.kind() == T!['{']
|
||||
{
|
||||
block.reset_indent()
|
||||
} else {
|
||||
make::block_expr(None, Some(expr.reset_indent().indent(1.into())))
|
||||
make.block_expr(None, Some(expr.reset_indent().indent(1.into())))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn wrap_paren(expr: ast::Expr, make: &SyntaxFactory, prec: ExprPrecedence) -> ast::Expr {
|
||||
if expr.precedence().needs_parentheses_in(prec) { make.expr_paren(expr).into() } else { expr }
|
||||
}
|
||||
|
||||
pub(crate) fn wrap_paren_in_call(expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr {
|
||||
if needs_parens_in_call(&expr) { make.expr_paren(expr).into() } else { expr }
|
||||
}
|
||||
|
||||
fn needs_parens_in_call(param: &ast::Expr) -> bool {
|
||||
let call = make::expr_call(make::ext::expr_unit(), make::arg_list(Vec::new()));
|
||||
let callable = call.expr().expect("invalid make call");
|
||||
param.needs_parens_in_place_of(call.syntax(), callable.syntax())
|
||||
}
|
||||
|
||||
/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
|
||||
/// `#[test_case(...)]`, `#[tokio::test]` and similar.
|
||||
/// Also a regular `#[test]` annotation is supported.
|
||||
@@ -275,11 +290,6 @@ pub(crate) fn invert_boolean_expression(make: &SyntaxFactory, expr: ast::Expr) -
|
||||
invert_special_case(make, &expr).unwrap_or_else(|| make.expr_prefix(T![!], expr).into())
|
||||
}
|
||||
|
||||
// FIXME: Migrate usages of this function to the above function and remove this.
|
||||
pub(crate) fn invert_boolean_expression_legacy(expr: ast::Expr) -> ast::Expr {
|
||||
invert_special_case_legacy(&expr).unwrap_or_else(|| make::expr_prefix(T![!], expr).into())
|
||||
}
|
||||
|
||||
fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Expr> {
|
||||
match expr {
|
||||
ast::Expr::BinExpr(bin) => {
|
||||
@@ -343,62 +353,11 @@ fn invert_special_case(make: &SyntaxFactory, expr: &ast::Expr) -> Option<ast::Ex
|
||||
}
|
||||
}
|
||||
|
||||
fn invert_special_case_legacy(expr: &ast::Expr) -> Option<ast::Expr> {
|
||||
match expr {
|
||||
ast::Expr::BinExpr(bin) => {
|
||||
let bin = bin.clone_subtree();
|
||||
let op_token = bin.op_token()?;
|
||||
let rev_token = match op_token.kind() {
|
||||
T![==] => T![!=],
|
||||
T![!=] => T![==],
|
||||
T![<] => T![>=],
|
||||
T![<=] => T![>],
|
||||
T![>] => T![<=],
|
||||
T![>=] => T![<],
|
||||
// Parenthesize other expressions before prefixing `!`
|
||||
_ => {
|
||||
return Some(
|
||||
make::expr_prefix(T![!], make::expr_paren(expr.clone()).into()).into(),
|
||||
);
|
||||
}
|
||||
};
|
||||
let mut bin_editor = SyntaxEditor::new(bin.syntax().clone());
|
||||
bin_editor.replace(op_token, make::token(rev_token));
|
||||
ast::Expr::cast(bin_editor.finish().new_root().clone())
|
||||
}
|
||||
ast::Expr::MethodCallExpr(mce) => {
|
||||
let receiver = mce.receiver()?;
|
||||
let method = mce.name_ref()?;
|
||||
let arg_list = mce.arg_list()?;
|
||||
|
||||
let method = match method.text().as_str() {
|
||||
"is_some" => "is_none",
|
||||
"is_none" => "is_some",
|
||||
"is_ok" => "is_err",
|
||||
"is_err" => "is_ok",
|
||||
_ => return None,
|
||||
};
|
||||
Some(make::expr_method_call(receiver, make::name_ref(method), arg_list).into())
|
||||
}
|
||||
ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::UnaryOp::Not => match pe.expr()? {
|
||||
ast::Expr::ParenExpr(parexpr) => parexpr.expr(),
|
||||
_ => pe.expr(),
|
||||
},
|
||||
ast::Expr::Literal(lit) => match lit.kind() {
|
||||
ast::LiteralKind::Bool(b) => match b {
|
||||
true => Some(ast::Expr::Literal(make::expr_literal("false"))),
|
||||
false => Some(ast::Expr::Literal(make::expr_literal("true"))),
|
||||
},
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert_attributes(
|
||||
before: impl Element,
|
||||
edit: &mut SyntaxEditor,
|
||||
attrs: impl IntoIterator<Item = ast::Attr>,
|
||||
make: &SyntaxFactory,
|
||||
) {
|
||||
let mut attrs = attrs.into_iter().peekable();
|
||||
if attrs.peek().is_none() {
|
||||
@@ -410,9 +369,7 @@ pub(crate) fn insert_attributes(
|
||||
edit.insert_all(
|
||||
syntax::syntax_editor::Position::before(elem),
|
||||
attrs
|
||||
.flat_map(|attr| {
|
||||
[attr.syntax().clone().into(), make::tokens::whitespace(&whitespace).into()]
|
||||
})
|
||||
.flat_map(|attr| [attr.syntax().clone().into(), make.whitespace(&whitespace).into()])
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
@@ -1095,18 +1052,21 @@ pub(crate) fn trimmed_text_range(source_file: &SourceFile, initial_range: TextRa
|
||||
|
||||
/// Convert a list of function params to a list of arguments that can be passed
|
||||
/// into a function call.
|
||||
pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgList {
|
||||
pub(crate) fn convert_param_list_to_arg_list(
|
||||
list: ast::ParamList,
|
||||
make: &SyntaxFactory,
|
||||
) -> ast::ArgList {
|
||||
let mut args = vec![];
|
||||
for param in list.params() {
|
||||
if let Some(ast::Pat::IdentPat(pat)) = param.pat()
|
||||
&& let Some(name) = pat.name()
|
||||
{
|
||||
let name = name.to_string();
|
||||
let expr = make::expr_path(make::ext::ident_path(&name));
|
||||
let expr = make.expr_path(make.ident_path(&name));
|
||||
args.push(expr);
|
||||
}
|
||||
}
|
||||
make::arg_list(args)
|
||||
make.arg_list(args)
|
||||
}
|
||||
|
||||
/// Calculate the number of hashes required for a raw string containing `s`
|
||||
@@ -1191,7 +1151,10 @@ pub(crate) fn replace_record_field_expr(
|
||||
|
||||
/// Creates a token tree list from a syntax node, creating the needed delimited sub token trees.
|
||||
/// Assumes that the input syntax node is a valid syntax tree.
|
||||
pub(crate) fn tt_from_syntax(node: SyntaxNode) -> Vec<NodeOrToken<ast::TokenTree, SyntaxToken>> {
|
||||
pub(crate) fn tt_from_syntax(
|
||||
node: SyntaxNode,
|
||||
make: &SyntaxFactory,
|
||||
) -> Vec<NodeOrToken<ast::TokenTree, SyntaxToken>> {
|
||||
let mut tt_stack = vec![(None, vec![])];
|
||||
|
||||
for element in node.descendants_with_tokens() {
|
||||
@@ -1219,7 +1182,7 @@ pub(crate) fn tt_from_syntax(node: SyntaxNode) -> Vec<NodeOrToken<ast::TokenTree
|
||||
"mismatched opening and closing delimiters"
|
||||
);
|
||||
|
||||
let sub_tt = make::token_tree(delimiter.expect("unbalanced delimiters"), tt);
|
||||
let sub_tt = make.token_tree(delimiter.expect("unbalanced delimiters"), tt);
|
||||
parent_tt.push(NodeOrToken::Node(sub_tt));
|
||||
}
|
||||
_ => {
|
||||
@@ -1254,6 +1217,20 @@ pub(crate) fn cover_let_chain(mut expr: ast::Expr, range: TextRange) -> Option<a
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn cover_edit_range(
|
||||
source: &impl AstNode,
|
||||
range: TextRange,
|
||||
) -> std::ops::RangeInclusive<syntax::SyntaxElement> {
|
||||
let node = match source.syntax().covering_element(range) {
|
||||
NodeOrToken::Node(node) => node,
|
||||
NodeOrToken::Token(t) => t.parent().unwrap(),
|
||||
};
|
||||
let mut iter = node.children_with_tokens().filter(|it| range.contains_range(it.text_range()));
|
||||
let first = iter.next().unwrap_or(node.into());
|
||||
let last = iter.last().unwrap_or_else(|| first.clone());
|
||||
first..=last
|
||||
}
|
||||
|
||||
pub(crate) fn is_selected(
|
||||
it: &impl AstNode,
|
||||
selection: syntax::TextRange,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! based on the parent of the existing expression.
|
||||
use syntax::{
|
||||
AstNode, T,
|
||||
ast::{self, FieldExpr, MethodCallExpr, make, syntax_factory::SyntaxFactory},
|
||||
ast::{self, FieldExpr, MethodCallExpr, syntax_factory::SyntaxFactory},
|
||||
};
|
||||
|
||||
use crate::AssistContext;
|
||||
@@ -119,13 +119,13 @@ pub(crate) struct RefData {
|
||||
|
||||
impl RefData {
|
||||
/// Derefs `expr` and wraps it in parens if necessary
|
||||
pub(crate) fn wrap_expr(&self, mut expr: ast::Expr) -> ast::Expr {
|
||||
pub(crate) fn wrap_expr(&self, mut expr: ast::Expr, make: &SyntaxFactory) -> ast::Expr {
|
||||
if self.needs_deref {
|
||||
expr = make::expr_prefix(T![*], expr).into();
|
||||
expr = make.expr_prefix(T![*], expr).into();
|
||||
}
|
||||
|
||||
if self.needs_parentheses {
|
||||
expr = make::expr_paren(expr).into();
|
||||
expr = make.expr_paren(expr).into();
|
||||
}
|
||||
|
||||
expr
|
||||
|
||||
@@ -30,14 +30,27 @@ pub(crate) fn complete_fn_param(
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let qualifier = param_qualifier(param);
|
||||
let comma_wrapper = comma_wrapper(ctx);
|
||||
let mut add_new_item_to_acc = |label: &str| {
|
||||
let mk_item = |label: &str, range: TextRange| {
|
||||
CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition)
|
||||
let label = label.strip_prefix(qualifier.as_str()).unwrap_or(label);
|
||||
let insert = if label.starts_with('#') {
|
||||
// FIXME: `#[attr] it: i32` -> `#[attr] mut it: i32`
|
||||
label.to_smolstr()
|
||||
} else {
|
||||
format_smolstr!("{qualifier}{label}")
|
||||
};
|
||||
let mk_item = |insert_text: &str, range: TextRange| {
|
||||
let mut item =
|
||||
CompletionItem::new(CompletionItemKind::Binding, range, label, ctx.edition);
|
||||
if insert_text != label {
|
||||
item.insert_text(insert_text);
|
||||
}
|
||||
item
|
||||
};
|
||||
let item = match &comma_wrapper {
|
||||
Some((fmt, range)) => mk_item(&fmt(label), *range),
|
||||
None => mk_item(label, ctx.source_range()),
|
||||
Some((fmt, range)) => mk_item(&fmt(&insert), *range),
|
||||
None => mk_item(&insert, ctx.source_range()),
|
||||
};
|
||||
// Completion lookup is omitted intentionally here.
|
||||
// See the full discussion: https://github.com/rust-lang/rust-analyzer/issues/12073
|
||||
@@ -75,9 +88,6 @@ fn fill_fn_params(
|
||||
let mut file_params = FxHashMap::default();
|
||||
|
||||
let mut extract_params = |f: ast::Fn| {
|
||||
if !is_simple_param(current_param) {
|
||||
return;
|
||||
}
|
||||
f.param_list().into_iter().flat_map(|it| it.params()).for_each(|param| {
|
||||
if let Some(pat) = param.pat() {
|
||||
let whole_param = param.to_smolstr();
|
||||
@@ -88,6 +98,9 @@ fn fill_fn_params(
|
||||
};
|
||||
|
||||
for node in ctx.token.parent_ancestors() {
|
||||
if !is_simple_param(current_param) {
|
||||
break;
|
||||
}
|
||||
match_ast! {
|
||||
match node {
|
||||
ast::SourceFile(it) => it.items().filter_map(|item| match item {
|
||||
@@ -214,3 +227,16 @@ fn is_simple_param(param: &ast::Param) -> bool {
|
||||
.pat()
|
||||
.is_none_or(|pat| matches!(pat, ast::Pat::IdentPat(ident_pat) if ident_pat.pat().is_none()))
|
||||
}
|
||||
|
||||
fn param_qualifier(param: &ast::Param) -> SmolStr {
|
||||
let mut b = syntax::SmolStrBuilder::new();
|
||||
if let Some(ast::Pat::IdentPat(pat)) = param.pat() {
|
||||
if pat.ref_token().is_some() {
|
||||
b.push_str("ref ");
|
||||
}
|
||||
if pat.mut_token().is_some() {
|
||||
b.push_str("mut ");
|
||||
}
|
||||
}
|
||||
b.finish()
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
use stdx::never;
|
||||
use syntax::{
|
||||
SmolStr,
|
||||
SyntaxKind::{EXPR_STMT, STMT_LIST},
|
||||
SyntaxKind::{CLOSURE_EXPR, EXPR_STMT, MATCH_ARM, STMT_LIST},
|
||||
T, TextRange, TextSize, ToSmolStr,
|
||||
ast::{self, AstNode, AstToken},
|
||||
format_smolstr, match_ast,
|
||||
@@ -66,6 +66,12 @@ pub(crate) fn complete_postfix(
|
||||
Some(it) => it,
|
||||
None => return,
|
||||
};
|
||||
let semi =
|
||||
if expr_ctx.in_block_expr && ctx.token.next_token().is_none_or(|it| it.kind() != T![;]) {
|
||||
";"
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let cfg = ctx.config.find_path_config(ctx.is_nightly);
|
||||
|
||||
@@ -151,12 +157,12 @@ pub(crate) fn complete_postfix(
|
||||
.add_to(acc, ctx.db);
|
||||
}
|
||||
_ if matches!(parent.kind(), STMT_LIST | EXPR_STMT) => {
|
||||
postfix_snippet("let", "let", &format!("let $0 = {receiver_text};"))
|
||||
postfix_snippet("let", "let", &format!("let $0 = {receiver_text}{semi}"))
|
||||
.add_to(acc, ctx.db);
|
||||
postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text};"))
|
||||
postfix_snippet("letm", "let mut", &format!("let mut $0 = {receiver_text}{semi}"))
|
||||
.add_to(acc, ctx.db);
|
||||
}
|
||||
_ if ast::MatchArm::can_cast(parent.kind()) => {
|
||||
_ if matches!(parent.kind(), MATCH_ARM | CLOSURE_EXPR) => {
|
||||
postfix_snippet(
|
||||
"let",
|
||||
"let",
|
||||
@@ -307,26 +313,12 @@ pub(crate) fn complete_postfix(
|
||||
add_format_like_completions(acc, ctx, &dot_receiver_including_refs, cap, &literal_text);
|
||||
}
|
||||
|
||||
postfix_snippet(
|
||||
"return",
|
||||
"return expr",
|
||||
&format!(
|
||||
"return {receiver_text}{semi}",
|
||||
semi = if expr_ctx.in_block_expr { ";" } else { "" }
|
||||
),
|
||||
)
|
||||
.add_to(acc, ctx.db);
|
||||
postfix_snippet("return", "return expr", &format!("return {receiver_text}{semi}"))
|
||||
.add_to(acc, ctx.db);
|
||||
|
||||
if let Some(BreakableKind::Block | BreakableKind::Loop) = expr_ctx.in_breakable {
|
||||
postfix_snippet(
|
||||
"break",
|
||||
"break expr",
|
||||
&format!(
|
||||
"break {receiver_text}{semi}",
|
||||
semi = if expr_ctx.in_block_expr { ";" } else { "" }
|
||||
),
|
||||
)
|
||||
.add_to(acc, ctx.db);
|
||||
postfix_snippet("break", "break expr", &format!("break {receiver_text}{semi}"))
|
||||
.add_to(acc, ctx.db);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -371,12 +363,20 @@ fn get_receiver_text(
|
||||
range.range = TextRange::at(range.range.start(), range.range.len() - TextSize::of('.'))
|
||||
}
|
||||
let file_text = sema.db.file_text(range.file_id.file_id(sema.db));
|
||||
let mut text = file_text.text(sema.db)[range.range].to_owned();
|
||||
let text = file_text.text(sema.db);
|
||||
let indent_spaces = indent_of_tail_line(&text[TextRange::up_to(range.range.start())]);
|
||||
let mut text = stdx::dedent_by(indent_spaces, &text[range.range]);
|
||||
|
||||
// The receiver texts should be interpreted as-is, as they are expected to be
|
||||
// normal Rust expressions.
|
||||
escape_snippet_bits(&mut text);
|
||||
text
|
||||
return text;
|
||||
|
||||
fn indent_of_tail_line(text: &str) -> usize {
|
||||
let tail_line = text.rsplit_once('\n').map_or(text, |(_, s)| s);
|
||||
let trimmed = tail_line.trim_start_matches(' ');
|
||||
tail_line.len() - trimmed.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// Escapes `\` and `$` so that they don't get interpreted as snippet-specific constructs.
|
||||
@@ -402,6 +402,10 @@ fn receiver_accessor(receiver: &ast::Expr) -> ast::Expr {
|
||||
.unwrap_or_else(|| receiver.clone())
|
||||
}
|
||||
|
||||
/// Given an `initial_element`, tries to expand it to include deref(s), and then references.
|
||||
/// Returns the expanded expressions, and the added prefix as a string
|
||||
///
|
||||
/// For example, if called with the `42` in `&&mut *42`, would return `(&&mut *42, "&&mut *")`.
|
||||
fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
|
||||
let mut resulting_element = initial_element.clone();
|
||||
let mut prefix = String::new();
|
||||
@@ -410,11 +414,8 @@ fn include_references(initial_element: &ast::Expr) -> (ast::Expr, String) {
|
||||
|
||||
while let Some(parent_deref_element) =
|
||||
resulting_element.syntax().parent().and_then(ast::PrefixExpr::cast)
|
||||
&& parent_deref_element.op_kind() == Some(ast::UnaryOp::Deref)
|
||||
{
|
||||
if parent_deref_element.op_kind() != Some(ast::UnaryOp::Deref) {
|
||||
break;
|
||||
}
|
||||
|
||||
found_ref_or_deref = true;
|
||||
resulting_element = ast::Expr::from(parent_deref_element);
|
||||
|
||||
@@ -663,6 +664,22 @@ fn main() {
|
||||
|
||||
#[test]
|
||||
fn let_middle_block() {
|
||||
check_edit(
|
||||
"let",
|
||||
r#"
|
||||
fn main() {
|
||||
baz.l$0
|
||||
res
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let $0 = baz;
|
||||
res
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
@@ -719,6 +736,20 @@ fn main() {
|
||||
|
||||
#[test]
|
||||
fn let_tail_block() {
|
||||
check_edit(
|
||||
"let",
|
||||
r#"
|
||||
fn main() {
|
||||
baz.l$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let $0 = baz;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check(
|
||||
r#"
|
||||
fn main() {
|
||||
@@ -772,6 +803,23 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn let_before_semicolon() {
|
||||
check_edit(
|
||||
"let",
|
||||
r#"
|
||||
fn main() {
|
||||
baz.l$0;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let $0 = baz;
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_iflet() {
|
||||
check_edit(
|
||||
@@ -965,6 +1013,28 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn closure_let_block() {
|
||||
check_edit(
|
||||
"let",
|
||||
r#"
|
||||
fn main() {
|
||||
let bar = 2;
|
||||
let f = || bar.$0;
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn main() {
|
||||
let bar = 2;
|
||||
let f = || {
|
||||
let $1 = bar;
|
||||
$0
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_letelse() {
|
||||
check_edit(
|
||||
@@ -1040,6 +1110,7 @@ fn main() {
|
||||
#[test]
|
||||
fn postfix_completion_for_references() {
|
||||
check_edit("dbg", r#"fn main() { &&42.$0 }"#, r#"fn main() { dbg!(&&42) }"#);
|
||||
check_edit("dbg", r#"fn main() { &&*"hello".$0 }"#, r#"fn main() { dbg!(&&*"hello") }"#);
|
||||
check_edit("refm", r#"fn main() { &&42.$0 }"#, r#"fn main() { &&&mut 42 }"#);
|
||||
check_edit(
|
||||
"ifl",
|
||||
@@ -1198,9 +1269,9 @@ fn main() {
|
||||
|
||||
fn main() {
|
||||
ControlFlow::Break(match true {
|
||||
true => "\${1:placeholder}",
|
||||
false => "\\\$",
|
||||
})
|
||||
true => "\${1:placeholder}",
|
||||
false => "\\\$",
|
||||
})
|
||||
}
|
||||
"#,
|
||||
);
|
||||
@@ -1440,4 +1511,31 @@ fn foo() {
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn snippet_dedent() {
|
||||
check_edit(
|
||||
"let",
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn foo(x: Option<i32>, y: Option<i32>) {
|
||||
let _f = || {
|
||||
x
|
||||
.and(y)
|
||||
.map(|it| it+2)
|
||||
.$0
|
||||
};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo(x: Option<i32>, y: Option<i32>) {
|
||||
let _f = || {
|
||||
let $0 = x
|
||||
.and(y)
|
||||
.map(|it| it+2);
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -778,6 +778,16 @@ fn expected_type_and_name<'db>(
|
||||
let ty = sema.type_of_pat(&ast::Pat::from(it)).map(TypeInfo::original);
|
||||
(ty, None)
|
||||
},
|
||||
ast::TupleStructPat(it) => {
|
||||
let fields = it.path().and_then(|path| match sema.resolve_path(&path)? {
|
||||
hir::PathResolution::Def(hir::ModuleDef::Adt(adt)) => Some(adt.as_struct()?.fields(sema.db)),
|
||||
hir::PathResolution::Def(hir::ModuleDef::Variant(variant)) => Some(variant.fields(sema.db)),
|
||||
_ => None,
|
||||
});
|
||||
let nr = it.fields().take_while(|it| it.syntax().text_range().end() <= token.text_range().start()).count();
|
||||
let ty = fields.and_then(|fields| Some(fields.get(nr)?.ty(sema.db).to_type(sema.db)));
|
||||
(ty, None)
|
||||
},
|
||||
ast::Fn(it) => {
|
||||
cov_mark::hit!(expected_type_fn_ret_with_leading_char);
|
||||
cov_mark::hit!(expected_type_fn_ret_without_leading_char);
|
||||
@@ -944,10 +954,10 @@ fn classify_name_ref<'db>(
|
||||
let field_expr_handle = |receiver, node| {
|
||||
let receiver = find_opt_node_in_file(original_file, receiver);
|
||||
let receiver_is_ambiguous_float_literal = match &receiver {
|
||||
Some(ast::Expr::Literal(l)) => matches! {
|
||||
l.kind(),
|
||||
ast::LiteralKind::FloatNumber { .. } if l.syntax().last_token().is_some_and(|it| it.text().ends_with('.'))
|
||||
},
|
||||
Some(ast::Expr::Literal(l)) => {
|
||||
matches!(l.kind(), ast::LiteralKind::FloatNumber { .. })
|
||||
&& l.syntax().last_token().is_some_and(|it| it.text().ends_with('.'))
|
||||
}
|
||||
_ => false,
|
||||
};
|
||||
|
||||
|
||||
@@ -287,6 +287,50 @@ fn foo() -> Foo {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_tuple_struct_pat() {
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
//- minicore: option
|
||||
struct Foo(Option<i32>);
|
||||
fn foo(x: Foo) -> Foo {
|
||||
match x { Foo($0) => () }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: Option<i32>, name: ?"#]],
|
||||
);
|
||||
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo(i32, u32, f32);
|
||||
fn foo(x: Foo) -> Foo {
|
||||
match x { Foo($0) => () }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: i32, name: ?"#]],
|
||||
);
|
||||
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo(i32, u32, f32);
|
||||
fn foo(x: Foo) -> Foo {
|
||||
match x { Foo(num,$0) => () }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
);
|
||||
|
||||
check_expected_type_and_name(
|
||||
r#"
|
||||
struct Foo(i32, u32, f32);
|
||||
fn foo(x: Foo) -> Foo {
|
||||
match x { Foo(num,$0,float) => () }
|
||||
}
|
||||
"#,
|
||||
expect![[r#"ty: u32, name: ?"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn expected_type_if_let_without_leading_char() {
|
||||
cov_mark::check!(expected_type_if_let_without_leading_char);
|
||||
|
||||
@@ -678,7 +678,7 @@ fn main() {
|
||||
fn complete_fn_param() {
|
||||
// has mut kw
|
||||
check_edit(
|
||||
"mut bar: u32",
|
||||
"bar: u32",
|
||||
r#"
|
||||
fn f(foo: (), mut bar: u32) {}
|
||||
fn g(foo: (), mut ba$0)
|
||||
@@ -689,10 +689,35 @@ fn g(foo: (), mut bar: u32)
|
||||
"#,
|
||||
);
|
||||
|
||||
// has type param
|
||||
// has unmatched mut kw
|
||||
check_edit(
|
||||
"bar: u32",
|
||||
r#"
|
||||
fn f(foo: (), bar: u32) {}
|
||||
fn g(foo: (), mut ba$0)
|
||||
"#,
|
||||
r#"
|
||||
fn f(foo: (), bar: u32) {}
|
||||
fn g(foo: (), mut bar: u32)
|
||||
"#,
|
||||
);
|
||||
|
||||
check_edit(
|
||||
"mut bar: u32",
|
||||
r#"
|
||||
fn f(foo: (), mut bar: u32) {}
|
||||
fn g(foo: (), ba$0)
|
||||
"#,
|
||||
r#"
|
||||
fn f(foo: (), mut bar: u32) {}
|
||||
fn g(foo: (), mut bar: u32)
|
||||
"#,
|
||||
);
|
||||
|
||||
// has type param
|
||||
check_edit(
|
||||
"bar: u32",
|
||||
r#"
|
||||
fn g(foo: (), mut ba$0: u32)
|
||||
fn f(foo: (), mut bar: u32) {}
|
||||
"#,
|
||||
@@ -707,7 +732,7 @@ fn f(foo: (), mut bar: u32) {}
|
||||
fn complete_fn_mut_param_add_comma() {
|
||||
// add leading and trailing comma
|
||||
check_edit(
|
||||
", mut bar: u32,",
|
||||
"bar: u32",
|
||||
r#"
|
||||
fn f(foo: (), mut bar: u32) {}
|
||||
fn g(foo: ()mut ba$0 baz: ())
|
||||
@@ -746,7 +771,7 @@ fn g(foo: (), #[baz = "qux"] mut bar: u32)
|
||||
);
|
||||
|
||||
check_edit(
|
||||
r#", #[baz = "qux"] mut bar: u32"#,
|
||||
r#"#[baz = "qux"] mut bar: u32"#,
|
||||
r#"
|
||||
fn f(foo: (), #[baz = "qux"] mut bar: u32) {}
|
||||
fn g(foo: ()#[baz = "qux"] mut ba$0)
|
||||
|
||||
@@ -43,7 +43,7 @@ fn bar(file_id: usize) {}
|
||||
fn baz(file$0 id: u32) {}
|
||||
"#,
|
||||
expect![[r#"
|
||||
bn file_id: usize,
|
||||
bn file_id: usize
|
||||
kw mut
|
||||
kw ref
|
||||
"#]],
|
||||
|
||||
@@ -97,25 +97,37 @@ fn missing_record_expr_field_fixes(
|
||||
make::ty(&new_field_type.display_source_code(sema.db, module.into(), true).ok()?),
|
||||
);
|
||||
|
||||
let last_field = record_fields.fields().last()?;
|
||||
let last_field_syntax = last_field.syntax();
|
||||
let indent = IndentLevel::from_node(last_field_syntax);
|
||||
let (indent, offset, postfix, needs_comma) =
|
||||
if let Some(last_field) = record_fields.fields().last() {
|
||||
let indent = IndentLevel::from_node(last_field.syntax());
|
||||
let offset = last_field.syntax().text_range().end();
|
||||
let needs_comma = !last_field.to_string().ends_with(',');
|
||||
(indent, offset, String::new(), needs_comma)
|
||||
} else {
|
||||
let indent = IndentLevel::from_node(record_fields.syntax());
|
||||
let offset = record_fields.l_curly_token()?.text_range().end();
|
||||
let postfix = if record_fields.syntax().text().contains_char('\n') {
|
||||
",".into()
|
||||
} else {
|
||||
format!(",\n{indent}")
|
||||
};
|
||||
(indent + 1, offset, postfix, false)
|
||||
};
|
||||
|
||||
let mut new_field = new_field.to_string();
|
||||
// FIXME: check submodule instead of FileId
|
||||
if usage_file_id != def_file_id && !matches!(def_id, hir::VariantDef::Variant(_)) {
|
||||
new_field = format!("pub(crate) {new_field}");
|
||||
}
|
||||
new_field = format!("\n{indent}{new_field}");
|
||||
new_field = format!("\n{indent}{new_field}{postfix}");
|
||||
|
||||
let needs_comma = !last_field_syntax.to_string().ends_with(',');
|
||||
if needs_comma {
|
||||
new_field = format!(",{new_field}");
|
||||
}
|
||||
|
||||
let source_change = SourceChange::from_text_edit(
|
||||
def_file_id.file_id(sema.db),
|
||||
TextEdit::insert(last_field_syntax.text_range().end(), new_field),
|
||||
TextEdit::insert(offset, new_field),
|
||||
);
|
||||
|
||||
return Some(vec![fix(
|
||||
@@ -334,6 +346,44 @@ struct Foo {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_field_from_usage_with_empty_struct() {
|
||||
check_fix(
|
||||
r"
|
||||
fn main() {
|
||||
Foo { bar$0: false };
|
||||
}
|
||||
struct Foo {}
|
||||
",
|
||||
r"
|
||||
fn main() {
|
||||
Foo { bar: false };
|
||||
}
|
||||
struct Foo {
|
||||
bar: bool,
|
||||
}
|
||||
",
|
||||
);
|
||||
|
||||
check_fix(
|
||||
r"
|
||||
fn main() {
|
||||
Foo { bar$0: false };
|
||||
}
|
||||
struct Foo {
|
||||
}
|
||||
",
|
||||
r"
|
||||
fn main() {
|
||||
Foo { bar: false };
|
||||
}
|
||||
struct Foo {
|
||||
bar: bool,
|
||||
}
|
||||
",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_field_in_other_file_from_usage() {
|
||||
check_fix(
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
|
||||
use either::Either;
|
||||
use hir::Semantics;
|
||||
use ide_db::text_edit::TextEdit;
|
||||
use ide_db::ty_filter::TryEnum;
|
||||
use ide_db::{RootDatabase, source_change::SourceChange};
|
||||
use syntax::{AstNode, ast};
|
||||
|
||||
use crate::{Assist, Diagnostic, DiagnosticCode, DiagnosticsContext, fix};
|
||||
|
||||
// Diagnostic: non-exhaustive-let
|
||||
//
|
||||
@@ -15,11 +22,74 @@ pub(crate) fn non_exhaustive_let(
|
||||
d.pat.map(Into::into),
|
||||
)
|
||||
.stable()
|
||||
.with_fixes(fixes(&ctx.sema, d))
|
||||
}
|
||||
|
||||
fn fixes(sema: &Semantics<'_, RootDatabase>, d: &hir::NonExhaustiveLet) -> Option<Vec<Assist>> {
|
||||
let root = sema.parse_or_expand(d.pat.file_id);
|
||||
let pat = d.pat.value.to_node(&root);
|
||||
let let_stmt = ast::LetStmt::cast(pat.syntax().parent()?)?;
|
||||
let early_node =
|
||||
sema.ancestors_with_macros(let_stmt.syntax().clone()).find_map(AstNode::cast)?;
|
||||
let early_text = early_text(sema, &early_node);
|
||||
|
||||
if let_stmt.let_else().is_some() {
|
||||
return None;
|
||||
}
|
||||
let hir::FileRangeWrapper { file_id, range } = sema.original_range_opt(let_stmt.syntax())?;
|
||||
let insert_offset = if let Some(semicolon) = let_stmt.semicolon_token()
|
||||
&& let Some(token) = sema.parse(file_id).syntax().token_at_offset(range.end()).left_biased()
|
||||
&& token.kind() == semicolon.kind()
|
||||
{
|
||||
token.text_range().start()
|
||||
} else {
|
||||
range.end()
|
||||
};
|
||||
let semicolon = if let_stmt.semicolon_token().is_none() { ";" } else { "" };
|
||||
let else_block = format!(" else {{ {early_text} }}{semicolon}");
|
||||
let file_id = file_id.file_id(sema.db);
|
||||
|
||||
let source_change =
|
||||
SourceChange::from_text_edit(file_id, TextEdit::insert(insert_offset, else_block));
|
||||
let target = sema.original_range(let_stmt.syntax()).range;
|
||||
Some(vec![fix("add_let_else_block", "Add let-else block", source_change, target)])
|
||||
}
|
||||
|
||||
fn early_text(
|
||||
sema: &Semantics<'_, RootDatabase>,
|
||||
early_node: &Either<ast::AnyHasLoopBody, Either<ast::Fn, ast::ClosureExpr>>,
|
||||
) -> &'static str {
|
||||
match early_node {
|
||||
Either::Left(_any_loop) => "continue",
|
||||
Either::Right(Either::Left(fn_)) => sema
|
||||
.to_def(fn_)
|
||||
.map(|fn_def| fn_def.ret_type(sema.db))
|
||||
.map(|ty| return_text(&ty, sema))
|
||||
.unwrap_or("return"),
|
||||
Either::Right(Either::Right(closure)) => closure
|
||||
.body()
|
||||
.and_then(|expr| sema.type_of_expr(&expr))
|
||||
.map(|ty| return_text(&ty.adjusted(), sema))
|
||||
.unwrap_or("return"),
|
||||
}
|
||||
}
|
||||
|
||||
fn return_text(ty: &hir::Type<'_>, sema: &Semantics<'_, RootDatabase>) -> &'static str {
|
||||
if ty.is_unit() {
|
||||
"return"
|
||||
} else if let Some(try_enum) = TryEnum::from_ty(sema, ty) {
|
||||
match try_enum {
|
||||
TryEnum::Option => "return None",
|
||||
TryEnum::Result => "return Err($0)",
|
||||
}
|
||||
} else {
|
||||
"return $0"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::tests::check_diagnostics;
|
||||
use crate::tests::{check_diagnostics, check_fix};
|
||||
|
||||
#[test]
|
||||
fn option_nonexhaustive() {
|
||||
@@ -28,7 +98,7 @@ fn option_nonexhaustive() {
|
||||
//- minicore: option
|
||||
fn main() {
|
||||
let None = Some(5);
|
||||
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
|
||||
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
|
||||
}
|
||||
"#,
|
||||
);
|
||||
@@ -54,7 +124,7 @@ fn option_nonexhaustive_inside_blocks() {
|
||||
fn main() {
|
||||
'_a: {
|
||||
let None = Some(5);
|
||||
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
|
||||
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
|
||||
}
|
||||
}
|
||||
"#,
|
||||
@@ -66,7 +136,7 @@ fn main() {
|
||||
fn main() {
|
||||
let _ = async {
|
||||
let None = Some(5);
|
||||
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
|
||||
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
|
||||
};
|
||||
}
|
||||
"#,
|
||||
@@ -78,7 +148,7 @@ fn main() {
|
||||
fn main() {
|
||||
unsafe {
|
||||
let None = Some(5);
|
||||
//^^^^ error: non-exhaustive pattern: `Some(_)` not covered
|
||||
//^^^^ 💡 error: non-exhaustive pattern: `Some(_)` not covered
|
||||
}
|
||||
}
|
||||
"#,
|
||||
@@ -101,7 +171,7 @@ fn test(x: Result<i32, !>) {
|
||||
//- minicore: result
|
||||
fn test(x: Result<i32, &'static !>) {
|
||||
let Ok(_y) = x;
|
||||
//^^^^^^ error: non-exhaustive pattern: `Err(_)` not covered
|
||||
//^^^^^^ 💡 error: non-exhaustive pattern: `Err(_)` not covered
|
||||
}
|
||||
"#,
|
||||
);
|
||||
@@ -132,6 +202,136 @@ fn foo(v: Enum<()>) {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_return_in_loop() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn foo() {
|
||||
while cond {
|
||||
let None$0 = Some(5);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
while cond {
|
||||
let None = Some(5) else { continue };
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_return_in_fn() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn foo() {
|
||||
let None$0 = Some(5);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let None = Some(5) else { return };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_return_in_macro_expanded() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option
|
||||
macro_rules! identity { ($($t:tt)*) => { $($t)* }; }
|
||||
fn foo() {
|
||||
identity! {
|
||||
let None$0 = Some(5);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
macro_rules! identity { ($($t:tt)*) => { $($t)* }; }
|
||||
fn foo() {
|
||||
identity! {
|
||||
let None = Some(5) else { return };
|
||||
}
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_return_in_incomplete_let() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn foo() {
|
||||
let None$0 = Some(5)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let None = Some(5) else { return };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_return_in_closure() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn foo() -> Option<()> {
|
||||
let _f = || {
|
||||
let None$0 = Some(5);
|
||||
};
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() -> Option<()> {
|
||||
let _f = || {
|
||||
let None = Some(5) else { return };
|
||||
};
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fix_return_try_in_fn() {
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option
|
||||
fn foo() -> Option<()> {
|
||||
let None$0 = Some(5);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() -> Option<()> {
|
||||
let None = Some(5) else { return None };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
||||
check_fix(
|
||||
r#"
|
||||
//- minicore: option, result
|
||||
fn foo() -> Result<(), i32> {
|
||||
let None$0 = Some(5);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn foo() -> Result<(), i32> {
|
||||
let None = Some(5) else { return Err($0) };
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regression_20259() {
|
||||
check_diagnostics(
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveUnnecessaryElse) -> Option<Vec<
|
||||
let mut indent = IndentLevel::from_node(if_expr.syntax());
|
||||
let has_parent_if_expr = if_expr.syntax().parent().and_then(ast::IfExpr::cast).is_some();
|
||||
if has_parent_if_expr {
|
||||
indent = indent + 1;
|
||||
indent += 1;
|
||||
}
|
||||
let else_replacement = match if_expr.else_branch()? {
|
||||
ast::ElseBranch::Block(block) => block
|
||||
|
||||
@@ -42,6 +42,7 @@ pub struct LoadCargoConfig {
|
||||
pub load_out_dirs_from_check: bool,
|
||||
pub with_proc_macro_server: ProcMacroServerChoice,
|
||||
pub prefill_caches: bool,
|
||||
pub num_worker_threads: usize,
|
||||
pub proc_macro_processes: usize,
|
||||
}
|
||||
|
||||
@@ -197,7 +198,7 @@ pub fn load_workspace_into_db(
|
||||
);
|
||||
|
||||
if load_config.prefill_caches {
|
||||
prime_caches::parallel_prime_caches(db, 1, &|_| ());
|
||||
prime_caches::parallel_prime_caches(db, load_config.num_worker_threads, &|_| ());
|
||||
}
|
||||
|
||||
Ok((vfs, proc_macro_server.and_then(Result::ok)))
|
||||
@@ -744,16 +745,26 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_loading_rust_analyzer() {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap().parent().unwrap();
|
||||
let cargo_toml_path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.parent()
|
||||
.unwrap()
|
||||
.parent()
|
||||
.unwrap()
|
||||
.join("Cargo.toml");
|
||||
let cargo_toml_path = AbsPathBuf::assert_utf8(cargo_toml_path);
|
||||
let manifest = ProjectManifest::from_manifest_file(cargo_toml_path).unwrap();
|
||||
|
||||
let cargo_config = CargoConfig { set_test: true, ..CargoConfig::default() };
|
||||
let load_cargo_config = LoadCargoConfig {
|
||||
load_out_dirs_from_check: false,
|
||||
with_proc_macro_server: ProcMacroServerChoice::None,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let workspace = ProjectWorkspace::load(manifest, &cargo_config, &|_| {}).unwrap();
|
||||
let (db, _vfs, _proc_macro) =
|
||||
load_workspace_at(path, &cargo_config, &load_cargo_config, &|_| {}).unwrap();
|
||||
load_workspace(workspace, &cargo_config.extra_env, &load_cargo_config).unwrap();
|
||||
|
||||
let n_crates = db.all_crates().len();
|
||||
// RA has quite a few crates, but the exact count doesn't matter
|
||||
|
||||
@@ -365,9 +365,27 @@ pub enum RunnableKind {
|
||||
/// May include {test_id} which will get the test clicked on by the user.
|
||||
TestOne,
|
||||
|
||||
/// Run tests matching a pattern (in RA, usually a path::to::module::of::tests)
|
||||
/// May include {label} which will get the label from the `build` section of a crate.
|
||||
/// May include {test_pattern} which will get the test module clicked on by the user.
|
||||
TestMod,
|
||||
|
||||
/// Run a single doctest
|
||||
/// May include {label} which will get the label from the `build` section of a crate.
|
||||
/// May include {test_id} which will get the doctest clicked on by the user.
|
||||
DocTestOne,
|
||||
|
||||
/// Run a single benchmark
|
||||
/// May include {label} which will get the label from the `build` section of a crate.
|
||||
/// May include {bench_id} which will get the benchmark clicked on by the user.
|
||||
BenchOne,
|
||||
|
||||
/// Template for checking a target, emitting rustc JSON diagnostics.
|
||||
/// May include {label} which will get the label from the `build` section of a crate.
|
||||
Flycheck,
|
||||
|
||||
/// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
|
||||
@@ -380,6 +398,8 @@ pub struct ProjectJsonData {
|
||||
crates: Vec<CrateData>,
|
||||
#[serde(default)]
|
||||
runnables: Vec<RunnableData>,
|
||||
//
|
||||
// New fields should be Option or #[serde(default)]. This applies to most of this datastructure.
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq, Default)]
|
||||
@@ -453,32 +473,40 @@ enum EditionData {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
|
||||
pub struct BuildData {
|
||||
struct BuildData {
|
||||
label: String,
|
||||
build_file: Utf8PathBuf,
|
||||
target_kind: TargetKindData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct RunnableData {
|
||||
pub program: String,
|
||||
pub args: Vec<String>,
|
||||
pub cwd: Utf8PathBuf,
|
||||
pub kind: RunnableKindData,
|
||||
struct RunnableData {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
cwd: Utf8PathBuf,
|
||||
kind: RunnableKindData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum RunnableKindData {
|
||||
enum RunnableKindData {
|
||||
Flycheck,
|
||||
Check,
|
||||
Run,
|
||||
TestOne,
|
||||
TestMod,
|
||||
DocTestOne,
|
||||
BenchOne,
|
||||
|
||||
/// For forwards-compatibility, i.e. old rust-analyzer binary with newer workspace discovery tools
|
||||
#[allow(unused)]
|
||||
#[serde(other)]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum TargetKindData {
|
||||
enum TargetKindData {
|
||||
Bin,
|
||||
/// Any kind of Cargo lib crate-type (dylib, rlib, proc-macro, ...).
|
||||
Lib,
|
||||
@@ -541,7 +569,11 @@ fn from(data: RunnableKindData) -> Self {
|
||||
RunnableKindData::Check => RunnableKind::Check,
|
||||
RunnableKindData::Run => RunnableKind::Run,
|
||||
RunnableKindData::TestOne => RunnableKind::TestOne,
|
||||
RunnableKindData::TestMod => RunnableKind::TestMod,
|
||||
RunnableKindData::DocTestOne => RunnableKind::DocTestOne,
|
||||
RunnableKindData::BenchOne => RunnableKind::BenchOne,
|
||||
RunnableKindData::Flycheck => RunnableKind::Flycheck,
|
||||
RunnableKindData::Unknown => RunnableKind::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,6 +192,12 @@ fn rust_project_hello_world_project_model() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_project_labeled_project_model() {
|
||||
// This just needs to parse.
|
||||
_ = load_rust_project("labeled-project.json");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_project_cfg_groups() {
|
||||
let (crate_graph, _proc_macros) = load_rust_project("cfg-groups.json");
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"sysroot_src": null,
|
||||
"crates": [
|
||||
{
|
||||
"display_name": "hello_world",
|
||||
"root_module": "$ROOT$src/lib.rs",
|
||||
"edition": "2018",
|
||||
"deps": [],
|
||||
"is_workspace_member": true,
|
||||
"build": {
|
||||
"label": "//:hello_world",
|
||||
"build_file": "$ROOT$BUILD",
|
||||
"target_kind": "bin"
|
||||
}
|
||||
}
|
||||
],
|
||||
"runnables": [
|
||||
{
|
||||
"kind": "run",
|
||||
"program": "bazel",
|
||||
"args": ["run", "{label}"],
|
||||
"cwd": "$ROOT$"
|
||||
},
|
||||
{
|
||||
"kind": "flycheck",
|
||||
"program": "$ROOT$custom-flychecker.sh",
|
||||
"args": ["{label}"],
|
||||
"cwd": "$ROOT$"
|
||||
},
|
||||
{
|
||||
"kind": "we-ignore-unknown-runnable-kinds-for-forwards-compatibility",
|
||||
"program": "abc",
|
||||
"args": ["{label}"],
|
||||
"cwd": "$ROOT$"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -91,6 +91,7 @@ pub fn run(self, verbosity: Verbosity) -> anyhow::Result<()> {
|
||||
}
|
||||
},
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ fn run_(self) -> anyhow::Result<()> {
|
||||
load_out_dirs_from_check: !self.disable_build_scripts,
|
||||
with_proc_macro_server,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let (db, _vfs, _proc_macro) =
|
||||
|
||||
@@ -191,6 +191,9 @@
|
||||
|
||||
/// Exclude code from vendored libraries from the resulting index.
|
||||
optional --exclude-vendored-libraries
|
||||
|
||||
/// The number of worker threads for cache priming. Defaults to the number of physical cores.
|
||||
optional --num-threads num_threads: usize
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -338,6 +341,7 @@ pub struct Scip {
|
||||
pub output: Option<PathBuf>,
|
||||
pub config_path: Option<PathBuf>,
|
||||
pub exclude_vendored_libraries: bool,
|
||||
pub num_threads: Option<usize>,
|
||||
}
|
||||
|
||||
impl RustAnalyzer {
|
||||
|
||||
@@ -293,6 +293,7 @@ pub fn run(
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let path = AbsPathBuf::assert_utf8(env::current_dir()?.join(self.path));
|
||||
|
||||
@@ -38,6 +38,7 @@ pub fn run(self) -> anyhow::Result<()> {
|
||||
// we want to ensure that this command, not `load_workspace_at`,
|
||||
// is responsible for that work.
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: config.proc_macro_num_processes(),
|
||||
};
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ pub fn run(self) -> Result<()> {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let (ref db, _vfs, _proc_macro) =
|
||||
|
||||
@@ -103,6 +103,7 @@ fn new() -> Result<Self> {
|
||||
load_out_dirs_from_check: false,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let (db, _vfs, _proc_macro) =
|
||||
|
||||
@@ -52,6 +52,7 @@ pub fn run(self) -> anyhow::Result<()> {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: true,
|
||||
num_worker_threads: self.num_threads.unwrap_or_else(num_cpus::get_physical),
|
||||
proc_macro_processes: config.proc_macro_num_processes(),
|
||||
};
|
||||
let cargo_config = config.cargo(None);
|
||||
|
||||
@@ -20,6 +20,7 @@ pub fn run(self) -> anyhow::Result<()> {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let (ref db, vfs, _proc_macro) = load_workspace_at(
|
||||
@@ -57,6 +58,7 @@ pub fn run(self) -> anyhow::Result<()> {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
let (ref db, _vfs, _proc_macro) = load_workspace_at(
|
||||
|
||||
@@ -44,6 +44,7 @@ fn run_(self) -> anyhow::Result<()> {
|
||||
load_out_dirs_from_check: !self.disable_build_scripts,
|
||||
with_proc_macro_server,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: config.proc_macro_num_processes(),
|
||||
};
|
||||
let (db, vfs, _proc_macro) =
|
||||
|
||||
@@ -948,18 +948,18 @@ pub enum MaxSubstitutionLength {
|
||||
/// Override the command used for bench runnables.
|
||||
/// The first element of the array should be the program to execute (for example, `cargo`).
|
||||
///
|
||||
/// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
|
||||
/// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically
|
||||
/// replace the package name, target option (such as `--bin` or `--example`), the target name and
|
||||
/// the test name (name of test function or test mod path).
|
||||
/// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).
|
||||
runnables_bench_overrideCommand: Option<Vec<String>> = None,
|
||||
/// Command to be executed instead of 'cargo' for runnables.
|
||||
runnables_command: Option<String> = None,
|
||||
/// Override the command used for bench runnables.
|
||||
/// The first element of the array should be the program to execute (for example, `cargo`).
|
||||
///
|
||||
/// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
|
||||
/// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically
|
||||
/// replace the package name, target option (such as `--bin` or `--example`), the target name and
|
||||
/// the test name (name of test function or test mod path).
|
||||
/// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).
|
||||
runnables_doctest_overrideCommand: Option<Vec<String>> = None,
|
||||
/// Additional arguments to be passed to cargo for runnables such as
|
||||
/// tests or binaries. For example, it may be `--release`.
|
||||
@@ -977,9 +977,9 @@ pub enum MaxSubstitutionLength {
|
||||
/// Override the command used for test runnables.
|
||||
/// The first element of the array should be the program to execute (for example, `cargo`).
|
||||
///
|
||||
/// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
|
||||
/// Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically
|
||||
/// replace the package name, target option (such as `--bin` or `--example`), the target name and
|
||||
/// the test name (name of test function or test mod path).
|
||||
/// the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).
|
||||
runnables_test_overrideCommand: Option<Vec<String>> = None,
|
||||
|
||||
/// Path to the Cargo.toml of the rust compiler workspace, for usage in rustc_private
|
||||
|
||||
@@ -53,6 +53,7 @@ fn integrated_highlighting_benchmark() {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: false,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
|
||||
@@ -122,6 +123,7 @@ fn integrated_completion_benchmark() {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: true,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
|
||||
@@ -324,6 +326,7 @@ fn integrated_diagnostics_benchmark() {
|
||||
load_out_dirs_from_check: true,
|
||||
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
|
||||
prefill_caches: true,
|
||||
num_worker_threads: 1,
|
||||
proc_macro_processes: 1,
|
||||
};
|
||||
|
||||
|
||||
@@ -303,6 +303,15 @@ fn next_event(
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn trigger_garbage_collection(&mut self) {
|
||||
if cfg!(test) {
|
||||
// Slow tests run the main loop in multiple threads, but GC isn't thread safe.
|
||||
return;
|
||||
}
|
||||
|
||||
self.analysis_host.trigger_garbage_collection();
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: Event) {
|
||||
let loop_start = Instant::now();
|
||||
let _p = tracing::info_span!("GlobalState::handle_event", event = %event).entered();
|
||||
@@ -383,7 +392,7 @@ fn handle_event(&mut self, event: Event) {
|
||||
));
|
||||
}
|
||||
PrimeCachesProgress::End { cancelled } => {
|
||||
self.analysis_host.trigger_garbage_collection();
|
||||
self.trigger_garbage_collection();
|
||||
self.prime_caches_queue.op_completed(());
|
||||
if cancelled {
|
||||
self.prime_caches_queue
|
||||
@@ -542,7 +551,7 @@ fn handle_event(&mut self, event: Event) {
|
||||
&& self.fmt_pool.handle.is_empty()
|
||||
&& current_revision != self.last_gc_revision
|
||||
{
|
||||
self.analysis_host.trigger_garbage_collection();
|
||||
self.trigger_garbage_collection();
|
||||
self.last_gc_revision = current_revision;
|
||||
}
|
||||
}
|
||||
@@ -1178,6 +1187,8 @@ fn handle_flycheck_msg(&mut self, message: FlycheckMessage, cargo_finished: &mut
|
||||
} => self.diagnostics.clear_check_older_than_for_package(id, package_id, generation),
|
||||
FlycheckMessage::Progress { id, progress } => {
|
||||
let format_with_id = |user_facing_command: String| {
|
||||
// When we're running multiple flychecks, we have to include a disambiguator in
|
||||
// the title, or the editor complains. Note that this is a user-facing string.
|
||||
if self.flycheck.len() == 1 {
|
||||
user_facing_command
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
use cfg::{CfgAtom, CfgExpr};
|
||||
use hir::sym;
|
||||
use ide::{Cancellable, Crate, FileId, RunnableKind, TestId};
|
||||
use project_model::project_json::Runnable;
|
||||
use project_model::project_json::{self, Runnable};
|
||||
use project_model::{CargoFeatures, ManifestPath, TargetKind};
|
||||
use rustc_hash::FxHashSet;
|
||||
use triomphe::Arc;
|
||||
@@ -72,48 +72,51 @@ pub(crate) struct ProjectJsonTargetSpec {
|
||||
}
|
||||
|
||||
impl ProjectJsonTargetSpec {
|
||||
fn find_replace_runnable(
|
||||
&self,
|
||||
kind: project_json::RunnableKind,
|
||||
replacer: &dyn Fn(&Self, &str) -> String,
|
||||
) -> Option<Runnable> {
|
||||
for runnable in &self.shell_runnables {
|
||||
if runnable.kind == kind {
|
||||
let mut runnable = runnable.clone();
|
||||
|
||||
let replaced_args: Vec<_> =
|
||||
runnable.args.iter().map(|arg| replacer(self, arg)).collect();
|
||||
runnable.args = replaced_args;
|
||||
|
||||
return Some(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub(crate) fn runnable_args(&self, kind: &RunnableKind) -> Option<Runnable> {
|
||||
match kind {
|
||||
RunnableKind::Bin => {
|
||||
for runnable in &self.shell_runnables {
|
||||
if matches!(runnable.kind, project_model::project_json::RunnableKind::Run) {
|
||||
let mut runnable = runnable.clone();
|
||||
|
||||
let replaced_args: Vec<_> = runnable
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| arg.replace("{label}", &self.label))
|
||||
.collect();
|
||||
runnable.args = replaced_args;
|
||||
|
||||
return Some(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
RunnableKind::Bin => self
|
||||
.find_replace_runnable(project_json::RunnableKind::Run, &|this, arg| {
|
||||
arg.replace("{label}", &this.label)
|
||||
}),
|
||||
RunnableKind::Test { test_id, .. } => {
|
||||
for runnable in &self.shell_runnables {
|
||||
if matches!(runnable.kind, project_model::project_json::RunnableKind::TestOne) {
|
||||
let mut runnable = runnable.clone();
|
||||
|
||||
let replaced_args: Vec<_> = runnable
|
||||
.args
|
||||
.iter()
|
||||
.map(|arg| arg.replace("{test_id}", &test_id.to_string()))
|
||||
.map(|arg| arg.replace("{label}", &self.label))
|
||||
.collect();
|
||||
runnable.args = replaced_args;
|
||||
|
||||
return Some(runnable);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
self.find_replace_runnable(project_json::RunnableKind::Run, &|this, arg| {
|
||||
arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string())
|
||||
})
|
||||
}
|
||||
RunnableKind::TestMod { path } => self
|
||||
.find_replace_runnable(project_json::RunnableKind::TestMod, &|this, arg| {
|
||||
arg.replace("{label}", &this.label).replace("{test_pattern}", path)
|
||||
}),
|
||||
RunnableKind::Bench { test_id } => {
|
||||
self.find_replace_runnable(project_json::RunnableKind::BenchOne, &|this, arg| {
|
||||
arg.replace("{label}", &this.label).replace("{bench_id}", &test_id.to_string())
|
||||
})
|
||||
}
|
||||
RunnableKind::DocTest { test_id } => {
|
||||
self.find_replace_runnable(project_json::RunnableKind::DocTestOne, &|this, arg| {
|
||||
arg.replace("{label}", &this.label).replace("{test_id}", &test_id.to_string())
|
||||
})
|
||||
}
|
||||
RunnableKind::TestMod { .. } => None,
|
||||
RunnableKind::Bench { .. } => None,
|
||||
RunnableKind::DocTest { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,38 +132,21 @@ pub(crate) fn runnable_args(
|
||||
let extra_test_binary_args = config.extra_test_binary_args;
|
||||
|
||||
let mut cargo_args = Vec::new();
|
||||
let mut executable_args = Vec::new();
|
||||
let executable_args = Self::executable_args_for(kind, extra_test_binary_args);
|
||||
|
||||
match kind {
|
||||
RunnableKind::Test { test_id, attr } => {
|
||||
RunnableKind::Test { .. } => {
|
||||
cargo_args.push(config.test_command);
|
||||
executable_args.push(test_id.to_string());
|
||||
if let TestId::Path(_) = test_id {
|
||||
executable_args.push("--exact".to_owned());
|
||||
}
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
if attr.ignore {
|
||||
executable_args.push("--ignored".to_owned());
|
||||
}
|
||||
}
|
||||
RunnableKind::TestMod { path } => {
|
||||
RunnableKind::TestMod { .. } => {
|
||||
cargo_args.push(config.test_command);
|
||||
executable_args.push(path.clone());
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
}
|
||||
RunnableKind::Bench { test_id } => {
|
||||
RunnableKind::Bench { .. } => {
|
||||
cargo_args.push(config.bench_command);
|
||||
executable_args.push(test_id.to_string());
|
||||
if let TestId::Path(_) = test_id {
|
||||
executable_args.push("--exact".to_owned());
|
||||
}
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
}
|
||||
RunnableKind::DocTest { test_id } => {
|
||||
RunnableKind::DocTest { .. } => {
|
||||
cargo_args.push("test".to_owned());
|
||||
cargo_args.push("--doc".to_owned());
|
||||
executable_args.push(test_id.to_string());
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
}
|
||||
RunnableKind::Bin => {
|
||||
let subcommand = match spec {
|
||||
@@ -253,16 +239,70 @@ pub(crate) fn override_command(
|
||||
TargetKind::BuildScript | TargetKind::Other => "",
|
||||
};
|
||||
|
||||
let target = |kind, target| match kind {
|
||||
TargetKind::Bin | TargetKind::Test | TargetKind::Bench | TargetKind::Example => target,
|
||||
_ => "",
|
||||
};
|
||||
|
||||
let replace_placeholders = |arg: String| match &spec {
|
||||
Some(spec) => arg
|
||||
.replace("${package}", &spec.package)
|
||||
.replace("${target_arg}", target_arg(spec.target_kind))
|
||||
.replace("${target}", &spec.target)
|
||||
.replace("${target}", target(spec.target_kind, &spec.target))
|
||||
.replace("${test_name}", &test_name),
|
||||
_ => arg,
|
||||
};
|
||||
|
||||
args.map(|args| args.into_iter().map(replace_placeholders).collect())
|
||||
let extra_test_binary_args = config.extra_test_binary_args;
|
||||
let executable_args = Self::executable_args_for(kind, extra_test_binary_args);
|
||||
|
||||
args.map(|mut args| {
|
||||
let exec_args_idx = args.iter().position(|a| a == "${executable_args}");
|
||||
|
||||
if let Some(idx) = exec_args_idx {
|
||||
args.splice(idx..idx + 1, executable_args);
|
||||
}
|
||||
|
||||
args.into_iter().map(replace_placeholders).filter(|a| !a.trim().is_empty()).collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn executable_args_for(
|
||||
kind: &RunnableKind,
|
||||
extra_test_binary_args: impl IntoIterator<Item = String>,
|
||||
) -> Vec<String> {
|
||||
let mut executable_args = Vec::new();
|
||||
|
||||
match kind {
|
||||
RunnableKind::Test { test_id, attr } => {
|
||||
executable_args.push(test_id.to_string());
|
||||
if let TestId::Path(_) = test_id {
|
||||
executable_args.push("--exact".to_owned());
|
||||
}
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
if attr.ignore {
|
||||
executable_args.push("--ignored".to_owned());
|
||||
}
|
||||
}
|
||||
RunnableKind::TestMod { path } => {
|
||||
executable_args.push(path.clone());
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
}
|
||||
RunnableKind::Bench { test_id } => {
|
||||
executable_args.push(test_id.to_string());
|
||||
if let TestId::Path(_) = test_id {
|
||||
executable_args.push("--exact".to_owned());
|
||||
}
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
}
|
||||
RunnableKind::DocTest { test_id } => {
|
||||
executable_args.push(test_id.to_string());
|
||||
executable_args.extend(extra_test_binary_args);
|
||||
}
|
||||
RunnableKind::Bin => {}
|
||||
}
|
||||
|
||||
executable_args
|
||||
}
|
||||
|
||||
pub(crate) fn push_to(self, buf: &mut Vec<String>, kind: &RunnableKind) {
|
||||
|
||||
@@ -221,12 +221,7 @@ pub fn trim_indent(mut text: &str) -> String {
|
||||
if text.starts_with('\n') {
|
||||
text = &text[1..];
|
||||
}
|
||||
let indent = text
|
||||
.lines()
|
||||
.filter(|it| !it.trim().is_empty())
|
||||
.map(|it| it.len() - it.trim_start().len())
|
||||
.min()
|
||||
.unwrap_or(0);
|
||||
let indent = indent_of(text);
|
||||
text.split_inclusive('\n')
|
||||
.map(
|
||||
|line| {
|
||||
@@ -236,6 +231,25 @@ pub fn trim_indent(mut text: &str) -> String {
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
fn indent_of(text: &str) -> usize {
|
||||
text.lines()
|
||||
.filter(|it| !it.trim().is_empty())
|
||||
.map(|it| it.len() - it.trim_start().len())
|
||||
.min()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn dedent_by(spaces: usize, text: &str) -> String {
|
||||
text.split_inclusive('\n')
|
||||
.map(|line| {
|
||||
let trimmed = line.trim_start_matches(' ');
|
||||
if line.len() - trimmed.len() <= spaces { trimmed } else { &line[spaces..] }
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn equal_range_by<T, F>(slice: &[T], mut key: F) -> ops::Range<usize>
|
||||
where
|
||||
F: FnMut(&T) -> Ordering,
|
||||
@@ -366,6 +380,37 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dedent() {
|
||||
assert_eq!(dedent_by(0, ""), "");
|
||||
assert_eq!(dedent_by(1, ""), "");
|
||||
assert_eq!(dedent_by(2, ""), "");
|
||||
assert_eq!(dedent_by(0, "foo"), "foo");
|
||||
assert_eq!(dedent_by(2, "foo"), "foo");
|
||||
assert_eq!(dedent_by(2, " foo"), "foo");
|
||||
assert_eq!(dedent_by(2, " foo"), " foo");
|
||||
assert_eq!(dedent_by(2, " foo\nbar"), " foo\nbar");
|
||||
assert_eq!(dedent_by(2, "foo\n bar"), "foo\n bar");
|
||||
assert_eq!(dedent_by(2, "foo\n\n bar"), "foo\n\n bar");
|
||||
assert_eq!(dedent_by(2, "foo\n.\n bar"), "foo\n.\n bar");
|
||||
assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n.\n bar");
|
||||
assert_eq!(dedent_by(2, "foo\n .\n bar"), "foo\n .\n bar");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_indent_of() {
|
||||
assert_eq!(indent_of(""), 0);
|
||||
assert_eq!(indent_of(" "), 0);
|
||||
assert_eq!(indent_of(" x"), 1);
|
||||
assert_eq!(indent_of(" x\n"), 1);
|
||||
assert_eq!(indent_of(" x\ny"), 0);
|
||||
assert_eq!(indent_of(" x\n y"), 1);
|
||||
assert_eq!(indent_of(" x\n y"), 1);
|
||||
assert_eq!(indent_of(" x\n y"), 2);
|
||||
assert_eq!(indent_of(" x\n y\n"), 2);
|
||||
assert_eq!(indent_of(" x\n\n y\n"), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_replace() {
|
||||
#[track_caller]
|
||||
|
||||
@@ -132,3 +132,19 @@ pub fn previous_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxTo
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn next_non_trivia_token(e: impl Into<SyntaxElement>) -> Option<SyntaxToken> {
|
||||
let mut token = match e.into() {
|
||||
SyntaxElement::Node(n) => n.last_token()?,
|
||||
SyntaxElement::Token(t) => t,
|
||||
}
|
||||
.next_token();
|
||||
while let Some(inner) = token {
|
||||
if !inner.kind().is_trivia() {
|
||||
return Some(inner);
|
||||
} else {
|
||||
token = inner.next_token();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@@ -43,6 +43,12 @@ fn add(self, rhs: u8) -> IndentLevel {
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::AddAssign<u8> for IndentLevel {
|
||||
fn add_assign(&mut self, rhs: u8) {
|
||||
self.0 += rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl IndentLevel {
|
||||
pub fn single() -> IndentLevel {
|
||||
IndentLevel(0)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
AstNode, NodeOrToken, SyntaxKind, SyntaxNode, SyntaxToken,
|
||||
ast::{
|
||||
self, HasArgList, HasAttrs, HasGenericArgs, HasGenericParams, HasLoopBody, HasName,
|
||||
HasTypeBounds, HasVisibility, Param, RangeItem, make,
|
||||
HasTypeBounds, HasVisibility, Lifetime, Param, RangeItem, make,
|
||||
},
|
||||
syntax_editor::SyntaxMappingBuilder,
|
||||
};
|
||||
@@ -21,6 +21,14 @@ pub fn name_ref(&self, name: &str) -> ast::NameRef {
|
||||
make::name_ref(name).clone_for_update()
|
||||
}
|
||||
|
||||
pub fn name_ref_self_ty(&self) -> ast::NameRef {
|
||||
make::name_ref_self_ty().clone_for_update()
|
||||
}
|
||||
|
||||
pub fn expr_todo(&self) -> ast::Expr {
|
||||
make::ext::expr_todo().clone_for_update()
|
||||
}
|
||||
|
||||
pub fn lifetime(&self, text: &str) -> ast::Lifetime {
|
||||
make::lifetime(text).clone_for_update()
|
||||
}
|
||||
@@ -96,24 +104,47 @@ pub fn struct_(
|
||||
generic_param_list: Option<ast::GenericParamList>,
|
||||
field_list: ast::FieldList,
|
||||
) -> ast::Struct {
|
||||
make::struct_(visibility, strukt_name, generic_param_list, field_list).clone_for_update()
|
||||
}
|
||||
let ast = make::struct_(
|
||||
visibility.clone(),
|
||||
strukt_name.clone(),
|
||||
generic_param_list.clone(),
|
||||
field_list.clone(),
|
||||
)
|
||||
.clone_for_update();
|
||||
|
||||
pub fn enum_(
|
||||
&self,
|
||||
attrs: impl IntoIterator<Item = ast::Attr>,
|
||||
visibility: Option<ast::Visibility>,
|
||||
enum_name: ast::Name,
|
||||
generic_param_list: Option<ast::GenericParamList>,
|
||||
where_clause: Option<ast::WhereClause>,
|
||||
variant_list: ast::VariantList,
|
||||
) -> ast::Enum {
|
||||
make::enum_(attrs, visibility, enum_name, generic_param_list, where_clause, variant_list)
|
||||
.clone_for_update()
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
if let Some(visibility) = visibility {
|
||||
builder.map_node(
|
||||
visibility.syntax().clone(),
|
||||
ast.visibility().unwrap().syntax().clone(),
|
||||
);
|
||||
}
|
||||
builder.map_node(strukt_name.syntax().clone(), ast.name().unwrap().syntax().clone());
|
||||
if let Some(generic_param_list) = generic_param_list {
|
||||
builder.map_node(
|
||||
generic_param_list.syntax().clone(),
|
||||
ast.generic_param_list().unwrap().syntax().clone(),
|
||||
);
|
||||
}
|
||||
builder
|
||||
.map_node(field_list.syntax().clone(), ast.field_list().unwrap().syntax().clone());
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn unnamed_param(&self, ty: ast::Type) -> ast::Param {
|
||||
make::unnamed_param(ty).clone_for_update()
|
||||
let ast = make::unnamed_param(ty.clone()).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone());
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
|
||||
@@ -123,7 +154,27 @@ pub fn ty_fn_ptr<I: Iterator<Item = Param>>(
|
||||
params: I,
|
||||
ret_type: Option<ast::RetType>,
|
||||
) -> ast::FnPtrType {
|
||||
make::ty_fn_ptr(is_unsafe, abi, params, ret_type).clone_for_update()
|
||||
let (params, params_input) = iterator_input(params);
|
||||
let ast = make::ty_fn_ptr(is_unsafe, abi.clone(), params.into_iter(), ret_type.clone())
|
||||
.clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
if let Some(abi) = abi {
|
||||
builder.map_node(abi.syntax().clone(), ast.abi().unwrap().syntax().clone());
|
||||
}
|
||||
builder.map_children(
|
||||
params_input,
|
||||
ast.param_list().unwrap().params().map(|p| p.syntax().clone()),
|
||||
);
|
||||
if let Some(ret_type) = ret_type {
|
||||
builder
|
||||
.map_node(ret_type.syntax().clone(), ast.ret_type().unwrap().syntax().clone());
|
||||
}
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn where_pred(
|
||||
@@ -131,18 +182,61 @@ pub fn where_pred(
|
||||
path: Either<ast::Lifetime, ast::Type>,
|
||||
bounds: impl IntoIterator<Item = ast::TypeBound>,
|
||||
) -> ast::WherePred {
|
||||
make::where_pred(path, bounds).clone_for_update()
|
||||
let (bounds, bounds_input) = iterator_input(bounds);
|
||||
let ast = make::where_pred(path.clone(), bounds).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
match &path {
|
||||
Either::Left(lifetime) => {
|
||||
builder.map_node(
|
||||
lifetime.syntax().clone(),
|
||||
ast.lifetime().unwrap().syntax().clone(),
|
||||
);
|
||||
}
|
||||
Either::Right(ty) => {
|
||||
builder.map_node(ty.syntax().clone(), ast.ty().unwrap().syntax().clone());
|
||||
}
|
||||
}
|
||||
if let Some(type_bound_list) = ast.type_bound_list() {
|
||||
builder.map_children(
|
||||
bounds_input,
|
||||
type_bound_list.bounds().map(|b| b.syntax().clone()),
|
||||
);
|
||||
}
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn where_clause(
|
||||
&self,
|
||||
predicates: impl IntoIterator<Item = ast::WherePred>,
|
||||
) -> ast::WhereClause {
|
||||
make::where_clause(predicates).clone_for_update()
|
||||
let (predicates, input) = iterator_input(predicates);
|
||||
let ast = make::where_clause(predicates).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_children(input, ast.predicates().map(|p| p.syntax().clone()));
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn impl_trait_type(&self, bounds: ast::TypeBoundList) -> ast::ImplTraitType {
|
||||
make::impl_trait_type(bounds).clone_for_update()
|
||||
let ast = make::impl_trait_type(bounds.clone()).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder
|
||||
.map_node(bounds.syntax().clone(), ast.type_bound_list().unwrap().syntax().clone());
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn expr_field(&self, receiver: ast::Expr, field: &str) -> ast::FieldExpr {
|
||||
@@ -340,15 +434,53 @@ pub fn generic_ty_path_segment(
|
||||
name_ref: ast::NameRef,
|
||||
generic_args: impl IntoIterator<Item = ast::GenericArg>,
|
||||
) -> ast::PathSegment {
|
||||
make::generic_ty_path_segment(name_ref, generic_args).clone_for_update()
|
||||
let (generic_args, input) = iterator_input(generic_args);
|
||||
let ast = make::generic_ty_path_segment(name_ref.clone(), generic_args).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_node(name_ref.syntax().clone(), ast.name_ref().unwrap().syntax().clone());
|
||||
builder.map_children(
|
||||
input,
|
||||
ast.generic_arg_list().unwrap().generic_args().map(|a| a.syntax().clone()),
|
||||
);
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn tail_only_block_expr(&self, tail_expr: ast::Expr) -> ast::BlockExpr {
|
||||
make::tail_only_block_expr(tail_expr)
|
||||
let ast = make::tail_only_block_expr(tail_expr.clone()).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let stmt_list = ast.stmt_list().unwrap();
|
||||
let mut builder = SyntaxMappingBuilder::new(stmt_list.syntax().clone());
|
||||
builder.map_node(
|
||||
tail_expr.syntax().clone(),
|
||||
stmt_list.tail_expr().unwrap().syntax().clone(),
|
||||
);
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn expr_bin_op(&self, lhs: ast::Expr, op: ast::BinaryOp, rhs: ast::Expr) -> ast::Expr {
|
||||
make::expr_bin_op(lhs, op, rhs)
|
||||
let ast::Expr::BinExpr(ast) =
|
||||
make::expr_bin_op(lhs.clone(), op, rhs.clone()).clone_for_update()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_node(lhs.syntax().clone(), ast.lhs().unwrap().syntax().clone());
|
||||
builder.map_node(rhs.syntax().clone(), ast.rhs().unwrap().syntax().clone());
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast.into()
|
||||
}
|
||||
|
||||
pub fn ty_placeholder(&self) -> ast::Type {
|
||||
@@ -385,7 +517,23 @@ pub fn use_(
|
||||
visibility: Option<ast::Visibility>,
|
||||
use_tree: ast::UseTree,
|
||||
) -> ast::Use {
|
||||
make::use_(attrs, visibility, use_tree).clone_for_update()
|
||||
let (attrs, attrs_input) = iterator_input(attrs);
|
||||
let ast = make::use_(attrs, visibility.clone(), use_tree.clone()).clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_children(attrs_input, ast.attrs().map(|attr| attr.syntax().clone()));
|
||||
if let Some(visibility) = visibility {
|
||||
builder.map_node(
|
||||
visibility.syntax().clone(),
|
||||
ast.visibility().unwrap().syntax().clone(),
|
||||
);
|
||||
}
|
||||
builder.map_node(use_tree.syntax().clone(), ast.use_tree().unwrap().syntax().clone());
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn use_tree(
|
||||
@@ -395,7 +543,25 @@ pub fn use_tree(
|
||||
alias: Option<ast::Rename>,
|
||||
add_star: bool,
|
||||
) -> ast::UseTree {
|
||||
make::use_tree(path, use_tree_list, alias, add_star).clone_for_update()
|
||||
let ast = make::use_tree(path.clone(), use_tree_list.clone(), alias.clone(), add_star)
|
||||
.clone_for_update();
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_node(path.syntax().clone(), ast.path().unwrap().syntax().clone());
|
||||
if let Some(use_tree_list) = use_tree_list {
|
||||
builder.map_node(
|
||||
use_tree_list.syntax().clone(),
|
||||
ast.use_tree_list().unwrap().syntax().clone(),
|
||||
);
|
||||
}
|
||||
if let Some(alias) = alias {
|
||||
builder.map_node(alias.syntax().clone(), ast.rename().unwrap().syntax().clone());
|
||||
}
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn path_unqualified(&self, segment: ast::PathSegment) -> ast::Path {
|
||||
@@ -896,10 +1062,6 @@ pub fn expr_underscore(&self) -> ast::UnderscoreExpr {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
SyntaxMappingBuilder::new(ast.syntax().clone()).finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
@@ -1765,6 +1927,65 @@ pub fn ty_ref(&self, ty: ast::Type, is_mut: bool) -> ast::Type {
|
||||
}
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn field_from_idents<'a>(
|
||||
&self,
|
||||
parts: impl std::iter::IntoIterator<Item = &'a str>,
|
||||
) -> Option<ast::Expr> {
|
||||
make::ext::field_from_idents(parts)
|
||||
}
|
||||
|
||||
pub fn expr_await(&self, expr: ast::Expr) -> ast::AwaitExpr {
|
||||
let ast::Expr::AwaitExpr(ast) = make::expr_await(expr.clone()).clone_for_update() else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone());
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn expr_break(&self, label: Option<Lifetime>, expr: Option<ast::Expr>) -> ast::BreakExpr {
|
||||
let ast::Expr::BreakExpr(ast) =
|
||||
make::expr_break(label.clone(), expr.clone()).clone_for_update()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
if let Some(label) = label {
|
||||
builder.map_node(label.syntax().clone(), ast.lifetime().unwrap().syntax().clone());
|
||||
}
|
||||
if let Some(expr) = expr {
|
||||
builder.map_node(expr.syntax().clone(), ast.expr().unwrap().syntax().clone());
|
||||
}
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
|
||||
pub fn expr_continue(&self, label: Option<Lifetime>) -> ast::ContinueExpr {
|
||||
let ast::Expr::ContinueExpr(ast) = make::expr_continue(label.clone()).clone_for_update()
|
||||
else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
if let Some(mut mapping) = self.mappings() {
|
||||
let mut builder = SyntaxMappingBuilder::new(ast.syntax().clone());
|
||||
if let Some(label) = label {
|
||||
builder.map_node(label.syntax().clone(), ast.lifetime().unwrap().syntax().clone());
|
||||
}
|
||||
builder.finish(&mut mapping);
|
||||
}
|
||||
|
||||
ast
|
||||
}
|
||||
}
|
||||
|
||||
// `ext` constructors
|
||||
|
||||
@@ -1380,9 +1380,9 @@ Default: `null`
|
||||
Override the command used for bench runnables.
|
||||
The first element of the array should be the program to execute (for example, `cargo`).
|
||||
|
||||
Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
|
||||
Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically
|
||||
replace the package name, target option (such as `--bin` or `--example`), the target name and
|
||||
the test name (name of test function or test mod path).
|
||||
the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).
|
||||
|
||||
|
||||
## rust-analyzer.runnables.command {#runnables.command}
|
||||
@@ -1399,9 +1399,9 @@ Default: `null`
|
||||
Override the command used for bench runnables.
|
||||
The first element of the array should be the program to execute (for example, `cargo`).
|
||||
|
||||
Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
|
||||
Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically
|
||||
replace the package name, target option (such as `--bin` or `--example`), the target name and
|
||||
the test name (name of test function or test mod path).
|
||||
the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).
|
||||
|
||||
|
||||
## rust-analyzer.runnables.extraArgs {#runnables.extraArgs}
|
||||
@@ -1444,9 +1444,9 @@ Default: `null`
|
||||
Override the command used for test runnables.
|
||||
The first element of the array should be the program to execute (for example, `cargo`).
|
||||
|
||||
Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically
|
||||
Use the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically
|
||||
replace the package name, target option (such as `--bin` or `--example`), the target name and
|
||||
the test name (name of test function or test mod path).
|
||||
the arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).
|
||||
|
||||
|
||||
## rust-analyzer.rustc.source {#rustc.source}
|
||||
|
||||
+6
-6
@@ -3697,9 +3697,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/flatted": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
|
||||
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
|
||||
"version": "3.4.2",
|
||||
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
|
||||
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
@@ -6719,9 +6719,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.21.3",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz",
|
||||
"integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==",
|
||||
"version": "6.24.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
|
||||
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
|
||||
@@ -2865,7 +2865,7 @@
|
||||
"title": "Runnables",
|
||||
"properties": {
|
||||
"rust-analyzer.runnables.bench.overrideCommand": {
|
||||
"markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).",
|
||||
"markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
@@ -2894,7 +2894,7 @@
|
||||
"title": "Runnables",
|
||||
"properties": {
|
||||
"rust-analyzer.runnables.doctest.overrideCommand": {
|
||||
"markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).",
|
||||
"markdownDescription": "Override the command used for bench runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
@@ -2948,7 +2948,7 @@
|
||||
"title": "Runnables",
|
||||
"properties": {
|
||||
"rust-analyzer.runnables.test.overrideCommand": {
|
||||
"markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${test_name}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe test name (name of test function or test mod path).",
|
||||
"markdownDescription": "Override the command used for test runnables.\nThe first element of the array should be the program to execute (for example, `cargo`).\n\nUse the placeholders `${package}`, `${target_arg}`, `${target}`, `${executable_args}` to dynamically\nreplace the package name, target option (such as `--bin` or `--example`), the target name and\nthe arguments passed to test binary args (includes `rust-analyzer.runnables.extraTestBinaryArgs`).",
|
||||
"default": null,
|
||||
"type": [
|
||||
"null",
|
||||
|
||||
Reference in New Issue
Block a user