Skip to content

Commit cb3ee99

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 ce16958 commit cb3ee99

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};
@@ -2448,31 +2448,73 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
24482448
def_id: DefId,
24492449
span: Span,
24502450
) {
2451-
let Some(variants) = self.collect_enum_ctors(def_id) else {
2451+
let Some(variant_ctors) = self.collect_enum_ctors(def_id) else {
24522452
err.note("you might have meant to use one of the enum's variants");
24532453
return;
24542454
};
24552455

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

2468-
let non_suggestable_variant_count = variants.len() - suggestable_variants.len();
2512+
let non_suggestable_variant_count = variant_ctors.len() - suggestable_variants.len();
24692513

2470-
let source_msg = if source.is_call() {
2471-
"to construct"
2472-
} else if matches!(source, PathSource::TupleStruct(..)) {
2514+
let source_msg = if matches!(source, PathSource::TupleStruct(..)) {
24732515
"to match against"
24742516
} else {
2475-
unreachable!()
2517+
"to construct"
24762518
};
24772519

24782520
if !suggestable_variants.is_empty() {
@@ -2491,7 +2533,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
24912533
}
24922534

24932535
// If the enum has no tuple variants..
2494-
if non_suggestable_variant_count == variants.len() {
2536+
if non_suggestable_variant_count == variant_ctors.len() {
24952537
err.help(format!("the enum has no tuple variants {source_msg}"));
24962538
}
24972539

@@ -2514,7 +2556,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25142556
}
25152557
};
25162558

2517-
let mut suggestable_variants = variants
2559+
let mut suggestable_variants = variant_ctors
25182560
.iter()
25192561
.filter(|(_, def_id, kind)| !needs_placeholder(*def_id, *kind))
25202562
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
@@ -2541,7 +2583,7 @@ impl<'ast, 'ra: 'ast, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
25412583
);
25422584
}
25432585

2544-
let mut suggestable_variants_with_placeholders = variants
2586+
let mut suggestable_variants_with_placeholders = variant_ctors
25452587
.iter()
25462588
.filter(|(_, def_id, kind)| needs_placeholder(*def_id, *kind))
25472589
.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)