Skip to content

Commit bfde43c

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 fe37ada commit bfde43c

File tree

3 files changed

+339
-14
lines changed

3 files changed

+339
-14
lines changed

compiler/rustc_resolve/src/late/diagnostics.rs

Lines changed: 56 additions & 14 deletions
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};
@@ -2469,31 +2469,73 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
24692469
def_id: DefId,
24702470
span: Span,
24712471
) {
2472-
let Some(variants) = self.collect_enum_ctors(def_id) else {
2472+
let Some(variant_ctors) = self.collect_enum_ctors(def_id) else {
24732473
err.note("you might have meant to use one of the enum's variants");
24742474
return;
24752475
};
24762476

2477-
let suggest_only_tuple_variants =
2478-
matches!(source, PathSource::TupleStruct(..)) || source.is_call();
2479-
if suggest_only_tuple_variants {
2477+
// If the expression is a field-access or method-call, try to find a variant with the field/method name
2478+
// that could have been intended, and suggest replacing the `.` with `::`.
2479+
// Otherwise, suggest adding `::VariantName` after the enum;
2480+
// and if the expression is call-like, only suggest tuple variants.
2481+
let (suggest_path_sep_dot_span, suggest_only_tuple_variants) = match source {
2482+
// `Type(a, b)` in a pattern, only suggest adding a tuple variant after `Type`.
2483+
PathSource::TupleStruct(..) => (None, true),
2484+
PathSource::Expr(Some(expr)) => match &expr.kind {
2485+
// `Type(a, b)`, only suggest adding a tuple variant after `Type`.
2486+
ExprKind::Call(..) => (None, true),
2487+
// `Type.Foo(a, b)`, suggest replacing `.` -> `::` if variant `Foo` exists and is a tuple variant,
2488+
// otherwise suggest adding a variant after `Type`.
2489+
ExprKind::MethodCall(box MethodCall {
2490+
receiver,
2491+
span,
2492+
seg: PathSegment { ident, .. },
2493+
..
2494+
}) => {
2495+
let dot_span = receiver.span.between(*span);
2496+
let found_tuple_variant = variant_ctors.iter().any(|(path, _, ctor_kind)| {
2497+
*ctor_kind == CtorKind::Fn
2498+
&& path.segments.last().is_some_and(|seg| seg.ident == *ident)
2499+
});
2500+
(found_tuple_variant.then_some(dot_span), false)
2501+
}
2502+
// `Type.Foo`, suggest replacing `.` -> `::` if variant `Foo` exists and is a unit or tuple variant,
2503+
// otherwise suggest adding a variant after `Type`.
2504+
ExprKind::Field(base, ident) => {
2505+
let dot_span = base.span.between(ident.span);
2506+
let found_tuple_or_unit_variant = variant_ctors.iter().any(|(path, ..)| {
2507+
path.segments.last().is_some_and(|seg| seg.ident == *ident)
2508+
});
2509+
(found_tuple_or_unit_variant.then_some(dot_span), false)
2510+
}
2511+
_ => (None, false),
2512+
},
2513+
_ => (None, false),
2514+
};
2515+
2516+
if let Some(dot_span) = suggest_path_sep_dot_span {
2517+
err.span_suggestion_verbose(
2518+
dot_span,
2519+
"use the path separator to refer to a variant",
2520+
"::",
2521+
Applicability::MaybeIncorrect,
2522+
);
2523+
} else if suggest_only_tuple_variants {
24802524
// Suggest only tuple variants regardless of whether they have fields and do not
24812525
// suggest path with added parentheses.
2482-
let mut suggestable_variants = variants
2526+
let mut suggestable_variants = variant_ctors
24832527
.iter()
24842528
.filter(|(.., kind)| *kind == CtorKind::Fn)
24852529
.map(|(variant, ..)| path_names_to_string(variant))
24862530
.collect::<Vec<_>>();
24872531
suggestable_variants.sort();
24882532

2489-
let non_suggestable_variant_count = variants.len() - suggestable_variants.len();
2533+
let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len();
24902534

2491-
let source_msg = if source.is_call() {
2492-
"to construct"
2493-
} else if matches!(source, PathSource::TupleStruct(..)) {
2535+
let source_msg = if matches!(source, PathSource::TupleStruct(..)) {
24942536
"to match against"
24952537
} else {
2496-
unreachable!()
2538+
"to construct"
24972539
};
24982540

24992541
if !suggestable_variants.is_empty() {
@@ -2512,7 +2554,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25122554
}
25132555

25142556
// If the enum has no tuple variants..
2515-
if non_suggestable_variant_count == variants.len() {
2557+
if non_suggestable_variant_count == variant_ctors.len() {
25162558
err.help(format!("the enum has no tuple variants {source_msg}"));
25172559
}
25182560

@@ -2535,7 +2577,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25352577
}
25362578
};
25372579

2538-
let mut suggestable_variants = variants
2580+
let mut suggestable_variants = variant_ctors
25392581
.iter()
25402582
.filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind))
25412583
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
@@ -2562,7 +2604,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25622604
);
25632605
}
25642606

2565-
let mut suggestable_variants_with_placeholders = variants
2607+
let mut suggestable_variants_with_placeholders = variant_ctors
25662608
.iter()
25672609
.filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind))
25682610
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
Lines changed: 58 additions & 0 deletions
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)