Auto merge of #14065 - lowr:patch/generate-generic-function, r=Veykril

Support generic function in `generate_function` assist

Part of #3639

This PR adds support for generic function generation in `generate_function` assist. Now the assist looks for generic parameters and trait bounds in scope, filters out irrelevant ones, and generates new function with them.

See `fn_generic_params()` for the outline of the procedure, and see comments on `filter_unnecessary_bounds()` for criteria for filtering. I think it's good criteria for most cases, but I'm open to opinions and suggestions.

The diff is pretty big, but it should run in linear time w.r.t. the number of nodes we operate on and should be fast enough.

Some notes:
- When we generate function in an existing impl, generic parameters may cause name conflict. While we can detect the conflict and rename conflicting params, I didn't find it worthwhile mainly because it's really easy to resolve on IDE: use Rename functionality.
- I've implemented graph structure myself, because we don't have graph library as a dependency and we only need the simplest one.
  - Although `petgraph` is in our dependency graph and I was initially looking to use it, we don't actually depend on it AFAICT since it's only used in chalk's specialization graph handling, which we don't use. I'd be happy to replace my implementation with `petgraph` if it's okay to use it though.
- There are some caveats that I consider out of scope of this PR. See FIXME notes on added tests.
This commit is contained in:
bors 2023-02-02 09:06:22 +00:00
commit eeceba7480
7 changed files with 980 additions and 64 deletions

View File

@ -39,11 +39,13 @@ use std::sync::Arc;
use chalk_ir::{
fold::{Shift, TypeFoldable},
interner::HasInterner,
NoSolution,
visit::{TypeSuperVisitable, TypeVisitable, TypeVisitor},
NoSolution, TyData,
};
use hir_def::{expr::ExprId, type_ref::Rawness, TypeOrConstParamId};
use hir_expand::name;
use itertools::Either;
use rustc_hash::FxHashSet;
use traits::FnTrait;
use utils::Generics;
@ -562,3 +564,68 @@ pub fn callable_sig_from_fnonce(
Some(CallableSig::from_params_and_return(params, ret_ty, false, Safety::Safe))
}
struct PlaceholderCollector<'db> {
db: &'db dyn HirDatabase,
placeholders: FxHashSet<TypeOrConstParamId>,
}
impl PlaceholderCollector<'_> {
fn collect(&mut self, idx: PlaceholderIndex) {
let id = from_placeholder_idx(self.db, idx);
self.placeholders.insert(id);
}
}
impl TypeVisitor<Interner> for PlaceholderCollector<'_> {
type BreakTy = ();
fn as_dyn(&mut self) -> &mut dyn TypeVisitor<Interner, BreakTy = Self::BreakTy> {
self
}
fn interner(&self) -> Interner {
Interner
}
fn visit_ty(
&mut self,
ty: &Ty,
outer_binder: DebruijnIndex,
) -> std::ops::ControlFlow<Self::BreakTy> {
let has_placeholder_bits = TypeFlags::HAS_TY_PLACEHOLDER | TypeFlags::HAS_CT_PLACEHOLDER;
let TyData { kind, flags } = ty.data(Interner);
if let TyKind::Placeholder(idx) = kind {
self.collect(*idx);
} else if flags.intersects(has_placeholder_bits) {
return ty.super_visit_with(self, outer_binder);
} else {
// Fast path: don't visit inner types (e.g. generic arguments) when `flags` indicate
// that there are no placeholders.
}
std::ops::ControlFlow::Continue(())
}
fn visit_const(
&mut self,
constant: &chalk_ir::Const<Interner>,
_outer_binder: DebruijnIndex,
) -> std::ops::ControlFlow<Self::BreakTy> {
if let chalk_ir::ConstValue::Placeholder(idx) = constant.data(Interner).value {
self.collect(idx);
}
std::ops::ControlFlow::Continue(())
}
}
/// Returns unique placeholders for types and consts contained in `value`.
pub fn collect_placeholders<T>(value: &T, db: &dyn HirDatabase) -> Vec<TypeOrConstParamId>
where
T: ?Sized + TypeVisitable<Interner>,
{
let mut collector = PlaceholderCollector { db, placeholders: FxHashSet::default() };
value.visit_with(&mut collector, DebruijnIndex::INNERMOST);
collector.placeholders.into_iter().collect()
}

View File

@ -2165,6 +2165,16 @@ impl AsAssocItem for ModuleDef {
}
}
}
impl AsAssocItem for DefWithBody {
fn as_assoc_item(self, db: &dyn HirDatabase) -> Option<AssocItem> {
match self {
DefWithBody::Function(it) => it.as_assoc_item(db),
DefWithBody::Const(it) => it.as_assoc_item(db),
DefWithBody::Static(_) | DefWithBody::Variant(_) => None,
}
}
}
fn as_assoc_item<ID, DEF, CTOR, AST>(db: &dyn HirDatabase, ctor: CTOR, id: ID) -> Option<AssocItem>
where
ID: Lookup<Data = AssocItemLoc<AST>>,
@ -2565,6 +2575,14 @@ impl GenericParam {
GenericParam::LifetimeParam(it) => it.name(db),
}
}
pub fn parent(self) -> GenericDef {
match self {
GenericParam::TypeParam(it) => it.id.parent().into(),
GenericParam::ConstParam(it) => it.id.parent().into(),
GenericParam::LifetimeParam(it) => it.id.parent.into(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -3144,15 +3162,15 @@ impl Type {
}
pub fn is_closure(&self) -> bool {
matches!(&self.ty.kind(Interner), TyKind::Closure { .. })
matches!(self.ty.kind(Interner), TyKind::Closure { .. })
}
pub fn is_fn(&self) -> bool {
matches!(&self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
matches!(self.ty.kind(Interner), TyKind::FnDef(..) | TyKind::Function { .. })
}
pub fn is_array(&self) -> bool {
matches!(&self.ty.kind(Interner), TyKind::Array(..))
matches!(self.ty.kind(Interner), TyKind::Array(..))
}
pub fn is_packed(&self, db: &dyn HirDatabase) -> bool {
@ -3169,7 +3187,7 @@ impl Type {
}
pub fn is_raw_ptr(&self) -> bool {
matches!(&self.ty.kind(Interner), TyKind::Raw(..))
matches!(self.ty.kind(Interner), TyKind::Raw(..))
}
pub fn contains_unknown(&self) -> bool {
@ -3604,6 +3622,14 @@ impl Type {
_ => None,
}
}
/// Returns unique `GenericParam`s contained in this type.
pub fn generic_params(&self, db: &dyn HirDatabase) -> FxHashSet<GenericParam> {
hir_ty::collect_placeholders(&self.ty, db)
.into_iter()
.map(|id| TypeOrConstParam { id }.split(db).either_into())
.collect()
}
}
#[derive(Debug)]

View File

@ -1319,10 +1319,7 @@ impl<'db> SemanticsImpl<'db> {
let _p = profile::span("Semantics::analyze_impl");
let node = self.find_file(node);
let container = match self.with_ctx(|ctx| ctx.find_container(node)) {
Some(it) => it,
None => return None,
};
let container = self.with_ctx(|ctx| ctx.find_container(node))?;
let resolver = match container {
ChildContainer::DefWithBodyId(def) => {
@ -1582,7 +1579,7 @@ fn find_root(node: &SyntaxNode) -> SyntaxNode {
node.ancestors().last().unwrap()
}
/// `SemanticScope` encapsulates the notion of a scope (the set of visible
/// `SemanticsScope` encapsulates the notion of a scope (the set of visible
/// names) at a particular program point.
///
/// It is a bit tricky, as scopes do not really exist inside the compiler.

View File

@ -109,7 +109,7 @@ pub(crate) fn generate_delegate_methods(acc: &mut Assists, ctx: &AssistContext<'
let tail_expr_finished =
if is_async { make::expr_await(tail_expr) } else { tail_expr };
let body = make::block_expr([], Some(tail_expr_finished));
let f = make::fn_(vis, name, type_params, params, body, ret_type, is_async)
let f = make::fn_(vis, name, type_params, None, params, body, ret_type, is_async)
.indent(ast::edit::IndentLevel(1))
.clone_for_update();

File diff suppressed because it is too large Load Diff

View File

@ -33,7 +33,7 @@ use syntax::{
/// }
/// ```
pub struct PathTransform<'a> {
generic_def: hir::GenericDef,
generic_def: Option<hir::GenericDef>,
substs: Vec<ast::Type>,
target_scope: &'a SemanticsScope<'a>,
source_scope: &'a SemanticsScope<'a>,
@ -49,7 +49,7 @@ impl<'a> PathTransform<'a> {
PathTransform {
source_scope,
target_scope,
generic_def: trait_.into(),
generic_def: Some(trait_.into()),
substs: get_syntactic_substs(impl_).unwrap_or_default(),
}
}
@ -63,28 +63,42 @@ impl<'a> PathTransform<'a> {
PathTransform {
source_scope,
target_scope,
generic_def: function.into(),
generic_def: Some(function.into()),
substs: get_type_args_from_arg_list(generic_arg_list).unwrap_or_default(),
}
}
pub fn generic_transformation(
target_scope: &'a SemanticsScope<'a>,
source_scope: &'a SemanticsScope<'a>,
) -> PathTransform<'a> {
PathTransform { source_scope, target_scope, generic_def: None, substs: Vec::new() }
}
pub fn apply(&self, syntax: &SyntaxNode) {
self.build_ctx().apply(syntax)
}
pub fn apply_all<'b>(&self, nodes: impl IntoIterator<Item = &'b SyntaxNode>) {
let ctx = self.build_ctx();
for node in nodes {
ctx.apply(node);
}
}
fn build_ctx(&self) -> Ctx<'a> {
let db = self.source_scope.db;
let target_module = self.target_scope.module();
let source_module = self.source_scope.module();
let skip = match self.generic_def {
// this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
hir::GenericDef::Trait(_) => 1,
Some(hir::GenericDef::Trait(_)) => 1,
_ => 0,
};
let substs_by_param: FxHashMap<_, _> = self
.generic_def
.type_params(db)
.into_iter()
.flat_map(|it| it.type_params(db))
.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.

View File

@ -823,6 +823,7 @@ pub fn fn_(
visibility: Option<ast::Visibility>,
fn_name: ast::Name,
type_params: Option<ast::GenericParamList>,
where_clause: Option<ast::WhereClause>,
params: ast::ParamList,
body: ast::BlockExpr,
ret_type: Option<ast::RetType>,
@ -832,6 +833,10 @@ pub fn fn_(
Some(type_params) => format!("{type_params}"),
None => "".into(),
};
let where_clause = match where_clause {
Some(it) => format!("{it} "),
None => "".into(),
};
let ret_type = match ret_type {
Some(ret_type) => format!("{ret_type} "),
None => "".into(),
@ -844,7 +849,7 @@ pub fn fn_(
let async_literal = if is_async { "async " } else { "" };
ast_from_text(&format!(
"{visibility}{async_literal}fn {fn_name}{type_params}{params} {ret_type}{body}",
"{visibility}{async_literal}fn {fn_name}{type_params}{params} {ret_type}{where_clause}{body}",
))
}