use either::Either;
use ide_db::assists::{AssistId, GroupLabel};
use syntax::{
AstNode,
ast::{self, HasGenericParams, HasName, edit::IndentLevel, make},
syntax_editor,
};
use crate::{AssistContext, Assists};
// Assist: generate_fn_type_alias_named
//
// Generate a type alias for the function with named parameters.
//
// ```
// unsafe fn fo$0o(n: i32) -> i32 { 42i32 }
// ```
// ->
// ```
// type ${0:FooFn} = unsafe fn(n: i32) -> i32;
//
// unsafe fn foo(n: i32) -> i32 { 42i32 }
// ```
// Assist: generate_fn_type_alias_unnamed
//
// Generate a type alias for the function with unnamed parameters.
//
// ```
// unsafe fn fo$0o(n: i32) -> i32 { 42i32 }
// ```
// ->
// ```
// type ${0:FooFn} = unsafe fn(i32) -> i32;
//
// unsafe fn foo(n: i32) -> i32 { 42i32 }
// ```
pub(crate) fn generate_fn_type_alias(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let name = ctx.find_node_at_offset::<ast::Name>()?;
let func = &name.syntax().parent()?;
let func_node = ast::Fn::cast(func.clone())?;
let param_list = func_node.param_list()?;
let assoc_owner = func.ancestors().nth(2).and_then(Either::<ast::Trait, ast::Impl>::cast);
// This is where we'll insert the type alias, since type aliases in `impl`s or `trait`s are not supported
let insertion_node = assoc_owner
.as_ref()
.map_or_else(|| func, |impl_| impl_.as_ref().either(AstNode::syntax, AstNode::syntax));
for style in ParamStyle::ALL {
acc.add_group(
&GroupLabel("Generate a type alias for function...".into()),
style.assist_id(),
style.label(),
func_node.syntax().text_range(),
|builder| {
let mut edit = builder.make_editor(func);
let alias_name = format!("{}Fn", stdx::to_camel_case(&name.to_string()));
let mut fn_params_vec = Vec::new();
if let Some(self_ty) =
param_list.self_param().and_then(|p| ctx.sema.type_of_self(&p))
{
let is_ref = self_ty.is_reference();
let is_mut = self_ty.is_mutable_reference();
if let Some(adt) = self_ty.strip_references().as_adt() {
let inner_type = make::ty(adt.name(ctx.db()).as_str());
let ast_self_ty =
if is_ref { make::ty_ref(inner_type, is_mut) } else { inner_type };
fn_params_vec.push(make::unnamed_param(ast_self_ty));
}
}
fn_params_vec.extend(param_list.params().filter_map(|p| match style {
ParamStyle::Named => Some(p),
ParamStyle::Unnamed => p.ty().map(make::unnamed_param),
}));
let generic_params = func_node.generic_param_list();
let is_unsafe = func_node.unsafe_token().is_some();
let ty = make::ty_fn_ptr(
is_unsafe,
func_node.abi(),
fn_params_vec.into_iter(),
func_node.ret_type(),
);
// Insert new alias
let ty_alias = make::ty_alias(
&alias_name,
generic_params,
None,
None,
Some((ast::Type::FnPtrType(ty), None)),
)
.clone_for_update();
let indent = IndentLevel::from_node(insertion_node);
edit.insert_all(
syntax_editor::Position::before(insertion_node),
vec![
ty_alias.syntax().clone().into(),
make::tokens::whitespace(&format!("\n\n{indent}")).into(),
],
);
if let Some(cap) = ctx.config.snippet_cap {
if let Some(name) = ty_alias.name() {
edit.add_annotation(name.syntax(), builder.make_placeholder_snippet(cap));
}
}
builder.add_file_edits(ctx.vfs_file_id(), edit);
},
);
}
Some(())
}
enum ParamStyle {
Named,
Unnamed,
}
impl ParamStyle {
const ALL: &'static [ParamStyle] = &[ParamStyle::Named, ParamStyle::Unnamed];
fn assist_id(&self) -> AssistId {
let s = match self {
ParamStyle::Named => "generate_fn_type_alias_named",
ParamStyle::Unnamed => "generate_fn_type_alias_unnamed",
};
AssistId::generate(s)
}
fn label(&self) -> &'static str {
match self {
ParamStyle::Named => "Generate a type alias for function with named params",
ParamStyle::Unnamed => "Generate a type alias for function with unnamed params",
}
}
}
#[cfg(test)]
mod tests {
use crate::tests::check_assist_by_label;
use super::*;
#[test]
fn generate_fn_alias_unnamed_simple() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = fn(u32) -> i32;
fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_unsafe() {
check_assist_by_label(
generate_fn_type_alias,
r#"
unsafe fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = unsafe fn(u32) -> i32;
unsafe fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_extern() {
check_assist_by_label(
generate_fn_type_alias,
r#"
extern fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = extern fn(u32) -> i32;
extern fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_type_unnamed_extern_abi() {
check_assist_by_label(
generate_fn_type_alias,
r#"
extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = extern "FooABI" fn(u32) -> i32;
extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_unsafe_extern_abi() {
check_assist_by_label(
generate_fn_type_alias,
r#"
unsafe extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = unsafe extern "FooABI" fn(u32) -> i32;
unsafe extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_generics() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o<A, B>(a: A, b: B) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn}<A, B> = fn(A, B) -> i32;
fn foo<A, B>(a: A, b: B) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_generics_bounds() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn}<A: Trait, B: Trait> = fn(A, B) -> i32;
fn foo<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_unnamed_self() {
check_assist_by_label(
generate_fn_type_alias,
r#"
struct S;
impl S {
fn fo$0o(&mut self, param: u32) -> i32 { return 42; }
}
"#,
r#"
struct S;
type ${0:FooFn} = fn(&mut S, u32) -> i32;
impl S {
fn foo(&mut self, param: u32) -> i32 { return 42; }
}
"#,
ParamStyle::Unnamed.label(),
);
}
#[test]
fn generate_fn_alias_named_simple() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = fn(param: u32) -> i32;
fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_unsafe() {
check_assist_by_label(
generate_fn_type_alias,
r#"
unsafe fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = unsafe fn(param: u32) -> i32;
unsafe fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_extern() {
check_assist_by_label(
generate_fn_type_alias,
r#"
extern fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = extern fn(param: u32) -> i32;
extern fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_type_named_extern_abi() {
check_assist_by_label(
generate_fn_type_alias,
r#"
extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = extern "FooABI" fn(param: u32) -> i32;
extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_unsafe_extern_abi() {
check_assist_by_label(
generate_fn_type_alias,
r#"
unsafe extern "FooABI" fn fo$0o(param: u32) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn} = unsafe extern "FooABI" fn(param: u32) -> i32;
unsafe extern "FooABI" fn foo(param: u32) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_generics() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o<A, B>(a: A, b: B) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn}<A, B> = fn(a: A, b: B) -> i32;
fn foo<A, B>(a: A, b: B) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_generics_bounds() {
check_assist_by_label(
generate_fn_type_alias,
r#"
fn fo$0o<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
"#,
r#"
type ${0:FooFn}<A: Trait, B: Trait> = fn(a: A, b: B) -> i32;
fn foo<A: Trait, B: Trait>(a: A, b: B) -> i32 { return 42; }
"#,
ParamStyle::Named.label(),
);
}
#[test]
fn generate_fn_alias_named_self() {
check_assist_by_label(
generate_fn_type_alias,
r#"
struct S;
impl S {
fn fo$0o(&mut self, param: u32) -> i32 { return 42; }
}
"#,
r#"
struct S;
type ${0:FooFn} = fn(&mut S, param: u32) -> i32;
impl S {
fn foo(&mut self, param: u32) -> i32 { return 42; }
}
"#,
ParamStyle::Named.label(),
);
}
}
{
ctor = match pat.kind.as_ref() {
PatKind::Leaf { .. } if matches!(adt, hir_def::AdtId::UnionId(_)) => {
UnionField
}
PatKind::Leaf { .. } => Struct,
PatKind::Variant { enum_variant, .. } => {
Variant(EnumVariantContiguousIndex::from_enum_variant_id(
self.db,
*enum_variant,
))
}
_ => {
never!();
Wildcard
}
};
let variant = Self::variant_id_for_adt(self.db, &ctor, adt).unwrap();
arity = variant.variant_data(self.db.upcast()).fields().len();
}
_ => {
never!("pattern has unexpected type: pat: {:?}, ty: {:?}", pat, &pat.ty);
ctor = Wildcard;
fields.clear();
arity = 0;
}
}
}
&PatKind::LiteralBool { value } => {
ctor = Bool(value);
fields = Vec::new();
arity = 0;
}
PatKind::Never => {
ctor = Never;
fields = Vec::new();
arity = 0;
}
PatKind::Or { pats } => {
ctor = Or;
fields = pats
.iter()
.enumerate()
.map(|(i, pat)| self.lower_pat(pat).at_index(i))
.collect();
arity = pats.len();
}
}
DeconstructedPat::new(ctor, fields, arity, pat.ty.clone(), ())
}
pub(crate) fn hoist_witness_pat(&self, pat: &WitnessPat<'db>) -> Pat {
let mut subpatterns = pat.iter_fields().map(|p| self.hoist_witness_pat(p));
let kind = match pat.ctor() {
&Bool(value) => PatKind::LiteralBool { value },
IntRange(_) => unimplemented!(),
Struct | Variant(_) | UnionField => match pat.ty().kind(Interner) {
TyKind::Tuple(..) => PatKind::Leaf {
subpatterns: subpatterns
.zip(0u32..)
.map(|(p, i)| FieldPat {
field: LocalFieldId::from_raw(i.into()),
pattern: p,
})
.collect(),
},
TyKind::Adt(adt, _) if is_box(self.db, adt.0) => {
// Without `box_patterns`, the only legal pattern of type `Box` is `_` (outside
// of `std`). So this branch is only reachable when the feature is enabled and
// the pattern is a box pattern.
PatKind::Deref { subpattern: subpatterns.next().unwrap() }
}
TyKind::Adt(adt, substs) => {
let variant = Self::variant_id_for_adt(self.db, pat.ctor(), adt.0).unwrap();
let subpatterns = self
.list_variant_fields(pat.ty(), variant)
.zip(subpatterns)
.map(|((field, _ty), pattern)| FieldPat { field, pattern })
.collect();
if let VariantId::EnumVariantId(enum_variant) = variant {
PatKind::Variant { substs: substs.clone(), enum_variant, subpatterns }
} else {
PatKind::Leaf { subpatterns }
}
}
_ => {
never!("unexpected ctor for type {:?} {:?}", pat.ctor(), pat.ty());
PatKind::Wild
}
},
// Note: given the expansion of `&str` patterns done in `expand_pattern`, we should
// be careful to reconstruct the correct constant pattern here. However a string
// literal pattern will never be reported as a non-exhaustiveness witness, so we
// ignore this issue.
Ref => PatKind::Deref { subpattern: subpatterns.next().unwrap() },
Slice(_) => unimplemented!(),
&Str(void) => match void {},
Wildcard | NonExhaustive | Hidden | PrivateUninhabited => PatKind::Wild,
Never => PatKind::Never,
Missing | F16Range(..) | F32Range(..) | F64Range(..) | F128Range(..) | Opaque(..)
| Or => {
never!("can't convert to pattern: {:?}", pat.ctor());
PatKind::Wild
}
};
Pat { ty: pat.ty().clone(), kind: Box::new(kind) }
}
}
impl PatCx for MatchCheckCtx<'_> {
type Error = ();
type Ty = Ty;
type VariantIdx = EnumVariantContiguousIndex;
type StrLit = Void;
type ArmData = ();
type PatData = ();
fn is_exhaustive_patterns_feature_on(&self) -> bool {
self.exhaustive_patterns
}
fn ctor_arity(
&self,
ctor: &rustc_pattern_analysis::constructor::Constructor<Self>,
ty: &Self::Ty,
) -> usize {
match ctor {
Struct | Variant(_) | UnionField => match *ty.kind(Interner) {
TyKind::Tuple(arity, ..) => arity,
TyKind::Adt(AdtId(adt), ..) => {
if is_box(self.db, adt) {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
1
} else {
let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap();
variant.variant_data(self.db.upcast()).fields().len()
}
}
_ => {
never!("Unexpected type for `Single` constructor: {:?}", ty);
0
}
},
Ref => 1,
Slice(..) => unimplemented!(),
Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
| F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited
| Hidden | Missing | Wildcard => 0,
Or => {
never!("The `Or` constructor doesn't have a fixed arity");
0
}
}
}
fn ctor_sub_tys<'a>(
&'a self,
ctor: &'a rustc_pattern_analysis::constructor::Constructor<Self>,
ty: &'a Self::Ty,
) -> impl ExactSizeIterator<Item = (Self::Ty, PrivateUninhabitedField)> + Captures<'a> {
let single = |ty| smallvec![(ty, PrivateUninhabitedField(false))];
let tys: SmallVec<[_; 2]> = match ctor {
Struct | Variant(_) | UnionField => match ty.kind(Interner) {
TyKind::Tuple(_, substs) => {
let tys = substs.iter(Interner).map(|ty| ty.assert_ty_ref(Interner));
tys.cloned().map(|ty| (ty, PrivateUninhabitedField(false))).collect()
}
TyKind::Ref(.., rty) => single(rty.clone()),
&TyKind::Adt(AdtId(adt), ref substs) => {
if is_box(self.db, adt) {
// The only legal patterns of type `Box` (outside `std`) are `_` and box
// patterns. If we're here we can assume this is a box pattern.
let subst_ty = substs.at(Interner, 0).assert_ty_ref(Interner).clone();
single(subst_ty)
} else {
let variant = Self::variant_id_for_adt(self.db, ctor, adt).unwrap();
let visibilities = LazyCell::new(|| self.db.field_visibilities(variant));
self.list_variant_fields(ty, variant)
.map(move |(fid, ty)| {
let is_visible = || {
matches!(adt, hir_def::AdtId::EnumId(..))
|| visibilities[fid]
.is_visible_from(self.db.upcast(), self.module)
};
let is_uninhabited = self.is_uninhabited(&ty);
let private_uninhabited = is_uninhabited && !is_visible();
(ty, PrivateUninhabitedField(private_uninhabited))
})
.collect()
}
}
ty_kind => {
never!("Unexpected type for `{:?}` constructor: {:?}", ctor, ty_kind);
single(ty.clone())
}
},
Ref => match ty.kind(Interner) {
TyKind::Ref(.., rty) => single(rty.clone()),
ty_kind => {
never!("Unexpected type for `{:?}` constructor: {:?}", ctor, ty_kind);
single(ty.clone())
}
},
Slice(_) => unreachable!("Found a `Slice` constructor in match checking"),
Never | Bool(..) | IntRange(..) | F16Range(..) | F32Range(..) | F64Range(..)
| F128Range(..) | Str(..) | Opaque(..) | NonExhaustive | PrivateUninhabited
| Hidden | Missing | Wildcard => {
smallvec![]
}
Or => {
never!("called `Fields::wildcards` on an `Or` ctor");
smallvec![]
}
};
tys.into_iter()
}
fn ctors_for_ty(
&self,
ty: &Self::Ty,
) -> Result<rustc_pattern_analysis::constructor::ConstructorSet<Self>, Self::Error> {
let cx = self;
// Unhandled types are treated as non-exhaustive. Being explicit here instead of falling
// to catchall arm to ease further implementation.
let unhandled = || ConstructorSet::Unlistable;
// This determines the set of all possible constructors for the type `ty`. For numbers,
// arrays and slices we use ranges and variable-length slices when appropriate.
//
// If the `exhaustive_patterns` feature is enabled, we make sure to omit constructors that
// are statically impossible. E.g., for `Option<!>`, we do not include `Some(_)` in the
// returned list of constructors.
// Invariant: this is empty if and only if the type is uninhabited (as determined by
// `cx.is_uninhabited()`).
Ok(match ty.kind(Interner) {
TyKind::Scalar(Scalar::Bool) => ConstructorSet::Bool,
TyKind::Scalar(Scalar::Char) => unhandled(),
TyKind::Scalar(Scalar::Int(..) | Scalar::Uint(..)) => unhandled(),
TyKind::Array(..) | TyKind::Slice(..) => unhandled(),
&TyKind::Adt(AdtId(adt @ hir_def::AdtId::EnumId(enum_id)), ref subst) => {
let enum_data = cx.db.enum_data(enum_id);
let is_declared_nonexhaustive = cx.is_foreign_non_exhaustive(adt);
if enum_data.variants.is_empty() && !is_declared_nonexhaustive {
ConstructorSet::NoConstructors
} else {
let mut variants = IndexVec::with_capacity(enum_data.variants.len());
for &(variant, _) in enum_data.variants.iter() {
let is_uninhabited =
is_enum_variant_uninhabited_from(cx.db, variant, subst, cx.module);
let visibility = if is_uninhabited {
VariantVisibility::Empty
} else {
VariantVisibility::Visible
};
variants.push(visibility);
}
ConstructorSet::Variants { variants, non_exhaustive: is_declared_nonexhaustive }
}
}
TyKind::Adt(AdtId(hir_def::AdtId::UnionId(_)), _) => ConstructorSet::Union,
TyKind::Adt(..) | TyKind::Tuple(..) => {
ConstructorSet::Struct { empty: cx.is_uninhabited(ty) }
}
TyKind::Ref(..) => ConstructorSet::Ref,
TyKind::Never => ConstructorSet::NoConstructors,
// This type is one for which we cannot list constructors, like `str` or `f64`.
_ => ConstructorSet::Unlistable,
})
}
fn write_variant_name(
f: &mut fmt::Formatter<'_>,
_ctor: &Constructor<Self>,
_ty: &Self::Ty,
) -> fmt::Result {
write!(f, "<write_variant_name unsupported>")
// We lack the database here ...
// let variant = ty.as_adt().and_then(|(adt, _)| Self::variant_id_for_adt(db, ctor, adt));
// if let Some(variant) = variant {
// match variant {
// VariantId::EnumVariantId(v) => {
// write!(f, "{}", db.enum_variant_data(v).name.display(db.upcast()))?;
// }
// VariantId::StructId(s) => {
// write!(f, "{}", db.struct_data(s).name.display(db.upcast()))?
// }
// VariantId::UnionId(u) => {
// write!(f, "{}", db.union_data(u).name.display(db.upcast()))?
// }
// }
// }
// Ok(())
}
fn bug(&self, fmt: fmt::Arguments<'_>) {
never!("{}", fmt)
}
fn complexity_exceeded(&self) -> Result<(), Self::Error> {
Err(())
}
}
impl fmt::Debug for MatchCheckCtx<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MatchCheckCtx").finish()
}
}