Skip to content

Commit

Permalink
Rollup merge of rust-lang#74173 - estebank:struct-pat-as-enum, r=petr…
Browse files Browse the repository at this point in the history
…ochenkov

Detect tuple struct incorrectly used as struct pat

Subpart of rust-lang#74005.

r? @petrochenkov
  • Loading branch information
Dylan-DPC committed Jul 14, 2020
2 parents af56daa + 5daedea commit 88f072b
Show file tree
Hide file tree
Showing 10 changed files with 137 additions and 49 deletions.
1 change: 1 addition & 0 deletions Cargo.lock
Expand Up @@ -3996,6 +3996,7 @@ dependencies = [
"rustc_data_structures",
"rustc_errors",
"rustc_hir",
"rustc_hir_pretty",
"rustc_index",
"rustc_infer",
"rustc_middle",
Expand Down
1 change: 1 addition & 0 deletions src/librustc_error_codes/error_codes.rs
Expand Up @@ -451,6 +451,7 @@ E0765: include_str!("./error_codes/E0765.md"),
E0766: include_str!("./error_codes/E0766.md"),
E0767: include_str!("./error_codes/E0767.md"),
E0768: include_str!("./error_codes/E0768.md"),
E0769: include_str!("./error_codes/E0769.md"),
;
// E0006, // merged with E0005
// E0008, // cannot bind by-move into a pattern guard
Expand Down
39 changes: 39 additions & 0 deletions src/librustc_error_codes/error_codes/E0769.md
@@ -0,0 +1,39 @@
A tuple struct or tuple variant was used in a pattern as if it were a
struct or struct variant.

Erroneous code example:

```compile_fail,E0769
enum E {
A(i32),
}
let e = E::A(42);
match e {
E::A { number } => println!("{}", x),
}
```

To fix this error, you can use the tuple pattern:

```
# enum E {
# A(i32),
# }
# let e = E::A(42);
match e {
E::A(number) => println!("{}", number),
}
```

Alternatively, you can also use the struct pattern by using the correct
field names and binding them to new identifiers:

```
# enum E {
# A(i32),
# }
# let e = E::A(42);
match e {
E::A { 0: number } => println!("{}", number),
}
```
1 change: 1 addition & 0 deletions src/librustc_typeck/Cargo.toml
Expand Up @@ -18,6 +18,7 @@ rustc_attr = { path = "../librustc_attr" }
rustc_data_structures = { path = "../librustc_data_structures" }
rustc_errors = { path = "../librustc_errors" }
rustc_hir = { path = "../librustc_hir" }
rustc_hir_pretty = { path = "../librustc_hir_pretty" }
rustc_target = { path = "../librustc_target" }
rustc_session = { path = "../librustc_session" }
smallvec = { version = "1.0", features = ["union", "may_dangle"] }
Expand Down
91 changes: 78 additions & 13 deletions src/librustc_typeck/check/pat.rs
Expand Up @@ -1082,20 +1082,23 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
.filter(|ident| !used_fields.contains_key(&ident))
.collect::<Vec<_>>();

if !inexistent_fields.is_empty() && !variant.recovered {
self.error_inexistent_fields(
let inexistent_fields_err = if !inexistent_fields.is_empty() && !variant.recovered {
Some(self.error_inexistent_fields(
adt.variant_descr(),
&inexistent_fields,
&mut unmentioned_fields,
variant,
);
}
))
} else {
None
};

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

let mut unmentioned_err = None;
// Report an error if incorrect number of the fields were specified.
if adt.is_union() {
if fields.len() != 1 {
Expand All @@ -1107,7 +1110,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
tcx.sess.struct_span_err(pat.span, "`..` cannot be used in union patterns").emit();
}
} else if !etc && !unmentioned_fields.is_empty() {
self.error_unmentioned_fields(pat.span, &unmentioned_fields, variant);
unmentioned_err = Some(self.error_unmentioned_fields(pat.span, &unmentioned_fields));
}
match (inexistent_fields_err, unmentioned_err) {
(Some(mut i), Some(mut u)) => {
if let Some(mut e) = self.error_tuple_variant_as_struct_pat(pat, fields, variant) {
// We don't want to show the inexistent fields error when this was
// `Foo { a, b }` when it should have been `Foo(a, b)`.
i.delay_as_bug();
u.delay_as_bug();
e.emit();
} else {
i.emit();
u.emit();
}
}
(None, Some(mut err)) | (Some(mut err), None) => {
err.emit();
}
(None, None) => {}
}
no_field_errors
}
Expand Down Expand Up @@ -1154,7 +1175,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
inexistent_fields: &[Ident],
unmentioned_fields: &mut Vec<Ident>,
variant: &ty::VariantDef,
) {
) -> DiagnosticBuilder<'tcx> {
let tcx = self.tcx;
let (field_names, t, plural) = if inexistent_fields.len() == 1 {
(format!("a field named `{}`", inexistent_fields[0]), "this", "")
Expand Down Expand Up @@ -1221,15 +1242,62 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
it explicitly.",
);
}
err.emit();
err
}

fn error_tuple_variant_as_struct_pat(
&self,
pat: &Pat<'_>,
fields: &'tcx [hir::FieldPat<'tcx>],
variant: &ty::VariantDef,
) -> Option<DiagnosticBuilder<'tcx>> {
if let (CtorKind::Fn, PatKind::Struct(qpath, ..)) = (variant.ctor_kind, &pat.kind) {
let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
s.print_qpath(qpath, false)
});
let mut err = struct_span_err!(
self.tcx.sess,
pat.span,
E0769,
"tuple variant `{}` written as struct variant",
path
);
let (sugg, appl) = if fields.len() == variant.fields.len() {
(
fields
.iter()
.map(|f| match self.tcx.sess.source_map().span_to_snippet(f.pat.span) {
Ok(f) => f,
Err(_) => rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| {
s.print_pat(f.pat)
}),
})
.collect::<Vec<String>>()
.join(", "),
Applicability::MachineApplicable,
)
} else {
(
variant.fields.iter().map(|_| "_").collect::<Vec<&str>>().join(", "),
Applicability::MaybeIncorrect,
)
};
err.span_suggestion(
pat.span,
"use the tuple variant pattern syntax instead",
format!("{}({})", path, sugg),
appl,
);
return Some(err);
}
None
}

fn error_unmentioned_fields(
&self,
span: Span,
unmentioned_fields: &[Ident],
variant: &ty::VariantDef,
) {
) -> DiagnosticBuilder<'tcx> {
let field_names = if unmentioned_fields.len() == 1 {
format!("field `{}`", unmentioned_fields[0])
} else {
Expand All @@ -1248,9 +1316,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
field_names
);
diag.span_label(span, format!("missing {}", field_names));
if variant.ctor_kind == CtorKind::Fn {
diag.note("trying to match a tuple variant with a struct variant pattern");
}
if self.tcx.sess.teach(&diag.get_code().unwrap()) {
diag.note(
"This error indicates that a pattern for a struct fails to specify a \
Expand All @@ -1259,7 +1324,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
ignore unwanted fields.",
);
}
diag.emit();
diag
}

fn check_pat_box(
Expand Down
3 changes: 1 addition & 2 deletions src/test/ui/missing/missing-fields-in-struct-pattern.rs
Expand Up @@ -2,8 +2,7 @@ struct S(usize, usize, usize, usize);

fn main() {
if let S { a, b, c, d } = S(1, 2, 3, 4) {
//~^ ERROR struct `S` does not have fields named `a`, `b`, `c`, `d` [E0026]
//~| ERROR pattern does not mention fields `0`, `1`, `2`, `3` [E0027]
//~^ ERROR tuple variant `S` written as struct variant
println!("hi");
}
}
17 changes: 4 additions & 13 deletions src/test/ui/missing/missing-fields-in-struct-pattern.stderr
@@ -1,18 +1,9 @@
error[E0026]: struct `S` does not have fields named `a`, `b`, `c`, `d`
--> $DIR/missing-fields-in-struct-pattern.rs:4:16
|
LL | if let S { a, b, c, d } = S(1, 2, 3, 4) {
| ^ ^ ^ ^ struct `S` does not have these fields

error[E0027]: pattern does not mention fields `0`, `1`, `2`, `3`
error[E0769]: tuple variant `S` written as struct variant
--> $DIR/missing-fields-in-struct-pattern.rs:4:12
|
LL | if let S { a, b, c, d } = S(1, 2, 3, 4) {
| ^^^^^^^^^^^^^^^^ missing fields `0`, `1`, `2`, `3`
|
= note: trying to match a tuple variant with a struct variant pattern
| ^^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `S(a, b, c, d)`

error: aborting due to 2 previous errors
error: aborting due to previous error

Some errors have detailed explanations: E0026, E0027.
For more information about an error, try `rustc --explain E0026`.
For more information about this error, try `rustc --explain E0769`.
4 changes: 2 additions & 2 deletions src/test/ui/type/type-check/issue-41314.rs
Expand Up @@ -4,7 +4,7 @@ enum X {

fn main() {
match X::Y(0) {
X::Y { number } => {} //~ ERROR does not have a field named `number`
//~^ ERROR pattern does not mention field `0`
X::Y { number } => {}
//~^ ERROR tuple variant `X::Y` written as struct variant
}
}
17 changes: 4 additions & 13 deletions src/test/ui/type/type-check/issue-41314.stderr
@@ -1,18 +1,9 @@
error[E0026]: variant `X::Y` does not have a field named `number`
--> $DIR/issue-41314.rs:7:16
|
LL | X::Y { number } => {}
| ^^^^^^ variant `X::Y` does not have this field

error[E0027]: pattern does not mention field `0`
error[E0769]: tuple variant `X::Y` written as struct variant
--> $DIR/issue-41314.rs:7:9
|
LL | X::Y { number } => {}
| ^^^^^^^^^^^^^^^ missing field `0`
|
= note: trying to match a tuple variant with a struct variant pattern
| ^^^^^^^^^^^^^^^ help: use the tuple variant pattern syntax instead: `X::Y(number)`

error: aborting due to 2 previous errors
error: aborting due to previous error

Some errors have detailed explanations: E0026, E0027.
For more information about an error, try `rustc --explain E0026`.
For more information about this error, try `rustc --explain E0769`.
12 changes: 6 additions & 6 deletions src/test/ui/union/union-fields-2.stderr
Expand Up @@ -48,18 +48,18 @@ error: union patterns should have exactly one field
LL | let U { a, b } = u;
| ^^^^^^^^^^

error[E0026]: union `U` does not have a field named `c`
--> $DIR/union-fields-2.rs:18:19
|
LL | let U { a, b, c } = u;
| ^ union `U` does not have this field

error: union patterns should have exactly one field
--> $DIR/union-fields-2.rs:18:9
|
LL | let U { a, b, c } = u;
| ^^^^^^^^^^^^^

error[E0026]: union `U` does not have a field named `c`
--> $DIR/union-fields-2.rs:18:19
|
LL | let U { a, b, c } = u;
| ^ union `U` does not have this field

error: union patterns should have exactly one field
--> $DIR/union-fields-2.rs:20:9
|
Expand Down

0 comments on commit 88f072b

Please sign in to comment.