about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMatthias Krüger <matthias.krueger@famsik.de>2022-01-16 16:58:18 +0100
committerGitHub <noreply@github.com>2022-01-16 16:58:18 +0100
commit9323a0d1bef9b0708e25f0a8abaf5e5f88599f8d (patch)
tree6b4a7fe7ebdf25c0ed07bddc513162b8073c5245
parent9835b90c91e7012f27fc78a60599aab0011d2336 (diff)
parentcfc0bd12581651e5d0f51d0d4c2d8306cc13cb51 (diff)
downloadrust-9323a0d1bef9b0708e25f0a8abaf5e5f88599f8d.tar.gz
rust-9323a0d1bef9b0708e25f0a8abaf5e5f88599f8d.zip
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.
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs30
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs4
-rw-r--r--compiler/rustc_parse/src/parser/ty.rs26
-rw-r--r--src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs4
-rw-r--r--src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr4
-rw-r--r--src/test/ui/parser/issues/issue-84148-1.rs5
-rw-r--r--src/test/ui/parser/issues/issue-84148-1.stderr18
-rw-r--r--src/test/ui/parser/issues/issue-84148-2.rs3
-rw-r--r--src/test/ui/parser/issues/issue-84148-2.stderr24
-rw-r--r--src/test/ui/parser/trailing-question-in-type.fixed10
-rw-r--r--src/test/ui/parser/trailing-question-in-type.rs10
-rw-r--r--src/test/ui/parser/trailing-question-in-type.stderr24
12 files changed, 125 insertions, 37 deletions
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 4121a759c37..612d4508565 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -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,
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index f706a98a4fc..cd3846d5a22 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -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",
diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs
index 02a774ba129..566b77a5e9e 100644
--- a/compiler/rustc_parse/src/parser/ty.rs
+++ b/compiler/rustc_parse/src/parser/ty.rs
@@ -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)
     }
 
diff --git a/src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs b/src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs
index e725aa5d73d..23f245a5168 100644
--- a/src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs
+++ b/src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs
@@ -117,9 +117,9 @@ static bar2: &[i32] = &(&[1i32,2,3]: &[i32; 3][0..1]);
 
 pub fn cast_then_try() -> 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>?;
-    //~^ ERROR: casts cannot be followed by ?
+    //~^ ERROR: casts cannot be followed by `?`
     Ok(1)
 }
 
diff --git a/src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr b/src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr
index 19b68556d79..e96b67da336 100644
--- a/src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr
+++ b/src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr
@@ -265,7 +265,7 @@ help: try surrounding the expression in parentheses
 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
    |
 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>)?;
    |     +                            +
 
-error: casts cannot be followed by ?
+error: casts cannot be followed by `?`
   --> $DIR/issue-35813-postfix-after-cast.rs:121:5
    |
 LL |     Err(0u64): Result<u64,u64>?;
diff --git a/src/test/ui/parser/issues/issue-84148-1.rs b/src/test/ui/parser/issues/issue-84148-1.rs
index 25f7ba4d1f8..9fa8086c2c9 100644
--- a/src/test/ui/parser/issues/issue-84148-1.rs
+++ b/src/test/ui/parser/issues/issue-84148-1.rs
@@ -1,4 +1,3 @@
 fn f(t:for<>t?)
-//~^ ERROR: expected parameter name
-//~| ERROR: expected one of
-//~| ERROR: expected one of
+//~^ ERROR: expected one of
+//~| ERROR: invalid `?` in type
diff --git a/src/test/ui/parser/issues/issue-84148-1.stderr b/src/test/ui/parser/issues/issue-84148-1.stderr
index 77f0896e9c1..9261067c221 100644
--- a/src/test/ui/parser/issues/issue-84148-1.stderr
+++ b/src/test/ui/parser/issues/issue-84148-1.stderr
@@ -1,17 +1,13 @@
-error: expected parameter name, found `?`
+error: invalid `?` in type
   --> $DIR/issue-84148-1.rs:1:14
    |
 LL | fn f(t:for<>t?)
-   |              ^ expected parameter name
-
-error: expected one of `(`, `)`, `+`, `,`, `::`, or `<`, found `?`
-  --> $DIR/issue-84148-1.rs:1:14
+   |              ^ `?` is only allowed on expressions, not types
    |
-LL | fn f(t:for<>t?)
-   |              ^
-   |              |
-   |              expected one of `(`, `)`, `+`, `,`, `::`, or `<`
-   |              help: missing `,`
+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>)
+   |        +++++++      ~
 
 error: expected one of `->`, `where`, or `{`, found `<eof>`
   --> $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?)
    |               ^ expected one of `->`, `where`, or `{`
 
-error: aborting due to 3 previous errors
+error: aborting due to 2 previous errors
 
diff --git a/src/test/ui/parser/issues/issue-84148-2.rs b/src/test/ui/parser/issues/issue-84148-2.rs
index 257a3fd6720..2f6a7facfb2 100644
--- a/src/test/ui/parser/issues/issue-84148-2.rs
+++ b/src/test/ui/parser/issues/issue-84148-2.rs
@@ -1,4 +1,3 @@
 // error-pattern: this file contains an unclosed delimiter
-// error-pattern: expected parameter name
-// error-pattern: expected one of
+// error-pattern: invalid `?` in type
 fn f(t:for<>t?
diff --git a/src/test/ui/parser/issues/issue-84148-2.stderr b/src/test/ui/parser/issues/issue-84148-2.stderr
index 396208316df..71d543f9b73 100644
--- a/src/test/ui/parser/issues/issue-84148-2.stderr
+++ b/src/test/ui/parser/issues/issue-84148-2.stderr
@@ -1,31 +1,27 @@
 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?
    |     -          ^
    |     |
    |     unclosed delimiter
 
-error: expected parameter name, found `?`
-  --> $DIR/issue-84148-2.rs:4:14
+error: invalid `?` in type
+  --> $DIR/issue-84148-2.rs:3:14
    |
 LL | fn f(t:for<>t?
-   |              ^ expected parameter name
-
-error: expected one of `(`, `)`, `+`, `,`, `::`, or `<`, found `?`
-  --> $DIR/issue-84148-2.rs:4:14
+   |              ^ `?` is only allowed on expressions, not types
    |
-LL | fn f(t:for<>t?
-   |              ^
-   |              |
-   |              expected one of `(`, `)`, `+`, `,`, `::`, or `<`
-   |              help: missing `,`
+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>
+   |        +++++++      ~
 
 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?
    |                ^ expected one of `->`, `where`, or `{`
 
-error: aborting due to 4 previous errors
+error: aborting due to 3 previous errors
 
diff --git a/src/test/ui/parser/trailing-question-in-type.fixed b/src/test/ui/parser/trailing-question-in-type.fixed
new file mode 100644
index 00000000000..6ea24484e03
--- /dev/null
+++ b/src/test/ui/parser/trailing-question-in-type.fixed
@@ -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();
+}
diff --git a/src/test/ui/parser/trailing-question-in-type.rs b/src/test/ui/parser/trailing-question-in-type.rs
new file mode 100644
index 00000000000..b1c508365cf
--- /dev/null
+++ b/src/test/ui/parser/trailing-question-in-type.rs
@@ -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();
+}
diff --git a/src/test/ui/parser/trailing-question-in-type.stderr b/src/test/ui/parser/trailing-question-in-type.stderr
new file mode 100644
index 00000000000..a3cd419c0c7
--- /dev/null
+++ b/src/test/ui/parser/trailing-question-in-type.stderr
@@ -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
+