From 33696fa9cab5bec947147d65833233b957b5edd5 Mon Sep 17 00:00:00 2001 From: Frank Steffahn Date: Tue, 25 Aug 2020 17:30:46 +0200 Subject: [PATCH 01/11] Add Arc::into_inner for safely discarding Arcs without calling the destructor on the inner type. Mainly rebased and squashed from PR rust-lang/rust#79665, furthermore includes minor changes in comments. --- library/alloc/src/sync.rs | 145 ++++++++++++++++++++++++++++++++ library/alloc/src/sync/tests.rs | 32 +++++++ 2 files changed, 177 insertions(+) diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index d833d4d1dfbd3..a31b597930c83 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -654,6 +654,20 @@ impl Arc { /// /// This will succeed even if there are outstanding weak references. /// + // FIXME: when `Arc::into_inner` is stabilized, add this paragraph: + /* + /// It is strongly recommended to use [`Arc::into_inner`] instead if you don't + /// want to keep the `Arc` in the [`Err`] case. + /// Immediately dropping the [`Err`] payload, like in the expression + /// `Arc::try_unwrap(this).ok()`, can still cause the strong count to + /// drop to zero and the inner value of the `Arc` to be dropped: + /// For instance if two threads execute this expression in parallel, then + /// there is a race condition. The threads could first both check whether they + /// have the last clone of their `Arc` via `Arc::try_unwrap`, and then + /// both drop their `Arc` in the call to [`ok`][`Result::ok`], + /// taking the strong count from two down to zero. + /// + */ /// # Examples /// /// ``` @@ -685,6 +699,137 @@ impl Arc { Ok(elem) } } + + /// Returns the inner value, if the `Arc` has exactly one strong reference. + /// + /// Otherwise, [`None`] is returned and the `Arc` is dropped. + /// + /// This will succeed even if there are outstanding weak references. + /// + /// If `Arc::into_inner` is called on every clone of this `Arc`, + /// it is guaranteed that exactly one of the calls returns the inner value. + /// This means in particular that the inner value is not dropped. + /// + /// The similar expression `Arc::try_unwrap(this).ok()` does not + /// offer such a guarantee. See the last example below. + // + // FIXME: when `Arc::into_inner` is stabilized, add this to end + // of the previous sentence: + /* + /// and the documentation of [`Arc::try_unwrap`]. + */ + /// + /// # Examples + /// + /// Minimal example demonstrating the guarantee that `Arc::into_inner` gives. + /// ``` + /// #![feature(arc_into_inner)] + /// + /// use std::sync::Arc; + /// + /// let x = Arc::new(3); + /// let y = Arc::clone(&x); + /// + /// // Two threads calling `Arc::into_inner` on both clones of an `Arc`: + /// let x_thread = std::thread::spawn(|| Arc::into_inner(x)); + /// let y_thread = std::thread::spawn(|| Arc::into_inner(y)); + /// + /// let x_inner_value = x_thread.join().unwrap(); + /// let y_inner_value = y_thread.join().unwrap(); + /// + /// // One of the threads is guaranteed to receive the inner value: + /// assert!(matches!( + /// (x_inner_value, y_inner_value), + /// (None, Some(3)) | (Some(3), None) + /// )); + /// // The result could also be `(None, None)` if the threads called + /// // `Arc::try_unwrap(x).ok()` and `Arc::try_unwrap(y).ok()` instead. + /// ``` + /// + /// A more practical example demonstrating the need for `Arc::into_inner`: + /// ``` + /// #![feature(arc_into_inner)] + /// + /// use std::sync::Arc; + /// + /// // Definition of a simple singly linked list using `Arc`: + /// #[derive(Clone)] + /// struct LinkedList(Option>>); + /// struct Node(T, Option>>); + /// + /// // Dropping a long `LinkedList` relying on the destructor of `Arc` + /// // can cause a stack overflow. To prevent this, we can provide a + /// // manual `Drop` implementation that does the destruction in a loop: + /// impl Drop for LinkedList { + /// fn drop(&mut self) { + /// let mut link = self.0.take(); + /// while let Some(arc_node) = link.take() { + /// if let Some(Node(_value, next)) = Arc::into_inner(arc_node) { + /// link = next; + /// } + /// } + /// } + /// } + /// + /// // Implementation of `new` and `push` omitted + /// impl LinkedList { + /// /* ... */ + /// # fn new() -> Self { + /// # LinkedList(None) + /// # } + /// # fn push(&mut self, x: T) { + /// # self.0 = Some(Arc::new(Node(x, self.0.take()))); + /// # } + /// } + /// + /// // The following code could have still caused a stack overflow + /// // despite the manual `Drop` impl if that `Drop` impl had used + /// // `Arc::try_unwrap(arc).ok()` instead of `Arc::into_inner(arc)`. + /// + /// // Create a long list and clone it + /// let mut x = LinkedList::new(); + /// for i in 0..100000 { + /// x.push(i); // Adds i to the front of x + /// } + /// let y = x.clone(); + /// + /// // Drop the clones in parallel + /// let x_thread = std::thread::spawn(|| drop(x)); + /// let y_thread = std::thread::spawn(|| drop(y)); + /// x_thread.join().unwrap(); + /// y_thread.join().unwrap(); + /// ``` + + // FIXME: when `Arc::into_inner` is stabilized, adjust above documentation + // and the documentation of `Arc::try_unwrap` according to the `FIXME`s. Also + // open an issue on rust-lang/rust-clippy, asking for a lint against + // `Arc::try_unwrap(...).ok()`. + #[inline] + #[unstable(feature = "arc_into_inner", issue = "106894")] + pub fn into_inner(this: Self) -> Option { + // Make sure that the ordinary `Drop` implementation isn’t called as well + let mut this = mem::ManuallyDrop::new(this); + + // Following the implementation of `drop` and `drop_slow` + if this.inner().strong.fetch_sub(1, Release) != 1 { + return None; + } + + acquire!(this.inner().strong); + + // SAFETY: This mirrors the line + // + // unsafe { ptr::drop_in_place(Self::get_mut_unchecked(self)) }; + // + // in `drop_slow`. Instead of dropping the value behind the pointer, + // it is read and eventually returned; `ptr::read` has the same + // safety conditions as `ptr::drop_in_place`. + let inner = unsafe { ptr::read(Self::get_mut_unchecked(&mut this)) }; + + drop(Weak { ptr: this.ptr }); + + Some(inner) + } } impl Arc<[T]> { diff --git a/library/alloc/src/sync/tests.rs b/library/alloc/src/sync/tests.rs index 0fae8953aa2c7..863d58bdf4d9c 100644 --- a/library/alloc/src/sync/tests.rs +++ b/library/alloc/src/sync/tests.rs @@ -101,6 +101,38 @@ fn try_unwrap() { assert_eq!(Arc::try_unwrap(x), Ok(5)); } +#[test] +fn into_inner() { + for _ in 0..100 + // ^ Increase chances of hitting potential race conditions + { + let x = Arc::new(3); + let y = Arc::clone(&x); + let r_thread = std::thread::spawn(|| Arc::into_inner(x)); + let s_thread = std::thread::spawn(|| Arc::into_inner(y)); + let r = r_thread.join().expect("r_thread panicked"); + let s = s_thread.join().expect("s_thread panicked"); + assert!( + matches!((r, s), (None, Some(3)) | (Some(3), None)), + "assertion failed: unexpected result `{:?}`\ + \n expected `(None, Some(3))` or `(Some(3), None)`", + (r, s), + ); + } + + let x = Arc::new(3); + assert_eq!(Arc::into_inner(x), Some(3)); + + let x = Arc::new(4); + let y = Arc::clone(&x); + assert_eq!(Arc::into_inner(x), None); + assert_eq!(Arc::into_inner(y), Some(4)); + + let x = Arc::new(5); + let _w = Arc::downgrade(&x); + assert_eq!(Arc::into_inner(x), Some(5)); +} + #[test] fn into_from_raw() { let x = Arc::new(Box::new("hello")); From 26e2360b263930aa919a0604a79e8dba2c1284cf Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 22 Jan 2023 11:11:29 +0100 Subject: [PATCH 02/11] Use correct pseudo-element selector --- src/librustdoc/html/static/css/rustdoc.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/librustdoc/html/static/css/rustdoc.css b/src/librustdoc/html/static/css/rustdoc.css index 8ff8ea088be91..260e5c1c44ca5 100644 --- a/src/librustdoc/html/static/css/rustdoc.css +++ b/src/librustdoc/html/static/css/rustdoc.css @@ -1897,21 +1897,21 @@ in storage.js right: 0.25em; } -.scraped-example:not(.expanded) .code-wrapper:before, -.scraped-example:not(.expanded) .code-wrapper:after { +.scraped-example:not(.expanded) .code-wrapper::before, +.scraped-example:not(.expanded) .code-wrapper::after { content: " "; width: 100%; height: 5px; position: absolute; z-index: 1; } -.scraped-example:not(.expanded) .code-wrapper:before { +.scraped-example:not(.expanded) .code-wrapper::before { top: 0; background: linear-gradient(to bottom, var(--scrape-example-code-wrapper-background-start), var(--scrape-example-code-wrapper-background-end)); } -.scraped-example:not(.expanded) .code-wrapper:after { +.scraped-example:not(.expanded) .code-wrapper::after { bottom: 0; background: linear-gradient(to top, var(--scrape-example-code-wrapper-background-start), From e0b3d7290e113457dd779b9ec98f221acd62361d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 22 Jan 2023 12:36:04 +0100 Subject: [PATCH 03/11] add fmease to mailmap --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 022cdd0fd50c1..43646db44aa3a 100644 --- a/.mailmap +++ b/.mailmap @@ -324,6 +324,7 @@ Lennart Kudling Léo Lanteri Thauvin Léo Lanteri Thauvin <38361244+LeSeulArtichaut@users.noreply.github.com> Léo Testard +León Orell Valerian Liehr Leonardo Yvens Liigo Zhuang Lily Ballard From 12a72f0329476aa57a465c4fc3043ee40028b325 Mon Sep 17 00:00:00 2001 From: Samuel Moelius <35515885+smoelius@users.noreply.github.com> Date: Sun, 22 Jan 2023 07:38:02 -0500 Subject: [PATCH 04/11] Update universal_regions.rs --- compiler/rustc_borrowck/src/universal_regions.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_borrowck/src/universal_regions.rs b/compiler/rustc_borrowck/src/universal_regions.rs index 5b4d99682d986..8bff66f8d5cca 100644 --- a/compiler/rustc_borrowck/src/universal_regions.rs +++ b/compiler/rustc_borrowck/src/universal_regions.rs @@ -162,7 +162,7 @@ struct UniversalRegionIndices<'tcx> { /// `ty::Region` to the internal `RegionVid` we are using. This is /// used because trait matching and type-checking will feed us /// region constraints that reference those regions and we need to - /// be able to map them our internal `RegionVid`. This is + /// be able to map them to our internal `RegionVid`. This is /// basically equivalent to an `InternalSubsts`, except that it also /// contains an entry for `ReStatic` -- it might be nice to just /// use a substs, and then handle `ReStatic` another way. From 81ee6aebaa3d93c5e86d4d7c0fe80c3af74d3ec3 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 24 Nov 2022 14:26:57 -0300 Subject: [PATCH 05/11] Remove duplicated debug call --- compiler/rustc_hir_typeck/src/fallback.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index dde8797804f04..1015cea74b359 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -281,7 +281,6 @@ impl<'tcx> FnCtxt<'_, 'tcx> { roots_reachable_from_non_diverging, ); - debug!("inherited: {:#?}", self.inh.fulfillment_cx.borrow_mut().pending_obligations()); debug!("obligations: {:#?}", self.fulfillment_cx.borrow_mut().pending_obligations()); debug!("relationships: {:#?}", relationships); From 7fe472223e4bcdd960d73e323979d15168ce4e39 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 25 Nov 2022 18:18:03 -0300 Subject: [PATCH 06/11] Store relationships on Inherent --- compiler/rustc_hir_typeck/src/fallback.rs | 7 ++----- compiler/rustc_hir_typeck/src/inherited.rs | 12 ++++++++++- compiler/rustc_hir_typeck/src/lib.rs | 1 + .../src}/relationships.rs | 20 ++++++++---------- compiler/rustc_infer/src/traits/engine.rs | 3 --- .../src/solve/fulfill.rs | 6 ------ .../src/traits/chalk_fulfill.rs | 18 +++------------- .../src/traits/fulfill.rs | 21 ++----------------- .../rustc_trait_selection/src/traits/mod.rs | 1 - 9 files changed, 28 insertions(+), 61 deletions(-) rename compiler/{rustc_trait_selection/src/traits => rustc_hir_typeck/src}/relationships.rs (77%) diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 1015cea74b359..4ef32023e7d8b 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -196,8 +196,6 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ) -> FxHashMap, Ty<'tcx>> { debug!("calculate_diverging_fallback({:?})", unsolved_variables); - let relationships = self.fulfillment_cx.borrow_mut().relationships().clone(); - // Construct a coercion graph where an edge `A -> B` indicates // a type variable is that is coerced let coercion_graph = self.create_coercion_graph(); @@ -282,7 +280,6 @@ impl<'tcx> FnCtxt<'_, 'tcx> { ); debug!("obligations: {:#?}", self.fulfillment_cx.borrow_mut().pending_obligations()); - debug!("relationships: {:#?}", relationships); // For each diverging variable, figure out whether it can // reach a member of N. If so, it falls back to `()`. Else @@ -298,8 +295,8 @@ impl<'tcx> FnCtxt<'_, 'tcx> { let mut relationship = ty::FoundRelationships { self_in_trait: false, output: false }; - for (vid, rel) in relationships.iter() { - if self.root_var(*vid) == root_vid { + for (vid, rel) in self.inh.relationships.borrow().iter() { + if self.infcx.root_var(*vid) == root_vid { relationship.self_in_trait |= rel.self_in_trait; relationship.output |= rel.output; } diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs index b33e7b8d68cf9..f5b6578a9d329 100644 --- a/compiler/rustc_hir_typeck/src/inherited.rs +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -1,6 +1,6 @@ use super::callee::DeferredCallResolution; -use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def_id::LocalDefId; use rustc_hir::HirIdMap; @@ -63,6 +63,8 @@ pub struct Inherited<'tcx> { /// we record that type variable here. This is later used to inform /// fallback. See the `fallback` module for details. pub(super) diverging_type_vars: RefCell>>, + + pub(super) relationships: RefCell>, } impl<'tcx> Deref for Inherited<'tcx> { @@ -128,6 +130,7 @@ impl<'tcx> Inherited<'tcx> { deferred_generator_interiors: RefCell::new(Vec::new()), diverging_type_vars: RefCell::new(Default::default()), body_id, + relationships: RefCell::new(Default::default()), } } @@ -136,6 +139,13 @@ impl<'tcx> Inherited<'tcx> { if obligation.has_escaping_bound_vars() { span_bug!(obligation.cause.span, "escaping bound vars in predicate {:?}", obligation); } + + super::relationships::update( + &self.infcx, + &mut self.relationships.borrow_mut(), + &obligation, + ); + self.fulfillment_cx.borrow_mut().register_predicate_obligation(self, obligation); } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 7ddf9eaa4d899..54fd03d6493b6 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -40,6 +40,7 @@ mod method; mod op; mod pat; mod place_op; +mod relationships; mod rvalue_scopes; mod upvar; mod writeback; diff --git a/compiler/rustc_trait_selection/src/traits/relationships.rs b/compiler/rustc_hir_typeck/src/relationships.rs similarity index 77% rename from compiler/rustc_trait_selection/src/traits/relationships.rs rename to compiler/rustc_hir_typeck/src/relationships.rs index 34b5fc4891eb3..66aba08497457 100644 --- a/compiler/rustc_trait_selection/src/traits/relationships.rs +++ b/compiler/rustc_hir_typeck/src/relationships.rs @@ -1,16 +1,14 @@ -use crate::infer::InferCtxt; -use crate::traits::query::evaluate_obligation::InferCtxtExt; -use crate::traits::PredicateObligation; -use rustc_infer::traits::TraitEngine; +use rustc_data_structures::fx::FxHashMap; use rustc_middle::ty; +use rustc_trait_selection::infer::InferCtxt; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; +use rustc_trait_selection::traits::PredicateObligation; -pub(crate) fn update<'tcx, T>( - engine: &mut T, +pub fn update<'tcx>( infcx: &InferCtxt<'tcx>, + relationships: &mut FxHashMap, obligation: &PredicateObligation<'tcx>, -) where - T: TraitEngine<'tcx>, -{ +) { // (*) binder skipped if let ty::PredicateKind::Clause(ty::Clause::Trait(tpred)) = obligation.predicate.kind().skip_binder() && let Some(ty) = infcx.shallow_resolve(tpred.self_ty()).ty_vid().map(|t| infcx.root_var(t)) @@ -31,7 +29,7 @@ pub(crate) fn update<'tcx, T>( ); // Don't report overflow errors. Otherwise equivalent to may_hold. if let Ok(result) = infcx.probe(|_| infcx.evaluate_obligation(&o)) && result.may_apply() { - engine.relationships().entry(ty).or_default().self_in_trait = true; + relationships.entry(ty).or_default().self_in_trait = true; } } @@ -42,7 +40,7 @@ pub(crate) fn update<'tcx, T>( // we need to make it into one. if let Some(vid) = predicate.term.ty().and_then(|ty| ty.ty_vid()) { debug!("relationship: {:?}.output = true", vid); - engine.relationships().entry(vid).or_default().output = true; + relationships.entry(vid).or_default().output = true; } } } diff --git a/compiler/rustc_infer/src/traits/engine.rs b/compiler/rustc_infer/src/traits/engine.rs index d3519f4b37b82..fcde00056cbf1 100644 --- a/compiler/rustc_infer/src/traits/engine.rs +++ b/compiler/rustc_infer/src/traits/engine.rs @@ -1,6 +1,5 @@ use crate::infer::InferCtxt; use crate::traits::Obligation; -use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::DefId; use rustc_middle::ty::{self, ToPredicate, Ty}; @@ -42,8 +41,6 @@ pub trait TraitEngine<'tcx>: 'tcx { fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec>; fn pending_obligations(&self) -> Vec>; - - fn relationships(&mut self) -> &mut FxHashMap; } pub trait TraitEngineExt<'tcx> { diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs index a6240666ed43a..40b9bedc84fd3 100644 --- a/compiler/rustc_trait_selection/src/solve/fulfill.rs +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -1,6 +1,5 @@ use std::mem; -use rustc_data_structures::fx::FxHashMap; use rustc_infer::{ infer::InferCtxt, traits::{ @@ -8,7 +7,6 @@ use rustc_infer::{ SelectionError, TraitEngine, }, }; -use rustc_middle::ty; use super::{search_graph, Certainty, EvalCtxt}; @@ -102,8 +100,4 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { fn pending_obligations(&self) -> Vec> { self.obligations.clone() } - - fn relationships(&mut self) -> &mut FxHashMap { - unimplemented!("Should be moved out of `TraitEngine`") - } } diff --git a/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs b/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs index e88950523537f..61d09189798ea 100644 --- a/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/chalk_fulfill.rs @@ -7,24 +7,18 @@ use crate::traits::{ ChalkEnvironmentAndGoal, FulfillmentError, FulfillmentErrorCode, PredicateObligation, SelectionError, TraitEngine, }; -use rustc_data_structures::fx::{FxHashMap, FxIndexSet}; -use rustc_middle::ty::{self, TypeVisitable}; +use rustc_data_structures::fx::FxIndexSet; +use rustc_middle::ty::TypeVisitable; pub struct FulfillmentContext<'tcx> { obligations: FxIndexSet>, - relationships: FxHashMap, - usable_in_snapshot: bool, } impl FulfillmentContext<'_> { pub(super) fn new() -> Self { - FulfillmentContext { - obligations: FxIndexSet::default(), - relationships: FxHashMap::default(), - usable_in_snapshot: false, - } + FulfillmentContext { obligations: FxIndexSet::default(), usable_in_snapshot: false } } pub(crate) fn new_in_snapshot() -> Self { @@ -43,8 +37,6 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> { } let obligation = infcx.resolve_vars_if_possible(obligation); - super::relationships::update(self, infcx, &obligation); - self.obligations.insert(obligation); } @@ -154,8 +146,4 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> { fn pending_obligations(&self) -> Vec> { self.obligations.iter().cloned().collect() } - - fn relationships(&mut self) -> &mut FxHashMap { - &mut self.relationships - } } diff --git a/compiler/rustc_trait_selection/src/traits/fulfill.rs b/compiler/rustc_trait_selection/src/traits/fulfill.rs index 76a755ed9e09d..5a58d37e18362 100644 --- a/compiler/rustc_trait_selection/src/traits/fulfill.rs +++ b/compiler/rustc_trait_selection/src/traits/fulfill.rs @@ -1,5 +1,4 @@ use crate::infer::{InferCtxt, TyOrConstInferVar}; -use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::obligation_forest::ProcessResult; use rustc_data_structures::obligation_forest::{Error, ForestObligation, Outcome}; use rustc_data_structures::obligation_forest::{ObligationForest, ObligationProcessor}; @@ -54,8 +53,6 @@ pub struct FulfillmentContext<'tcx> { // fulfillment context. predicates: ObligationForest>, - relationships: FxHashMap, - // Is it OK to register obligations into this infcx inside // an infcx snapshot? // @@ -85,19 +82,11 @@ static_assert_size!(PendingPredicateObligation<'_>, 72); impl<'a, 'tcx> FulfillmentContext<'tcx> { /// Creates a new fulfillment context. pub(super) fn new() -> FulfillmentContext<'tcx> { - FulfillmentContext { - predicates: ObligationForest::new(), - relationships: FxHashMap::default(), - usable_in_snapshot: false, - } + FulfillmentContext { predicates: ObligationForest::new(), usable_in_snapshot: false } } pub(super) fn new_in_snapshot() -> FulfillmentContext<'tcx> { - FulfillmentContext { - predicates: ObligationForest::new(), - relationships: FxHashMap::default(), - usable_in_snapshot: true, - } + FulfillmentContext { predicates: ObligationForest::new(), usable_in_snapshot: true } } /// Attempts to select obligations using `selcx`. @@ -139,8 +128,6 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> { assert!(!infcx.is_in_snapshot() || self.usable_in_snapshot); - super::relationships::update(self, infcx, &obligation); - self.predicates .register_obligation(PendingPredicateObligation { obligation, stalled_on: vec![] }); } @@ -164,10 +151,6 @@ impl<'tcx> TraitEngine<'tcx> for FulfillmentContext<'tcx> { fn pending_obligations(&self) -> Vec> { self.predicates.map_pending_obligations(|o| o.obligation.clone()) } - - fn relationships(&mut self) -> &mut FxHashMap { - &mut self.relationships - } } struct FulfillProcessor<'a, 'tcx> { diff --git a/compiler/rustc_trait_selection/src/traits/mod.rs b/compiler/rustc_trait_selection/src/traits/mod.rs index f036a311d464c..3c640cdc503ce 100644 --- a/compiler/rustc_trait_selection/src/traits/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/mod.rs @@ -14,7 +14,6 @@ mod object_safety; pub mod outlives_bounds; mod project; pub mod query; -pub(crate) mod relationships; mod select; mod specialize; mod structural_match; From fb0a4e958950ed4dabc954c8a258594847b1d9bc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 20 Jan 2023 16:42:22 -0300 Subject: [PATCH 07/11] Move relationships::update to Inherited::update_infer_var_info --- compiler/rustc_hir_typeck/src/inherited.rs | 48 ++++++++++++++++--- compiler/rustc_hir_typeck/src/lib.rs | 1 - .../rustc_hir_typeck/src/relationships.rs | 46 ------------------ 3 files changed, 42 insertions(+), 53 deletions(-) delete mode 100644 compiler/rustc_hir_typeck/src/relationships.rs diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs index f5b6578a9d329..ce7e5caaefc53 100644 --- a/compiler/rustc_hir_typeck/src/inherited.rs +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -10,7 +10,8 @@ use rustc_middle::ty::visit::TypeVisitable; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_span::def_id::LocalDefIdMap; use rustc_span::{self, Span}; -use rustc_trait_selection::traits::{self, TraitEngine, TraitEngineExt as _}; +use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; +use rustc_trait_selection::traits::{self, PredicateObligation, TraitEngine, TraitEngineExt as _}; use std::cell::RefCell; use std::ops::Deref; @@ -140,11 +141,7 @@ impl<'tcx> Inherited<'tcx> { span_bug!(obligation.cause.span, "escaping bound vars in predicate {:?}", obligation); } - super::relationships::update( - &self.infcx, - &mut self.relationships.borrow_mut(), - &obligation, - ); + self.update_infer_var_info(&obligation); self.fulfillment_cx.borrow_mut().register_predicate_obligation(self, obligation); } @@ -162,4 +159,43 @@ impl<'tcx> Inherited<'tcx> { self.register_predicates(infer_ok.obligations); infer_ok.value } + + pub fn update_infer_var_info(&self, obligation: &PredicateObligation<'tcx>) { + let relationships = &mut self.relationships.borrow_mut(); + + // (*) binder skipped + if let ty::PredicateKind::Clause(ty::Clause::Trait(tpred)) = obligation.predicate.kind().skip_binder() + && let Some(ty) = self.shallow_resolve(tpred.self_ty()).ty_vid().map(|t| self.root_var(t)) + && self.tcx.lang_items().sized_trait().map_or(false, |st| st != tpred.trait_ref.def_id) + { + let new_self_ty = self.tcx.types.unit; + + // Then construct a new obligation with Self = () added + // to the ParamEnv, and see if it holds. + let o = obligation.with(self.tcx, + obligation + .predicate + .kind() + .rebind( + // (*) binder moved here + ty::PredicateKind::Clause(ty::Clause::Trait(tpred.with_self_ty(self.tcx, new_self_ty))) + ), + ); + // Don't report overflow errors. Otherwise equivalent to may_hold. + if let Ok(result) = self.probe(|_| self.evaluate_obligation(&o)) && result.may_apply() { + relationships.entry(ty).or_default().self_in_trait = true; + } + } + + if let ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) = + obligation.predicate.kind().skip_binder() + { + // If the projection predicate (Foo::Bar == X) has X as a non-TyVid, + // we need to make it into one. + if let Some(vid) = predicate.term.ty().and_then(|ty| ty.ty_vid()) { + debug!("relationships: {:?}.output = true", vid); + relationships.entry(vid).or_default().output = true; + } + } + } } diff --git a/compiler/rustc_hir_typeck/src/lib.rs b/compiler/rustc_hir_typeck/src/lib.rs index 54fd03d6493b6..7ddf9eaa4d899 100644 --- a/compiler/rustc_hir_typeck/src/lib.rs +++ b/compiler/rustc_hir_typeck/src/lib.rs @@ -40,7 +40,6 @@ mod method; mod op; mod pat; mod place_op; -mod relationships; mod rvalue_scopes; mod upvar; mod writeback; diff --git a/compiler/rustc_hir_typeck/src/relationships.rs b/compiler/rustc_hir_typeck/src/relationships.rs deleted file mode 100644 index 66aba08497457..0000000000000 --- a/compiler/rustc_hir_typeck/src/relationships.rs +++ /dev/null @@ -1,46 +0,0 @@ -use rustc_data_structures::fx::FxHashMap; -use rustc_middle::ty; -use rustc_trait_selection::infer::InferCtxt; -use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; -use rustc_trait_selection::traits::PredicateObligation; - -pub fn update<'tcx>( - infcx: &InferCtxt<'tcx>, - relationships: &mut FxHashMap, - obligation: &PredicateObligation<'tcx>, -) { - // (*) binder skipped - if let ty::PredicateKind::Clause(ty::Clause::Trait(tpred)) = obligation.predicate.kind().skip_binder() - && let Some(ty) = infcx.shallow_resolve(tpred.self_ty()).ty_vid().map(|t| infcx.root_var(t)) - && infcx.tcx.lang_items().sized_trait().map_or(false, |st| st != tpred.trait_ref.def_id) - { - let new_self_ty = infcx.tcx.types.unit; - - // Then construct a new obligation with Self = () added - // to the ParamEnv, and see if it holds. - let o = obligation.with(infcx.tcx, - obligation - .predicate - .kind() - .rebind( - // (*) binder moved here - ty::PredicateKind::Clause(ty::Clause::Trait(tpred.with_self_ty(infcx.tcx, new_self_ty))) - ), - ); - // Don't report overflow errors. Otherwise equivalent to may_hold. - if let Ok(result) = infcx.probe(|_| infcx.evaluate_obligation(&o)) && result.may_apply() { - relationships.entry(ty).or_default().self_in_trait = true; - } - } - - if let ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) = - obligation.predicate.kind().skip_binder() - { - // If the projection predicate (Foo::Bar == X) has X as a non-TyVid, - // we need to make it into one. - if let Some(vid) = predicate.term.ty().and_then(|ty| ty.ty_vid()) { - debug!("relationship: {:?}.output = true", vid); - relationships.entry(vid).or_default().output = true; - } - } -} From 6155a803805acaddd2518f09c2da70fbc320b274 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 20 Jan 2023 16:45:01 -0300 Subject: [PATCH 08/11] Rename relationships to infer_var_info --- compiler/rustc_hir_typeck/src/fallback.rs | 10 +++++----- compiler/rustc_hir_typeck/src/inherited.rs | 12 ++++++------ compiler/rustc_middle/src/ty/mod.rs | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fallback.rs b/compiler/rustc_hir_typeck/src/fallback.rs index 4ef32023e7d8b..943dc9b9646fc 100644 --- a/compiler/rustc_hir_typeck/src/fallback.rs +++ b/compiler/rustc_hir_typeck/src/fallback.rs @@ -293,16 +293,16 @@ impl<'tcx> FnCtxt<'_, 'tcx> { .depth_first_search(root_vid) .any(|n| roots_reachable_from_non_diverging.visited(n)); - let mut relationship = ty::FoundRelationships { self_in_trait: false, output: false }; + let mut found_infer_var_info = ty::InferVarInfo { self_in_trait: false, output: false }; - for (vid, rel) in self.inh.relationships.borrow().iter() { + for (vid, info) in self.inh.infer_var_info.borrow().iter() { if self.infcx.root_var(*vid) == root_vid { - relationship.self_in_trait |= rel.self_in_trait; - relationship.output |= rel.output; + found_infer_var_info.self_in_trait |= info.self_in_trait; + found_infer_var_info.output |= info.output; } } - if relationship.self_in_trait && relationship.output { + if found_infer_var_info.self_in_trait && found_infer_var_info.output { // This case falls back to () to ensure that the code pattern in // tests/ui/never_type/fallback-closure-ret.rs continues to // compile when never_type_fallback is enabled. diff --git a/compiler/rustc_hir_typeck/src/inherited.rs b/compiler/rustc_hir_typeck/src/inherited.rs index ce7e5caaefc53..ba34f299453ec 100644 --- a/compiler/rustc_hir_typeck/src/inherited.rs +++ b/compiler/rustc_hir_typeck/src/inherited.rs @@ -65,7 +65,7 @@ pub struct Inherited<'tcx> { /// fallback. See the `fallback` module for details. pub(super) diverging_type_vars: RefCell>>, - pub(super) relationships: RefCell>, + pub(super) infer_var_info: RefCell>, } impl<'tcx> Deref for Inherited<'tcx> { @@ -131,7 +131,7 @@ impl<'tcx> Inherited<'tcx> { deferred_generator_interiors: RefCell::new(Vec::new()), diverging_type_vars: RefCell::new(Default::default()), body_id, - relationships: RefCell::new(Default::default()), + infer_var_info: RefCell::new(Default::default()), } } @@ -161,7 +161,7 @@ impl<'tcx> Inherited<'tcx> { } pub fn update_infer_var_info(&self, obligation: &PredicateObligation<'tcx>) { - let relationships = &mut self.relationships.borrow_mut(); + let infer_var_info = &mut self.infer_var_info.borrow_mut(); // (*) binder skipped if let ty::PredicateKind::Clause(ty::Clause::Trait(tpred)) = obligation.predicate.kind().skip_binder() @@ -183,7 +183,7 @@ impl<'tcx> Inherited<'tcx> { ); // Don't report overflow errors. Otherwise equivalent to may_hold. if let Ok(result) = self.probe(|_| self.evaluate_obligation(&o)) && result.may_apply() { - relationships.entry(ty).or_default().self_in_trait = true; + infer_var_info.entry(ty).or_default().self_in_trait = true; } } @@ -193,8 +193,8 @@ impl<'tcx> Inherited<'tcx> { // If the projection predicate (Foo::Bar == X) has X as a non-TyVid, // we need to make it into one. if let Some(vid) = predicate.term.ty().and_then(|ty| ty.ty_vid()) { - debug!("relationships: {:?}.output = true", vid); - relationships.entry(vid).or_default().output = true; + debug!("infer_var_info: {:?}.output = true", vid); + infer_var_info.entry(vid).or_default().output = true; } } } diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 7dfcd1bb5074d..f83bceca3b53b 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -2619,7 +2619,7 @@ impl<'tcx> fmt::Debug for SymbolName<'tcx> { } #[derive(Debug, Default, Copy, Clone)] -pub struct FoundRelationships { +pub struct InferVarInfo { /// This is true if we identified that this Ty (`?T`) is found in a `?T: Foo` /// obligation, where: /// From b905f80036a886c359149934b0772edd2f556c2a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 22 Jan 2023 12:36:58 -0300 Subject: [PATCH 09/11] fn-trait-closure test now pass on new solver --- tests/ui/traits/new-solver/fn-trait-closure.rs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/tests/ui/traits/new-solver/fn-trait-closure.rs b/tests/ui/traits/new-solver/fn-trait-closure.rs index c0ecf1c91fb38..bd65737ee3989 100644 --- a/tests/ui/traits/new-solver/fn-trait-closure.rs +++ b/tests/ui/traits/new-solver/fn-trait-closure.rs @@ -1,12 +1,5 @@ // compile-flags: -Ztrait-solver=next -// known-bug: unknown -// failure-status: 101 -// dont-check-compiler-stderr - -// This test will fail until we fix `FulfillmentCtxt::relationships`. That's -// because we create a type variable for closure upvar types, which is not -// constrained until after we try to do fallback on diverging type variables. -// Thus, we will call that function, which is unimplemented. +// check-pass fn require_fn(_: impl Fn() -> i32) {} From 2aa5555ad3911536e2d0d363633f1a4cb67b1c49 Mon Sep 17 00:00:00 2001 From: yukang Date: Mon, 23 Jan 2023 00:42:20 +0800 Subject: [PATCH 10/11] Fix #106496, suggest remove deref for type mismatch --- compiler/rustc_hir_typeck/src/demand.rs | 16 +++++++ .../issue-88360.fixed | 20 +++++++++ .../generic-associated-types/issue-88360.rs | 2 + .../issue-88360.stderr | 12 +++--- .../ui/suggestions/suggest-remove-deref.fixed | 28 ++++++++++++ tests/ui/suggestions/suggest-remove-deref.rs | 28 ++++++++++++ .../suggestions/suggest-remove-deref.stderr | 43 +++++++++++++++++++ 7 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 tests/ui/generic-associated-types/issue-88360.fixed create mode 100644 tests/ui/suggestions/suggest-remove-deref.fixed create mode 100644 tests/ui/suggestions/suggest-remove-deref.rs create mode 100644 tests/ui/suggestions/suggest-remove-deref.stderr diff --git a/compiler/rustc_hir_typeck/src/demand.rs b/compiler/rustc_hir_typeck/src/demand.rs index 665dc8b6a2f2a..bd1626dff7951 100644 --- a/compiler/rustc_hir_typeck/src/demand.rs +++ b/compiler/rustc_hir_typeck/src/demand.rs @@ -1233,6 +1233,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { sugg_sp = receiver.span; } } + + if let hir::ExprKind::Unary(hir::UnOp::Deref, ref inner) = expr.kind + && let Some(1) = self.deref_steps(expected, checked_ty) { + // We have `*&T`, check if what was expected was `&T`. + // If so, we may want to suggest removing a `*`. + sugg_sp = sugg_sp.with_hi(inner.span.lo()); + return Some(( + sugg_sp, + "consider removing deref here".to_string(), + "".to_string(), + Applicability::MachineApplicable, + true, + false, + )); + } + if let Ok(src) = sm.span_to_snippet(sugg_sp) { let needs_parens = match expr.kind { // parenthesize if needed (Issue #46756) diff --git a/tests/ui/generic-associated-types/issue-88360.fixed b/tests/ui/generic-associated-types/issue-88360.fixed new file mode 100644 index 0000000000000..3dea8bf7ac81d --- /dev/null +++ b/tests/ui/generic-associated-types/issue-88360.fixed @@ -0,0 +1,20 @@ +// run-rustfix + +trait GatTrait { + type Gat<'a> where Self: 'a; + + fn test(&self) -> Self::Gat<'_>; +} + +trait SuperTrait +where + Self: 'static, + for<'a> Self: GatTrait = &'a T>, +{ + fn copy(&self) -> Self::Gat<'_> where T: Copy { + self.test() + //~^ mismatched types + } +} + +fn main() {} diff --git a/tests/ui/generic-associated-types/issue-88360.rs b/tests/ui/generic-associated-types/issue-88360.rs index c02690618d0ee..4d4c7ea318078 100644 --- a/tests/ui/generic-associated-types/issue-88360.rs +++ b/tests/ui/generic-associated-types/issue-88360.rs @@ -1,3 +1,5 @@ +// run-rustfix + trait GatTrait { type Gat<'a> where Self: 'a; diff --git a/tests/ui/generic-associated-types/issue-88360.stderr b/tests/ui/generic-associated-types/issue-88360.stderr index cd3750344dda1..520aeff189483 100644 --- a/tests/ui/generic-associated-types/issue-88360.stderr +++ b/tests/ui/generic-associated-types/issue-88360.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/issue-88360.rs:13:9 + --> $DIR/issue-88360.rs:15:9 | LL | trait SuperTrait | - this type parameter @@ -7,13 +7,15 @@ LL | trait SuperTrait LL | fn copy(&self) -> Self::Gat<'_> where T: Copy { | ------------- expected `&T` because of return type LL | *self.test() - | ^^^^^^^^^^^^ - | | - | expected `&T`, found type parameter `T` - | help: consider borrowing here: `&*self.test()` + | ^^^^^^^^^^^^ expected `&T`, found type parameter `T` | = note: expected reference `&T` found type parameter `T` +help: consider removing deref here + | +LL - *self.test() +LL + self.test() + | error: aborting due to previous error diff --git a/tests/ui/suggestions/suggest-remove-deref.fixed b/tests/ui/suggestions/suggest-remove-deref.fixed new file mode 100644 index 0000000000000..4dc12da03dd02 --- /dev/null +++ b/tests/ui/suggestions/suggest-remove-deref.fixed @@ -0,0 +1,28 @@ +// run-rustfix + +//issue #106496 + +struct S; + +trait X {} +impl X for S {} + +fn foo(_: &T) {} +fn test_foo() { + let hello = &S; + foo(hello); + //~^ ERROR mismatched types +} + +fn bar(_: &String) {} +fn test_bar() { + let v = String::from("hello"); + let s = &v; + bar(s); + //~^ ERROR mismatched types +} + +fn main() { + test_foo(); + test_bar(); +} diff --git a/tests/ui/suggestions/suggest-remove-deref.rs b/tests/ui/suggestions/suggest-remove-deref.rs new file mode 100644 index 0000000000000..c2d385cbdc378 --- /dev/null +++ b/tests/ui/suggestions/suggest-remove-deref.rs @@ -0,0 +1,28 @@ +// run-rustfix + +//issue #106496 + +struct S; + +trait X {} +impl X for S {} + +fn foo(_: &T) {} +fn test_foo() { + let hello = &S; + foo(*hello); + //~^ ERROR mismatched types +} + +fn bar(_: &String) {} +fn test_bar() { + let v = String::from("hello"); + let s = &v; + bar(*s); + //~^ ERROR mismatched types +} + +fn main() { + test_foo(); + test_bar(); +} diff --git a/tests/ui/suggestions/suggest-remove-deref.stderr b/tests/ui/suggestions/suggest-remove-deref.stderr new file mode 100644 index 0000000000000..f5d810e36f035 --- /dev/null +++ b/tests/ui/suggestions/suggest-remove-deref.stderr @@ -0,0 +1,43 @@ +error[E0308]: mismatched types + --> $DIR/suggest-remove-deref.rs:13:9 + | +LL | foo(*hello); + | --- ^^^^^^ expected reference, found struct `S` + | | + | arguments to this function are incorrect + | + = note: expected reference `&_` + found struct `S` +note: function defined here + --> $DIR/suggest-remove-deref.rs:10:4 + | +LL | fn foo(_: &T) {} + | ^^^ ----- +help: consider removing deref here + | +LL - foo(*hello); +LL + foo(hello); + | + +error[E0308]: mismatched types + --> $DIR/suggest-remove-deref.rs:21:9 + | +LL | bar(*s); + | --- ^^ expected `&String`, found struct `String` + | | + | arguments to this function are incorrect + | +note: function defined here + --> $DIR/suggest-remove-deref.rs:17:4 + | +LL | fn bar(_: &String) {} + | ^^^ ---------- +help: consider removing deref here + | +LL - bar(*s); +LL + bar(s); + | + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0308`. From f908f0be5a088a0e1748c7853a8acf75be6d1c18 Mon Sep 17 00:00:00 2001 From: Robin Schroer Date: Wed, 18 Jan 2023 19:58:50 +0900 Subject: [PATCH 11/11] Consider doc(alias) when providing typo suggestions This means that ```rust impl Foo { #[doc(alias = "quux")] fn bar(&self) {} } fn main() { (Foo {}).quux(); } ``` will suggest `bar`. This currently uses the "there is a method with a similar name" help text, because the point where we choose and emit a suggestion is different from where we gather the suggestions. Changes have mainly been made to the latter. The selection code will now fall back to aliased candidates, but generally only if there is no candidate that matches based on the existing Levenshtein methodology. Fixes #83968. --- compiler/rustc_hir_typeck/src/method/mod.rs | 2 +- compiler/rustc_hir_typeck/src/method/probe.rs | 49 +++++++++++++++++-- .../rustc_hir_typeck/src/method/suggest.rs | 16 +++--- .../methods/method-not-found-but-doc-alias.rs | 11 +++++ .../method-not-found-but-doc-alias.stderr | 12 +++++ 5 files changed, 77 insertions(+), 13 deletions(-) create mode 100644 tests/ui/methods/method-not-found-but-doc-alias.rs create mode 100644 tests/ui/methods/method-not-found-but-doc-alias.stderr diff --git a/compiler/rustc_hir_typeck/src/method/mod.rs b/compiler/rustc_hir_typeck/src/method/mod.rs index b810a967a2451..47396204b14e7 100644 --- a/compiler/rustc_hir_typeck/src/method/mod.rs +++ b/compiler/rustc_hir_typeck/src/method/mod.rs @@ -76,7 +76,7 @@ pub struct NoMatchData<'tcx> { pub unsatisfied_predicates: Vec<(ty::Predicate<'tcx>, Option>, Option>)>, pub out_of_scope_traits: Vec, - pub lev_candidate: Option, + pub similar_candidate: Option, pub mode: probe::Mode, } diff --git a/compiler/rustc_hir_typeck/src/method/probe.rs b/compiler/rustc_hir_typeck/src/method/probe.rs index a2481431363dd..9c06a22315bcb 100644 --- a/compiler/rustc_hir_typeck/src/method/probe.rs +++ b/compiler/rustc_hir_typeck/src/method/probe.rs @@ -461,7 +461,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { static_candidates: Vec::new(), unsatisfied_predicates: Vec::new(), out_of_scope_traits: Vec::new(), - lev_candidate: None, + similar_candidate: None, mode, })); } @@ -1076,13 +1076,13 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { if let Some((kind, def_id)) = private_candidate { return Err(MethodError::PrivateMatch(kind, def_id, out_of_scope_traits)); } - let lev_candidate = self.probe_for_lev_candidate()?; + let similar_candidate = self.probe_for_similar_candidate()?; Err(MethodError::NoMatch(NoMatchData { static_candidates, unsatisfied_predicates, out_of_scope_traits, - lev_candidate, + similar_candidate, mode: self.mode, })) } @@ -1787,7 +1787,7 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { /// Similarly to `probe_for_return_type`, this method attempts to find the best matching /// candidate method where the method name may have been misspelled. Similarly to other /// Levenshtein based suggestions, we provide at most one such suggestion. - fn probe_for_lev_candidate(&mut self) -> Result, MethodError<'tcx>> { + fn probe_for_similar_candidate(&mut self) -> Result, MethodError<'tcx>> { debug!("probing for method names similar to {:?}", self.method_name); let steps = self.steps.clone(); @@ -1831,6 +1831,12 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { None, ) } + .or_else(|| { + applicable_close_candidates + .iter() + .find(|cand| self.matches_by_doc_alias(cand.def_id)) + .map(|cand| cand.name) + }) .unwrap(); Ok(applicable_close_candidates.into_iter().find(|method| method.name == best_name)) } @@ -1981,6 +1987,38 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { } } + /// Determine if the associated item withe the given DefId matches + /// the desired name via a doc alias. + fn matches_by_doc_alias(&self, def_id: DefId) -> bool { + let Some(name) = self.method_name else { return false; }; + let Some(local_def_id) = def_id.as_local() else { return false; }; + let hir_id = self.fcx.tcx.hir().local_def_id_to_hir_id(local_def_id); + let attrs = self.fcx.tcx.hir().attrs(hir_id); + for attr in attrs { + let sym::doc = attr.name_or_empty() else { continue; }; + let Some(values) = attr.meta_item_list() else { continue; }; + for v in values { + if v.name_or_empty() != sym::alias { + continue; + } + if let Some(nested) = v.meta_item_list() { + // #[doc(alias("foo", "bar"))] + for n in nested { + if let Some(lit) = n.lit() && name.as_str() == lit.symbol.as_str() { + return true; + } + } + } else if let Some(meta) = v.meta_item() + && let Some(lit) = meta.name_value_literal() + && name.as_str() == lit.symbol.as_str() { + // #[doc(alias = "foo")] + return true; + } + } + } + false + } + /// Finds the method with the appropriate name (or return type, as the case may be). If /// `allow_similar_names` is set, find methods with close-matching names. // The length of the returned iterator is nearly always 0 or 1 and this @@ -1996,6 +2034,9 @@ impl<'a, 'tcx> ProbeContext<'a, 'tcx> { if !self.is_relevant_kind_for_mode(x.kind) { return false; } + if self.matches_by_doc_alias(x.def_id) { + return true; + } match lev_distance_with_substrings(name.as_str(), x.name.as_str(), max_dist) { Some(d) => d > 0, diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 2e1fc4c38b542..8c54e9bdb5fb3 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -262,7 +262,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let ty_str = with_forced_trimmed_paths!(self.ty_to_string(rcvr_ty)); let is_method = mode == Mode::MethodCall; let unsatisfied_predicates = &no_match_data.unsatisfied_predicates; - let lev_candidate = no_match_data.lev_candidate; + let similar_candidate = no_match_data.similar_candidate; let item_kind = if is_method { "method" } else if rcvr_ty.is_enum() { @@ -937,7 +937,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // give a helping note that it has to be called as `(x.f)(...)`. if let SelfSource::MethodCall(expr) = source { if !self.suggest_calling_field_as_fn(span, rcvr_ty, expr, item_name, &mut err) - && lev_candidate.is_none() + && similar_candidate.is_none() && !custom_span_label { label_span_not_found(&mut err); @@ -1015,20 +1015,20 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if fallback_span { err.span_label(span, msg); } - } else if let Some(lev_candidate) = lev_candidate { + } else if let Some(similar_candidate) = similar_candidate { // Don't emit a suggestion if we found an actual method // that had unsatisfied trait bounds if unsatisfied_predicates.is_empty() { - let def_kind = lev_candidate.kind.as_def_kind(); + let def_kind = similar_candidate.kind.as_def_kind(); // Methods are defined within the context of a struct and their first parameter is always self, // which represents the instance of the struct the method is being called on // Associated functions don’t take self as a parameter and // they are not methods because they don’t have an instance of the struct to work with. - if def_kind == DefKind::AssocFn && lev_candidate.fn_has_self_parameter { + if def_kind == DefKind::AssocFn && similar_candidate.fn_has_self_parameter { err.span_suggestion( span, "there is a method with a similar name", - lev_candidate.name, + similar_candidate.name, Applicability::MaybeIncorrect, ); } else { @@ -1037,9 +1037,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &format!( "there is {} {} with a similar name", def_kind.article(), - def_kind.descr(lev_candidate.def_id), + def_kind.descr(similar_candidate.def_id), ), - lev_candidate.name, + similar_candidate.name, Applicability::MaybeIncorrect, ); } diff --git a/tests/ui/methods/method-not-found-but-doc-alias.rs b/tests/ui/methods/method-not-found-but-doc-alias.rs new file mode 100644 index 0000000000000..9c6d10029239b --- /dev/null +++ b/tests/ui/methods/method-not-found-but-doc-alias.rs @@ -0,0 +1,11 @@ +struct Foo; + +impl Foo { + #[doc(alias = "quux")] + fn bar(&self) {} +} + +fn main() { + Foo.quux(); + //~^ ERROR no method named `quux` found for struct `Foo` in the current scope +} diff --git a/tests/ui/methods/method-not-found-but-doc-alias.stderr b/tests/ui/methods/method-not-found-but-doc-alias.stderr new file mode 100644 index 0000000000000..5102a452f0c28 --- /dev/null +++ b/tests/ui/methods/method-not-found-but-doc-alias.stderr @@ -0,0 +1,12 @@ +error[E0599]: no method named `quux` found for struct `Foo` in the current scope + --> $DIR/method-not-found-but-doc-alias.rs:9:9 + | +LL | struct Foo; + | ---------- method `quux` not found for this struct +... +LL | Foo.quux(); + | ^^^^ help: there is a method with a similar name: `bar` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0599`.