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.
This commit is contained in:
commit
9323a0d1be
12 changed files with 125 additions and 37 deletions
|
@ -1,5 +1,5 @@
|
||||||
use super::pat::Expected;
|
use super::pat::Expected;
|
||||||
use super::ty::AllowPlus;
|
use super::ty::{AllowPlus, IsAsCast};
|
||||||
use super::{
|
use super::{
|
||||||
BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep,
|
BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep,
|
||||||
TokenExpectType, TokenType,
|
TokenExpectType, TokenType,
|
||||||
|
@ -1032,6 +1032,34 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Swift lets users write `Ty?` to mean `Option<Ty>`. Parse the construct and recover from it.
|
||||||
|
pub(super) fn maybe_recover_from_question_mark(
|
||||||
|
&mut self,
|
||||||
|
ty: P<Ty>,
|
||||||
|
is_as_cast: IsAsCast,
|
||||||
|
) -> P<Ty> {
|
||||||
|
if let IsAsCast::Yes = is_as_cast {
|
||||||
|
return ty;
|
||||||
|
}
|
||||||
|
if self.token == token::Question {
|
||||||
|
self.bump();
|
||||||
|
self.struct_span_err(self.prev_token.span, "invalid `?` in type")
|
||||||
|
.span_label(self.prev_token.span, "`?` is only allowed on expressions, not types")
|
||||||
|
.multipart_suggestion(
|
||||||
|
"if you meant to express that the type might not contain a value, use the `Option` wrapper type",
|
||||||
|
vec![
|
||||||
|
(ty.span.shrink_to_lo(), "Option<".to_string()),
|
||||||
|
(self.prev_token.span, ">".to_string()),
|
||||||
|
],
|
||||||
|
Applicability::MachineApplicable,
|
||||||
|
)
|
||||||
|
.emit();
|
||||||
|
self.mk_ty(ty.span.to(self.prev_token.span), TyKind::Err)
|
||||||
|
} else {
|
||||||
|
ty
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn maybe_recover_from_bad_type_plus(
|
pub(super) fn maybe_recover_from_bad_type_plus(
|
||||||
&mut self,
|
&mut self,
|
||||||
allow_plus: AllowPlus,
|
allow_plus: AllowPlus,
|
||||||
|
|
|
@ -682,7 +682,7 @@ impl<'a> Parser<'a> {
|
||||||
// Save the state of the parser before parsing type normally, in case there is a
|
// Save the state of the parser before parsing type normally, in case there is a
|
||||||
// LessThan comparison after this cast.
|
// LessThan comparison after this cast.
|
||||||
let parser_snapshot_before_type = self.clone();
|
let parser_snapshot_before_type = self.clone();
|
||||||
let cast_expr = match self.parse_ty_no_plus() {
|
let cast_expr = match self.parse_as_cast_ty() {
|
||||||
Ok(rhs) => mk_expr(self, lhs, rhs),
|
Ok(rhs) => mk_expr(self, lhs, rhs),
|
||||||
Err(mut type_err) => {
|
Err(mut type_err) => {
|
||||||
// Rewind to before attempting to parse the type with generics, to recover
|
// Rewind to before attempting to parse the type with generics, to recover
|
||||||
|
@ -808,7 +808,7 @@ impl<'a> Parser<'a> {
|
||||||
"casts cannot be followed by {}",
|
"casts cannot be followed by {}",
|
||||||
match with_postfix.kind {
|
match with_postfix.kind {
|
||||||
ExprKind::Index(_, _) => "indexing",
|
ExprKind::Index(_, _) => "indexing",
|
||||||
ExprKind::Try(_) => "?",
|
ExprKind::Try(_) => "`?`",
|
||||||
ExprKind::Field(_, _) => "a field access",
|
ExprKind::Field(_, _) => "a field access",
|
||||||
ExprKind::MethodCall(_, _, _) => "a method call",
|
ExprKind::MethodCall(_, _, _) => "a method call",
|
||||||
ExprKind::Call(_, _) => "a function call",
|
ExprKind::Call(_, _) => "a function call",
|
||||||
|
|
|
@ -44,6 +44,11 @@ pub(super) enum RecoverQPath {
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) enum IsAsCast {
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
}
|
||||||
|
|
||||||
/// Signals whether parsing a type should recover `->`.
|
/// Signals whether parsing a type should recover `->`.
|
||||||
///
|
///
|
||||||
/// More specifically, when parsing a function like:
|
/// More specifically, when parsing a function like:
|
||||||
|
@ -100,6 +105,7 @@ impl<'a> Parser<'a> {
|
||||||
RecoverQPath::Yes,
|
RecoverQPath::Yes,
|
||||||
RecoverReturnSign::Yes,
|
RecoverReturnSign::Yes,
|
||||||
None,
|
None,
|
||||||
|
IsAsCast::No,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +119,7 @@ impl<'a> Parser<'a> {
|
||||||
RecoverQPath::Yes,
|
RecoverQPath::Yes,
|
||||||
RecoverReturnSign::Yes,
|
RecoverReturnSign::Yes,
|
||||||
Some(ty_params),
|
Some(ty_params),
|
||||||
|
IsAsCast::No,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +133,7 @@ impl<'a> Parser<'a> {
|
||||||
RecoverQPath::Yes,
|
RecoverQPath::Yes,
|
||||||
RecoverReturnSign::Yes,
|
RecoverReturnSign::Yes,
|
||||||
None,
|
None,
|
||||||
|
IsAsCast::No,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,9 +150,22 @@ impl<'a> Parser<'a> {
|
||||||
RecoverQPath::Yes,
|
RecoverQPath::Yes,
|
||||||
RecoverReturnSign::Yes,
|
RecoverReturnSign::Yes,
|
||||||
None,
|
None,
|
||||||
|
IsAsCast::No,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a type following an `as` cast. Similar to `parse_ty_no_plus`, but signaling origin
|
||||||
|
/// for better diagnostics involving `?`.
|
||||||
|
pub(super) fn parse_as_cast_ty(&mut self) -> PResult<'a, P<Ty>> {
|
||||||
|
self.parse_ty_common(
|
||||||
|
AllowPlus::No,
|
||||||
|
AllowCVariadic::No,
|
||||||
|
RecoverQPath::Yes,
|
||||||
|
RecoverReturnSign::Yes,
|
||||||
|
None,
|
||||||
|
IsAsCast::Yes,
|
||||||
|
)
|
||||||
|
}
|
||||||
/// Parse a type without recovering `:` as `->` to avoid breaking code such as `where fn() : for<'a>`
|
/// Parse a type without recovering `:` as `->` to avoid breaking code such as `where fn() : for<'a>`
|
||||||
pub(super) fn parse_ty_for_where_clause(&mut self) -> PResult<'a, P<Ty>> {
|
pub(super) fn parse_ty_for_where_clause(&mut self) -> PResult<'a, P<Ty>> {
|
||||||
self.parse_ty_common(
|
self.parse_ty_common(
|
||||||
|
@ -153,6 +174,7 @@ impl<'a> Parser<'a> {
|
||||||
RecoverQPath::Yes,
|
RecoverQPath::Yes,
|
||||||
RecoverReturnSign::OnlyFatArrow,
|
RecoverReturnSign::OnlyFatArrow,
|
||||||
None,
|
None,
|
||||||
|
IsAsCast::No,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -171,6 +193,7 @@ impl<'a> Parser<'a> {
|
||||||
recover_qpath,
|
recover_qpath,
|
||||||
recover_return_sign,
|
recover_return_sign,
|
||||||
None,
|
None,
|
||||||
|
IsAsCast::No,
|
||||||
)?;
|
)?;
|
||||||
FnRetTy::Ty(ty)
|
FnRetTy::Ty(ty)
|
||||||
} else if recover_return_sign.can_recover(&self.token.kind) {
|
} else if recover_return_sign.can_recover(&self.token.kind) {
|
||||||
|
@ -191,6 +214,7 @@ impl<'a> Parser<'a> {
|
||||||
recover_qpath,
|
recover_qpath,
|
||||||
recover_return_sign,
|
recover_return_sign,
|
||||||
None,
|
None,
|
||||||
|
IsAsCast::No,
|
||||||
)?;
|
)?;
|
||||||
FnRetTy::Ty(ty)
|
FnRetTy::Ty(ty)
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,6 +229,7 @@ impl<'a> Parser<'a> {
|
||||||
recover_qpath: RecoverQPath,
|
recover_qpath: RecoverQPath,
|
||||||
recover_return_sign: RecoverReturnSign,
|
recover_return_sign: RecoverReturnSign,
|
||||||
ty_generics: Option<&Generics>,
|
ty_generics: Option<&Generics>,
|
||||||
|
is_as_cast: IsAsCast,
|
||||||
) -> PResult<'a, P<Ty>> {
|
) -> PResult<'a, P<Ty>> {
|
||||||
let allow_qpath_recovery = recover_qpath == RecoverQPath::Yes;
|
let allow_qpath_recovery = recover_qpath == RecoverQPath::Yes;
|
||||||
maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery);
|
maybe_recover_from_interpolated_ty_qpath!(self, allow_qpath_recovery);
|
||||||
|
@ -280,6 +305,7 @@ impl<'a> Parser<'a> {
|
||||||
// Try to recover from use of `+` with incorrect priority.
|
// Try to recover from use of `+` with incorrect priority.
|
||||||
self.maybe_report_ambiguous_plus(allow_plus, impl_dyn_multi, &ty);
|
self.maybe_report_ambiguous_plus(allow_plus, impl_dyn_multi, &ty);
|
||||||
self.maybe_recover_from_bad_type_plus(allow_plus, &ty)?;
|
self.maybe_recover_from_bad_type_plus(allow_plus, &ty)?;
|
||||||
|
let ty = self.maybe_recover_from_question_mark(ty, is_as_cast);
|
||||||
self.maybe_recover_from_bad_qpath(ty, allow_qpath_recovery)
|
self.maybe_recover_from_bad_qpath(ty, allow_qpath_recovery)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -117,9 +117,9 @@ static bar2: &[i32] = &(&[1i32,2,3]: &[i32; 3][0..1]);
|
||||||
|
|
||||||
pub fn cast_then_try() -> Result<u64,u64> {
|
pub fn cast_then_try() -> Result<u64,u64> {
|
||||||
Err(0u64) as Result<u64,u64>?;
|
Err(0u64) as Result<u64,u64>?;
|
||||||
//~^ ERROR: casts cannot be followed by ?
|
//~^ ERROR: casts cannot be followed by `?`
|
||||||
Err(0u64): Result<u64,u64>?;
|
Err(0u64): Result<u64,u64>?;
|
||||||
//~^ ERROR: casts cannot be followed by ?
|
//~^ ERROR: casts cannot be followed by `?`
|
||||||
Ok(1)
|
Ok(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -265,7 +265,7 @@ help: try surrounding the expression in parentheses
|
||||||
LL | static bar2: &[i32] = &((&[1i32,2,3]: &[i32; 3])[0..1]);
|
LL | static bar2: &[i32] = &((&[1i32,2,3]: &[i32; 3])[0..1]);
|
||||||
| + +
|
| + +
|
||||||
|
|
||||||
error: casts cannot be followed by ?
|
error: casts cannot be followed by `?`
|
||||||
--> $DIR/issue-35813-postfix-after-cast.rs:119:5
|
--> $DIR/issue-35813-postfix-after-cast.rs:119:5
|
||||||
|
|
|
|
||||||
LL | Err(0u64) as Result<u64,u64>?;
|
LL | Err(0u64) as Result<u64,u64>?;
|
||||||
|
@ -276,7 +276,7 @@ help: try surrounding the expression in parentheses
|
||||||
LL | (Err(0u64) as Result<u64,u64>)?;
|
LL | (Err(0u64) as Result<u64,u64>)?;
|
||||||
| + +
|
| + +
|
||||||
|
|
||||||
error: casts cannot be followed by ?
|
error: casts cannot be followed by `?`
|
||||||
--> $DIR/issue-35813-postfix-after-cast.rs:121:5
|
--> $DIR/issue-35813-postfix-after-cast.rs:121:5
|
||||||
|
|
|
|
||||||
LL | Err(0u64): Result<u64,u64>?;
|
LL | Err(0u64): Result<u64,u64>?;
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
fn f(t:for<>t?)
|
fn f(t:for<>t?)
|
||||||
//~^ ERROR: expected parameter name
|
//~^ ERROR: expected one of
|
||||||
//~| ERROR: expected one of
|
//~| ERROR: invalid `?` in type
|
||||||
//~| ERROR: expected one of
|
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
error: expected parameter name, found `?`
|
error: invalid `?` in type
|
||||||
--> $DIR/issue-84148-1.rs:1:14
|
--> $DIR/issue-84148-1.rs:1:14
|
||||||
|
|
|
|
||||||
LL | fn f(t:for<>t?)
|
LL | fn f(t:for<>t?)
|
||||||
| ^ expected parameter name
|
| ^ `?` is only allowed on expressions, not types
|
||||||
|
|
||||||
error: expected one of `(`, `)`, `+`, `,`, `::`, or `<`, found `?`
|
|
||||||
--> $DIR/issue-84148-1.rs:1:14
|
|
||||||
|
|
|
|
||||||
LL | fn f(t:for<>t?)
|
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
|
||||||
| ^
|
|
|
||||||
| |
|
LL | fn f(t:Option<for<>t>)
|
||||||
| expected one of `(`, `)`, `+`, `,`, `::`, or `<`
|
| +++++++ ~
|
||||||
| help: missing `,`
|
|
||||||
|
|
||||||
error: expected one of `->`, `where`, or `{`, found `<eof>`
|
error: expected one of `->`, `where`, or `{`, found `<eof>`
|
||||||
--> $DIR/issue-84148-1.rs:1:15
|
--> $DIR/issue-84148-1.rs:1:15
|
||||||
|
@ -19,5 +15,5 @@ error: expected one of `->`, `where`, or `{`, found `<eof>`
|
||||||
LL | fn f(t:for<>t?)
|
LL | fn f(t:for<>t?)
|
||||||
| ^ expected one of `->`, `where`, or `{`
|
| ^ expected one of `->`, `where`, or `{`
|
||||||
|
|
||||||
error: aborting due to 3 previous errors
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
// error-pattern: this file contains an unclosed delimiter
|
// error-pattern: this file contains an unclosed delimiter
|
||||||
// error-pattern: expected parameter name
|
// error-pattern: invalid `?` in type
|
||||||
// error-pattern: expected one of
|
|
||||||
fn f(t:for<>t?
|
fn f(t:for<>t?
|
||||||
|
|
|
@ -1,31 +1,27 @@
|
||||||
error: this file contains an unclosed delimiter
|
error: this file contains an unclosed delimiter
|
||||||
--> $DIR/issue-84148-2.rs:4:16
|
--> $DIR/issue-84148-2.rs:3:16
|
||||||
|
|
|
|
||||||
LL | fn f(t:for<>t?
|
LL | fn f(t:for<>t?
|
||||||
| - ^
|
| - ^
|
||||||
| |
|
| |
|
||||||
| unclosed delimiter
|
| unclosed delimiter
|
||||||
|
|
||||||
error: expected parameter name, found `?`
|
error: invalid `?` in type
|
||||||
--> $DIR/issue-84148-2.rs:4:14
|
--> $DIR/issue-84148-2.rs:3:14
|
||||||
|
|
|
|
||||||
LL | fn f(t:for<>t?
|
LL | fn f(t:for<>t?
|
||||||
| ^ expected parameter name
|
| ^ `?` is only allowed on expressions, not types
|
||||||
|
|
||||||
error: expected one of `(`, `)`, `+`, `,`, `::`, or `<`, found `?`
|
|
||||||
--> $DIR/issue-84148-2.rs:4:14
|
|
||||||
|
|
|
|
||||||
LL | fn f(t:for<>t?
|
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
|
||||||
| ^
|
|
|
||||||
| |
|
LL | fn f(t:Option<for<>t>
|
||||||
| expected one of `(`, `)`, `+`, `,`, `::`, or `<`
|
| +++++++ ~
|
||||||
| help: missing `,`
|
|
||||||
|
|
||||||
error: expected one of `->`, `where`, or `{`, found `<eof>`
|
error: expected one of `->`, `where`, or `{`, found `<eof>`
|
||||||
--> $DIR/issue-84148-2.rs:4:16
|
--> $DIR/issue-84148-2.rs:3:16
|
||||||
|
|
|
|
||||||
LL | fn f(t:for<>t?
|
LL | fn f(t:for<>t?
|
||||||
| ^ expected one of `->`, `where`, or `{`
|
| ^ expected one of `->`, `where`, or `{`
|
||||||
|
|
||||||
error: aborting due to 4 previous errors
|
error: aborting due to 3 previous errors
|
||||||
|
|
||||||
|
|
10
src/test/ui/parser/trailing-question-in-type.fixed
Normal file
10
src/test/ui/parser/trailing-question-in-type.fixed
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// run-rustfix
|
||||||
|
|
||||||
|
fn foo() -> Option<i32> { //~ ERROR invalid `?` in type
|
||||||
|
let x: Option<i32> = Some(1); //~ ERROR invalid `?` in type
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _: Option<i32> = foo();
|
||||||
|
}
|
10
src/test/ui/parser/trailing-question-in-type.rs
Normal file
10
src/test/ui/parser/trailing-question-in-type.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
// run-rustfix
|
||||||
|
|
||||||
|
fn foo() -> i32? { //~ ERROR invalid `?` in type
|
||||||
|
let x: i32? = Some(1); //~ ERROR invalid `?` in type
|
||||||
|
x
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let _: Option<i32> = foo();
|
||||||
|
}
|
24
src/test/ui/parser/trailing-question-in-type.stderr
Normal file
24
src/test/ui/parser/trailing-question-in-type.stderr
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
error: invalid `?` in type
|
||||||
|
--> $DIR/trailing-question-in-type.rs:3:16
|
||||||
|
|
|
||||||
|
LL | fn foo() -> i32? {
|
||||||
|
| ^ `?` is only allowed on expressions, not types
|
||||||
|
|
|
||||||
|
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
|
||||||
|
|
|
||||||
|
LL | fn foo() -> Option<i32> {
|
||||||
|
| +++++++ ~
|
||||||
|
|
||||||
|
error: invalid `?` in type
|
||||||
|
--> $DIR/trailing-question-in-type.rs:4:15
|
||||||
|
|
|
||||||
|
LL | let x: i32? = Some(1);
|
||||||
|
| ^ `?` is only allowed on expressions, not types
|
||||||
|
|
|
||||||
|
help: if you meant to express that the type might not contain a value, use the `Option` wrapper type
|
||||||
|
|
|
||||||
|
LL | let x: Option<i32> = Some(1);
|
||||||
|
| +++++++ ~
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue