Auto merge of #15874 - DropDemBits:structured-snippet-migrate-4, r=Veykril

internal: Migrate assists to the structured snippet API, part 4

Continuing from #15260

Migrates the following assists:
- `add_turbo_fish`
- `add_type_ascription`
- `destructure_tuple_binding`
- `destructure_tuple_binding_in_subpattern`

I did this a while ago, but forgot to make a PR for the changes until now. 😅
This commit is contained in:
bors 2023-11-15 09:54:45 +00:00
commit 535eb0da9d
5 changed files with 433 additions and 141 deletions

View file

@ -3,18 +3,17 @@
use std::iter::{empty, successors};
use parser::{SyntaxKind, T};
use rowan::SyntaxElement;
use crate::{
algo::{self, neighbor},
ast::{self, edit::IndentLevel, make, HasGenericParams},
ted::{self, Position},
AstNode, AstToken, Direction,
AstNode, AstToken, Direction, SyntaxElement,
SyntaxKind::{ATTR, COMMENT, WHITESPACE},
SyntaxNode, SyntaxToken,
};
use super::HasName;
use super::{HasArgList, HasName};
pub trait GenericParamsOwnerEdit: ast::HasGenericParams {
fn get_or_create_generic_param_list(&self) -> ast::GenericParamList;
@ -362,6 +361,24 @@ impl ast::PathSegment {
}
}
impl ast::MethodCallExpr {
pub fn get_or_create_generic_arg_list(&self) -> ast::GenericArgList {
if self.generic_arg_list().is_none() {
let generic_arg_list = make::turbofish_generic_arg_list(empty()).clone_for_update();
if let Some(arg_list) = self.arg_list() {
ted::insert_raw(
ted::Position::before(arg_list.syntax()),
generic_arg_list.syntax(),
);
} else {
ted::append_child(self.syntax(), generic_arg_list.syntax());
}
}
self.generic_arg_list().unwrap()
}
}
impl Removable for ast::UseTree {
fn remove(&self) {
for dir in [Direction::Next, Direction::Prev] {
@ -559,7 +576,7 @@ impl ast::AssocItemList {
None => (IndentLevel::single(), Position::last_child_of(self.syntax()), "\n"),
},
};
let elements: Vec<SyntaxElement<_>> = vec![
let elements: Vec<SyntaxElement> = vec![
make::tokens::whitespace(&format!("{whitespace}{indent}")).into(),
item.syntax().clone().into(),
];
@ -629,6 +646,50 @@ impl ast::MatchArmList {
}
}
impl ast::LetStmt {
pub fn set_ty(&self, ty: Option<ast::Type>) {
match ty {
None => {
if let Some(colon_token) = self.colon_token() {
ted::remove(colon_token);
}
if let Some(existing_ty) = self.ty() {
if let Some(sibling) = existing_ty.syntax().prev_sibling_or_token() {
if sibling.kind() == SyntaxKind::WHITESPACE {
ted::remove(sibling);
}
}
ted::remove(existing_ty.syntax());
}
// Remove any trailing ws
if let Some(last) = self.syntax().last_token().filter(|it| it.kind() == WHITESPACE)
{
last.detach();
}
}
Some(new_ty) => {
if self.colon_token().is_none() {
ted::insert_raw(
Position::after(
self.pat().expect("let stmt should have a pattern").syntax(),
),
make::token(T![:]),
);
}
if let Some(old_ty) = self.ty() {
ted::replace(old_ty.syntax(), new_ty.syntax());
} else {
ted::insert(Position::after(self.colon_token().unwrap()), new_ty.syntax());
}
}
}
}
}
impl ast::RecordExprFieldList {
pub fn add_field(&self, field: ast::RecordExprField) {
let is_multiline = self.syntax().text().contains_char('\n');
@ -753,7 +814,7 @@ impl ast::VariantList {
None => (IndentLevel::single(), Position::last_child_of(self.syntax())),
},
};
let elements: Vec<SyntaxElement<_>> = vec![
let elements: Vec<SyntaxElement> = vec![
make::tokens::whitespace(&format!("{}{indent}", "\n")).into(),
variant.syntax().clone().into(),
ast::make::token(T![,]).into(),
@ -788,6 +849,53 @@ fn normalize_ws_between_braces(node: &SyntaxNode) -> Option<()> {
Some(())
}
impl ast::IdentPat {
pub fn set_pat(&self, pat: Option<ast::Pat>) {
match pat {
None => {
if let Some(at_token) = self.at_token() {
// Remove `@ Pat`
let start = at_token.clone().into();
let end = self
.pat()
.map(|it| it.syntax().clone().into())
.unwrap_or_else(|| at_token.into());
ted::remove_all(start..=end);
// Remove any trailing ws
if let Some(last) =
self.syntax().last_token().filter(|it| it.kind() == WHITESPACE)
{
last.detach();
}
}
}
Some(pat) => {
if let Some(old_pat) = self.pat() {
// Replace existing pattern
ted::replace(old_pat.syntax(), pat.syntax())
} else if let Some(at_token) = self.at_token() {
// Have an `@` token but not a pattern yet
ted::insert(ted::Position::after(at_token), pat.syntax());
} else {
// Don't have an `@`, should have a name
let name = self.name().unwrap();
ted::insert_all(
ted::Position::after(name.syntax()),
vec![
make::token(T![@]).into(),
make::tokens::single_space().into(),
pat.syntax().clone().into(),
],
)
}
}
}
}
}
pub trait HasVisibilityEdit: ast::HasVisibility {
fn set_visibility(&self, visbility: ast::Visibility) {
match self.visibility() {
@ -889,6 +997,65 @@ mod tests {
);
}
#[test]
fn test_ident_pat_set_pat() {
#[track_caller]
fn check(before: &str, expected: &str, pat: Option<ast::Pat>) {
let pat = pat.map(|it| it.clone_for_update());
let ident_pat = ast_mut_from_text::<ast::IdentPat>(&format!("fn f() {{ {before} }}"));
ident_pat.set_pat(pat);
let after = ast_mut_from_text::<ast::IdentPat>(&format!("fn f() {{ {expected} }}"));
assert_eq!(ident_pat.to_string(), after.to_string());
}
// replacing
check("let a @ _;", "let a @ ();", Some(make::tuple_pat([]).into()));
// note: no trailing semicolon is added for the below tests since it
// seems to be picked up by the ident pat during error recovery?
// adding
check("let a ", "let a @ ()", Some(make::tuple_pat([]).into()));
check("let a @ ", "let a @ ()", Some(make::tuple_pat([]).into()));
// removing
check("let a @ ()", "let a", None);
check("let a @ ", "let a", None);
}
#[test]
fn test_let_stmt_set_ty() {
#[track_caller]
fn check(before: &str, expected: &str, ty: Option<ast::Type>) {
let ty = ty.map(|it| it.clone_for_update());
let let_stmt = ast_mut_from_text::<ast::LetStmt>(&format!("fn f() {{ {before} }}"));
let_stmt.set_ty(ty);
let after = ast_mut_from_text::<ast::LetStmt>(&format!("fn f() {{ {expected} }}"));
assert_eq!(let_stmt.to_string(), after.to_string(), "{let_stmt:#?}\n!=\n{after:#?}");
}
// adding
check("let a;", "let a: ();", Some(make::ty_tuple([])));
// no semicolon due to it being eaten during error recovery
check("let a:", "let a: ()", Some(make::ty_tuple([])));
// replacing
check("let a: u8;", "let a: ();", Some(make::ty_tuple([])));
check("let a: u8 = 3;", "let a: () = 3;", Some(make::ty_tuple([])));
check("let a: = 3;", "let a: () = 3;", Some(make::ty_tuple([])));
// removing
check("let a: u8;", "let a;", None);
check("let a:;", "let a;", None);
check("let a: u8 = 3;", "let a = 3;", None);
check("let a: = 3;", "let a = 3;", None);
}
#[test]
fn add_variant_to_empty_enum() {
let variant = make::variant(make::name("Bar"), None).clone_for_update();

View file

@ -941,6 +941,13 @@ pub fn lifetime_arg(lifetime: ast::Lifetime) -> ast::LifetimeArg {
ast_from_text(&format!("const S: T<{lifetime}> = ();"))
}
pub fn turbofish_generic_arg_list(
args: impl IntoIterator<Item = ast::GenericArg>,
) -> ast::GenericArgList {
let args = args.into_iter().join(", ");
ast_from_text(&format!("const S: T::<{args}> = ();"))
}
pub(crate) fn generic_arg_list(
args: impl IntoIterator<Item = ast::GenericArg>,
) -> ast::GenericArgList {
@ -1126,7 +1133,7 @@ pub mod tokens {
pub(super) static SOURCE_FILE: Lazy<Parse<SourceFile>> = Lazy::new(|| {
SourceFile::parse(
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p)\n;\n\n",
"const C: <()>::Item = ( true && true , true || true , 1 != 1, 2 == 2, 3 < 3, 4 <= 4, 5 > 5, 6 >= 6, !true, *p, &p , &mut p, { let a @ [] })\n;\n\n",
)
});