7335: added region folding r=matklad a=LucianoBestia Regions of code that you'd like to be folded can be wrapped with `// #region` and `// #endregion` line comments. This is called "Region Folding". It is originally available for many languages in VSCode. But Rust-analyzer has its own folding function and this is missing. With this Pull Request I am suggesting a simple solution. The regions are a special kind of comments, so I added a bit of code in the comment folding function. The regex to match are: `^\s*//\s*#?region\b` and `^\s*//\s*#?endregion\b`. The number of space characters is not important. There is an optional # character. The line can end with a name of the region. Example: ```rust // 1. some normal comment // region: test // 2. some normal comment calling_function(x,y); // endregion: test ``` I added a test for this new functionality in `folding_ranges.rs`. Please, take a look and comment. I found that these exact regexes are already present in the file `language-configuration.json`, but I don't find a way to read this configuration. So my regex is hardcoded in the code. 7691: Suggest name in extract variable r=matklad a=cpud36 Generate better default name in extract variable assist as was mentioned in issue #1587 # Currently supported (in order of declining precedence) 1. Expr is argument to a function; use corresponding parameter name 2. Expr is result of a function or method call; use this function/method's name 3. Use expr type name (if possible) 4. Fallback to `var_name` otherwise # Showcase ![generate_derive_variable_name_from_method](https://user-images.githubusercontent.com/4218373/108013304-72105400-701c-11eb-9f13-eec52e74d0cc.gif) ![generate_derive_variable_name_from_param](https://user-images.githubusercontent.com/4218373/108013305-72a8ea80-701c-11eb-957e-2214f7f005de.gif) # Questions * Should we more aggressively strip known types? E.g. we already strip `&T -> T`; should we strip `Option<T> -> T`, `Result<T, E> -> T`, and others? * Integers and floats use `var_name` by default. Should we introduce a name, like `i`, `f` etc? * Can we return a list and suggest a name when renaming(like IntelliJ does)? * Should we add counters to remove duplicate variables? E.g. `type`, `type1`, type2`, etc. Co-authored-by: Luciano Bestia <LucianoBestia@gmail.com> Co-authored-by: Luciano <31509965+LucianoBestia@users.noreply.github.com> Co-authored-by: Vladyslav Katasonov <cpud47@gmail.com>
This commit is contained in:
commit
657ec3616f
@ -6,7 +6,7 @@
|
||||
ast::{self, AstNode, AstToken, VisibilityOwner},
|
||||
Direction, NodeOrToken, SourceFile,
|
||||
SyntaxKind::{self, *},
|
||||
SyntaxNode, TextRange,
|
||||
SyntaxNode, TextRange, TextSize,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@ -16,6 +16,7 @@ pub enum FoldKind {
|
||||
Mods,
|
||||
Block,
|
||||
ArgList,
|
||||
Region,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -29,6 +30,8 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
|
||||
let mut visited_comments = FxHashSet::default();
|
||||
let mut visited_imports = FxHashSet::default();
|
||||
let mut visited_mods = FxHashSet::default();
|
||||
// regions can be nested, here is a LIFO buffer
|
||||
let mut regions_starts: Vec<TextSize> = vec![];
|
||||
|
||||
for element in file.syntax().descendants_with_tokens() {
|
||||
// Fold items that span multiple lines
|
||||
@ -48,10 +51,25 @@ pub(crate) fn folding_ranges(file: &SourceFile) -> Vec<Fold> {
|
||||
// Fold groups of comments
|
||||
if let Some(comment) = ast::Comment::cast(token) {
|
||||
if !visited_comments.contains(&comment) {
|
||||
if let Some(range) =
|
||||
contiguous_range_for_comment(comment, &mut visited_comments)
|
||||
{
|
||||
res.push(Fold { range, kind: FoldKind::Comment })
|
||||
// regions are not real comments
|
||||
if comment.text().trim().starts_with("// region:") {
|
||||
regions_starts.push(comment.syntax().text_range().start());
|
||||
} else if comment.text().trim().starts_with("// endregion") {
|
||||
if let Some(region) = regions_starts.pop() {
|
||||
res.push(Fold {
|
||||
range: TextRange::new(
|
||||
region,
|
||||
comment.syntax().text_range().end(),
|
||||
),
|
||||
kind: FoldKind::Region,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
if let Some(range) =
|
||||
contiguous_range_for_comment(comment, &mut visited_comments)
|
||||
{
|
||||
res.push(Fold { range, kind: FoldKind::Comment })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -175,9 +193,16 @@ fn contiguous_range_for_comment(
|
||||
}
|
||||
if let Some(c) = ast::Comment::cast(token) {
|
||||
if c.kind() == group_kind {
|
||||
visited.insert(c.clone());
|
||||
last = c;
|
||||
continue;
|
||||
// regions are not real comments
|
||||
if c.text().trim().starts_with("// region:")
|
||||
|| c.text().trim().starts_with("// endregion")
|
||||
{
|
||||
break;
|
||||
} else {
|
||||
visited.insert(c.clone());
|
||||
last = c;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
// The comment group ends because either:
|
||||
@ -224,6 +249,7 @@ fn check(ra_fixture: &str) {
|
||||
FoldKind::Mods => "mods",
|
||||
FoldKind::Block => "block",
|
||||
FoldKind::ArgList => "arglist",
|
||||
FoldKind::Region => "region",
|
||||
};
|
||||
assert_eq!(kind, &attr.unwrap());
|
||||
}
|
||||
@ -415,6 +441,19 @@ fn foo<fold arglist>(
|
||||
x: i32,
|
||||
y: String,
|
||||
)</fold> {}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fold_region() {
|
||||
check(
|
||||
r#"
|
||||
// 1. some normal comment
|
||||
<fold region>// region: test
|
||||
// 2. some normal comment
|
||||
calling_function(x,y);
|
||||
// endregion: test</fold>
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
};
|
||||
use test_utils::mark;
|
||||
|
||||
use crate::{AssistContext, AssistId, AssistKind, Assists};
|
||||
use crate::{utils::suggest_name, AssistContext, AssistId, AssistKind, Assists};
|
||||
|
||||
// Assist: extract_variable
|
||||
//
|
||||
@ -54,7 +54,7 @@ pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option
|
||||
|
||||
let var_name = match &field_shorthand {
|
||||
Some(it) => it.to_string(),
|
||||
None => "var_name".to_string(),
|
||||
None => suggest_name::variable(&to_extract, &ctx.sema),
|
||||
};
|
||||
let expr_range = match &field_shorthand {
|
||||
Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
|
||||
@ -274,8 +274,8 @@ fn foo() {
|
||||
"#,
|
||||
r#"
|
||||
fn foo() {
|
||||
let $0var_name = bar(1 + 1);
|
||||
var_name
|
||||
let $0bar = bar(1 + 1);
|
||||
bar
|
||||
}
|
||||
"#,
|
||||
)
|
||||
@ -401,8 +401,8 @@ fn main() {
|
||||
",
|
||||
"
|
||||
fn main() {
|
||||
let $0var_name = bar.foo();
|
||||
let v = var_name;
|
||||
let $0foo = bar.foo();
|
||||
let v = foo;
|
||||
}
|
||||
",
|
||||
);
|
||||
@ -556,6 +556,202 @@ fn main() {
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_name_from_type() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct Test(i32);
|
||||
|
||||
fn foo() -> Test {
|
||||
$0{ Test(10) }$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct Test(i32);
|
||||
|
||||
fn foo() -> Test {
|
||||
let $0test = { Test(10) };
|
||||
test
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_name_from_parameter() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn bar(test: u32, size: u32)
|
||||
|
||||
fn foo() {
|
||||
bar(1, $01+1$0);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn bar(test: u32, size: u32)
|
||||
|
||||
fn foo() {
|
||||
let $0size = 1+1;
|
||||
bar(1, size);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_parameter_name_has_precedence_over_type() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct TextSize(u32);
|
||||
fn bar(test: u32, size: TextSize)
|
||||
|
||||
fn foo() {
|
||||
bar(1, $0{ TextSize(1+1) }$0);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct TextSize(u32);
|
||||
fn bar(test: u32, size: TextSize)
|
||||
|
||||
fn foo() {
|
||||
let $0size = { TextSize(1+1) };
|
||||
bar(1, size);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_name_from_function() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn is_required(test: u32, size: u32) -> bool
|
||||
|
||||
fn foo() -> bool {
|
||||
$0is_required(1, 2)$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn is_required(test: u32, size: u32) -> bool
|
||||
|
||||
fn foo() -> bool {
|
||||
let $0is_required = is_required(1, 2);
|
||||
is_required
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_name_from_method() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: u32) -> u32 { n }
|
||||
}
|
||||
|
||||
fn foo() -> u32 {
|
||||
$0S.bar(1)$0
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: u32) -> u32 { n }
|
||||
}
|
||||
|
||||
fn foo() -> u32 {
|
||||
let $0bar = S.bar(1);
|
||||
bar
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_name_from_method_param() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: u32, size: u32) { n }
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
S.bar($01 + 1$0, 2)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: u32, size: u32) { n }
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
let $0n = 1 + 1;
|
||||
S.bar(n, 2)
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_name_from_ufcs_method_param() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: u32, size: u32) { n }
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
S::bar(&S, $01 + 1$0, 2)
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: u32, size: u32) { n }
|
||||
}
|
||||
|
||||
fn foo() {
|
||||
let $0n = 1 + 1;
|
||||
S::bar(&S, n, 2)
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_var_parameter_name_has_precedence_over_function() {
|
||||
check_assist(
|
||||
extract_variable,
|
||||
r#"
|
||||
fn bar(test: u32, size: u32)
|
||||
|
||||
fn foo() {
|
||||
bar(1, $0symbol_size(1, 2)$0);
|
||||
}
|
||||
"#,
|
||||
r#"
|
||||
fn bar(test: u32, size: u32)
|
||||
|
||||
fn foo() {
|
||||
let $0size = symbol_size(1, 2);
|
||||
bar(1, size);
|
||||
}
|
||||
"#,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_var_for_return_not_applicable() {
|
||||
check_assist_not_applicable(extract_variable, "fn foo() { $0return$0; } ");
|
||||
|
@ -12,7 +12,7 @@
|
||||
RootDatabase,
|
||||
};
|
||||
use stdx::{format_to, trim_indent};
|
||||
use syntax::TextRange;
|
||||
use syntax::{ast, AstNode, TextRange};
|
||||
use test_utils::{assert_eq_text, extract_offset};
|
||||
|
||||
use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
|
||||
@ -180,6 +180,50 @@ fn labels(assists: &[Assist]) -> String {
|
||||
labels.into_iter().collect::<String>()
|
||||
}
|
||||
|
||||
pub(crate) type NameSuggestion = fn(&ast::Expr, &Semantics<'_, RootDatabase>) -> Option<String>;
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn check_name_suggestion(
|
||||
suggestion: NameSuggestion,
|
||||
ra_fixture: &str,
|
||||
suggested_name: &str,
|
||||
) {
|
||||
check_name(suggestion, ra_fixture, Some(suggested_name));
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn check_name_suggestion_not_applicable(suggestion: NameSuggestion, ra_fixture: &str) {
|
||||
check_name(suggestion, ra_fixture, None);
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn check_name(suggestion: NameSuggestion, ra_fixture: &str, expected: Option<&str>) {
|
||||
let (db, file_with_carret_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
|
||||
let frange = FileRange { file_id: file_with_carret_id, range: range_or_offset.into() };
|
||||
|
||||
let sema = Semantics::new(&db);
|
||||
let source_file = sema.parse(frange.file_id);
|
||||
let element = source_file.syntax().covering_element(frange.range);
|
||||
let expr =
|
||||
element.ancestors().find_map(ast::Expr::cast).expect("selection is not an expression");
|
||||
assert_eq!(
|
||||
expr.syntax().text_range(),
|
||||
frange.range,
|
||||
"selection is not an expression(yet contained in one)"
|
||||
);
|
||||
|
||||
let name = suggestion(&expr, &sema);
|
||||
|
||||
match (name, expected) {
|
||||
(Some(name), Some(expected_name)) => {
|
||||
assert_eq_text!(&name, expected_name);
|
||||
}
|
||||
(Some(_), None) => panic!("name suggestion should not be applicable"),
|
||||
(None, Some(_)) => panic!("name suggestion is not applicable"),
|
||||
(None, None) => (),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assist_order_field_struct() {
|
||||
let before = "struct Foo { $0bar: u32 }";
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Assorted functions shared by several assists.
|
||||
|
||||
pub(crate) mod suggest_name;
|
||||
|
||||
use std::ops;
|
||||
|
||||
use ast::TypeBoundsOwner;
|
||||
|
877
crates/ide_assists/src/utils/suggest_name.rs
Normal file
877
crates/ide_assists/src/utils/suggest_name.rs
Normal file
@ -0,0 +1,877 @@
|
||||
//! This module contains functions to suggest names for expressions, functions and other items
|
||||
|
||||
use hir::Semantics;
|
||||
use ide_db::RootDatabase;
|
||||
use itertools::Itertools;
|
||||
use stdx::to_lower_snake_case;
|
||||
use syntax::{
|
||||
ast::{self, NameOwner},
|
||||
match_ast, AstNode,
|
||||
};
|
||||
|
||||
/// Trait names, that will be ignored when in `impl Trait` and `dyn Trait`
|
||||
const USELESS_TRAITS: &[&str] = &["Send", "Sync", "Copy", "Clone", "Eq", "PartialEq"];
|
||||
/// Identifier names that won't be suggested, ever
|
||||
///
|
||||
/// **NOTE**: they all must be snake lower case
|
||||
const USELESS_NAMES: &[&str] =
|
||||
&["new", "default", "option", "some", "none", "ok", "err", "str", "string"];
|
||||
/// Generic types replaced by their first argument
|
||||
///
|
||||
/// # Examples
|
||||
/// `Option<Name>` -> `Name`
|
||||
/// `Result<User, Error>` -> `User`
|
||||
const WRAPPER_TYPES: &[&str] = &["Box", "Option", "Result"];
|
||||
/// Prefixes to strip from methods names
|
||||
///
|
||||
/// # Examples
|
||||
/// `vec.as_slice()` -> `slice`
|
||||
/// `args.into_config()` -> `config`
|
||||
/// `bytes.to_vec()` -> `vec`
|
||||
const USELESS_METHOD_PREFIXES: &[&str] = &["into_", "as_", "to_"];
|
||||
/// Useless methods that are stripped from expression
|
||||
///
|
||||
/// # Examples
|
||||
/// `var.name().to_string()` -> `var.name()`
|
||||
const USELESS_METHODS: &[&str] = &[
|
||||
"to_string",
|
||||
"as_str",
|
||||
"to_owned",
|
||||
"as_ref",
|
||||
"clone",
|
||||
"cloned",
|
||||
"expect",
|
||||
"expect_none",
|
||||
"unwrap",
|
||||
"unwrap_none",
|
||||
"unwrap_or",
|
||||
"unwrap_or_default",
|
||||
"unwrap_or_else",
|
||||
"unwrap_unchecked",
|
||||
"iter",
|
||||
"into_iter",
|
||||
"iter_mut",
|
||||
];
|
||||
|
||||
/// Suggest name of variable for given expression
|
||||
///
|
||||
/// **NOTE**: it is caller's responsibility to guarantee uniqueness of the name.
|
||||
/// I.e. it doesn't look for names in scope.
|
||||
///
|
||||
/// # Current implementation
|
||||
///
|
||||
/// In current implementation, the function tries to get the name from
|
||||
/// the following sources:
|
||||
///
|
||||
/// * if expr is an argument to function/method, use paramter name
|
||||
/// * if expr is a function/method call, use function name
|
||||
/// * expression type name if it exists (E.g. `()`, `fn() -> ()` or `!` do not have names)
|
||||
/// * fallback: `var_name`
|
||||
///
|
||||
/// It also applies heuristics to filter out less informative names
|
||||
///
|
||||
/// Currently it sticks to the first name found.
|
||||
pub(crate) fn variable(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> String {
|
||||
// `from_param` does not benifit from stripping
|
||||
// it need the largest context possible
|
||||
// so we check firstmost
|
||||
if let Some(name) = from_param(expr, sema) {
|
||||
return name;
|
||||
}
|
||||
|
||||
let mut next_expr = Some(expr.clone());
|
||||
while let Some(expr) = next_expr {
|
||||
let name = from_call(&expr).or_else(|| from_type(&expr, sema));
|
||||
if let Some(name) = name {
|
||||
return name;
|
||||
}
|
||||
|
||||
match expr {
|
||||
ast::Expr::RefExpr(inner) => next_expr = inner.expr(),
|
||||
ast::Expr::BoxExpr(inner) => next_expr = inner.expr(),
|
||||
ast::Expr::AwaitExpr(inner) => next_expr = inner.expr(),
|
||||
// ast::Expr::BlockExpr(block) => expr = block.tail_expr(),
|
||||
ast::Expr::CastExpr(inner) => next_expr = inner.expr(),
|
||||
ast::Expr::MethodCallExpr(method) if is_useless_method(&method) => {
|
||||
next_expr = method.receiver();
|
||||
}
|
||||
ast::Expr::ParenExpr(inner) => next_expr = inner.expr(),
|
||||
ast::Expr::TryExpr(inner) => next_expr = inner.expr(),
|
||||
ast::Expr::PrefixExpr(prefix) if prefix.op_kind() == Some(ast::PrefixOp::Deref) => {
|
||||
next_expr = prefix.expr()
|
||||
}
|
||||
_ => break,
|
||||
}
|
||||
}
|
||||
|
||||
"var_name".to_string()
|
||||
}
|
||||
|
||||
fn normalize(name: &str) -> Option<String> {
|
||||
let name = to_lower_snake_case(name);
|
||||
|
||||
if USELESS_NAMES.contains(&name.as_str()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
if !is_valid_name(&name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(name)
|
||||
}
|
||||
|
||||
fn is_valid_name(name: &str) -> bool {
|
||||
match syntax::lex_single_syntax_kind(name) {
|
||||
Some((syntax::SyntaxKind::IDENT, _error)) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn is_useless_method(method: &ast::MethodCallExpr) -> bool {
|
||||
let ident = method.name_ref().and_then(|it| it.ident_token());
|
||||
|
||||
if let Some(ident) = ident {
|
||||
USELESS_METHODS.contains(&ident.text())
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn from_call(expr: &ast::Expr) -> Option<String> {
|
||||
from_func_call(expr).or_else(|| from_method_call(expr))
|
||||
}
|
||||
|
||||
fn from_func_call(expr: &ast::Expr) -> Option<String> {
|
||||
let call = match expr {
|
||||
ast::Expr::CallExpr(call) => call,
|
||||
_ => return None,
|
||||
};
|
||||
let func = match call.expr()? {
|
||||
ast::Expr::PathExpr(path) => path,
|
||||
_ => return None,
|
||||
};
|
||||
let ident = func.path()?.segment()?.name_ref()?.ident_token()?;
|
||||
normalize(ident.text())
|
||||
}
|
||||
|
||||
fn from_method_call(expr: &ast::Expr) -> Option<String> {
|
||||
let method = match expr {
|
||||
ast::Expr::MethodCallExpr(call) => call,
|
||||
_ => return None,
|
||||
};
|
||||
let ident = method.name_ref()?.ident_token()?;
|
||||
let mut name = ident.text();
|
||||
|
||||
if USELESS_METHODS.contains(&name) {
|
||||
return None;
|
||||
}
|
||||
|
||||
for prefix in USELESS_METHOD_PREFIXES {
|
||||
if let Some(suffix) = name.strip_prefix(prefix) {
|
||||
name = suffix;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
normalize(&name)
|
||||
}
|
||||
|
||||
fn from_param(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
|
||||
let arg_list = expr.syntax().parent().and_then(ast::ArgList::cast)?;
|
||||
let args_parent = arg_list.syntax().parent()?;
|
||||
let func = match_ast! {
|
||||
match args_parent {
|
||||
ast::CallExpr(call) => {
|
||||
let func = call.expr()?;
|
||||
let func_ty = sema.type_of_expr(&func)?;
|
||||
func_ty.as_callable(sema.db)?
|
||||
},
|
||||
ast::MethodCallExpr(method) => sema.resolve_method_call_as_callable(&method)?,
|
||||
_ => return None,
|
||||
}
|
||||
};
|
||||
|
||||
let (idx, _) = arg_list.args().find_position(|it| it == expr).unwrap();
|
||||
let (pat, _) = func.params(sema.db).into_iter().nth(idx)?;
|
||||
let pat = match pat? {
|
||||
either::Either::Right(pat) => pat,
|
||||
_ => return None,
|
||||
};
|
||||
let name = var_name_from_pat(&pat)?;
|
||||
normalize(&name.to_string())
|
||||
}
|
||||
|
||||
fn var_name_from_pat(pat: &ast::Pat) -> Option<ast::Name> {
|
||||
match pat {
|
||||
ast::Pat::IdentPat(var) => var.name(),
|
||||
ast::Pat::RefPat(ref_pat) => var_name_from_pat(&ref_pat.pat()?),
|
||||
ast::Pat::BoxPat(box_pat) => var_name_from_pat(&box_pat.pat()?),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_type(expr: &ast::Expr, sema: &Semantics<'_, RootDatabase>) -> Option<String> {
|
||||
let ty = sema.type_of_expr(expr)?;
|
||||
let ty = ty.remove_ref().unwrap_or(ty);
|
||||
|
||||
name_of_type(&ty, sema.db)
|
||||
}
|
||||
|
||||
fn name_of_type(ty: &hir::Type, db: &RootDatabase) -> Option<String> {
|
||||
let name = if let Some(adt) = ty.as_adt() {
|
||||
let name = adt.name(db).to_string();
|
||||
|
||||
if WRAPPER_TYPES.contains(&name.as_str()) {
|
||||
let inner_ty = ty.type_parameters().next()?;
|
||||
return name_of_type(&inner_ty, db);
|
||||
}
|
||||
|
||||
name
|
||||
} else if let Some(trait_) = ty.as_dyn_trait() {
|
||||
trait_name(&trait_, db)?
|
||||
} else if let Some(traits) = ty.as_impl_traits(db) {
|
||||
let mut iter = traits.into_iter().filter_map(|t| trait_name(&t, db));
|
||||
let name = iter.next()?;
|
||||
if iter.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
name
|
||||
} else {
|
||||
return None;
|
||||
};
|
||||
normalize(&name)
|
||||
}
|
||||
|
||||
fn trait_name(trait_: &hir::Trait, db: &RootDatabase) -> Option<String> {
|
||||
let name = trait_.name(db).to_string();
|
||||
if USELESS_TRAITS.contains(&name.as_str()) {
|
||||
return None;
|
||||
}
|
||||
Some(name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::tests::check_name_suggestion;
|
||||
|
||||
mod from_func_call {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_args() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_func_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0bar()$0
|
||||
}"#,
|
||||
"bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_arg() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_func_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0bar(1)$0
|
||||
}"#,
|
||||
"bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_args() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_func_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0bar(1, 2, 3)$0
|
||||
}"#,
|
||||
"bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn path() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_func_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0i32::bar(1, 2, 3)$0
|
||||
}"#,
|
||||
"bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_params() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_func_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0bar::<i32>(1, 2, 3)$0
|
||||
}"#,
|
||||
"bar",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_method_call {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn no_args() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_method_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0bar.frobnicate()$0
|
||||
}"#,
|
||||
"frobnicate",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_params() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_method_call(e),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0bar.frobnicate::<i32, u32>()$0
|
||||
}"#,
|
||||
"frobnicate",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_name() {
|
||||
check_name_suggestion(
|
||||
|e, _| from_method_call(e),
|
||||
r#"
|
||||
struct Args;
|
||||
struct Config;
|
||||
impl Args {
|
||||
fn to_config(&self) -> Config {}
|
||||
}
|
||||
fn foo() {
|
||||
$0Args.to_config()$0;
|
||||
}"#,
|
||||
"config",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_param {
|
||||
use crate::tests::check_name_suggestion_not_applicable;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn plain_func() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar(n: i32, m: u32);
|
||||
fn foo() {
|
||||
bar($01$0, 2)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mut_param() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar(mut n: i32, m: u32);
|
||||
fn foo() {
|
||||
bar($01$0, 2)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn func_does_not_exist() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_param,
|
||||
r#"
|
||||
fn foo() {
|
||||
bar($01$0, 2)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unnamed_param() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar(_: i32, m: u32);
|
||||
fn foo() {
|
||||
bar($01$0, 2)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_pat() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar((n, k): (i32, i32), m: u32);
|
||||
fn foo() {
|
||||
bar($0(1, 2)$0, 3)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_pat() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar(&n: &i32, m: u32);
|
||||
fn foo() {
|
||||
bar($0&1$0, 3)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_pat() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar(box n: &i32, m: u32);
|
||||
fn foo() {
|
||||
bar($01$0, 3)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn param_out_of_index() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar(n: i32, m: u32);
|
||||
fn foo() {
|
||||
bar(1, 2, $03$0)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_param_resolved() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar<T>(n: T, m: u32);
|
||||
fn foo() {
|
||||
bar($01$0, 2)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generic_param_unresolved() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
fn bar<T>(n: T, m: u32);
|
||||
fn foo<T>(x: T) {
|
||||
bar($0x$0, 2)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: i32, m: u32);
|
||||
}
|
||||
fn foo() {
|
||||
S.bar($01$0, 2)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_ufcs() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: i32, m: u32);
|
||||
}
|
||||
fn foo() {
|
||||
S::bar(&S, $01$0, 2)
|
||||
}"#,
|
||||
"n",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_self() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_param,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(&self, n: i32, m: u32);
|
||||
}
|
||||
fn foo() {
|
||||
S::bar($0&S$0, 1, 2)
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_self_named() {
|
||||
check_name_suggestion(
|
||||
from_param,
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn bar(strukt: &Self, n: i32, m: u32);
|
||||
}
|
||||
fn foo() {
|
||||
S::bar($0&S$0, 1, 2)
|
||||
}"#,
|
||||
"strukt",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod from_type {
|
||||
use crate::tests::check_name_suggestion_not_applicable;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn i32() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_type,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: i32 = $01$0;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn u64() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_type,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: u64 = $01$0;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bool() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_type,
|
||||
r#"
|
||||
fn foo() {
|
||||
let _: bool = $0true$0;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_unit() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
struct Seed;
|
||||
fn foo() {
|
||||
let _ = $0Seed$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_unit_to_snake() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
struct SeedState;
|
||||
fn foo() {
|
||||
let _ = $0SeedState$0;
|
||||
}"#,
|
||||
"seed_state",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_single_arg() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
struct Seed(u32);
|
||||
fn foo() {
|
||||
let _ = $0Seed(0)$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struct_with_fields() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
struct Seed { value: u32 }
|
||||
fn foo() {
|
||||
let _ = $0Seed { value: 0 }$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
enum Kind { A, B }
|
||||
fn foo() {
|
||||
let _ = $0Kind::A$0;
|
||||
}"#,
|
||||
"kind",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_generic_resolved() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
enum Kind<T> { A(T), B }
|
||||
fn foo() {
|
||||
let _ = $0Kind::A(1)$0;
|
||||
}"#,
|
||||
"kind",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn enum_generic_unresolved() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
enum Kind<T> { A(T), B }
|
||||
fn foo<T>(x: T) {
|
||||
let _ = $0Kind::A(x)$0;
|
||||
}"#,
|
||||
"kind",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dyn_trait() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
trait DynHandler {}
|
||||
fn bar() -> dyn DynHandler {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"dyn_handler",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_trait() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
trait StaticHandler {}
|
||||
fn bar() -> impl StaticHandler {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"static_handler",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_trait_plus_clone() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
trait StaticHandler {}
|
||||
trait Clone {}
|
||||
fn bar() -> impl StaticHandler + Clone {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"static_handler",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_trait_plus_lifetime() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
trait StaticHandler {}
|
||||
trait Clone {}
|
||||
fn bar<'a>(&'a i32) -> impl StaticHandler + 'a {}
|
||||
fn foo() {
|
||||
$0bar(&1)$0;
|
||||
}"#,
|
||||
"static_handler",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_trait_plus_trait() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_type,
|
||||
r#"
|
||||
trait Handler {}
|
||||
trait StaticHandler {}
|
||||
fn bar() -> impl StaticHandler + Handler {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ref_value() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
struct Seed;
|
||||
fn bar() -> &Seed {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_value() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
struct Box<T>(*const T);
|
||||
struct Seed;
|
||||
fn bar() -> Box<Seed> {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_generic() {
|
||||
check_name_suggestion_not_applicable(
|
||||
from_type,
|
||||
r#"
|
||||
struct Box<T>(*const T);
|
||||
fn bar<T>() -> Box<T> {}
|
||||
fn foo<T>() {
|
||||
$0bar::<T>()$0;
|
||||
}"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn option_value() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
enum Option<T> { Some(T) }
|
||||
struct Seed;
|
||||
fn bar() -> Option<Seed> {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn result_value() {
|
||||
check_name_suggestion(
|
||||
from_type,
|
||||
r#"
|
||||
enum Result<T, E> { Ok(T), Err(E) }
|
||||
struct Seed;
|
||||
struct Error;
|
||||
fn bar() -> Result<Seed, Error> {}
|
||||
fn foo() {
|
||||
$0bar()$0;
|
||||
}"#,
|
||||
"seed",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod variable {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn ref_call() {
|
||||
check_name_suggestion(
|
||||
|e, c| Some(variable(e, c)),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0&bar(1, 3)$0
|
||||
}"#,
|
||||
"bar",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn name_to_string() {
|
||||
check_name_suggestion(
|
||||
|e, c| Some(variable(e, c)),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0function.name().to_string()$0
|
||||
}"#,
|
||||
"name",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_useless_method() {
|
||||
check_name_suggestion(
|
||||
|e, c| Some(variable(e, c)),
|
||||
r#"
|
||||
fn foo() {
|
||||
$0function.name().as_ref().unwrap().to_string()$0
|
||||
}"#,
|
||||
"name",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -474,7 +474,7 @@ pub(crate) fn folding_range(
|
||||
let kind = match fold.kind {
|
||||
FoldKind::Comment => Some(lsp_types::FoldingRangeKind::Comment),
|
||||
FoldKind::Imports => Some(lsp_types::FoldingRangeKind::Imports),
|
||||
FoldKind::Mods | FoldKind::Block | FoldKind::ArgList => None,
|
||||
FoldKind::Mods | FoldKind::Block | FoldKind::ArgList | FoldKind::Region => None,
|
||||
};
|
||||
|
||||
let range = range(line_index, fold.range);
|
||||
|
Loading…
Reference in New Issue
Block a user