about summary refs log tree commit diff
diff options
context:
space:
mode:
authorZachary S <zasample18+github@gmail.com>2025-01-30 20:45:30 -0600
committerZachary S <zasample18+github@gmail.com>2025-02-18 13:11:37 -0600
commitbfde43c84bfb35fd0d7c7ea46f8cdb0d682ef680 (patch)
tree96d9f1a9fd593740229f2521d2236ad1de4956f9
parentfe37adab4b378d68eda8a8893339606d7f381465 (diff)
downloadrust-bfde43c84bfb35fd0d7c7ea46f8cdb0d682ef680.tar.gz
rust-bfde43c84bfb35fd0d7c7ea46f8cdb0d682ef680.zip
Suggest using :: instead of . for enums in some cases.
Suggest replacing `.` with `::` when encountering "expected value, found enum":
- in a method-call expression and the method has the same name as a tuple variant
- in a field-access expression and the field has the same name as a unit or tuple variant
-rw-r--r--compiler/rustc_resolve/src/late/diagnostics.rs70
-rw-r--r--tests/ui/resolve/enum-expected-value-suggest-variants.rs58
-rw-r--r--tests/ui/resolve/enum-expected-value-suggest-variants.stderr225
3 files changed, 339 insertions, 14 deletions
diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs
index 524915b44e7..2731efdba48 100644
--- a/compiler/rustc_resolve/src/late/diagnostics.rs
+++ b/compiler/rustc_resolve/src/late/diagnostics.rs
@@ -8,7 +8,7 @@ use rustc_ast::ptr::P;
 use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty};
 use rustc_ast::{
     self as ast, AssocItemKind, DUMMY_NODE_ID, Expr, ExprKind, GenericParam, GenericParamKind,
-    Item, ItemKind, MethodCall, NodeId, Path, Ty, TyKind,
+    Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
 };
 use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
 use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
@@ -2469,31 +2469,73 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
         def_id: DefId,
         span: Span,
     ) {
-        let Some(variants) = self.collect_enum_ctors(def_id) else {
+        let Some(variant_ctors) = self.collect_enum_ctors(def_id) else {
             err.note("you might have meant to use one of the enum's variants");
             return;
         };
 
-        let suggest_only_tuple_variants =
-            matches!(source, PathSource::TupleStruct(..)) || source.is_call();
-        if suggest_only_tuple_variants {
+        // If the expression is a field-access or method-call, try to find a variant with the field/method name
+        // that could have been intended, and suggest replacing the `.` with `::`.
+        // Otherwise, suggest adding `::VariantName` after the enum;
+        // and if the expression is call-like, only suggest tuple variants.
+        let (suggest_path_sep_dot_span, suggest_only_tuple_variants) = match source {
+            // `Type(a, b)` in a pattern, only suggest adding a tuple variant after `Type`.
+            PathSource::TupleStruct(..) => (None, true),
+            PathSource::Expr(Some(expr)) => match &expr.kind {
+                // `Type(a, b)`, only suggest adding a tuple variant after `Type`.
+                ExprKind::Call(..) => (None, true),
+                // `Type.Foo(a, b)`, suggest replacing `.` -> `::` if variant `Foo` exists and is a tuple variant,
+                // otherwise suggest adding a variant after `Type`.
+                ExprKind::MethodCall(box MethodCall {
+                    receiver,
+                    span,
+                    seg: PathSegment { ident, .. },
+                    ..
+                }) => {
+                    let dot_span = receiver.span.between(*span);
+                    let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| {
+                        *ctor_kind == CtorKind::Fn
+                            && path.segments.last().is_some_and(|seg| seg.ident == *ident)
+                    });
+                    (found_tuple_variant.then_some(dot_span), false)
+                }
+                // `Type.Foo`, suggest replacing `.` -> `::` if variant `Foo` exists and is a unit or tuple variant,
+                // otherwise suggest adding a variant after `Type`.
+                ExprKind::Field(base, ident) => {
+                    let dot_span = base.span.between(ident.span);
+                    let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| {
+                        path.segments.last().is_some_and(|seg| seg.ident == *ident)
+                    });
+                    (found_tuple_or_unit_variant.then_some(dot_span), false)
+                }
+                _ => (None, false),
+            },
+            _ => (None, false),
+        };
+
+        if let Some(dot_span) = suggest_path_sep_dot_span {
+            err.span_suggestion_verbose(
+                dot_span,
+                "use the path separator to refer to a variant",
+                "::",
+                Applicability::MaybeIncorrect,
+            );
+        } else if suggest_only_tuple_variants {
             // Suggest only tuple variants regardless of whether they have fields and do not
             // suggest path with added parentheses.
-            let mut suggestable_variants = variants
+            let mut suggestable_variants = variant_ctors
                 .iter()
                 .filter(|(.., kind)| *kind == CtorKind::Fn)
                 .map(|(variant, ..)| path_names_to_string(variant))
                 .collect::<Vec<_>>();
             suggestable_variants.sort();
 
-            let non_suggestable_variant_count = variants.len() - suggestable_variants.len();
+            let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len();
 
-            let source_msg = if source.is_call() {
-                "to construct"
-            } else if matches!(source, PathSource::TupleStruct(..)) {
+            let source_msg = if matches!(source, PathSource::TupleStruct(..)) {
                 "to match against"
             } else {
-                unreachable!()
+                "to construct"
             };
 
             if !suggestable_variants.is_empty() {
@@ -2512,7 +2554,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
             }
 
             // If the enum has no tuple variants..
-            if non_suggestable_variant_count == variants.len() {
+            if non_suggestable_variant_count == variant_ctors.len() {
                 err.help(format!("the enum has no tuple variants {source_msg}"));
             }
 
@@ -2535,7 +2577,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
                 }
             };
 
-            let mut suggestable_variants = variants
+            let mut suggestable_variants = variant_ctors
                 .iter()
                 .filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind))
                 .map(|(variant, _, kind)| (path_names_to_string(variant), kind))
@@ -2562,7 +2604,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
                 );
             }
 
-            let mut suggestable_variants_with_placeholders = variants
+            let mut suggestable_variants_with_placeholders = variant_ctors
                 .iter()
                 .filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind))
                 .map(|(variant, _, kind)| (path_names_to_string(variant), kind))
diff --git a/tests/ui/resolve/enum-expected-value-suggest-variants.rs b/tests/ui/resolve/enum-expected-value-suggest-variants.rs
new file mode 100644
index 00000000000..9f86a0a1ecc
--- /dev/null
+++ b/tests/ui/resolve/enum-expected-value-suggest-variants.rs
@@ -0,0 +1,58 @@
+enum Foo {
+    //~^ HELP consider importing this tuple variant
+    A(u32),
+    B(u32),
+}
+
+enum Bar {
+    C(u32),
+    D(u32),
+    E,
+    F,
+}
+
+fn main() {
+    let _: Foo = Foo(0);
+    //~^ ERROR expected function
+    //~| HELP try to construct one of the enum's variants
+
+    let _: Foo = Foo.A(0);
+    //~^ ERROR expected value, found enum `Foo`
+    //~| HELP use the path separator to refer to a variant
+
+    let _: Foo = Foo.Bad(0);
+    //~^ ERROR expected value, found enum `Foo`
+    //~| HELP the following enum variants are available
+
+    let _: Bar = Bar(0);
+    //~^ ERROR expected function
+    //~| HELP try to construct one of the enum's variants
+    //~| HELP you might have meant to construct one of the enum's non-tuple variants
+
+    let _: Bar = Bar.C(0);
+    //~^ ERROR expected value, found enum `Bar`
+    //~| HELP use the path separator to refer to a variant
+
+    let _: Bar = Bar.E;
+    //~^ ERROR expected value, found enum `Bar`
+    //~| HELP use the path separator to refer to a variant
+
+    let _: Bar = Bar.Bad(0);
+    //~^ ERROR expected value, found enum `Bar`
+    //~| HELP you might have meant to use one of the following enum variants
+    //~| HELP alternatively, the following enum variants are also available
+
+    let _: Bar = Bar.Bad;
+    //~^ ERROR expected value, found enum `Bar`
+    //~| HELP you might have meant to use one of the following enum variants
+    //~| HELP alternatively, the following enum variants are also available
+
+    match Foo::A(42) {
+        A(..) => {}
+        //~^ ERROR cannot find tuple struct or tuple variant `A` in this scope
+        Foo(..) => {}
+        //~^ ERROR expected tuple struct or tuple variant
+        //~| HELP try to match against one of the enum's variants
+        _ => {}
+    }
+}
diff --git a/tests/ui/resolve/enum-expected-value-suggest-variants.stderr b/tests/ui/resolve/enum-expected-value-suggest-variants.stderr
new file mode 100644
index 00000000000..0bd6069b272
--- /dev/null
+++ b/tests/ui/resolve/enum-expected-value-suggest-variants.stderr
@@ -0,0 +1,225 @@
+error[E0423]: expected value, found enum `Foo`
+  --> $DIR/enum-expected-value-suggest-variants.rs:19:18
+   |
+LL |     let _: Foo = Foo.A(0);
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:1:1
+   |
+LL | / enum Foo {
+LL | |
+LL | |     A(u32),
+LL | |     B(u32),
+LL | | }
+   | |_^
+help: use the path separator to refer to a variant
+   |
+LL |     let _: Foo = Foo::A(0);
+   |                     ~~
+
+error[E0423]: expected value, found enum `Foo`
+  --> $DIR/enum-expected-value-suggest-variants.rs:23:18
+   |
+LL |     let _: Foo = Foo.Bad(0);
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:1:1
+   |
+LL | / enum Foo {
+LL | |
+LL | |     A(u32),
+LL | |     B(u32),
+LL | | }
+   | |_^
+help: the following enum variants are available
+   |
+LL |     let _: Foo = (Foo::A(/* fields */)).Bad(0);
+   |                  ~~~~~~~~~~~~~~~~~~~~~~
+LL |     let _: Foo = (Foo::B(/* fields */)).Bad(0);
+   |                  ~~~~~~~~~~~~~~~~~~~~~~
+
+error[E0423]: expected value, found enum `Bar`
+  --> $DIR/enum-expected-value-suggest-variants.rs:32:18
+   |
+LL |     let _: Bar = Bar.C(0);
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:7:1
+   |
+LL | / enum Bar {
+LL | |     C(u32),
+LL | |     D(u32),
+LL | |     E,
+LL | |     F,
+LL | | }
+   | |_^
+help: use the path separator to refer to a variant
+   |
+LL |     let _: Bar = Bar::C(0);
+   |                     ~~
+
+error[E0423]: expected value, found enum `Bar`
+  --> $DIR/enum-expected-value-suggest-variants.rs:36:18
+   |
+LL |     let _: Bar = Bar.E;
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:7:1
+   |
+LL | / enum Bar {
+LL | |     C(u32),
+LL | |     D(u32),
+LL | |     E,
+LL | |     F,
+LL | | }
+   | |_^
+help: use the path separator to refer to a variant
+   |
+LL |     let _: Bar = Bar::E;
+   |                     ~~
+
+error[E0423]: expected value, found enum `Bar`
+  --> $DIR/enum-expected-value-suggest-variants.rs:40:18
+   |
+LL |     let _: Bar = Bar.Bad(0);
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:7:1
+   |
+LL | / enum Bar {
+LL | |     C(u32),
+LL | |     D(u32),
+LL | |     E,
+LL | |     F,
+LL | | }
+   | |_^
+help: you might have meant to use one of the following enum variants
+   |
+LL |     let _: Bar = Bar::E.Bad(0);
+   |                  ~~~~~~
+LL |     let _: Bar = Bar::F.Bad(0);
+   |                  ~~~~~~
+help: alternatively, the following enum variants are also available
+   |
+LL |     let _: Bar = (Bar::C(/* fields */)).Bad(0);
+   |                  ~~~~~~~~~~~~~~~~~~~~~~
+LL |     let _: Bar = (Bar::D(/* fields */)).Bad(0);
+   |                  ~~~~~~~~~~~~~~~~~~~~~~
+
+error[E0423]: expected value, found enum `Bar`
+  --> $DIR/enum-expected-value-suggest-variants.rs:45:18
+   |
+LL |     let _: Bar = Bar.Bad;
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:7:1
+   |
+LL | / enum Bar {
+LL | |     C(u32),
+LL | |     D(u32),
+LL | |     E,
+LL | |     F,
+LL | | }
+   | |_^
+help: you might have meant to use one of the following enum variants
+   |
+LL |     let _: Bar = Bar::E.Bad;
+   |                  ~~~~~~
+LL |     let _: Bar = Bar::F.Bad;
+   |                  ~~~~~~
+help: alternatively, the following enum variants are also available
+   |
+LL |     let _: Bar = (Bar::C(/* fields */)).Bad;
+   |                  ~~~~~~~~~~~~~~~~~~~~~~
+LL |     let _: Bar = (Bar::D(/* fields */)).Bad;
+   |                  ~~~~~~~~~~~~~~~~~~~~~~
+
+error[E0531]: cannot find tuple struct or tuple variant `A` in this scope
+  --> $DIR/enum-expected-value-suggest-variants.rs:51:9
+   |
+LL |         A(..) => {}
+   |         ^ not found in this scope
+   |
+help: consider importing this tuple variant
+   |
+LL + use Foo::A;
+   |
+
+error[E0532]: expected tuple struct or tuple variant, found enum `Foo`
+  --> $DIR/enum-expected-value-suggest-variants.rs:53:9
+   |
+LL |         Foo(..) => {}
+   |         ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:1:1
+   |
+LL | / enum Foo {
+LL | |
+LL | |     A(u32),
+LL | |     B(u32),
+LL | | }
+   | |_^
+help: try to match against one of the enum's variants
+   |
+LL |         Foo::A(..) => {}
+   |         ~~~~~~
+LL |         Foo::B(..) => {}
+   |         ~~~~~~
+
+error[E0423]: expected function, tuple struct or tuple variant, found enum `Foo`
+  --> $DIR/enum-expected-value-suggest-variants.rs:15:18
+   |
+LL |     let _: Foo = Foo(0);
+   |                  ^^^
+   |
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:1:1
+   |
+LL | / enum Foo {
+LL | |
+LL | |     A(u32),
+LL | |     B(u32),
+LL | | }
+   | |_^
+help: try to construct one of the enum's variants
+   |
+LL |     let _: Foo = Foo::A(0);
+   |                  ~~~~~~
+LL |     let _: Foo = Foo::B(0);
+   |                  ~~~~~~
+
+error[E0423]: expected function, tuple struct or tuple variant, found enum `Bar`
+  --> $DIR/enum-expected-value-suggest-variants.rs:27:18
+   |
+LL |     let _: Bar = Bar(0);
+   |                  ^^^
+   |
+   = help: you might have meant to construct one of the enum's non-tuple variants
+note: the enum is defined here
+  --> $DIR/enum-expected-value-suggest-variants.rs:7:1
+   |
+LL | / enum Bar {
+LL | |     C(u32),
+LL | |     D(u32),
+LL | |     E,
+LL | |     F,
+LL | | }
+   | |_^
+help: try to construct one of the enum's variants
+   |
+LL |     let _: Bar = Bar::C(0);
+   |                  ~~~~~~
+LL |     let _: Bar = Bar::D(0);
+   |                  ~~~~~~
+
+error: aborting due to 10 previous errors
+
+Some errors have detailed explanations: E0423, E0531, E0532.
+For more information about an error, try `rustc --explain E0423`.