Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect tuple struct incorrectly used as struct pat #74173

Merged
merged 1 commit into from
Jul 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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| {
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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