Skip to content

Commit 9ef68f5

Browse files
committed
resolve: improve "try using the enum's variant"
This commit improves the "try using the enum's variant" suggestion: - Variants in suggestions would not result in more errors (e.g. use of a struct variant is only suggested if the suggestion can trivially construct that variant). Therefore, suggestions are only emitted for variants that have no fields (since the suggestion can't know what value fields would have). - Suggestions include the syntax for constructing the variant. If a struct or tuple variant is suggested, then it is constructed in the suggestion - unless in pattern-matching or when arguments are already provided. - A help message is added which mentions the variants which are no longer suggested. Signed-off-by: David Wood <[email protected]>
1 parent 26373fb commit 9ef68f5

File tree

5 files changed

+258
-88
lines changed

5 files changed

+258
-88
lines changed

compiler/rustc_resolve/src/late/diagnostics.rs

+124-21
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use rustc_data_structures::fx::FxHashSet;
1212
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
1313
use rustc_hir as hir;
1414
use rustc_hir::def::Namespace::{self, *};
15-
use rustc_hir::def::{self, CtorKind, DefKind};
15+
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind};
1616
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
1717
use rustc_hir::PrimTy;
1818
use rustc_session::config::nightly_options;
@@ -725,24 +725,8 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
725725
// We already suggested changing `:` into `::` during parsing.
726726
return false;
727727
}
728-
if let Some(variants) = self.collect_enum_variants(def_id) {
729-
if !variants.is_empty() {
730-
let msg = if variants.len() == 1 {
731-
"try using the enum's variant"
732-
} else {
733-
"try using one of the enum's variants"
734-
};
735728

736-
err.span_suggestions(
737-
span,
738-
msg,
739-
variants.iter().map(path_names_to_string),
740-
Applicability::MaybeIncorrect,
741-
);
742-
}
743-
} else {
744-
err.note("you might have meant to use one of the enum's variants");
745-
}
729+
self.suggest_using_enum_variant(err, source, def_id, span);
746730
}
747731
(Res::Def(DefKind::Struct, def_id), _) if ns == ValueNS => {
748732
if let Some((ctor_def, ctor_vis, fields)) =
@@ -1125,20 +1109,139 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
11251109
result
11261110
}
11271111

1128-
fn collect_enum_variants(&mut self, def_id: DefId) -> Option<Vec<Path>> {
1112+
fn collect_enum_ctors(&mut self, def_id: DefId) -> Option<Vec<(Path, DefId, CtorKind)>> {
11291113
self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| {
11301114
let mut variants = Vec::new();
11311115
enum_module.for_each_child(self.r, |_, ident, _, name_binding| {
1132-
if let Res::Def(DefKind::Variant, _) = name_binding.res() {
1116+
if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() {
11331117
let mut segms = enum_import_suggestion.path.segments.clone();
11341118
segms.push(ast::PathSegment::from_ident(ident));
1135-
variants.push(Path { span: name_binding.span, segments: segms, tokens: None });
1119+
let path = Path { span: name_binding.span, segments: segms, tokens: None };
1120+
variants.push((path, def_id, kind));
11361121
}
11371122
});
11381123
variants
11391124
})
11401125
}
11411126

1127+
/// Adds a suggestion for using an enum's variant when an enum is used instead.
1128+
fn suggest_using_enum_variant(
1129+
&mut self,
1130+
err: &mut DiagnosticBuilder<'a>,
1131+
source: PathSource<'_>,
1132+
def_id: DefId,
1133+
span: Span,
1134+
) {
1135+
let variants = match self.collect_enum_ctors(def_id) {
1136+
Some(variants) => variants,
1137+
None => {
1138+
err.note("you might have meant to use one of the enum's variants");
1139+
return;
1140+
}
1141+
};
1142+
1143+
let suggest_only_tuple_variants =
1144+
matches!(source, PathSource::TupleStruct(..)) || source.is_call();
1145+
let mut suggestable_variants = if suggest_only_tuple_variants {
1146+
// Suggest only tuple variants regardless of whether they have fields and do not
1147+
// suggest path with added parenthesis.
1148+
variants
1149+
.iter()
1150+
.filter(|(.., kind)| *kind == CtorKind::Fn)
1151+
.map(|(variant, ..)| path_names_to_string(variant))
1152+
.collect::<Vec<_>>()
1153+
} else {
1154+
variants
1155+
.iter()
1156+
.filter(|(_, def_id, kind)| {
1157+
// Suggest only variants that have no fields (these can definitely
1158+
// be constructed).
1159+
let has_fields =
1160+
self.r.field_names.get(&def_id).map(|f| f.is_empty()).unwrap_or(false);
1161+
match kind {
1162+
CtorKind::Const => true,
1163+
CtorKind::Fn | CtorKind::Fictive if has_fields => true,
1164+
_ => false,
1165+
}
1166+
})
1167+
.map(|(variant, _, kind)| (path_names_to_string(variant), kind))
1168+
.map(|(variant_str, kind)| {
1169+
// Add constructor syntax where appropriate.
1170+
match kind {
1171+
CtorKind::Const => variant_str,
1172+
CtorKind::Fn => format!("({}())", variant_str),
1173+
CtorKind::Fictive => format!("({} {{}})", variant_str),
1174+
}
1175+
})
1176+
.collect::<Vec<_>>()
1177+
};
1178+
1179+
let non_suggestable_variant_count = variants.len() - suggestable_variants.len();
1180+
1181+
if !suggestable_variants.is_empty() {
1182+
let msg = if non_suggestable_variant_count == 0 && suggestable_variants.len() == 1 {
1183+
"try using the enum's variant"
1184+
} else {
1185+
"try using one of the enum's variants"
1186+
};
1187+
1188+
err.span_suggestions(
1189+
span,
1190+
msg,
1191+
suggestable_variants.drain(..),
1192+
Applicability::MaybeIncorrect,
1193+
);
1194+
}
1195+
1196+
if suggest_only_tuple_variants {
1197+
let source_msg = if source.is_call() {
1198+
"to construct"
1199+
} else if matches!(source, PathSource::TupleStruct(..)) {
1200+
"to match against"
1201+
} else {
1202+
unreachable!()
1203+
};
1204+
1205+
// If the enum has no tuple variants..
1206+
if non_suggestable_variant_count == variants.len() {
1207+
err.help(&format!("the enum has no tuple variants {}", source_msg));
1208+
}
1209+
1210+
// If there are also non-tuple variants..
1211+
if non_suggestable_variant_count == 1 {
1212+
err.help(&format!(
1213+
"you might have meant {} the enum's non-tuple variant",
1214+
source_msg
1215+
));
1216+
} else if non_suggestable_variant_count >= 1 {
1217+
err.help(&format!(
1218+
"you might have meant {} one of the enum's non-tuple variants",
1219+
source_msg
1220+
));
1221+
}
1222+
} else {
1223+
let made_suggestion = non_suggestable_variant_count != variants.len();
1224+
if made_suggestion {
1225+
if non_suggestable_variant_count == 1 {
1226+
err.help(
1227+
"you might have meant to use the enum's other variant that has fields",
1228+
);
1229+
} else if non_suggestable_variant_count >= 1 {
1230+
err.help(
1231+
"you might have meant to use one of the enum's other variants that \
1232+
have fields",
1233+
);
1234+
}
1235+
} else {
1236+
if non_suggestable_variant_count == 1 {
1237+
err.help("you might have meant to use the enum's variant");
1238+
} else if non_suggestable_variant_count >= 1 {
1239+
err.help("you might have meant to use one of the enum's variants");
1240+
}
1241+
}
1242+
}
1243+
}
1244+
11421245
crate fn report_missing_type_error(
11431246
&self,
11441247
path: &[Segment],

src/test/ui/did_you_mean/issue-43871-enum-instead-of-variant.stderr

+10-32
Original file line numberDiff line numberDiff line change
@@ -2,64 +2,42 @@ error[E0423]: expected function, tuple struct or tuple variant, found enum `Opti
22
--> $DIR/issue-43871-enum-instead-of-variant.rs:19:13
33
|
44
LL | let x = Option(1);
5-
| ^^^^^^
5+
| ^^^^^^ help: try using one of the enum's variants: `std::option::Option::Some`
66
|
7-
help: try using one of the enum's variants
8-
|
9-
LL | let x = std::option::Option::None(1);
10-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
11-
LL | let x = std::option::Option::Some(1);
12-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
7+
= help: you might have meant to construct the enum's non-tuple variant
138

149
error[E0532]: expected tuple struct or tuple variant, found enum `Option`
1510
--> $DIR/issue-43871-enum-instead-of-variant.rs:21:12
1611
|
1712
LL | if let Option(_) = x {
18-
| ^^^^^^
19-
|
20-
help: try using one of the enum's variants
13+
| ^^^^^^ help: try using one of the enum's variants: `std::option::Option::Some`
2114
|
22-
LL | if let std::option::Option::None(_) = x {
23-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
24-
LL | if let std::option::Option::Some(_) = x {
25-
| ^^^^^^^^^^^^^^^^^^^^^^^^^
15+
= help: you might have meant to match against the enum's non-tuple variant
2616

2717
error[E0532]: expected tuple struct or tuple variant, found enum `Example`
2818
--> $DIR/issue-43871-enum-instead-of-variant.rs:27:12
2919
|
3020
LL | if let Example(_) = y {
31-
| ^^^^^^^
32-
|
33-
help: try using one of the enum's variants
21+
| ^^^^^^^ help: try using one of the enum's variants: `Example::Ex`
3422
|
35-
LL | if let Example::Ex(_) = y {
36-
| ^^^^^^^^^^^
37-
LL | if let Example::NotEx(_) = y {
38-
| ^^^^^^^^^^^^^^
23+
= help: you might have meant to match against the enum's non-tuple variant
3924

4025
error[E0423]: expected function, tuple struct or tuple variant, found enum `Void`
4126
--> $DIR/issue-43871-enum-instead-of-variant.rs:31:13
4227
|
4328
LL | let y = Void();
4429
| ^^^^
30+
|
31+
= help: the enum has no tuple variants to construct
4532

4633
error[E0423]: expected function, tuple struct or tuple variant, found enum `ManyVariants`
4734
--> $DIR/issue-43871-enum-instead-of-variant.rs:33:13
4835
|
4936
LL | let z = ManyVariants();
5037
| ^^^^^^^^^^^^
5138
|
52-
help: try using one of the enum's variants
53-
|
54-
LL | let z = ManyVariants::One();
55-
| ^^^^^^^^^^^^^^^^^
56-
LL | let z = ManyVariants::Two();
57-
| ^^^^^^^^^^^^^^^^^
58-
LL | let z = ManyVariants::Three();
59-
| ^^^^^^^^^^^^^^^^^^^
60-
LL | let z = ManyVariants::Four();
61-
| ^^^^^^^^^^^^^^^^^^
62-
and 6 other candidates
39+
= help: the enum has no tuple variants to construct
40+
= help: you might have meant to construct one of the enum's non-tuple variants
6341

6442
error: aborting due to 5 previous errors
6543

src/test/ui/issues/issue-73427.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
enum A {
2+
StructWithFields { x: () },
3+
TupleWithFields(()),
4+
Struct {},
5+
Tuple(),
6+
Unit,
7+
}
8+
9+
enum B {
10+
StructWithFields { x: () },
11+
TupleWithFields(()),
12+
}
13+
14+
enum C {
15+
StructWithFields { x: () },
16+
TupleWithFields(()),
17+
Unit,
18+
}
19+
20+
enum D {
21+
TupleWithFields(()),
22+
Unit,
23+
}
24+
25+
fn main() {
26+
// Only variants without fields are suggested (and others mentioned in a note) where an enum
27+
// is used rather than a variant.
28+
29+
A.foo();
30+
//~^ ERROR expected value, found enum `A`
31+
B.foo();
32+
//~^ ERROR expected value, found enum `B`
33+
C.foo();
34+
//~^ ERROR expected value, found enum `C`
35+
D.foo();
36+
//~^ ERROR expected value, found enum `D`
37+
38+
// Only tuple variants are suggested in calls or tuple struct pattern matching.
39+
40+
let x = A(3);
41+
//~^ ERROR expected function, tuple struct or tuple variant, found enum `A`
42+
if let A(3) = x { }
43+
//~^ ERROR expected tuple struct or tuple variant, found enum `A`
44+
}

src/test/ui/issues/issue-73427.stderr

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
error[E0423]: expected value, found enum `A`
2+
--> $DIR/issue-73427.rs:29:5
3+
|
4+
LL | A.foo();
5+
| ^
6+
|
7+
= help: you might have meant to use one of the enum's other variants that have fields
8+
help: try using one of the enum's variants
9+
|
10+
LL | (A::Struct {}).foo();
11+
| ^^^^^^^^^^^^^^
12+
LL | (A::Tuple()).foo();
13+
| ^^^^^^^^^^^^
14+
LL | A::Unit.foo();
15+
| ^^^^^^^
16+
17+
error[E0423]: expected value, found enum `B`
18+
--> $DIR/issue-73427.rs:31:5
19+
|
20+
LL | B.foo();
21+
| ^
22+
|
23+
= help: you might have meant to use one of the enum's variants
24+
25+
error[E0423]: expected value, found enum `C`
26+
--> $DIR/issue-73427.rs:33:5
27+
|
28+
LL | C.foo();
29+
| ^ help: try using one of the enum's variants: `C::Unit`
30+
|
31+
= help: you might have meant to use one of the enum's other variants that have fields
32+
33+
error[E0423]: expected value, found enum `D`
34+
--> $DIR/issue-73427.rs:35:5
35+
|
36+
LL | D.foo();
37+
| ^ help: try using one of the enum's variants: `D::Unit`
38+
|
39+
= help: you might have meant to use the enum's other variant that has fields
40+
41+
error[E0423]: expected function, tuple struct or tuple variant, found enum `A`
42+
--> $DIR/issue-73427.rs:40:13
43+
|
44+
LL | let x = A(3);
45+
| ^
46+
|
47+
= help: you might have meant to construct one of the enum's non-tuple variants
48+
help: try using one of the enum's variants
49+
|
50+
LL | let x = A::TupleWithFields(3);
51+
| ^^^^^^^^^^^^^^^^^^
52+
LL | let x = A::Tuple(3);
53+
| ^^^^^^^^
54+
55+
error[E0532]: expected tuple struct or tuple variant, found enum `A`
56+
--> $DIR/issue-73427.rs:42:12
57+
|
58+
LL | if let A(3) = x { }
59+
| ^
60+
|
61+
= help: you might have meant to match against one of the enum's non-tuple variants
62+
help: try using one of the enum's variants
63+
|
64+
LL | if let A::TupleWithFields(3) = x { }
65+
| ^^^^^^^^^^^^^^^^^^
66+
LL | if let A::Tuple(3) = x { }
67+
| ^^^^^^^^
68+
69+
error: aborting due to 6 previous errors
70+
71+
Some errors have detailed explanations: E0423, E0532.
72+
For more information about an error, try `rustc --explain E0423`.

0 commit comments

Comments
 (0)