mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-01 15:01:51 +00:00
Fallback to method resolution on unresolved field access with matching method name
This commit is contained in:
parent
9c3de09f6d
commit
35fbc0210c
@ -579,7 +579,7 @@ impl InferenceContext<'_> {
|
||||
}
|
||||
ty
|
||||
}
|
||||
Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name),
|
||||
Expr::Field { expr, name } => self.infer_field_access(tgt_expr, *expr, name, expected),
|
||||
Expr::Await { expr } => {
|
||||
let inner_ty = self.infer_expr_inner(*expr, &Expectation::none());
|
||||
self.resolve_associated_type(inner_ty, self.resolve_future_future_output())
|
||||
@ -1456,7 +1456,13 @@ impl InferenceContext<'_> {
|
||||
})
|
||||
}
|
||||
|
||||
fn infer_field_access(&mut self, tgt_expr: ExprId, receiver: ExprId, name: &Name) -> Ty {
|
||||
fn infer_field_access(
|
||||
&mut self,
|
||||
tgt_expr: ExprId,
|
||||
receiver: ExprId,
|
||||
name: &Name,
|
||||
expected: &Expectation,
|
||||
) -> Ty {
|
||||
let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none());
|
||||
|
||||
if name.is_missing() {
|
||||
@ -1482,28 +1488,42 @@ impl InferenceContext<'_> {
|
||||
ty
|
||||
}
|
||||
None => {
|
||||
// no field found,
|
||||
let method_with_same_name_exists = {
|
||||
self.get_traits_in_scope();
|
||||
|
||||
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
|
||||
method_resolution::lookup_method(
|
||||
self.db,
|
||||
&canonicalized_receiver.value,
|
||||
self.table.trait_env.clone(),
|
||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||
VisibleFromModule::Filter(self.resolver.module()),
|
||||
name,
|
||||
)
|
||||
.is_some()
|
||||
};
|
||||
// no field found, lets attempt to resolve it like a function so that IDE things
|
||||
// work out while people are typing
|
||||
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
|
||||
let resolved = method_resolution::lookup_method(
|
||||
self.db,
|
||||
&canonicalized_receiver.value,
|
||||
self.table.trait_env.clone(),
|
||||
self.get_traits_in_scope().as_ref().left_or_else(|&it| it),
|
||||
VisibleFromModule::Filter(self.resolver.module()),
|
||||
name,
|
||||
);
|
||||
self.result.diagnostics.push(InferenceDiagnostic::UnresolvedField {
|
||||
expr: tgt_expr,
|
||||
receiver: receiver_ty,
|
||||
receiver: receiver_ty.clone(),
|
||||
name: name.clone(),
|
||||
method_with_same_name_exists,
|
||||
method_with_same_name_exists: resolved.is_some(),
|
||||
});
|
||||
self.err_ty()
|
||||
match resolved {
|
||||
Some((adjust, func, _)) => {
|
||||
let (ty, adjustments) = adjust.apply(&mut self.table, receiver_ty);
|
||||
let generics = generics(self.db.upcast(), func.into());
|
||||
let substs = self.substs_for_method_call(generics, None);
|
||||
self.write_expr_adj(receiver, adjustments);
|
||||
self.write_method_resolution(tgt_expr, func, substs.clone());
|
||||
|
||||
self.check_method_call(
|
||||
tgt_expr,
|
||||
&[],
|
||||
self.db.value_ty(func.into()),
|
||||
substs,
|
||||
ty,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
None => self.err_ty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1517,7 +1537,7 @@ impl InferenceContext<'_> {
|
||||
generic_args: Option<&GenericArgs>,
|
||||
expected: &Expectation,
|
||||
) -> Ty {
|
||||
let receiver_ty = self.infer_expr(receiver, &Expectation::none());
|
||||
let receiver_ty = self.infer_expr_inner(receiver, &Expectation::none());
|
||||
let canonicalized_receiver = self.canonicalize(receiver_ty.clone());
|
||||
|
||||
let resolved = method_resolution::lookup_method(
|
||||
@ -1568,23 +1588,32 @@ impl InferenceContext<'_> {
|
||||
)
|
||||
}
|
||||
};
|
||||
self.check_method_call(tgt_expr, args, method_ty, substs, receiver_ty, expected)
|
||||
}
|
||||
|
||||
fn check_method_call(
|
||||
&mut self,
|
||||
tgt_expr: ExprId,
|
||||
args: &[ExprId],
|
||||
method_ty: Binders<Ty>,
|
||||
substs: Substitution,
|
||||
receiver_ty: Ty,
|
||||
expected: &Expectation,
|
||||
) -> Ty {
|
||||
let method_ty = method_ty.substitute(Interner, &substs);
|
||||
self.register_obligations_for_call(&method_ty);
|
||||
let (formal_receiver_ty, param_tys, ret_ty, is_varargs) =
|
||||
let ((formal_receiver_ty, param_tys), ret_ty, is_varargs) =
|
||||
match method_ty.callable_sig(self.db) {
|
||||
Some(sig) => {
|
||||
Some(sig) => (
|
||||
if !sig.params().is_empty() {
|
||||
(
|
||||
sig.params()[0].clone(),
|
||||
sig.params()[1..].to_vec(),
|
||||
sig.ret().clone(),
|
||||
sig.is_varargs,
|
||||
)
|
||||
(sig.params()[0].clone(), sig.params()[1..].to_vec())
|
||||
} else {
|
||||
(self.err_ty(), Vec::new(), sig.ret().clone(), sig.is_varargs)
|
||||
}
|
||||
}
|
||||
None => (self.err_ty(), Vec::new(), self.err_ty(), true),
|
||||
(self.err_ty(), Vec::new())
|
||||
},
|
||||
sig.ret().clone(),
|
||||
sig.is_varargs,
|
||||
),
|
||||
None => ((self.err_ty(), Vec::new()), self.err_ty(), true),
|
||||
};
|
||||
self.unify(&formal_receiver_ty, &receiver_ty);
|
||||
|
||||
|
@ -17,7 +17,7 @@ use hir_def::{
|
||||
nameres::MacroSubNs,
|
||||
resolver::{self, HasResolver, Resolver, TypeNs},
|
||||
type_ref::Mutability,
|
||||
AsMacroCall, DefWithBodyId, FieldId, FunctionId, MacroId, TraitId, VariantId,
|
||||
AsMacroCall, DefWithBodyId, FunctionId, MacroId, TraitId, VariantId,
|
||||
};
|
||||
use hir_expand::{
|
||||
db::ExpandDatabase, files::InRealFile, name::AsName, ExpansionInfo, InMacroFile, MacroCallId,
|
||||
@ -198,20 +198,6 @@ impl<'db, DB: HirDatabase> Semantics<'db, DB> {
|
||||
self.imp.descend_node_at_offset(node, offset).filter_map(|mut it| it.find_map(N::cast))
|
||||
}
|
||||
|
||||
pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
|
||||
self.imp.resolve_method_call(call).map(Function::from)
|
||||
}
|
||||
|
||||
/// Attempts to resolve this call expression as a method call falling back to resolving it as a field.
|
||||
pub fn resolve_method_call_field_fallback(
|
||||
&self,
|
||||
call: &ast::MethodCallExpr,
|
||||
) -> Option<Either<Function, Field>> {
|
||||
self.imp
|
||||
.resolve_method_call_fallback(call)
|
||||
.map(|it| it.map_left(Function::from).map_right(Field::from))
|
||||
}
|
||||
|
||||
pub fn resolve_await_to_poll(&self, await_expr: &ast::AwaitExpr) -> Option<Function> {
|
||||
self.imp.resolve_await_to_poll(await_expr).map(Function::from)
|
||||
}
|
||||
@ -1048,14 +1034,15 @@ impl<'db> SemanticsImpl<'db> {
|
||||
self.analyze(pat.syntax())?.binding_mode_of_pat(self.db, pat)
|
||||
}
|
||||
|
||||
fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<FunctionId> {
|
||||
pub fn resolve_method_call(&self, call: &ast::MethodCallExpr) -> Option<Function> {
|
||||
self.analyze(call.syntax())?.resolve_method_call(self.db, call)
|
||||
}
|
||||
|
||||
fn resolve_method_call_fallback(
|
||||
/// Attempts to resolve this call expression as a method call falling back to resolving it as a field.
|
||||
pub fn resolve_method_call_fallback(
|
||||
&self,
|
||||
call: &ast::MethodCallExpr,
|
||||
) -> Option<Either<FunctionId, FieldId>> {
|
||||
) -> Option<Either<Function, Field>> {
|
||||
self.analyze(call.syntax())?.resolve_method_call_fallback(self.db, call)
|
||||
}
|
||||
|
||||
@ -1087,6 +1074,13 @@ impl<'db> SemanticsImpl<'db> {
|
||||
self.analyze(field.syntax())?.resolve_field(self.db, field)
|
||||
}
|
||||
|
||||
pub fn resolve_field_fallback(
|
||||
&self,
|
||||
field: &ast::FieldExpr,
|
||||
) -> Option<Either<Field, Function>> {
|
||||
self.analyze(field.syntax())?.resolve_field_fallback(self.db, field)
|
||||
}
|
||||
|
||||
pub fn resolve_record_field(
|
||||
&self,
|
||||
field: &ast::RecordExprField,
|
||||
@ -1298,7 +1292,7 @@ impl<'db> SemanticsImpl<'db> {
|
||||
return None;
|
||||
}
|
||||
|
||||
let func = self.resolve_method_call(method_call_expr).map(Function::from)?;
|
||||
let func = self.resolve_method_call(method_call_expr)?;
|
||||
let res = match func.self_param(self.db)?.access(self.db) {
|
||||
Access::Shared | Access::Exclusive => true,
|
||||
Access::Owned => false,
|
||||
|
@ -280,25 +280,49 @@ impl SourceAnalyzer {
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
call: &ast::MethodCallExpr,
|
||||
) -> Option<FunctionId> {
|
||||
) -> Option<Function> {
|
||||
let expr_id = self.expr_id(db, &call.clone().into())?;
|
||||
let (f_in_trait, substs) = self.infer.as_ref()?.method_resolution(expr_id)?;
|
||||
|
||||
Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs))
|
||||
Some(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs).into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_method_call_fallback(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
call: &ast::MethodCallExpr,
|
||||
) -> Option<Either<FunctionId, FieldId>> {
|
||||
) -> Option<Either<Function, Field>> {
|
||||
let expr_id = self.expr_id(db, &call.clone().into())?;
|
||||
let inference_result = self.infer.as_ref()?;
|
||||
match inference_result.method_resolution(expr_id) {
|
||||
Some((f_in_trait, substs)) => {
|
||||
Some(Either::Left(self.resolve_impl_method_or_trait_def(db, f_in_trait, substs)))
|
||||
}
|
||||
None => inference_result.field_resolution(expr_id).map(Either::Right),
|
||||
Some((f_in_trait, substs)) => Some(Either::Left(
|
||||
self.resolve_impl_method_or_trait_def(db, f_in_trait, substs).into(),
|
||||
)),
|
||||
None => inference_result.field_resolution(expr_id).map(Into::into).map(Either::Right),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_field(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
field: &ast::FieldExpr,
|
||||
) -> Option<Field> {
|
||||
let expr_id = self.expr_id(db, &field.clone().into())?;
|
||||
self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_field_fallback(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
field: &ast::FieldExpr,
|
||||
) -> Option<Either<Field, Function>> {
|
||||
let expr_id = self.expr_id(db, &field.clone().into())?;
|
||||
let inference_result = self.infer.as_ref()?;
|
||||
match inference_result.field_resolution(expr_id) {
|
||||
Some(field) => Some(Either::Left(field.into())),
|
||||
None => inference_result.method_resolution(expr_id).map(|(f, substs)| {
|
||||
Either::Right(self.resolve_impl_method_or_trait_def(db, f, substs).into())
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,15 +441,6 @@ impl SourceAnalyzer {
|
||||
Some(self.resolve_impl_method_or_trait_def(db, op_fn, substs))
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_field(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
field: &ast::FieldExpr,
|
||||
) -> Option<Field> {
|
||||
let expr_id = self.expr_id(db, &field.clone().into())?;
|
||||
self.infer.as_ref()?.field_resolution(expr_id).map(|it| it.into())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_record_field(
|
||||
&self,
|
||||
db: &dyn HirDatabase,
|
||||
|
@ -492,7 +492,7 @@ impl NameRefClass {
|
||||
match_ast! {
|
||||
match parent {
|
||||
ast::MethodCallExpr(method_call) => {
|
||||
sema.resolve_method_call_field_fallback(&method_call)
|
||||
sema.resolve_method_call_fallback(&method_call)
|
||||
.map(|it| {
|
||||
it.map_left(Definition::Function)
|
||||
.map_right(Definition::Field)
|
||||
@ -500,9 +500,12 @@ impl NameRefClass {
|
||||
})
|
||||
},
|
||||
ast::FieldExpr(field_expr) => {
|
||||
sema.resolve_field(&field_expr)
|
||||
.map(Definition::Field)
|
||||
.map(NameRefClass::Definition)
|
||||
sema.resolve_field_fallback(&field_expr)
|
||||
.map(|it| {
|
||||
it.map_left(Definition::Field)
|
||||
.map_right(Definition::Function)
|
||||
.either(NameRefClass::Definition, NameRefClass::Definition)
|
||||
})
|
||||
},
|
||||
ast::RecordPatField(record_pat_field) => {
|
||||
sema.resolve_record_pat_field(&record_pat_field)
|
||||
|
@ -6698,3 +6698,30 @@ foo!(r"{$0aaaaa}");
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn method_call_without_parens() {
|
||||
check(
|
||||
r#"
|
||||
struct S;
|
||||
impl S {
|
||||
fn foo<T>(&self, t: T) {}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
S.foo$0;
|
||||
}
|
||||
"#,
|
||||
expect![[r#"
|
||||
*foo*
|
||||
|
||||
```rust
|
||||
test::S
|
||||
```
|
||||
|
||||
```rust
|
||||
fn foo<T>(&self, t: T)
|
||||
```
|
||||
"#]],
|
||||
);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user