Skip to content

Commit deec530

Browse files
committed
Auto merge of #77341 - davidtwco:issue-73427-you-might-have-meant-variant, r=estebank
resolve: improve "try using the enum's variant" Fixes #73427. This PR 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. All of the diagnostic logic introduced by this PR is separated from the normal code path for a successful compilation. r? `@estebank`
2 parents a14bf48 + 9ef68f5 commit deec530

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;
@@ -726,24 +726,8 @@ impl<'a: 'ast, 'ast> LateResolutionVisitor<'a, '_, 'ast> {
726726
// We already suggested changing `:` into `::` during parsing.
727727
return false;
728728
}
729-
if let Some(variants) = self.collect_enum_variants(def_id) {
730-
if !variants.is_empty() {
731-
let msg = if variants.len() == 1 {
732-
"try using the enum's variant"
733-
} else {
734-
"try using one of the enum's variants"
735-
};
736729

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

1129-
fn collect_enum_variants(&mut self, def_id: DefId) -> Option<Vec<Path>> {
1113+
fn collect_enum_ctors(&mut self, def_id: DefId) -> Option<Vec<(Path, DefId, CtorKind)>> {
11301114
self.find_module(def_id).map(|(enum_module, enum_import_suggestion)| {
11311115
let mut variants = Vec::new();
11321116
enum_module.for_each_child(self.r, |_, ident, _, name_binding| {
1133-
if let Res::Def(DefKind::Variant, _) = name_binding.res() {
1117+
if let Res::Def(DefKind::Ctor(CtorOf::Variant, kind), def_id) = name_binding.res() {
11341118
let mut segms = enum_import_suggestion.path.segments.clone();
11351119
segms.push(ast::PathSegment::from_ident(ident));
1136-
variants.push(Path { span: name_binding.span, segments: segms, tokens: None });
1120+
let path = Path { span: name_binding.span, segments: segms, tokens: None };
1121+
variants.push((path, def_id, kind));
11371122
}
11381123
});
11391124
variants
11401125
})
11411126
}
11421127

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