fix: Fix move_bounds assists not working for lifetimes

This commit is contained in:
Lukas Wirth 2025-05-05 13:56:24 +02:00
parent cb18ead726
commit f9c83edf12
8 changed files with 194 additions and 45 deletions

View file

@ -1,5 +1,6 @@
//! Builtin derives.
use either::Either;
use intern::sym;
use itertools::{Itertools, izip};
use parser::SyntaxKind;
@ -1179,10 +1180,10 @@ fn coerce_pointee_expand(
};
new_predicates.push(
make::where_pred(
make::ty_path(make::path_from_segments(
Either::Right(make::ty_path(make::path_from_segments(
[make::path_segment(new_bounds_target)],
false,
)),
))),
new_bounds,
)
.clone_for_update(),
@ -1245,7 +1246,9 @@ fn coerce_pointee_expand(
substitute_type_in_bound(ty, &pointee_param_name.text(), ADDED_PARAM)
})
});
new_predicates.push(make::where_pred(pred_target, new_bounds).clone_for_update());
new_predicates.push(
make::where_pred(Either::Right(pred_target), new_bounds).clone_for_update(),
);
}
}
@ -1260,10 +1263,10 @@ fn coerce_pointee_expand(
// Find the `#[pointee]` parameter and add an `Unsize<__S>` bound to it.
where_clause.add_predicate(
make::where_pred(
make::ty_path(make::path_from_segments(
Either::Right(make::ty_path(make::path_from_segments(
[make::path_segment(make::name_ref(&pointee_param_name.text()))],
false,
)),
))),
[make::type_bound(make::ty_path(make::path_from_segments(
[
make::path_segment(make::name_ref("core")),

View file

@ -1,3 +1,4 @@
use either::Either;
use syntax::{
ast::{
self, AstNode, HasName, HasTypeBounds,
@ -30,10 +31,11 @@ pub(crate) fn move_bounds_to_where_clause(
) -> Option<()> {
let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
let mut type_params = type_param_list.type_or_const_params();
let mut type_params = type_param_list.generic_params();
if type_params.all(|p| match p {
ast::TypeOrConstParam::Type(t) => t.type_bound_list().is_none(),
ast::TypeOrConstParam::Const(_) => true,
ast::GenericParam::TypeParam(t) => t.type_bound_list().is_none(),
ast::GenericParam::LifetimeParam(l) => l.type_bound_list().is_none(),
ast::GenericParam::ConstParam(_) => true,
}) {
return None;
}
@ -53,20 +55,23 @@ pub(crate) fn move_bounds_to_where_clause(
match parent {
ast::Fn(it) => it.get_or_create_where_clause(),
ast::Trait(it) => it.get_or_create_where_clause(),
ast::TraitAlias(it) => it.get_or_create_where_clause(),
ast::Impl(it) => it.get_or_create_where_clause(),
ast::Enum(it) => it.get_or_create_where_clause(),
ast::Struct(it) => it.get_or_create_where_clause(),
ast::TypeAlias(it) => it.get_or_create_where_clause(),
_ => return,
}
};
for toc_param in type_param_list.type_or_const_params() {
let type_param = match toc_param {
ast::TypeOrConstParam::Type(x) => x,
ast::TypeOrConstParam::Const(_) => continue,
for generic_param in type_param_list.generic_params() {
let param: &dyn HasTypeBounds = match &generic_param {
ast::GenericParam::TypeParam(t) => t,
ast::GenericParam::LifetimeParam(l) => l,
ast::GenericParam::ConstParam(_) => continue,
};
if let Some(tbl) = type_param.type_bound_list() {
if let Some(predicate) = build_predicate(type_param) {
if let Some(tbl) = param.type_bound_list() {
if let Some(predicate) = build_predicate(generic_param) {
where_clause.add_predicate(predicate)
}
tbl.remove()
@ -76,9 +81,23 @@ pub(crate) fn move_bounds_to_where_clause(
)
}
fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
let path = make::ext::ident_path(&param.name()?.syntax().to_string());
let predicate = make::where_pred(make::ty_path(path), param.type_bound_list()?.bounds());
fn build_predicate(param: ast::GenericParam) -> Option<ast::WherePred> {
let target = match &param {
ast::GenericParam::TypeParam(t) => {
Either::Right(make::ty_path(make::ext::ident_path(&t.name()?.to_string())))
}
ast::GenericParam::LifetimeParam(l) => Either::Left(l.lifetime()?),
ast::GenericParam::ConstParam(_) => return None,
};
let predicate = make::where_pred(
target,
match param {
ast::GenericParam::TypeParam(t) => t.type_bound_list()?,
ast::GenericParam::LifetimeParam(l) => l.type_bound_list()?,
ast::GenericParam::ConstParam(_) => return None,
}
.bounds(),
);
Some(predicate.clone_for_update())
}
@ -123,4 +142,13 @@ mod tests {
r#"struct Pair<T>(T, T) where T: u32;"#,
);
}
#[test]
fn move_bounds_to_where_clause_trait() {
check_assist(
move_bounds_to_where_clause,
r#"trait T<'a: 'static, $0T: u32> {}"#,
r#"trait T<'a, T> where 'a: 'static, T: u32 {}"#,
);
}
}

View file

@ -168,16 +168,38 @@ impl server::TokenStream for RaSpanServer {
}
bridge::TokenTree::Literal(literal) => {
let literal = tt::Literal {
symbol: literal.symbol,
suffix: literal.suffix,
span: literal.span,
kind: literal_kind_to_internal(literal.kind),
};
let token_trees =
if let Some((_minus, symbol)) = literal.symbol.as_str().split_once('-') {
let punct = tt::Punct {
spacing: tt::Spacing::Alone,
span: literal.span,
char: '-' as char,
};
let leaf: tt::Leaf = tt::Leaf::from(punct);
let minus_tree = tt::TokenTree::from(leaf);
let leaf: tt::Leaf = tt::Leaf::from(literal);
let tree = tt::TokenTree::from(leaf);
TokenStream { token_trees: vec![tree] }
let literal = tt::Literal {
symbol: Symbol::intern(symbol),
suffix: literal.suffix,
span: literal.span,
kind: literal_kind_to_internal(literal.kind),
};
let leaf: tt::Leaf = tt::Leaf::from(literal);
let tree = tt::TokenTree::from(leaf);
vec![minus_tree, tree]
} else {
let literal = tt::Literal {
symbol: literal.symbol,
suffix: literal.suffix,
span: literal.span,
kind: literal_kind_to_internal(literal.kind),
};
let leaf: tt::Leaf = tt::Leaf::from(literal);
let tree = tt::TokenTree::from(leaf);
vec![tree]
};
TokenStream { token_trees }
}
bridge::TokenTree::Punct(p) => {

View file

@ -153,16 +153,38 @@ impl server::TokenStream for TokenIdServer {
}
bridge::TokenTree::Literal(literal) => {
let literal = Literal {
symbol: literal.symbol,
suffix: literal.suffix,
span: literal.span,
kind: literal_kind_to_internal(literal.kind),
};
let token_trees =
if let Some((_minus, symbol)) = literal.symbol.as_str().split_once('-') {
let punct = tt::Punct {
spacing: tt::Spacing::Alone,
span: literal.span,
char: '-' as char,
};
let leaf: tt::Leaf = tt::Leaf::from(punct);
let minus_tree = tt::TokenTree::from(leaf);
let leaf = tt::Leaf::from(literal);
let tree = TokenTree::from(leaf);
TokenStream { token_trees: vec![tree] }
let literal = Literal {
symbol: Symbol::intern(symbol),
suffix: literal.suffix,
span: literal.span,
kind: literal_kind_to_internal(literal.kind),
};
let leaf: tt::Leaf = tt::Leaf::from(literal);
let tree = tt::TokenTree::from(leaf);
vec![minus_tree, tree]
} else {
let literal = Literal {
symbol: literal.symbol,
suffix: literal.suffix,
span: literal.span,
kind: literal_kind_to_internal(literal.kind),
};
let leaf: tt::Leaf = tt::Leaf::from(literal);
let tree = tt::TokenTree::from(leaf);
vec![tree]
};
TokenStream { token_trees }
}
bridge::TokenTree::Punct(p) => {

View file

@ -68,6 +68,11 @@ impl<S: Copy> TokenStream<S> {
span: ident.span,
}))
}
// Note, we do not have to assemble our `-` punct and literal split into a single
// negative bridge literal here. As the proc-macro docs state
// > Literals created from negative numbers might not survive round-trips through
// > TokenStream or strings and may be broken into two tokens (- and positive
// > literal).
tt::TokenTree::Leaf(tt::Leaf::Literal(lit)) => {
result.push(bridge::TokenTree::Literal(bridge::Literal {
span: lit.span,

View file

@ -248,13 +248,17 @@ fn test_fn_like_mk_literals() {
LITERAL Str string 1
LITERAL CStr cstring 1
LITERAL Float 3.14f64 1
LITERAL Float -3.14f64 1
PUNCH - [alone] 1
LITERAL Float 3.14f64 1
LITERAL Float 3.14 1
PUNCH - [alone] 1
LITERAL Float 3.14 1
LITERAL Float -3.14 1
LITERAL Integer 123i64 1
LITERAL Integer -123i64 1
PUNCH - [alone] 1
LITERAL Integer 123i64 1
LITERAL Integer 123 1
LITERAL Integer -123 1"#]],
PUNCH - [alone] 1
LITERAL Integer 123 1"#]],
expect![[r#"
SUBTREE $$ 42:2@0..100#ROOT2024 42:2@0..100#ROOT2024
@ -266,13 +270,17 @@ fn test_fn_like_mk_literals() {
LITERAL Str string 42:2@0..100#ROOT2024
LITERAL CStr cstring 42:2@0..100#ROOT2024
LITERAL Float 3.14f64 42:2@0..100#ROOT2024
LITERAL Float -3.14f64 42:2@0..100#ROOT2024
PUNCH - [alone] 42:2@0..100#ROOT2024
LITERAL Float 3.14f64 42:2@0..100#ROOT2024
LITERAL Float 3.14 42:2@0..100#ROOT2024
PUNCH - [alone] 42:2@0..100#ROOT2024
LITERAL Float 3.14 42:2@0..100#ROOT2024
LITERAL Float -3.14 42:2@0..100#ROOT2024
LITERAL Integer 123i64 42:2@0..100#ROOT2024
LITERAL Integer -123i64 42:2@0..100#ROOT2024
PUNCH - [alone] 42:2@0..100#ROOT2024
LITERAL Integer 123i64 42:2@0..100#ROOT2024
LITERAL Integer 123 42:2@0..100#ROOT2024
LITERAL Integer -123 42:2@0..100#ROOT2024"#]],
PUNCH - [alone] 42:2@0..100#ROOT2024
LITERAL Integer 123 42:2@0..100#ROOT2024"#]],
);
}
@ -400,7 +408,6 @@ fn test_fn_like_macro_clone_literals() {
);
}
#[test]
fn test_fn_like_macro_negative_literals() {
assert_expand(

View file

@ -109,6 +109,67 @@ impl GenericParamsOwnerEdit for ast::Trait {
}
}
impl GenericParamsOwnerEdit for ast::TraitAlias {
fn get_or_create_generic_param_list(&self) -> ast::GenericParamList {
match self.generic_param_list() {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
Position::after(name.syntax)
} else if let Some(trait_token) = self.trait_token() {
Position::after(trait_token)
} else {
Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
}
}
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = match self.semicolon_token() {
Some(tok) => Position::before(tok),
None => Position::last_child_of(self.syntax()),
};
create_where_clause(position);
}
self.where_clause().unwrap()
}
}
impl GenericParamsOwnerEdit for ast::TypeAlias {
fn get_or_create_generic_param_list(&self) -> ast::GenericParamList {
match self.generic_param_list() {
Some(it) => it,
None => {
let position = if let Some(name) = self.name() {
Position::after(name.syntax)
} else if let Some(trait_token) = self.type_token() {
Position::after(trait_token)
} else {
Position::last_child_of(self.syntax())
};
create_generic_param_list(position)
}
}
}
fn get_or_create_where_clause(&self) -> ast::WhereClause {
if self.where_clause().is_none() {
let position = match self.eq_token() {
Some(tok) => Position::before(tok),
None => match self.semicolon_token() {
Some(tok) => Position::before(tok),
None => Position::last_child_of(self.syntax()),
},
};
create_where_clause(position);
}
self.where_clause().unwrap()
}
}
impl GenericParamsOwnerEdit for ast::Struct {
fn get_or_create_generic_param_list(&self) -> ast::GenericParamList {
match self.generic_param_list() {

View file

@ -13,6 +13,7 @@
mod quote;
use either::Either;
use itertools::Itertools;
use parser::{Edition, T};
use rowan::NodeOrToken;
@ -881,7 +882,7 @@ pub fn match_arm_list(arms: impl IntoIterator<Item = ast::MatchArm>) -> ast::Mat
}
pub fn where_pred(
path: ast::Type,
path: Either<ast::Lifetime, ast::Type>,
bounds: impl IntoIterator<Item = ast::TypeBound>,
) -> ast::WherePred {
let bounds = bounds.into_iter().join(" + ");