Skip to content

Commit b9a0f58

Browse files
authored
Rollup merge of #74173 - estebank:struct-pat-as-enum, r=petrochenkov
Detect tuple struct incorrectly used as struct pat Subpart of #74005. r? @petrochenkov
2 parents 79894df + 5daedea commit b9a0f58

File tree

10 files changed

+137
-49
lines changed

10 files changed

+137
-49
lines changed

Cargo.lock

+1
Original file line numberDiff line numberDiff line change
@@ -3996,6 +3996,7 @@ dependencies = [
39963996
"rustc_data_structures",
39973997
"rustc_errors",
39983998
"rustc_hir",
3999+
"rustc_hir_pretty",
39994000
"rustc_index",
40004001
"rustc_infer",
40014002
"rustc_middle",

src/librustc_error_codes/error_codes.rs

+1
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ E0765: include_str!("./error_codes/E0765.md"),
451451
E0766: include_str!("./error_codes/E0766.md"),
452452
E0767: include_str!("./error_codes/E0767.md"),
453453
E0768: include_str!("./error_codes/E0768.md"),
454+
E0769: include_str!("./error_codes/E0769.md"),
454455
;
455456
// E0006, // merged with E0005
456457
// E0008, // cannot bind by-move into a pattern guard
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
A tuple struct or tuple variant was used in a pattern as if it were a
2+
struct or struct variant.
3+
4+
Erroneous code example:
5+
6+
```compile_fail,E0769
7+
enum E {
8+
A(i32),
9+
}
10+
let e = E::A(42);
11+
match e {
12+
E::A { number } => println!("{}", x),
13+
}
14+
```
15+
16+
To fix this error, you can use the tuple pattern:
17+
18+
```
19+
# enum E {
20+
# A(i32),
21+
# }
22+
# let e = E::A(42);
23+
match e {
24+
E::A(number) => println!("{}", number),
25+
}
26+
```
27+
28+
Alternatively, you can also use the struct pattern by using the correct
29+
field names and binding them to new identifiers:
30+
31+
```
32+
# enum E {
33+
# A(i32),
34+
# }
35+
# let e = E::A(42);
36+
match e {
37+
E::A { 0: number } => println!("{}", number),
38+
}
39+
```

src/librustc_typeck/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ rustc_attr = { path = "../librustc_attr" }
1818
rustc_data_structures = { path = "../librustc_data_structures" }
1919
rustc_errors = { path = "../librustc_errors" }
2020
rustc_hir = { path = "../librustc_hir" }
21+
rustc_hir_pretty = { path = "../librustc_hir_pretty" }
2122
rustc_target = { path = "../librustc_target" }
2223
rustc_session = { path = "../librustc_session" }
2324
smallvec = { version = "1.0", features = ["union", "may_dangle"] }

src/librustc_typeck/check/pat.rs

+78-13
Original file line numberDiff line numberDiff line change
@@ -1082,20 +1082,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10821082
.filter(|ident| !used_fields.contains_key(&ident))
10831083
.collect::<Vec<_>>();
10841084

1085-
if !inexistent_fields.is_empty() && !variant.recovered {
1086-
self.error_inexistent_fields(
1085+
let inexistent_fields_err = if !inexistent_fields.is_empty() && !variant.recovered {
1086+
Some(self.error_inexistent_fields(
10871087
adt.variant_descr(),
10881088
&inexistent_fields,
10891089
&mut unmentioned_fields,
10901090
variant,
1091-
);
1092-
}
1091+
))
1092+
} else {
1093+
None
1094+
};
10931095

10941096
// Require `..` if struct has non_exhaustive attribute.
10951097
if variant.is_field_list_non_exhaustive() && !adt.did.is_local() && !etc {
10961098
self.error_foreign_non_exhaustive_spat(pat, adt.variant_descr(), fields.is_empty());
10971099
}
10981100

1101+
let mut unmentioned_err = None;
10991102
// Report an error if incorrect number of the fields were specified.
11001103
if adt.is_union() {
11011104
if fields.len() != 1 {
@@ -1107,7 +1110,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11071110
tcx.sess.struct_span_err(pat.span, "`..` cannot be used in union patterns").emit();
11081111
}
11091112
} else if !etc && !unmentioned_fields.is_empty() {
1110-
self.error_unmentioned_fields(pat.span, &unmentioned_fields, variant);
1113+
unmentioned_err = Some(self.error_unmentioned_fields(pat.span, &unmentioned_fields));
1114+
}
1115+
match (inexistent_fields_err, unmentioned_err) {
1116+
(Some(mut i), Some(mut u)) => {
1117+
if let Some(mut e) = self.error_tuple_variant_as_struct_pat(pat, fields, variant) {
1118+
// We don't want to show the inexistent fields error when this was
1119+
// `Foo { a, b }` when it should have been `Foo(a, b)`.
1120+
i.delay_as_bug();
1121+
u.delay_as_bug();
1122+
e.emit();
1123+
} else {
1124+
i.emit();
1125+
u.emit();
1126+
}
1127+
}
1128+
(None, Some(mut err)) | (Some(mut err), None) => {
1129+
err.emit();
1130+
}
1131+
(None, None) => {}
11111132
}
11121133
no_field_errors
11131134
}
@@ -1154,7 +1175,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11541175
inexistent_fields: &[Ident],
11551176
unmentioned_fields: &mut Vec<Ident>,
11561177
variant: &ty::VariantDef,
1157-
) {
1178+
) -> DiagnosticBuilder<'tcx> {
11581179
let tcx = self.tcx;
11591180
let (field_names, t, plural) = if inexistent_fields.len() == 1 {
11601181
(format!("a field named `{}`", inexistent_fields[0]), "this", "")
@@ -1221,15 +1242,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12211242
it explicitly.",
12221243
);
12231244
}
1224-
err.emit();
1245+
err
1246+
}
1247+
1248+
fn error_tuple_variant_as_struct_pat(
1249+
&self,
1250+
pat: &Pat<'_>,
1251+
fields: &'tcx [hir::FieldPat<'tcx>],
1252+
variant: &ty::VariantDef,
1253+
) -> Option<DiagnosticBuilder<'tcx>> {
1254+
if let (CtorKind::Fn, PatKind::Struct(qpath, ..)) = (variant.ctor_kind, &pat.kind) {
1255+
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
1256+
s.print_qpath(qpath, false)
1257+
});
1258+
let mut err = struct_span_err!(
1259+
self.tcx.sess,
1260+
pat.span,
1261+
E0769,
1262+
"tuple variant `{}` written as struct variant",
1263+
path
1264+
);
1265+
let (sugg, appl) = if fields.len() == variant.fields.len() {
1266+
(
1267+
fields
1268+
.iter()
1269+
.map(|f| match self.tcx.sess.source_map().span_to_snippet(f.pat.span) {
1270+
Ok(f) => f,
1271+
Err(_) => rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
1272+
s.print_pat(f.pat)
1273+
}),
1274+
})
1275+
.collect::<Vec<String>>()
1276+
.join(", "),
1277+
Applicability::MachineApplicable,
1278+
)
1279+
} else {
1280+
(
1281+
variant.fields.iter().map(|_| "_").collect::<Vec<&str>>().join(", "),
1282+
Applicability::MaybeIncorrect,
1283+
)
1284+
};
1285+
err.span_suggestion(
1286+
pat.span,
1287+
"use the tuple variant pattern syntax instead",
1288+
format!("{}({})", path, sugg),
1289+
appl,
1290+
);
1291+
return Some(err);
1292+
}
1293+
None
12251294
}
12261295

12271296
fn error_unmentioned_fields(
12281297
&self,
12291298
span: Span,
12301299
unmentioned_fields: &[Ident],
1231-
variant: &ty::VariantDef,
1232-
) {
1300+
) -> DiagnosticBuilder<'tcx> {
12331301
let field_names = if unmentioned_fields.len() == 1 {
12341302
format!("field `{}`", unmentioned_fields[0])
12351303
} else {
@@ -1248,9 +1316,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12481316
field_names
12491317
);
12501318
diag.span_label(span, format!("missing {}", field_names));
1251-
if variant.ctor_kind == CtorKind::Fn {
1252-
diag.note("trying to match a tuple variant with a struct variant pattern");
1253-
}
12541319
if self.tcx.sess.teach(&diag.get_code().unwrap()) {
12551320
diag.note(
12561321
"This error indicates that a pattern for a struct fails to specify a \
@@ -1259,7 +1324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12591324
ignore unwanted fields.",
12601325
);
12611326
}
1262-
diag.emit();
1327+
diag
12631328
}
12641329

12651330
fn check_pat_box(

src/test/ui/missing/missing-fields-in-struct-pattern.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ struct S(usize, usize, usize, usize);
22

33
fn main() {
44
if let S { a, b, c, d } = S(1, 2, 3, 4) {
5-
//~^ ERROR struct `S` does not have fields named `a`, `b`, `c`, `d` [E0026]
6-
//~| ERROR pattern does not mention fields `0`, `1`, `2`, `3` [E0027]
5+
//~^ ERROR tuple variant `S` written as struct variant
76
println!("hi");
87
}
98
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
error[E0026]: struct `S` does not have fields named `a`, `b`, `c`, `d`
2-
--> $DIR/missing-fields-in-struct-pattern.rs:4:16
3-
|
4-
LL | if let S { a, b, c, d } = S(1, 2, 3, 4) {
5-
| ^ ^ ^ ^ struct `S` does not have these fields
6-
7-
error[E0027]: pattern does not mention fields `0`, `1`, `2`, `3`
1+
error[E0769]: tuple variant `S` written as struct variant
82
--> $DIR/missing-fields-in-struct-pattern.rs:4:12
93
|
104
LL | if let S { a, b, c, d } = S(1, 2, 3, 4) {
11-
| ^^^^^^^^^^^^^^^^ missing fields `0`, `1`, `2`, `3`
12-
|
13-
= note: trying to match a tuple variant with a struct variant pattern
5+
| ^^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `S(a, b, c, d)`
146

15-
error: aborting due to 2 previous errors
7+
error: aborting due to previous error
168

17-
Some errors have detailed explanations: E0026, E0027.
18-
For more information about an error, try `rustc --explain E0026`.
9+
For more information about this error, try `rustc --explain E0769`.

src/test/ui/type/type-check/issue-41314.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ enum X {
44

55
fn main() {
66
match X::Y(0) {
7-
X::Y { number } => {} //~ ERROR does not have a field named `number`
8-
//~^ ERROR pattern does not mention field `0`
7+
X::Y { number } => {}
8+
//~^ ERROR tuple variant `X::Y` written as struct variant
99
}
1010
}
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
error[E0026]: variant `X::Y` does not have a field named `number`
2-
--> $DIR/issue-41314.rs:7:16
3-
|
4-
LL | X::Y { number } => {}
5-
| ^^^^^^ variant `X::Y` does not have this field
6-
7-
error[E0027]: pattern does not mention field `0`
1+
error[E0769]: tuple variant `X::Y` written as struct variant
82
--> $DIR/issue-41314.rs:7:9
93
|
104
LL | X::Y { number } => {}
11-
| ^^^^^^^^^^^^^^^ missing field `0`
12-
|
13-
= note: trying to match a tuple variant with a struct variant pattern
5+
| ^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `X::Y(number)`
146

15-
error: aborting due to 2 previous errors
7+
error: aborting due to previous error
168

17-
Some errors have detailed explanations: E0026, E0027.
18-
For more information about an error, try `rustc --explain E0026`.
9+
For more information about this error, try `rustc --explain E0769`.

src/test/ui/union/union-fields-2.stderr

+6-6
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,18 @@ error: union patterns should have exactly one field
4848
LL | let U { a, b } = u;
4949
| ^^^^^^^^^^
5050

51-
error[E0026]: union `U` does not have a field named `c`
52-
--> $DIR/union-fields-2.rs:18:19
53-
|
54-
LL | let U { a, b, c } = u;
55-
| ^ union `U` does not have this field
56-
5751
error: union patterns should have exactly one field
5852
--> $DIR/union-fields-2.rs:18:9
5953
|
6054
LL | let U { a, b, c } = u;
6155
| ^^^^^^^^^^^^^
6256

57+
error[E0026]: union `U` does not have a field named `c`
58+
--> $DIR/union-fields-2.rs:18:19
59+
|
60+
LL | let U { a, b, c } = u;
61+
| ^ union `U` does not have this field
62+
6363
error: union patterns should have exactly one field
6464
--> $DIR/union-fields-2.rs:20:9
6565
|

0 commit comments

Comments
 (0)