From 06fc531c9631989ed681fc7252929da3ff84225e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 3 Nov 2024 17:15:22 +0000 Subject: [PATCH 1/2] Add test for assoc fn suggestion on enum variant --- tests/ui/enum/assoc-fn-call-on-variant.rs | 15 +++++++++++++++ tests/ui/enum/assoc-fn-call-on-variant.stderr | 14 ++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 tests/ui/enum/assoc-fn-call-on-variant.rs create mode 100644 tests/ui/enum/assoc-fn-call-on-variant.stderr diff --git a/tests/ui/enum/assoc-fn-call-on-variant.rs b/tests/ui/enum/assoc-fn-call-on-variant.rs new file mode 100644 index 00000000000..7fa8eb2da41 --- /dev/null +++ b/tests/ui/enum/assoc-fn-call-on-variant.rs @@ -0,0 +1,15 @@ +#[derive(Default)] +enum E { + A {}, + B {}, + #[default] + C, +} + +impl E { + fn f() {} +} + +fn main() { + E::A::f(); //~ ERROR failed to resolve: `A` is a variant, not a module +} diff --git a/tests/ui/enum/assoc-fn-call-on-variant.stderr b/tests/ui/enum/assoc-fn-call-on-variant.stderr new file mode 100644 index 00000000000..4775201d2bb --- /dev/null +++ b/tests/ui/enum/assoc-fn-call-on-variant.stderr @@ -0,0 +1,14 @@ +error[E0433]: failed to resolve: `A` is a variant, not a module + --> $DIR/assoc-fn-call-on-variant.rs:14:8 + | +LL | E::A::f(); + | ^ `A` is a variant, not a module + | +help: there is an enum variant `E::A`; try using the variant's enum + | +LL | E(); + | ~ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0433`. From e26ad8b67d31fa8c35bf1e5e3ed310c06418d18b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sun, 3 Nov 2024 18:55:19 +0000 Subject: [PATCH 2/2] Properly suggest `E::assoc` when we encounter `E::Variant::assoc` Use the right span when encountering an enum variant followed by an associated item so we don't lose the associated item in the resulting code. Do not suggest the thing twice, once as a removal of the associated item and a second time as a typo suggestion. --- .../rustc_resolve/src/late/diagnostics.rs | 46 +++++++++++++++---- tests/ui/enum/assoc-fn-call-on-variant.stderr | 2 +- .../resolve/resolve-variant-assoc-item.stderr | 6 +-- 3 files changed, 38 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index f0632a21091..c80fbe1a1c7 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -459,7 +459,7 @@ pub(crate) fn smart_resolve_report_errors( return (err, Vec::new()); } - let (found, mut candidates) = self.try_lookup_name_relaxed( + let (found, suggested_candidates, mut candidates) = self.try_lookup_name_relaxed( &mut err, source, path, @@ -478,7 +478,15 @@ pub(crate) fn smart_resolve_report_errors( } let mut fallback = self.suggest_trait_and_bounds(&mut err, source, res, span, &base_error); - fallback |= self.suggest_typo(&mut err, source, path, following_seg, span, &base_error); + fallback |= self.suggest_typo( + &mut err, + source, + path, + following_seg, + span, + &base_error, + suggested_candidates, + ); if fallback { // Fallback label. @@ -589,7 +597,16 @@ fn try_lookup_name_relaxed( span: Span, res: Option, base_error: &BaseError, - ) -> (bool, Vec) { + ) -> (bool, FxHashSet, Vec) { + let span = match following_seg { + Some(_) if path[0].ident.span.eq_ctxt(path[path.len() - 1].ident.span) => { + // The path `span` that comes in includes any following segments, which we don't + // want to replace in the suggestions. + path[0].ident.span.to(path[path.len() - 1].ident.span) + } + _ => span, + }; + let mut suggested_candidates = FxHashSet::default(); // Try to lookup name in more relaxed fashion for better error reporting. let ident = path.last().unwrap().ident; let is_expected = &|res| source.is_expected(res); @@ -646,6 +663,11 @@ fn try_lookup_name_relaxed( }; let msg = format!("{preamble}try using the variant's enum"); + suggested_candidates.extend( + enum_candidates + .iter() + .map(|(_variant_path, enum_ty_path)| enum_ty_path.clone()), + ); err.span_suggestions( span, msg, @@ -658,7 +680,8 @@ fn try_lookup_name_relaxed( // Try finding a suitable replacement. let typo_sugg = self .lookup_typo_candidate(path, following_seg, source.namespace(), is_expected) - .to_opt_suggestion(); + .to_opt_suggestion() + .filter(|sugg| !suggested_candidates.contains(sugg.candidate.as_str())); if let [segment] = path && !matches!(source, PathSource::Delegation) && self.self_type_is_available() @@ -719,7 +742,7 @@ fn try_lookup_name_relaxed( } } self.r.add_typo_suggestion(err, typo_sugg, ident_span); - return (true, candidates); + return (true, suggested_candidates, candidates); } // If the first argument in call is `self` suggest calling a method. @@ -737,7 +760,7 @@ fn try_lookup_name_relaxed( format!("self.{path_str}({args_snippet})"), Applicability::MachineApplicable, ); - return (true, candidates); + return (true, suggested_candidates, candidates); } } @@ -754,7 +777,7 @@ fn try_lookup_name_relaxed( ) { // We do this to avoid losing a secondary span when we override the main error span. self.r.add_typo_suggestion(err, typo_sugg, ident_span); - return (true, candidates); + return (true, suggested_candidates, candidates); } } @@ -772,7 +795,7 @@ fn try_lookup_name_relaxed( ident.span, format!("the binding `{path_str}` is available in a different scope in the same function"), ); - return (true, candidates); + return (true, suggested_candidates, candidates); } } } @@ -781,7 +804,7 @@ fn try_lookup_name_relaxed( candidates = self.smart_resolve_partial_mod_path_errors(path, following_seg); } - (false, candidates) + (false, suggested_candidates, candidates) } fn suggest_trait_and_bounds( @@ -869,13 +892,16 @@ fn suggest_typo( following_seg: Option<&Segment>, span: Span, base_error: &BaseError, + suggested_candidates: FxHashSet, ) -> bool { let is_expected = &|res| source.is_expected(res); let ident_span = path.last().map_or(span, |ident| ident.ident.span); let typo_sugg = self.lookup_typo_candidate(path, following_seg, source.namespace(), is_expected); let mut fallback = false; - let typo_sugg = typo_sugg.to_opt_suggestion(); + let typo_sugg = typo_sugg + .to_opt_suggestion() + .filter(|sugg| !suggested_candidates.contains(sugg.candidate.as_str())); if !self.r.add_typo_suggestion(err, typo_sugg, ident_span) { fallback = true; match self.diag_metadata.current_let_binding { diff --git a/tests/ui/enum/assoc-fn-call-on-variant.stderr b/tests/ui/enum/assoc-fn-call-on-variant.stderr index 4775201d2bb..47fc630c923 100644 --- a/tests/ui/enum/assoc-fn-call-on-variant.stderr +++ b/tests/ui/enum/assoc-fn-call-on-variant.stderr @@ -6,7 +6,7 @@ LL | E::A::f(); | help: there is an enum variant `E::A`; try using the variant's enum | -LL | E(); +LL | E::f(); | ~ error: aborting due to 1 previous error diff --git a/tests/ui/resolve/resolve-variant-assoc-item.stderr b/tests/ui/resolve/resolve-variant-assoc-item.stderr index ed157197d17..9a5a605ac05 100644 --- a/tests/ui/resolve/resolve-variant-assoc-item.stderr +++ b/tests/ui/resolve/resolve-variant-assoc-item.stderr @@ -6,7 +6,7 @@ LL | E::V::associated_item; | help: there is an enum variant `E::V`; try using the variant's enum | -LL | E; +LL | E::associated_item; | ~ error[E0433]: failed to resolve: `V` is a variant, not a module @@ -17,10 +17,6 @@ LL | V::associated_item; | help: there is an enum variant `E::V`; try using the variant's enum | -LL | E; - | ~ -help: an enum with a similar name exists - | LL | E::associated_item; | ~