Skip to content

Commit

Permalink
the generate_delegate_trait assist: fixed generic parameters handling
Browse files Browse the repository at this point in the history
  • Loading branch information
ponyii committed Aug 27, 2023
1 parent fec5ff9 commit d42e56d
Show file tree
Hide file tree
Showing 6 changed files with 170 additions and 26 deletions.
24 changes: 24 additions & 0 deletions crates/hir-def/src/item_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,33 @@ pub struct TraitAlias {
pub ast_id: FileAstId<ast::TraitAlias>,
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct GenericParamGroups {
// pub target_trait: Option<Vec<String>>,
pub self_ty: Option<Vec<String>>,
}

impl GenericParamGroups {
fn from_impl(impl_: &ast::Impl) -> Self {
Self {
// target_trait: Self::get_arg_list(impl_.trait_()),
self_ty: Self::get_arg_list(impl_.self_ty()),
}
}

fn get_arg_list(ty: Option<ast::Type>) -> Option<Vec<String>> {
let list = match ty? {
ast::Type::PathType(p) => p.path()?.segment()?.generic_arg_list(),
_ => None,
};
Some(list?.generic_args().map(|arg| arg.syntax().text().to_string()).collect())
}
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Impl {
pub generic_params: Interned<GenericParams>,
pub generic_param_groups: GenericParamGroups,
pub target_trait: Option<Interned<TraitRef>>,
pub self_ty: Interned<TypeRef>,
pub is_negative: bool,
Expand Down
10 changes: 9 additions & 1 deletion crates/hir-def/src/item_tree/lower.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,15 @@ impl<'a> Ctx<'a> {
})
.collect();
let ast_id = self.source_ast_id_map.ast_id(impl_def);
let res = Impl { generic_params, target_trait, self_ty, is_negative, items, ast_id };
let res = Impl {
generic_params,
generic_param_groups: GenericParamGroups::from_impl(impl_def),
target_trait,
self_ty,
is_negative,
items,
ast_id,
};
Some(id(self.data().impls.alloc(res)))
}

Expand Down
11 changes: 9 additions & 2 deletions crates/hir-def/src/item_tree/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,15 @@ impl Printer<'_> {
wln!(self);
}
ModItem::Impl(it) => {
let Impl { target_trait, self_ty, is_negative, items, generic_params, ast_id: _ } =
&self.tree[it];
let Impl {
target_trait,
generic_param_groups: _,
self_ty,
is_negative,
items,
generic_params,
ast_id: _,
} = &self.tree[it];
w!(self, "impl");
self.print_generic_params(generic_params);
w!(self, " ");
Expand Down
16 changes: 16 additions & 0 deletions crates/hir/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pub mod symbols;

mod display;

use rustc_hash::FxHashMap;
use std::{iter, ops::ControlFlow};

use arrayvec::ArrayVec;
Expand Down Expand Up @@ -3354,6 +3355,21 @@ impl Impl {
let src = self.source(db)?;
src.file_id.as_builtin_derive_attr_node(db.upcast())
}

pub fn filter_self_ty_params(
&self,
params: &Vec<TypeOrConstParam>,
db: &dyn HirDatabase,
) -> Option<Vec<TypeOrConstParam>> {
let id = self.id.lookup(db.upcast()).id;
let tree = id.item_tree(db.upcast());
let names = (&tree[id.value].generic_param_groups.self_ty).as_ref()?;
let name_to_param: FxHashMap<_, _> = params
.iter()
.map(|param| (param.name(db).display(db.upcast()).to_string(), param))
.collect();
Some(names.iter().map(|n| *name_to_param[n]).collect())
}
}

#[derive(Clone, PartialEq, Eq, Debug, Hash)]
Expand Down
79 changes: 65 additions & 14 deletions crates/ide-assists/src/handlers/generate_delegate_trait.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,9 @@ fn generate_impl(
let source: ast::Impl;
let genpar: Option<ast::GenericParamList>;
let db = ctx.db();
let base_path = make::path_from_text(&field_ty.to_string().as_str());
let s_path = make::ext::ident_path(&strukt.name.to_string());

// FIXME: deduplicate the code below
match delegee {
Delegee::Bound(delegee) => {
let in_file = ctx.sema.source(delegee.0.to_owned())?;
Expand Down Expand Up @@ -275,7 +275,7 @@ fn generate_impl(
// Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
let qualified_path_type = make::path_from_text(&format!(
"<{} as {}{}>",
base_path.to_string(),
field_ty.to_string(),
delegee.0.name(db).to_smol_str(),
gen_args.to_string()
));
Expand Down Expand Up @@ -319,19 +319,15 @@ fn generate_impl(
None,
)
.clone_for_update();
genpar = source.generic_param_list();
let delegate_assoc_items = delegate.get_or_create_assoc_item_list();
let gen_args: String =
genpar.map_or_else(String::new, |params| params.to_generic_args().to_string());

// Goto link : https://doc.rust-lang.org/reference/paths.html#qualified-paths
let qualified_path_type = make::path_from_text(&format!(
"<{} as {}{}>",
base_path.to_string().as_str(),
delegee.0.name(db).to_smol_str(),
gen_args.to_string().as_str()
"<{} as {}>",
field_ty.to_string(),
source.trait_().map_or_else(String::new, |t| t.syntax().text().to_string())
));

let delegate_assoc_items = delegate.get_or_create_assoc_item_list();
source
.get_or_create_assoc_item_list()
.assoc_items()
Expand All @@ -343,11 +339,16 @@ fn generate_impl(
}
});

let target = ctx.sema.scope(strukt.strukt.syntax())?;
let source = ctx.sema.scope(source.syntax())?;

let target_scope = ctx.sema.scope(strukt.strukt.syntax())?;
let source_scope = match source.get_or_create_assoc_item_list().assoc_items().next() {
// Scopes of all assoc items are identical, so one can be used for them all;
// moreover, the same scope can be used with the `impl` signature to resolve
// generic params, though it's a kind of cheating.
Some(item) => ctx.sema.scope(&item.syntax())?,
None => ctx.sema.scope(source.syntax())?,
};
let transform =
PathTransform::trait_impl(&target, &source, delegee.0, delegate.clone());
PathTransform::delegate_trait(&target_scope, &source_scope, delegee.1, &field_ty);
transform.apply(&delegate.syntax());
}
}
Expand Down Expand Up @@ -1045,6 +1046,56 @@ impl some_module::SomeTrait for B {
fn method_(&mut self) -> bool {
<some_module::A as some_module::SomeTrait>::method_( &mut self.a )
}
}"#,
)
}

#[test]
fn test_generic_substitution() {
// FIXME: the `impl ... ` line still has invalid params
check_assist(
generate_delegate_trait,
r#"
struct S(Foo<i32, bool>$0);
struct Foo<T, V> {
one: T,
another: V
}
trait Trait<T, V> {
type Ty;
fn f(self) -> Self::Ty;
}
impl<A, B, C> Trait<A, B> for Foo<C, B> {
type Ty = B;
fn f(self) -> Self::Ty {
self.another
}
}"#,
r#"
struct S(Foo<i32, bool>);
impl<A, B, C> Trait<A, bool, i32> for S {
type Ty = <Foo<i32, bool> as Trait<A, bool>>::Ty;
fn f(self) -> Self::Ty {
<Foo<i32, bool> as Trait<A, bool>>::f( self.0 )
}
}
struct Foo<T, V> {
one: T,
another: V
}
trait Trait<T, V> {
type Ty;
fn f(self) -> Self::Ty;
}
impl<A, B, C> Trait<A, B> for Foo<C, B> {
type Ty = B;
fn f(self) -> Self::Ty {
self.another
}
}"#,
)
}
Expand Down
56 changes: 47 additions & 9 deletions crates/ide-db/src/path_transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
use crate::helpers::mod_path_to_ast;
use either::Either;
use hir::{AsAssocItem, HirDisplay, SemanticsScope};
use hir::{AsAssocItem, GenericDef, HirDisplay, SemanticsScope};
use rustc_hash::FxHashMap;
use syntax::{
ast::{self, make, AstNode},
ted, SyntaxNode,
};

enum ParamsToSubstitute {
All,
SelfTyOnly,
}

#[derive(Default)]
struct AstSubsts {
types_and_consts: Vec<TypeOrConst>,
Expand Down Expand Up @@ -48,6 +53,7 @@ type DefaultedParam = Either<hir::TypeParam, hir::ConstParam>;
/// ```
pub struct PathTransform<'a> {
generic_def: Option<hir::GenericDef>,
params_to_substitute: ParamsToSubstitute,
substs: AstSubsts,
target_scope: &'a SemanticsScope<'a>,
source_scope: &'a SemanticsScope<'a>,
Expand All @@ -63,8 +69,24 @@ impl<'a> PathTransform<'a> {
PathTransform {
source_scope,
target_scope,
params_to_substitute: ParamsToSubstitute::All,
generic_def: Some(trait_.into()),
substs: get_syntactic_substs(impl_).unwrap_or_default(),
substs: get_substs_from_impl(impl_).unwrap_or_default(),
}
}

pub fn delegate_trait(
target_scope: &'a SemanticsScope<'a>,
source_scope: &'a SemanticsScope<'a>,
source: hir::Impl,
type_with_trait: &ast::Type,
) -> PathTransform<'a> {
PathTransform {
source_scope,
target_scope,
params_to_substitute: ParamsToSubstitute::SelfTyOnly,
generic_def: Some(source.into()),
substs: get_substs_from_ty(type_with_trait).unwrap_or_default(),
}
}

Expand All @@ -77,6 +99,7 @@ impl<'a> PathTransform<'a> {
PathTransform {
source_scope,
target_scope,
params_to_substitute: ParamsToSubstitute::All,
generic_def: Some(function.into()),
substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(),
}
Expand All @@ -89,6 +112,7 @@ impl<'a> PathTransform<'a> {
PathTransform {
source_scope,
target_scope,
params_to_substitute: ParamsToSubstitute::All,
generic_def: None,
substs: AstSubsts::default(),
}
Expand Down Expand Up @@ -117,9 +141,20 @@ impl<'a> PathTransform<'a> {
let mut type_substs: FxHashMap<hir::TypeParam, ast::Type> = Default::default();
let mut const_substs: FxHashMap<hir::ConstParam, SyntaxNode> = Default::default();
let mut defaulted_params: Vec<DefaultedParam> = Default::default();
self.generic_def
.into_iter()
.flat_map(|it| it.type_params(db))

// FIXME: get parameter list within a separate function
// FIXME: get default values (`ParamsToSubstitute::SelfTyOnly` case)
let mut params = self.generic_def.and_then(|it| Some(it.type_params(db)));
match (&self.params_to_substitute, &self.generic_def, &params) {
(ParamsToSubstitute::SelfTyOnly, Some(GenericDef::Impl(impl_)), Some(all_params)) => {
params = impl_.filter_self_ty_params(all_params, db);
}
_ => (),
}
let params = params.unwrap_or(vec![]);

params
.iter()
.skip(skip)
// The actual list of trait type parameters may be longer than the one
// used in the `impl` block due to trailing default type parameters.
Expand Down Expand Up @@ -338,16 +373,19 @@ impl Ctx<'_> {
}
}

fn get_substs_from_impl(impl_def: ast::Impl) -> Option<AstSubsts> {
let target_trait = impl_def.trait_()?;
get_substs_from_ty(&target_trait)
}

// FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
// trait ref, and then go from the types in the substs back to the syntax).
fn get_syntactic_substs(impl_def: ast::Impl) -> Option<AstSubsts> {
let target_trait = impl_def.trait_()?;
let path_type = match target_trait {
fn get_substs_from_ty(ty: &ast::Type) -> Option<AstSubsts> {
let path_type = match ty {
ast::Type::PathType(path) => path,
_ => return None,
};
let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;

get_type_args_from_arg_list(generic_arg_list)
}

Expand Down

0 comments on commit d42e56d

Please sign in to comment.