diff --git a/crates/hir/src/analysis/name_resolution/method_selection.rs b/crates/hir/src/analysis/name_resolution/method_selection.rs index 33ce451d30..6b4f07e0e3 100644 --- a/crates/hir/src/analysis/name_resolution/method_selection.rs +++ b/crates/hir/src/analysis/name_resolution/method_selection.rs @@ -114,6 +114,14 @@ struct CandidateAssembler<'db> { candidates: AssembledCandidates<'db>, } +fn receiver_is_ty_param_like<'db>(db: &'db dyn HirAnalysisDb, ty: TyId<'db>) -> bool { + let receiver_ty = ty.as_capability(db).map(|(_, inner)| inner).unwrap_or(ty); + matches!( + receiver_ty.base_ty(db).data(db), + TyData::TyParam(_) | TyData::AssocTy(_) | TyData::QualifiedTy(_) + ) +} + impl<'db> CandidateAssembler<'db> { fn assemble(mut self) -> AssembledCandidates<'db> { if self.trait_.is_none() { @@ -143,10 +151,7 @@ impl<'db> CandidateAssembler<'db> { // // In that case, rely on in-scope bounds (`assumptions`) to provide method // candidates. - let receiver_is_ty_param = matches!( - self.receiver_ty.value.base_ty(self.db).data(self.db), - TyData::TyParam(_) | TyData::AssocTy(_) | TyData::QualifiedTy(_) - ); + let receiver_is_ty_param = receiver_is_ty_param_like(self.db, self.receiver_ty.value); if !receiver_is_ty_param { let search_ingots = [ @@ -291,9 +296,23 @@ impl<'db> MethodSelector<'db> { Ok(self.check_inst(def, method)) } - _ => Err(MethodSelectionError::AmbiguousTraitMethod( - visible_traits.into_iter().map(|cand| cand.0).collect(), - )), + _ => { + // Some candidates are equivalent after trait solving (e.g., an explicit + // bound and an implied/blanket-derived bound for the same method). + // Collapse by the selected method candidate before reporting ambiguity. + let mut selected = IndexSet::default(); + for (inst, method) in visible_traits.iter().copied() { + selected.insert(self.check_inst(inst, method)); + } + + if selected.len() == 1 { + return Ok(*selected.iter().next().unwrap()); + } + + Err(MethodSelectionError::AmbiguousTraitMethod( + visible_traits.into_iter().map(|cand| cand.0).collect(), + )) + } } } @@ -316,10 +335,7 @@ impl<'db> MethodSelector<'db> { // introducing fresh inference vars. Otherwise, unconstrained trait args // can trigger spurious "type annotation needed" diagnostics on method calls // whose signatures don't mention those args (e.g. `AbiDecoder::read_word`). - let receiver_is_ty_param = matches!( - self.receiver.value.base_ty(self.db).data(self.db), - TyData::TyParam(_) | TyData::AssocTy(_) | TyData::QualifiedTy(_) - ); + let receiver_is_ty_param = receiver_is_ty_param_like(self.db, self.receiver.value); let canonical_cand = Canonicalized::new(self.db, inst); let inst = if receiver_is_ty_param { diff --git a/crates/hir/src/analysis/ty/ty_check/mod.rs b/crates/hir/src/analysis/ty/ty_check/mod.rs index d0737ae601..b48feccd9e 100644 --- a/crates/hir/src/analysis/ty/ty_check/mod.rs +++ b/crates/hir/src/analysis/ty/ty_check/mod.rs @@ -498,6 +498,30 @@ impl<'db> TyChecker<'db> { result }; + let dedup_equivalent_insts = |insts: Vec>| -> Vec> { + let mut unique: Vec> = Vec::new(); + 'outer: for inst in insts { + for &seen in &unique { + if inst.def(db) != seen.def(db) { + continue; + } + + let mut table = UnificationTable::new(db); + let lhs = table.instantiate_with_fresh_vars( + crate::analysis::ty::binder::Binder::bind(inst), + ); + let rhs = table.instantiate_with_fresh_vars( + crate::analysis::ty::binder::Binder::bind(seen), + ); + if table.unify(lhs, rhs).is_ok() { + continue 'outer; + } + } + unique.push(inst); + } + unique + }; + // Fixed-point pass over deferred tasks. let mut progressed = true; while progressed { @@ -528,6 +552,25 @@ impl<'db> TyChecker<'db> { progressed = true; } } + GoalSatisfiability::NeedsConfirmation(ambiguous) => { + let cands = dedup_equivalent_insts( + ambiguous + .iter() + .map(|s| { + canonical_inst + .extract_solution(&mut self.table, *s) + .inst + }) + .collect(), + ); + if let [solution] = cands.as_slice() { + if self.table.unify(inst, *solution).is_ok() { + progressed = true; + } + } else { + self.env.register_confirmation(inst, span); + } + } _ => self.env.register_confirmation(inst, span), } } @@ -568,6 +611,7 @@ impl<'db> TyChecker<'db> { ) }) .collect(); + let viable = dedup_equivalent_insts(viable); if let [inst] = viable.as_slice() { if self.env.callable_expr(pending.expr).is_none() { @@ -658,14 +702,18 @@ impl<'db> TyChecker<'db> { canonical_inst.value, ) { GoalSatisfiability::NeedsConfirmation(ambiguous) => { - let cands = ambiguous - .iter() - .map(|s| canonical_inst.extract_solution(&mut self.table, *s).inst) - .collect::>(); - if !inst.self_ty(db).has_var(db) { + let cands = dedup_equivalent_insts( + ambiguous + .iter() + .map(|s| { + canonical_inst.extract_solution(&mut self.table, *s).inst + }) + .collect(), + ); + if cands.len() > 1 && !inst.self_ty(db).has_var(db) { self.push_diag(BodyDiag::AmbiguousTraitInst { primary: span.clone(), - cands, + cands: cands.into_iter().collect(), }); } } @@ -704,7 +752,7 @@ impl<'db> TyChecker<'db> { _ => continue, }; - let viable: thin_vec::ThinVec<_> = pending + let viable: Vec<_> = pending .candidates .iter() .copied() @@ -720,11 +768,12 @@ impl<'db> TyChecker<'db> { ) }) .collect(); + let viable = dedup_equivalent_insts(viable); if viable.len() > 1 { self.push_diag(BodyDiag::AmbiguousTrait { primary: pending.span.clone(), method_name: pending.method_name, - traits: viable, + traits: viable.into_iter().collect(), }); } } diff --git a/crates/hir/src/core/lower/params.rs b/crates/hir/src/core/lower/params.rs index 708471af85..0e24cc323d 100644 --- a/crates/hir/src/core/lower/params.rs +++ b/crates/hir/src/core/lower/params.rs @@ -164,8 +164,15 @@ impl<'db> FuncParam<'db> { let is_label_suppressed = ast.is_label_suppressed(); let name = ast.name().map(|ast| FuncParamName::lower_ast(ctxt, ast)); - let self_ty_fallback = - name.is_some_and(|name| name.is_self(ctxt.db())) && ast.ty().is_none(); + let name_is_self = name.is_some_and(|name| name.is_self(ctxt.db())); + let self_ty_fallback = name_is_self && ast.ty().is_none(); + let receiver_prefixed_mode = if is_ref { + Some(TypeMode::Ref) + } else if is_own { + Some(TypeMode::Own) + } else { + None + }; let ty = if self_ty_fallback { let fallback_self = TypeId::fallback_self_ty(ctxt.db()); @@ -190,7 +197,17 @@ impl<'db> FuncParam<'db> { Partial::Present(fallback_self) } } else { - TypeId::lower_ast_partial(ctxt, ast.ty()) + let base_ty = TypeId::lower_ast_partial(ctxt, ast.ty()); + if name_is_self && let Some(mode) = receiver_prefixed_mode { + base_ty + .to_opt() + .map(|inner| { + TypeId::new(ctxt.db(), TypeKind::Mode(mode, Partial::Present(inner))) + }) + .into() + } else { + base_ty + } }; let mode = if ty diff --git a/crates/uitest/fixtures/mir_check/typed_own_self_prefix_moves_receiver.fe b/crates/uitest/fixtures/mir_check/typed_own_self_prefix_moves_receiver.fe new file mode 100644 index 0000000000..5665e83b85 --- /dev/null +++ b/crates/uitest/fixtures/mir_check/typed_own_self_prefix_moves_receiver.fe @@ -0,0 +1,15 @@ +struct S { + value: u8, +} + +impl S { + fn consume(own self: Self) -> u8 { + self.value + } +} + +fn use_after_consume() -> u8 { + let s = S { value: 1 } + let value = s.consume() + value + s.value +} diff --git a/crates/uitest/fixtures/mir_check/typed_own_self_prefix_moves_receiver.snap b/crates/uitest/fixtures/mir_check/typed_own_self_prefix_moves_receiver.snap new file mode 100644 index 0000000000..0816ed1c01 --- /dev/null +++ b/crates/uitest/fixtures/mir_check/typed_own_self_prefix_moves_receiver.snap @@ -0,0 +1,13 @@ +--- +source: crates/uitest/tests/mir_check.rs +assertion_line: 32 +expression: res +input_file: fixtures/mir_check/typed_own_self_prefix_moves_receiver.fe +--- +error[10-0002]: move conflict in `fn use_after_consume` + ┌─ typed_own_self_prefix_moves_receiver.fe:14:5 + │ +13 │ let value = s.consume() + │ - `s` is moved here +14 │ value + s.value + │ ^^^^^^^^^^^^^^^ cannot use a value after it was moved diff --git a/crates/uitest/fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.fe b/crates/uitest/fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.fe new file mode 100644 index 0000000000..f86b893f4e --- /dev/null +++ b/crates/uitest/fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.fe @@ -0,0 +1,9 @@ +struct S { + value: u8, +} + +impl S { + fn read(ref self: Self) -> ref u8 { + ref self.value + } +} diff --git a/crates/uitest/fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.snap b/crates/uitest/fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.snap new file mode 100644 index 0000000000..54cb790846 --- /dev/null +++ b/crates/uitest/fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.snap @@ -0,0 +1,7 @@ +--- +source: crates/uitest/tests/mir_check.rs +assertion_line: 32 +expression: res +input_file: fixtures/mir_check/typed_ref_self_prefix_allows_return_borrow.fe +--- +