feat: add assoc func quickfix for unresolved_method
diagnostic
This commit is contained in:
parent
34df29620a
commit
69410bb488
@ -217,6 +217,7 @@ pub enum InferenceDiagnostic {
|
|||||||
name: Name,
|
name: Name,
|
||||||
/// Contains the type the field resolves to
|
/// Contains the type the field resolves to
|
||||||
field_with_same_name: Option<Ty>,
|
field_with_same_name: Option<Ty>,
|
||||||
|
assoc_func_with_same_name: Option<AssocItemId>,
|
||||||
},
|
},
|
||||||
// FIXME: This should be emitted in body lowering
|
// FIXME: This should be emitted in body lowering
|
||||||
BreakOutsideOfLoop {
|
BreakOutsideOfLoop {
|
||||||
|
@ -1575,11 +1575,30 @@ impl InferenceContext<'_> {
|
|||||||
}
|
}
|
||||||
None => None,
|
None => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let assoc_func_with_same_name = method_resolution::iterate_method_candidates(
|
||||||
|
&canonicalized_receiver.value,
|
||||||
|
self.db,
|
||||||
|
self.table.trait_env.clone(),
|
||||||
|
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||||
|
VisibleFromModule::Filter(self.resolver.module()),
|
||||||
|
Some(method_name),
|
||||||
|
method_resolution::LookupMode::Path,
|
||||||
|
|_ty, item, visible| {
|
||||||
|
if visible {
|
||||||
|
Some(item)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall {
|
self.result.diagnostics.push(InferenceDiagnostic::UnresolvedMethodCall {
|
||||||
expr: tgt_expr,
|
expr: tgt_expr,
|
||||||
receiver: receiver_ty.clone(),
|
receiver: receiver_ty.clone(),
|
||||||
name: method_name.clone(),
|
name: method_name.clone(),
|
||||||
field_with_same_name: field_with_same_name_exists,
|
field_with_same_name: field_with_same_name_exists,
|
||||||
|
assoc_func_with_same_name,
|
||||||
});
|
});
|
||||||
(
|
(
|
||||||
receiver_ty,
|
receiver_ty,
|
||||||
|
@ -8,7 +8,7 @@ pub use hir_ty::diagnostics::{CaseType, IncorrectCase};
|
|||||||
use base_db::CrateId;
|
use base_db::CrateId;
|
||||||
use cfg::{CfgExpr, CfgOptions};
|
use cfg::{CfgExpr, CfgOptions};
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use hir_def::path::ModPath;
|
use hir_def::{path::ModPath, AssocItemId};
|
||||||
use hir_expand::{name::Name, HirFileId, InFile};
|
use hir_expand::{name::Name, HirFileId, InFile};
|
||||||
use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
|
use syntax::{ast, AstPtr, SyntaxError, SyntaxNodePtr, TextRange};
|
||||||
|
|
||||||
@ -215,6 +215,7 @@ pub struct UnresolvedMethodCall {
|
|||||||
pub receiver: Type,
|
pub receiver: Type,
|
||||||
pub name: Name,
|
pub name: Name,
|
||||||
pub field_with_same_name: Option<Type>,
|
pub field_with_same_name: Option<Type>,
|
||||||
|
pub assoc_func_with_same_name: Option<AssocItemId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -1680,6 +1680,7 @@ impl DefWithBody {
|
|||||||
receiver,
|
receiver,
|
||||||
name,
|
name,
|
||||||
field_with_same_name,
|
field_with_same_name,
|
||||||
|
assoc_func_with_same_name,
|
||||||
} => {
|
} => {
|
||||||
let expr = expr_syntax(*expr);
|
let expr = expr_syntax(*expr);
|
||||||
|
|
||||||
@ -1691,6 +1692,7 @@ impl DefWithBody {
|
|||||||
field_with_same_name: field_with_same_name
|
field_with_same_name: field_with_same_name
|
||||||
.clone()
|
.clone()
|
||||||
.map(|ty| Type::new(db, DefWithBodyId::from(self), ty)),
|
.map(|ty| Type::new(db, DefWithBodyId::from(self), ty)),
|
||||||
|
assoc_func_with_same_name: assoc_func_with_same_name.clone(),
|
||||||
}
|
}
|
||||||
.into(),
|
.into(),
|
||||||
)
|
)
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
use hir::{db::ExpandDatabase, HirDisplay};
|
use hir::{db::ExpandDatabase, AssocItem, HirDisplay};
|
||||||
use ide_db::{
|
use ide_db::{
|
||||||
assists::{Assist, AssistId, AssistKind},
|
assists::{Assist, AssistId, AssistKind},
|
||||||
base_db::FileRange,
|
base_db::FileRange,
|
||||||
label::Label,
|
label::Label,
|
||||||
source_change::SourceChange,
|
source_change::SourceChange,
|
||||||
};
|
};
|
||||||
use syntax::{ast, AstNode, TextRange};
|
use syntax::{
|
||||||
|
ast::{self, make, HasArgList},
|
||||||
|
AstNode, SmolStr, TextRange,
|
||||||
|
};
|
||||||
use text_edit::TextEdit;
|
use text_edit::TextEdit;
|
||||||
|
|
||||||
use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
|
use crate::{adjusted_display_range_new, Diagnostic, DiagnosticCode, DiagnosticsContext};
|
||||||
@ -17,15 +20,17 @@ pub(crate) fn unresolved_method(
|
|||||||
ctx: &DiagnosticsContext<'_>,
|
ctx: &DiagnosticsContext<'_>,
|
||||||
d: &hir::UnresolvedMethodCall,
|
d: &hir::UnresolvedMethodCall,
|
||||||
) -> Diagnostic {
|
) -> Diagnostic {
|
||||||
let field_suffix = if d.field_with_same_name.is_some() {
|
let suffix = if d.field_with_same_name.is_some() {
|
||||||
", but a field with a similar name exists"
|
", but a field with a similar name exists"
|
||||||
|
} else if d.assoc_func_with_same_name.is_some() {
|
||||||
|
", but an associated function with a similar name exists"
|
||||||
} else {
|
} else {
|
||||||
""
|
""
|
||||||
};
|
};
|
||||||
Diagnostic::new(
|
Diagnostic::new(
|
||||||
DiagnosticCode::RustcHardError("E0599"),
|
DiagnosticCode::RustcHardError("E0599"),
|
||||||
format!(
|
format!(
|
||||||
"no method `{}` on type `{}`{field_suffix}",
|
"no method `{}` on type `{}`{suffix}",
|
||||||
d.name.display(ctx.sema.db),
|
d.name.display(ctx.sema.db),
|
||||||
d.receiver.display(ctx.sema.db)
|
d.receiver.display(ctx.sema.db)
|
||||||
),
|
),
|
||||||
@ -46,11 +51,27 @@ pub(crate) fn unresolved_method(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> {
|
fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Vec<Assist>> {
|
||||||
if let Some(ty) = &d.field_with_same_name {
|
let field_fix = if let Some(ty) = &d.field_with_same_name {
|
||||||
field_fix(ctx, d, ty)
|
field_fix(ctx, d, ty)
|
||||||
} else {
|
} else {
|
||||||
// FIXME: add quickfix
|
// FIXME: add quickfix
|
||||||
None
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let assoc_func_fix = assoc_func_fix(ctx, d);
|
||||||
|
|
||||||
|
let mut fixes = vec![];
|
||||||
|
if let Some(field_fix) = field_fix {
|
||||||
|
fixes.push(field_fix);
|
||||||
|
}
|
||||||
|
if let Some(assoc_func_fix) = assoc_func_fix {
|
||||||
|
fixes.push(assoc_func_fix);
|
||||||
|
}
|
||||||
|
|
||||||
|
if fixes.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(fixes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +79,7 @@ fn field_fix(
|
|||||||
ctx: &DiagnosticsContext<'_>,
|
ctx: &DiagnosticsContext<'_>,
|
||||||
d: &hir::UnresolvedMethodCall,
|
d: &hir::UnresolvedMethodCall,
|
||||||
ty: &hir::Type,
|
ty: &hir::Type,
|
||||||
) -> Option<Vec<Assist>> {
|
) -> Option<Assist> {
|
||||||
if !ty.impls_fnonce(ctx.sema.db) {
|
if !ty.impls_fnonce(ctx.sema.db) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@ -78,7 +99,7 @@ fn field_fix(
|
|||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
Some(vec![Assist {
|
Some(Assist {
|
||||||
id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix),
|
id: AssistId("expected-method-found-field-fix", AssistKind::QuickFix),
|
||||||
label: Label::new("Use parentheses to call the value of the field".to_string()),
|
label: Label::new("Use parentheses to call the value of the field".to_string()),
|
||||||
group: None,
|
group: None,
|
||||||
@ -88,7 +109,93 @@ fn field_fix(
|
|||||||
(file_id, TextEdit::insert(range.end(), ")".to_owned())),
|
(file_id, TextEdit::insert(range.end(), ")".to_owned())),
|
||||||
])),
|
])),
|
||||||
trigger_signature_help: false,
|
trigger_signature_help: false,
|
||||||
}])
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assoc_func_fix(ctx: &DiagnosticsContext<'_>, d: &hir::UnresolvedMethodCall) -> Option<Assist> {
|
||||||
|
if let Some(assoc_item_id) = d.assoc_func_with_same_name {
|
||||||
|
let db = ctx.sema.db;
|
||||||
|
|
||||||
|
let expr_ptr = &d.expr;
|
||||||
|
let root = db.parse_or_expand(expr_ptr.file_id);
|
||||||
|
let expr: ast::Expr = expr_ptr.value.to_node(&root);
|
||||||
|
|
||||||
|
let call = ast::MethodCallExpr::cast(expr.syntax().clone())?;
|
||||||
|
let range = call.syntax().text_range();
|
||||||
|
|
||||||
|
let receiver = call.receiver()?;
|
||||||
|
let receiver_type = &ctx.sema.type_of_expr(&receiver)?.original;
|
||||||
|
|
||||||
|
let need_to_take_receiver_as_first_arg = match hir::AssocItem::from(assoc_item_id) {
|
||||||
|
AssocItem::Function(f) => {
|
||||||
|
let assoc_fn_params = f.assoc_fn_params(db);
|
||||||
|
if assoc_fn_params.is_empty() {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
assoc_fn_params
|
||||||
|
.first()
|
||||||
|
.map(|first_arg| {
|
||||||
|
// For generic type, say `Box`, take `Box::into_raw(b: Self)` as example,
|
||||||
|
// type of `b` is `Self`, which is `Box<T, A>`, containing unspecified generics.
|
||||||
|
// However, type of `receiver` is specified, it could be `Box<i32, Global>` or something like that,
|
||||||
|
// so `first_arg.ty() == receiver_type` evaluate to `false` here.
|
||||||
|
// Here add `first_arg.ty().as_adt() == receiver_type.as_adt()` as guard,
|
||||||
|
// apply `.as_adt()` over `Box<T, A>` or `Box<i32, Global>` gets `Box`, so we get `true` here.
|
||||||
|
|
||||||
|
// FIXME: it fails when type of `b` is `Box` with other generic param different from `receiver`
|
||||||
|
first_arg.ty() == receiver_type
|
||||||
|
|| first_arg.ty().as_adt() == receiver_type.as_adt()
|
||||||
|
})
|
||||||
|
.unwrap_or(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut receiver_type_adt_name = receiver_type.as_adt()?.name(db).to_smol_str().to_string();
|
||||||
|
|
||||||
|
let generic_parameters: Vec<SmolStr> = receiver_type.generic_parameters(db).collect();
|
||||||
|
// if receiver should be pass as first arg in the assoc func,
|
||||||
|
// we could omit generic parameters cause compiler can deduce it automatically
|
||||||
|
if !need_to_take_receiver_as_first_arg && !generic_parameters.is_empty() {
|
||||||
|
let generic_parameters = generic_parameters.join(", ").to_string();
|
||||||
|
receiver_type_adt_name =
|
||||||
|
format!("{}::<{}>", receiver_type_adt_name, generic_parameters);
|
||||||
|
}
|
||||||
|
|
||||||
|
let method_name = call.name_ref()?;
|
||||||
|
let assoc_func_call = format!("{}::{}()", receiver_type_adt_name, method_name);
|
||||||
|
|
||||||
|
let assoc_func_call = make::expr_path(make::path_from_text(&assoc_func_call));
|
||||||
|
|
||||||
|
let args: Vec<_> = if need_to_take_receiver_as_first_arg {
|
||||||
|
std::iter::once(receiver).chain(call.arg_list()?.args()).collect()
|
||||||
|
} else {
|
||||||
|
call.arg_list()?.args().collect()
|
||||||
|
};
|
||||||
|
let args = make::arg_list(args);
|
||||||
|
|
||||||
|
let assoc_func_call_expr_string = make::expr_call(assoc_func_call, args).to_string();
|
||||||
|
|
||||||
|
let file_id = ctx.sema.original_range_opt(call.receiver()?.syntax())?.file_id;
|
||||||
|
|
||||||
|
Some(Assist {
|
||||||
|
id: AssistId("method_call_to_assoc_func_call_fix", AssistKind::QuickFix),
|
||||||
|
label: Label::new(format!(
|
||||||
|
"Use associated func call instead: `{}`",
|
||||||
|
assoc_func_call_expr_string
|
||||||
|
)),
|
||||||
|
group: None,
|
||||||
|
target: range,
|
||||||
|
source_change: Some(SourceChange::from_text_edit(
|
||||||
|
file_id,
|
||||||
|
TextEdit::replace(range, assoc_func_call_expr_string),
|
||||||
|
)),
|
||||||
|
trigger_signature_help: false,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user