Skip to content

Commit e0752ad

Browse files
committed
Provide diagnostic for Struct(a, .., z) expression
People might extrapolate from `Struct { .. }` that `Struct(..)` would work, but it doesn't.
1 parent 550bcae commit e0752ad

8 files changed

+123
-8
lines changed

compiler/rustc_hir_typeck/src/fn_ctxt/checks.rs

+31
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,34 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
903903
}
904904
}
905905

906+
let detect_dotdot = |err: &mut Diag<'_>, ty: Ty<'_>, expr: &hir::Expr<'_>| {
907+
if let ty::Adt(adt, _) = ty.kind()
908+
&& self.tcx().lang_items().get(hir::LangItem::RangeFull) == Some(adt.did())
909+
&& let hir::ExprKind::Struct(
910+
hir::QPath::LangItem(hir::LangItem::RangeFull, _),
911+
[],
912+
_,
913+
) = expr.kind
914+
{
915+
// We have `Foo(a, .., c)`, where the user might be trying to use the "rest" syntax
916+
// from default field values, which is not supported on tuples.
917+
let explanation = if self.tcx.features().default_field_values() {
918+
"this is only supported on non-tuple struct literals"
919+
} else if self.tcx.sess.is_nightly_build() {
920+
"this is only supported on non-tuple struct literals when \
921+
`#![feature(default_field_values)]` is enabled"
922+
} else {
923+
"this is not supported"
924+
};
925+
let msg = format!(
926+
"you might have meant to use `..` to skip providing a value for \
927+
expected fields, but {explanation}; it is instead interpreted as a \
928+
`std::ops::RangeFull` literal",
929+
);
930+
err.span_help(expr.span, msg);
931+
}
932+
};
933+
906934
let mut reported = None;
907935
errors.retain(|error| {
908936
let Error::Invalid(provided_idx, expected_idx, Compatibility::Incompatible(Some(e))) =
@@ -1009,6 +1037,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
10091037
tuple_arguments,
10101038
);
10111039
suggest_confusable(&mut err);
1040+
detect_dotdot(&mut err, provided_ty, provided_args[provided_idx]);
10121041
return err.emit();
10131042
}
10141043

@@ -1133,6 +1162,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
11331162
None,
11341163
None,
11351164
);
1165+
detect_dotdot(&mut err, provided_ty, provided_args[provided_idx]);
11361166
}
11371167
Error::Extra(arg_idx) => {
11381168
let (provided_ty, provided_span) = provided_arg_tys[arg_idx];
@@ -1216,6 +1246,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
12161246
};
12171247
prev_extra_idx = Some(arg_idx.index())
12181248
}
1249+
detect_dotdot(&mut err, provided_ty, provided_args[arg_idx]);
12191250
}
12201251
Error::Missing(expected_idx) => {
12211252
// If there are multiple missing arguments adjacent to each other,

tests/ui/range/issue-54505-no-std.rs

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ fn main() {
3838

3939
take_range(..);
4040
//~^ ERROR mismatched types [E0308]
41+
//~| HELP you might have meant
4142
//~| HELP consider borrowing here
4243
//~| SUGGESTION &(
4344

tests/ui/range/issue-54505-no-std.stderr

+8-3
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,18 @@ note: function defined here
5353
|
5454
LL | fn take_range(_r: &impl RangeBounds<i8>) {}
5555
| ^^^^^^^^^^ -------------------------
56+
help: you might have meant to use `..` to skip providing a value for expected fields, but this is only supported on non-tuple struct literals when `#![feature(default_field_values)]` is enabled; it is instead interpreted as a `std::ops::RangeFull` literal
57+
--> $DIR/issue-54505-no-std.rs:39:16
58+
|
59+
LL | take_range(..);
60+
| ^^
5661
help: consider borrowing here
5762
|
5863
LL | take_range(&(..));
5964
| ++ +
6065

6166
error[E0308]: mismatched types
62-
--> $DIR/issue-54505-no-std.rs:44:16
67+
--> $DIR/issue-54505-no-std.rs:45:16
6368
|
6469
LL | take_range(0..=1);
6570
| ---------- ^^^^^ expected `&_`, found `RangeInclusive<{integer}>`
@@ -79,7 +84,7 @@ LL | take_range(&(0..=1));
7984
| ++ +
8085

8186
error[E0308]: mismatched types
82-
--> $DIR/issue-54505-no-std.rs:49:16
87+
--> $DIR/issue-54505-no-std.rs:50:16
8388
|
8489
LL | take_range(..5);
8590
| ---------- ^^^ expected `&_`, found `RangeTo<{integer}>`
@@ -99,7 +104,7 @@ LL | take_range(&(..5));
99104
| ++ +
100105

101106
error[E0308]: mismatched types
102-
--> $DIR/issue-54505-no-std.rs:54:16
107+
--> $DIR/issue-54505-no-std.rs:55:16
103108
|
104109
LL | take_range(..=42);
105110
| ---------- ^^^^^ expected `&_`, found `RangeToInclusive<{integer}>`

tests/ui/range/issue-54505.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ fn main() {
2323

2424
take_range(&(..));
2525
//~^ ERROR mismatched types [E0308]
26+
//~| HELP you might have meant
2627
//~| HELP consider borrowing here
2728
//~| SUGGESTION &(
2829

tests/ui/range/issue-54505.rs

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ fn main() {
2323

2424
take_range(..);
2525
//~^ ERROR mismatched types [E0308]
26+
//~| HELP you might have meant
2627
//~| HELP consider borrowing here
2728
//~| SUGGESTION &(
2829

tests/ui/range/issue-54505.stderr

+8-3
Original file line numberDiff line numberDiff line change
@@ -53,13 +53,18 @@ note: function defined here
5353
|
5454
LL | fn take_range(_r: &impl RangeBounds<i8>) {}
5555
| ^^^^^^^^^^ -------------------------
56+
help: you might have meant to use `..` to skip providing a value for expected fields, but this is only supported on non-tuple struct literals when `#![feature(default_field_values)]` is enabled; it is instead interpreted as a `std::ops::RangeFull` literal
57+
--> $DIR/issue-54505.rs:24:16
58+
|
59+
LL | take_range(..);
60+
| ^^
5661
help: consider borrowing here
5762
|
5863
LL | take_range(&(..));
5964
| ++ +
6065

6166
error[E0308]: mismatched types
62-
--> $DIR/issue-54505.rs:29:16
67+
--> $DIR/issue-54505.rs:30:16
6368
|
6469
LL | take_range(0..=1);
6570
| ---------- ^^^^^ expected `&_`, found `RangeInclusive<{integer}>`
@@ -79,7 +84,7 @@ LL | take_range(&(0..=1));
7984
| ++ +
8085

8186
error[E0308]: mismatched types
82-
--> $DIR/issue-54505.rs:34:16
87+
--> $DIR/issue-54505.rs:35:16
8388
|
8489
LL | take_range(..5);
8590
| ---------- ^^^ expected `&_`, found `RangeTo<{integer}>`
@@ -99,7 +104,7 @@ LL | take_range(&(..5));
99104
| ++ +
100105

101106
error[E0308]: mismatched types
102-
--> $DIR/issue-54505.rs:39:16
107+
--> $DIR/issue-54505.rs:40:16
103108
|
104109
LL | take_range(..=42);
105110
| ---------- ^^^^^ expected `&_`, found `RangeToInclusive<{integer}>`

tests/ui/structs/default-field-values-failures.rs

+7
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,11 @@ fn main () {
4848
let _ = Bar::default(); // silenced
4949
let _ = Bar { bar: S, .. }; // ok
5050
let _ = Qux::<4> { .. };
51+
let _ = Rak(..); //~ ERROR E0308
52+
//~^ you might have meant to use `..` to skip providing
53+
let _ = Rak(0, ..); //~ ERROR E0061
54+
//~^ you might have meant to use `..` to skip providing
55+
let _ = Rak(.., 0); //~ ERROR E0061
56+
//~^ you might have meant to use `..` to skip providing
57+
let _ = Rak { .. }; // ok
5158
}

tests/ui/structs/default-field-values-failures.stderr

+66-2
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,76 @@ error: missing mandatory field `bar`
3535
LL | let _ = Bar { .. };
3636
| ^
3737

38+
error[E0308]: mismatched types
39+
--> $DIR/default-field-values-failures.rs:51:17
40+
|
41+
LL | let _ = Rak(..);
42+
| --- ^^ expected `i32`, found `RangeFull`
43+
| |
44+
| arguments to this struct are incorrect
45+
|
46+
note: tuple struct defined here
47+
--> $DIR/default-field-values-failures.rs:26:12
48+
|
49+
LL | pub struct Rak(i32 = 42);
50+
| ^^^
51+
help: you might have meant to use `..` to skip providing a value for expected fields, but this is only supported on non-tuple struct literals; it is instead interpreted as a `std::ops::RangeFull` literal
52+
--> $DIR/default-field-values-failures.rs:51:17
53+
|
54+
LL | let _ = Rak(..);
55+
| ^^
56+
57+
error[E0061]: this struct takes 1 argument but 2 arguments were supplied
58+
--> $DIR/default-field-values-failures.rs:53:13
59+
|
60+
LL | let _ = Rak(0, ..);
61+
| ^^^ -- unexpected argument #2 of type `RangeFull`
62+
|
63+
help: you might have meant to use `..` to skip providing a value for expected fields, but this is only supported on non-tuple struct literals; it is instead interpreted as a `std::ops::RangeFull` literal
64+
--> $DIR/default-field-values-failures.rs:53:20
65+
|
66+
LL | let _ = Rak(0, ..);
67+
| ^^
68+
note: tuple struct defined here
69+
--> $DIR/default-field-values-failures.rs:26:12
70+
|
71+
LL | pub struct Rak(i32 = 42);
72+
| ^^^
73+
help: remove the extra argument
74+
|
75+
LL - let _ = Rak(0, ..);
76+
LL + let _ = Rak(0);
77+
|
78+
79+
error[E0061]: this struct takes 1 argument but 2 arguments were supplied
80+
--> $DIR/default-field-values-failures.rs:55:13
81+
|
82+
LL | let _ = Rak(.., 0);
83+
| ^^^ -- unexpected argument #1 of type `RangeFull`
84+
|
85+
help: you might have meant to use `..` to skip providing a value for expected fields, but this is only supported on non-tuple struct literals; it is instead interpreted as a `std::ops::RangeFull` literal
86+
--> $DIR/default-field-values-failures.rs:55:17
87+
|
88+
LL | let _ = Rak(.., 0);
89+
| ^^
90+
note: tuple struct defined here
91+
--> $DIR/default-field-values-failures.rs:26:12
92+
|
93+
LL | pub struct Rak(i32 = 42);
94+
| ^^^
95+
help: remove the extra argument
96+
|
97+
LL - let _ = Rak(.., 0);
98+
LL + let _ = Rak(0);
99+
|
100+
38101
error: generic `Self` types are currently not permitted in anonymous constants
39102
--> $DIR/default-field-values-failures.rs:20:14
40103
|
41104
LL | bar: S = Self::S,
42105
| ^^^^
43106

44-
error: aborting due to 5 previous errors
107+
error: aborting due to 8 previous errors
45108

46-
For more information about this error, try `rustc --explain E0277`.
109+
Some errors have detailed explanations: E0061, E0277, E0308.
110+
For more information about an error, try `rustc --explain E0061`.

0 commit comments

Comments
 (0)