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:
Esteban Kuber 2022-01-10 22:02:19 +00:00
parent 89b9f7b284
commit cfc0bd1258
12 changed files with 125 additions and 37 deletions

View file

@ -1,5 +1,5 @@
use super::pat::Expected;
use super::ty::AllowPlus;
use super::ty::{AllowPlus, IsAsCast};
use super::{
BlockMode, Parser, PathStyle, RecoverColon, RecoverComma, Restrictions, SemiColonMode, SeqSep,
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(
&mut self,
allow_plus: AllowPlus,

View file

@ -682,7 +682,7 @@ impl<'a> Parser<'a> {
// Save the state of the parser before parsing type normally, in case there is a
// LessThan comparison after this cast.
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),
Err(mut type_err) => {
// 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 {}",
match with_postfix.kind {
ExprKind::Index(_, _) => "indexing",
ExprKind::Try(_) => "?",
ExprKind::Try(_) => "`?`",
ExprKind::Field(_, _) => "a field access",
ExprKind::MethodCall(_, _, _) => "a method call",
ExprKind::Call(_, _) => "a function call",

View file

@ -44,6 +44,11 @@ pub(super) enum RecoverQPath {
No,
}
pub(super) enum IsAsCast {
Yes,
No,
}
/// Signals whether parsing a type should recover `->`.
///
/// More specifically, when parsing a function like:
@ -100,6 +105,7 @@ impl<'a> Parser<'a> {
RecoverQPath::Yes,
RecoverReturnSign::Yes,
None,
IsAsCast::No,
)
}
@ -113,6 +119,7 @@ impl<'a> Parser<'a> {
RecoverQPath::Yes,
RecoverReturnSign::Yes,
Some(ty_params),
IsAsCast::No,
)
}
@ -126,6 +133,7 @@ impl<'a> Parser<'a> {
RecoverQPath::Yes,
RecoverReturnSign::Yes,
None,
IsAsCast::No,
)
}
@ -142,9 +150,22 @@ impl<'a> Parser<'a> {
RecoverQPath::Yes,
RecoverReturnSign::Yes,
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>`
pub(super) fn parse_ty_for_where_clause(&mut self) -> PResult<'a, P<Ty>> {
self.parse_ty_common(
@ -153,6 +174,7 @@ impl<'a> Parser<'a> {
RecoverQPath::Yes,
RecoverReturnSign::OnlyFatArrow,
None,
IsAsCast::No,
)
}
@ -171,6 +193,7 @@ impl<'a> Parser<'a> {
recover_qpath,
recover_return_sign,
None,
IsAsCast::No,
)?;
FnRetTy::Ty(ty)
} else if recover_return_sign.can_recover(&self.token.kind) {
@ -191,6 +214,7 @@ impl<'a> Parser<'a> {
recover_qpath,
recover_return_sign,
None,
IsAsCast::No,
)?;
FnRetTy::Ty(ty)
} else {
@ -205,6 +229,7 @@ impl<'a> Parser<'a> {
recover_qpath: RecoverQPath,
recover_return_sign: RecoverReturnSign,
ty_generics: Option<&Generics>,
is_as_cast: IsAsCast,
) -> PResult<'a, P<Ty>> {
let allow_qpath_recovery = recover_qpath == RecoverQPath::Yes;
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.
self.maybe_report_ambiguous_plus(allow_plus, impl_dyn_multi, &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)
}