Skip to content

Commit 9c58163

Browse files
committed
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
1 parent b8f10df commit 9c58163

File tree

3 files changed

+339
-14
lines changed

3 files changed

+339
-14
lines changed

compiler/rustc_resolve/src/late/diagnostics.rs

+56-14
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use rustc_ast::ptr::P;
88
use rustc_ast::visit::{FnCtxt, FnKind, LifetimeCtxt, Visitor, walk_ty};
99
use rustc_ast::{
1010
self as ast, AssocItemKind, DUMMY_NODE_ID, Expr, ExprKind, GenericParam, GenericParamKind,
11-
Item, ItemKind, MethodCall, NodeId, Path, Ty, TyKind,
11+
Item, ItemKind, MethodCall, NodeId, Path, PathSegment, Ty, TyKind,
1212
};
1313
use rustc_ast_pretty::pprust::where_bound_predicate_to_string;
1414
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
@@ -2442,31 +2442,73 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
24422442
def_id: DefId,
24432443
span: Span,
24442444
) {
2445-
let Some(variants) = self.collect_enum_ctors(def_id) else {
2445+
let Some(variant_ctors) = self.collect_enum_ctors(def_id) else {
24462446
err.note("you might have meant to use one of the enum's variants");
24472447
return;
24482448
};
24492449

2450-
let suggest_only_tuple_variants =
2451-
matches!(source, PathSource::TupleStruct(..)) || source.is_call();
2452-
if suggest_only_tuple_variants {
2450+
// If the expression is a field-access or method-call, try to find a variant with the field/method name
2451+
// that could have been intended, and suggest replacing the `.` with `::`.
2452+
// Otherwise, suggest adding `::VariantName` after the enum;
2453+
// and if the expression is call-like, only suggest tuple variants.
2454+
let (suggest_path_sep_dot_span, suggest_only_tuple_variants) = match source {
2455+
// `Type(a, b)` in a pattern, only suggest adding a tuple variant after `Type`.
2456+
PathSource::TupleStruct(..) => (None, true),
2457+
PathSource::Expr(Some(expr)) => match &expr.kind {
2458+
// `Type(a, b)`, only suggest adding a tuple variant after `Type`.
2459+
ExprKind::Call(..) => (None, true),
2460+
// `Type.Foo(a, b)`, suggest replacing `.` -> `::` if variant `Foo` exists and is a tuple variant,
2461+
// otherwise suggest adding a variant after `Type`.
2462+
ExprKind::MethodCall(box MethodCall {
2463+
receiver,
2464+
span,
2465+
seg: PathSegment { ident, .. },
2466+
..
2467+
}) => {
2468+
let dot_span = receiver.span.between(*span);
2469+
let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| {
2470+
*ctor_kind == CtorKind::Fn
2471+
&& path.segments.last().is_some_and(|seg| seg.ident == *ident)
2472+
});
2473+
(found_tuple_variant.then_some(dot_span), false)
2474+
}
2475+
// `Type.Foo`, suggest replacing `.` -> `::` if variant `Foo` exists and is a unit or tuple variant,
2476+
// otherwise suggest adding a variant after `Type`.
2477+
ExprKind::Field(base, ident) => {
2478+
let dot_span = base.span.between(ident.span);
2479+
let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| {
2480+
path.segments.last().is_some_and(|seg| seg.ident == *ident)
2481+
});
2482+
(found_tuple_or_unit_variant.then_some(dot_span), false)
2483+
}
2484+
_ => (None, false),
2485+
},
2486+
_ => (None, false),
2487+
};
2488+
2489+
if let Some(dot_span) = suggest_path_sep_dot_span {
2490+
err.span_suggestion_verbose(
2491+
dot_span,
2492+
"use the path separator to refer to a variant",
2493+
"::",
2494+
Applicability::MaybeIncorrect,
2495+
);
2496+
} else if suggest_only_tuple_variants {
24532497
// Suggest only tuple variants regardless of whether they have fields and do not
24542498
// suggest path with added parentheses.
2455-
let mut suggestable_variants = variants
2499+
let mut suggestable_variants = variant_ctors
24562500
.iter()
24572501
.filter(|(.., kind)| *kind == CtorKind::Fn)
24582502
.map(|(variant, ..)| path_names_to_string(variant))
24592503
.collect::<Vec<_>>();
24602504
suggestable_variants.sort();
24612505

2462-
let non_suggestable_variant_count = variants.len() - suggestable_variants.len();
2506+
let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len();
24632507

2464-
let source_msg = if source.is_call() {
2465-
"to construct"
2466-
} else if matches!(source, PathSource::TupleStruct(..)) {
2508+
let source_msg = if matches!(source, PathSource::TupleStruct(..)) {
24672509
"to match against"
24682510
} else {
2469-
unreachable!()
2511+
"to construct"
24702512
};
24712513

24722514
if !suggestable_variants.is_empty() {
@@ -2485,7 +2527,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
24852527
}
24862528

24872529
// If the enum has no tuple variants..
2488-
if non_suggestable_variant_count == variants.len() {
2530+
if non_suggestable_variant_count == variant_ctors.len() {
24892531
err.help(format!("the enum has no tuple variants {source_msg}"));
24902532
}
24912533

@@ -2508,7 +2550,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25082550
}
25092551
};
25102552

2511-
let mut suggestable_variants = variants
2553+
let mut suggestable_variants = variant_ctors
25122554
.iter()
25132555
.filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind))
25142556
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
@@ -2535,7 +2577,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25352577
);
25362578
}
25372579

2538-
let mut suggestable_variants_with_placeholders = variants
2580+
let mut suggestable_variants_with_placeholders = variant_ctors
25392581
.iter()
25402582
.filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind))
25412583
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
enum Foo {
2+
//~^ HELP consider importing this tuple variant
3+
A(u32),
4+
B(u32),
5+
}
6+
7+
enum Bar {
8+
C(u32),
9+
D(u32),
10+
E,
11+
F,
12+
}
13+
14+
fn main() {
15+
let _: Foo = Foo(0);
16+
//~^ ERROR expected function
17+
//~| HELP try to construct one of the enum's variants
18+
19+
let _: Foo = Foo.A(0);
20+
//~^ ERROR expected value, found enum `Foo`
21+
//~| HELP use the path separator to refer to a variant
22+
23+
let _: Foo = Foo.Bad(0);
24+
//~^ ERROR expected value, found enum `Foo`
25+
//~| HELP the following enum variants are available
26+
27+
let _: Bar = Bar(0);
28+
//~^ ERROR expected function
29+
//~| HELP try to construct one of the enum's variants
30+
//~| HELP you might have meant to construct one of the enum's non-tuple variants
31+
32+
let _: Bar = Bar.C(0);
33+
//~^ ERROR expected value, found enum `Bar`
34+
//~| HELP use the path separator to refer to a variant
35+
36+
let _: Bar = Bar.E;
37+
//~^ ERROR expected value, found enum `Bar`
38+
//~| HELP use the path separator to refer to a variant
39+
40+
let _: Bar = Bar.Bad(0);
41+
//~^ ERROR expected value, found enum `Bar`
42+
//~| HELP you might have meant to use one of the following enum variants
43+
//~| HELP alternatively, the following enum variants are also available
44+
45+
let _: Bar = Bar.Bad;
46+
//~^ ERROR expected value, found enum `Bar`
47+
//~| HELP you might have meant to use one of the following enum variants
48+
//~| HELP alternatively, the following enum variants are also available
49+
50+
match Foo::A(42) {
51+
A(..) => {}
52+
//~^ ERROR cannot find tuple struct or tuple variant `A` in this scope
53+
Foo(..) => {}
54+
//~^ ERROR expected tuple struct or tuple variant
55+
//~| HELP try to match against one of the enum's variants
56+
_ => {}
57+
}
58+
}

0 commit comments

Comments
 (0)