Skip to content

Commit 9323a0d

Browse files
authored
Rollup merge of #92746 - estebank:question-mark-in-type, r=davidtwco
Parse `Ty?` as `Option<Ty>` and provide structured suggestion Swift has specific syntax that desugars to `Option<T>` similar to our `?` operator, which means that people might try to use it in Rust. Parse it and gracefully recover.
2 parents 9835b90 + cfc0bd1 commit 9323a0d

12 files changed

+125
-37
lines changed

compiler/rustc_parse/src/parser/diagnostics.rs

+29-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::pat::Expected;
2-
use super::ty::AllowPlus;
2+
use super::ty::{AllowPlus, IsAsCast};
33
use super::{
44
BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep,
55
TokenExpectType, TokenType,
@@ -1032,6 +1032,34 @@ impl<'a> Parser<'a> {
10321032
}
10331033
}
10341034

1035+
/// Swift lets users write `Ty?` to mean `Option<Ty>`. Parse the construct and recover from it.
1036+
pub(super) fn maybe_recover_from_question_mark(
1037+
&mut self,
1038+
ty: P<Ty>,
1039+
is_as_cast: IsAsCast,
1040+
) -> P<Ty> {
1041+
if let IsAsCast::Yes = is_as_cast {
1042+
return ty;
1043+
}
1044+
if self.token == token::Question {
1045+
self.bump();
1046+
self.struct_span_err(self.prev_token.span, "invalid `?` in type")
1047+
.span_label(self.prev_token.span, "`?` is only allowed on expressions, not types")
1048+
.multipart_suggestion(
1049+
"if you meant to express that the type might not contain a value, use the `Option` wrapper type",
1050+
vec![
1051+
(ty.span.shrink_to_lo(), "Option<".to_string()),
1052+
(self.prev_token.span, ">".to_string()),
1053+
],
1054+
Applicability::MachineApplicable,
1055+
)
1056+
.emit();
1057+
self.mk_ty(ty.span.to(self.prev_token.span), TyKind::Err)
1058+
} else {
1059+
ty
1060+
}
1061+
}
1062+
10351063
pub(super) fn maybe_recover_from_bad_type_plus(
10361064
&mut self,
10371065
allow_plus: AllowPlus,

compiler/rustc_parse/src/parser/expr.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,7 @@ impl<'a> Parser<'a> {
682682
// Save the state of the parser before parsing type normally, in case there is a
683683
// LessThan comparison after this cast.
684684
let parser_snapshot_before_type = self.clone();
685-
let cast_expr = match self.parse_ty_no_plus() {
685+
let cast_expr = match self.parse_as_cast_ty() {
686686
Ok(rhs) => mk_expr(self, lhs, rhs),
687687
Err(mut type_err) => {
688688
// Rewind to before attempting to parse the type with generics, to recover
@@ -808,7 +808,7 @@ impl<'a> Parser<'a> {
808808
"casts cannot be followed by {}",
809809
match with_postfix.kind {
810810
ExprKind::Index(_, _) => "indexing",
811-
ExprKind::Try(_) => "?",
811+
ExprKind::Try(_) => "`?`",
812812
ExprKind::Field(_, _) => "a field access",
813813
ExprKind::MethodCall(_, _, _) => "a method call",
814814
ExprKind::Call(_, _) => "a function call",

compiler/rustc_parse/src/parser/ty.rs

+26
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ pub(super) enum RecoverQPath {
4444
No,
4545
}
4646

47+
pub(super) enum IsAsCast {
48+
Yes,
49+
No,
50+
}
51+
4752
/// Signals whether parsing a type should recover `->`.
4853
///
4954
/// More specifically, when parsing a function like:
@@ -100,6 +105,7 @@ impl<'a> Parser<'a> {
100105
RecoverQPath::Yes,
101106
RecoverReturnSign::Yes,
102107
None,
108+
IsAsCast::No,
103109
)
104110
}
105111

@@ -113,6 +119,7 @@ impl<'a> Parser<'a> {
113119
RecoverQPath::Yes,
114120
RecoverReturnSign::Yes,
115121
Some(ty_params),
122+
IsAsCast::No,
116123
)
117124
}
118125

@@ -126,6 +133,7 @@ impl<'a> Parser<'a> {
126133
RecoverQPath::Yes,
127134
RecoverReturnSign::Yes,
128135
None,
136+
IsAsCast::No,
129137
)
130138
}
131139

@@ -142,9 +150,22 @@ impl<'a> Parser<'a> {
142150
RecoverQPath::Yes,
143151
RecoverReturnSign::Yes,
144152
None,
153+
IsAsCast::No,
145154
)
146155
}
147156

157+
/// Parses a type following an `as` cast. Similar to `parse_ty_no_plus`, but signaling origin
158+
/// for better diagnostics involving `?`.
159+
pub(super) fn parse_as_cast_ty(&mut self) -> PResult<'a, P<Ty>> {
160+
self.parse_ty_common(
161+
AllowPlus::No,
162+
AllowCVariadic::No,
163+
RecoverQPath::Yes,
164+
RecoverReturnSign::Yes,
165+
None,
166+
IsAsCast::Yes,
167+
)
168+
}
148169
/// Parse a type without recovering `:` as `->` to avoid breaking code such as `where fn() : for<'a>`
149170
pub(super) fn parse_ty_for_where_clause(&mut self) -> PResult<'a, P<Ty>> {
150171
self.parse_ty_common(
@@ -153,6 +174,7 @@ impl<'a> Parser<'a> {
153174
RecoverQPath::Yes,
154175
RecoverReturnSign::OnlyFatArrow,
155176
None,
177+
IsAsCast::No,
156178
)
157179
}
158180

@@ -171,6 +193,7 @@ impl<'a> Parser<'a> {
171193
recover_qpath,
172194
recover_return_sign,
173195
None,
196+
IsAsCast::No,
174197
)?;
175198
FnRetTy::Ty(ty)
176199
} else if recover_return_sign.can_recover(&self.token.kind) {
@@ -191,6 +214,7 @@ impl<'a> Parser<'a> {
191214
recover_qpath,
192215
recover_return_sign,
193216
None,
217+
IsAsCast::No,
194218
)?;
195219
FnRetTy::Ty(ty)
196220
} else {
@@ -205,6 +229,7 @@ impl<'a> Parser<'a> {
205229
recover_qpath: RecoverQPath,
206230
recover_return_sign: RecoverReturnSign,
207231
ty_generics: Option<&Generics>,
232+
is_as_cast: IsAsCast,
208233
) -> PResult<'a, P<Ty>> {
209234
let allow_qpath_recovery = recover_qpath == RecoverQPath::Yes;
210235
maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery);
@@ -280,6 +305,7 @@ impl<'a> Parser<'a> {
280305
// Try to recover from use of `+` with incorrect priority.
281306
self.maybe_report_ambiguous_plus(allow_plus, impl_dyn_multi, &ty);
282307
self.maybe_recover_from_bad_type_plus(allow_plus, &ty)?;
308+
let ty = self.maybe_recover_from_question_mark(ty, is_as_cast);
283309
self.maybe_recover_from_bad_qpath(ty, allow_qpath_recovery)
284310
}
285311

src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ static bar2: &[i32] = &(&[1i32,2,3]: &[i32; 3][0..1]);
117117

118118
pub fn cast_then_try() -> Result<u64,u64> {
119119
Err(0u64) as Result<u64,u64>?;
120-
//~^ ERROR: casts cannot be followed by ?
120+
//~^ ERROR: casts cannot be followed by `?`
121121
Err(0u64): Result<u64,u64>?;
122-
//~^ ERROR: casts cannot be followed by ?
122+
//~^ ERROR: casts cannot be followed by `?`
123123
Ok(1)
124124
}
125125

src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ help: try surrounding the expression in parentheses
265265
LL | static bar2: &[i32] = &((&[1i32,2,3]: &[i32; 3])[0..1]);
266266
| + +
267267

268-
error: casts cannot be followed by ?
268+
error: casts cannot be followed by `?`
269269
--> $DIR/issue-35813-postfix-after-cast.rs:119:5
270270
|
271271
LL | Err(0u64) as Result<u64,u64>?;
@@ -276,7 +276,7 @@ help: try surrounding the expression in parentheses
276276
LL | (Err(0u64) as Result<u64,u64>)?;
277277
| + +
278278

279-
error: casts cannot be followed by ?
279+
error: casts cannot be followed by `?`
280280
--> $DIR/issue-35813-postfix-after-cast.rs:121:5
281281
|
282282
LL | Err(0u64): Result<u64,u64>?;
+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
fn f(t:for<>t?)
2-
//~^ ERROR: expected parameter name
3-
//~| ERROR: expected one of
4-
//~| ERROR: expected one of
2+
//~^ ERROR: expected one of
3+
//~| ERROR: invalid `?` in type
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
1-
error: expected parameter name, found `?`
1+
error: invalid `?` in type
22
--> $DIR/issue-84148-1.rs:1:14
33
|
44
LL | fn f(t:for<>t?)
5-
| ^ expected parameter name
6-
7-
error: expected one of `(`, `)`, `+`, `,`, `::`, or `<`, found `?`
8-
--> $DIR/issue-84148-1.rs:1:14
5+
| ^ `?` is only allowed on expressions, not types
96
|
10-
LL | fn f(t:for<>t?)
11-
| ^
12-
| |
13-
| expected one of `(`, `)`, `+`, `,`, `::`, or `<`
14-
| help: missing `,`
7+
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
8+
|
9+
LL | fn f(t:Option<for<>t>)
10+
| +++++++ ~
1511

1612
error: expected one of `->`, `where`, or `{`, found `<eof>`
1713
--> $DIR/issue-84148-1.rs:1:15
1814
|
1915
LL | fn f(t:for<>t?)
2016
| ^ expected one of `->`, `where`, or `{`
2117

22-
error: aborting due to 3 previous errors
18+
error: aborting due to 2 previous errors
2319

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
// error-pattern: this file contains an unclosed delimiter
2-
// error-pattern: expected parameter name
3-
// error-pattern: expected one of
2+
// error-pattern: invalid `?` in type
43
fn f(t:for<>t?
+10-14
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
error: this file contains an unclosed delimiter
2-
--> $DIR/issue-84148-2.rs:4:16
2+
--> $DIR/issue-84148-2.rs:3:16
33
|
44
LL | fn f(t:for<>t?
55
| - ^
66
| |
77
| unclosed delimiter
88

9-
error: expected parameter name, found `?`
10-
--> $DIR/issue-84148-2.rs:4:14
9+
error: invalid `?` in type
10+
--> $DIR/issue-84148-2.rs:3:14
1111
|
1212
LL | fn f(t:for<>t?
13-
| ^ expected parameter name
14-
15-
error: expected one of `(`, `)`, `+`, `,`, `::`, or `<`, found `?`
16-
--> $DIR/issue-84148-2.rs:4:14
13+
| ^ `?` is only allowed on expressions, not types
1714
|
18-
LL | fn f(t:for<>t?
19-
| ^
20-
| |
21-
| expected one of `(`, `)`, `+`, `,`, `::`, or `<`
22-
| help: missing `,`
15+
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
16+
|
17+
LL | fn f(t:Option<for<>t>
18+
| +++++++ ~
2319

2420
error: expected one of `->`, `where`, or `{`, found `<eof>`
25-
--> $DIR/issue-84148-2.rs:4:16
21+
--> $DIR/issue-84148-2.rs:3:16
2622
|
2723
LL | fn f(t:for<>t?
2824
| ^ expected one of `->`, `where`, or `{`
2925

30-
error: aborting due to 4 previous errors
26+
error: aborting due to 3 previous errors
3127

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
3+
fn foo() -> Option<i32> { //~ ERROR invalid `?` in type
4+
let x: Option<i32> = Some(1); //~ ERROR invalid `?` in type
5+
x
6+
}
7+
8+
fn main() {
9+
let _: Option<i32> = foo();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// run-rustfix
2+
3+
fn foo() -> i32? { //~ ERROR invalid `?` in type
4+
let x: i32? = Some(1); //~ ERROR invalid `?` in type
5+
x
6+
}
7+
8+
fn main() {
9+
let _: Option<i32> = foo();
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error: invalid `?` in type
2+
--> $DIR/trailing-question-in-type.rs:3:16
3+
|
4+
LL | fn foo() -> i32? {
5+
| ^ `?` is only allowed on expressions, not types
6+
|
7+
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
8+
|
9+
LL | fn foo() -> Option<i32> {
10+
| +++++++ ~
11+
12+
error: invalid `?` in type
13+
--> $DIR/trailing-question-in-type.rs:4:15
14+
|
15+
LL | let x: i32? = Some(1);
16+
| ^ `?` is only allowed on expressions, not types
17+
|
18+
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
19+
|
20+
LL | let x: Option<i32> = Some(1);
21+
| +++++++ ~
22+
23+
error: aborting due to 2 previous errors
24+

0 commit comments

Comments
 (0)