]> git.lizzy.rs Git - rust.git/commitdiff
Parse `Ty?` as `Option<Ty>` and provide structured suggestion
authorEsteban Kuber <esteban@kuber.com.ar>
Mon, 10 Jan 2022 22:02:19 +0000 (22:02 +0000)
committerEsteban Kuber <esteban@kuber.com.ar>
Fri, 14 Jan 2022 00:07:23 +0000 (00:07 +0000)
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.

12 files changed:
compiler/rustc_parse/src/parser/diagnostics.rs
compiler/rustc_parse/src/parser/expr.rs
compiler/rustc_parse/src/parser/ty.rs
src/test/ui/parser/issues/issue-35813-postfix-after-cast.rs
src/test/ui/parser/issues/issue-35813-postfix-after-cast.stderr
src/test/ui/parser/issues/issue-84148-1.rs
src/test/ui/parser/issues/issue-84148-1.stderr
src/test/ui/parser/issues/issue-84148-2.rs
src/test/ui/parser/issues/issue-84148-2.stderr
src/test/ui/parser/trailing-question-in-type.fixed [new file with mode: 0644]
src/test/ui/parser/trailing-question-in-type.rs [new file with mode: 0644]
src/test/ui/parser/trailing-question-in-type.stderr [new file with mode: 0644]

index 9677e7642b88c9c4f522eb51dae3a4ac2b76fd0c..b844e96d39c54132877b2e69bbb87d818648afe1 100644 (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 @@ pub(super) fn maybe_report_ambiguous_plus(
         }
     }
 
+    /// 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,
index f706a98a4fcfa76fa625eed83bad1f2556b19f61..cd3846d5a224ef33934f5192650515c41ce22020 100644 (file)
@@ -682,7 +682,7 @@ fn parse_assoc_op_cast(
         // 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 @@ fn parse_and_disallow_postfix_after_cast(
                 "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",
index 02a774ba1291cf4228441acb11a1d863bc3d53bd..566b77a5e9e555f841b8010f72619ec5ad1738eb 100644 (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 @@ pub fn parse_ty(&mut self) -> PResult<'a, P<Ty>> {
             RecoverQPath::Yes,
             RecoverReturnSign::Yes,
             None,
+            IsAsCast::No,
         )
     }
 
@@ -113,6 +119,7 @@ pub(super) fn parse_ty_with_generics_recovery(
             RecoverQPath::Yes,
             RecoverReturnSign::Yes,
             Some(ty_params),
+            IsAsCast::No,
         )
     }
 
@@ -126,6 +133,7 @@ pub(super) fn parse_ty_for_param(&mut self) -> PResult<'a, P<Ty>> {
             RecoverQPath::Yes,
             RecoverReturnSign::Yes,
             None,
+            IsAsCast::No,
         )
     }
 
@@ -142,9 +150,22 @@ pub(super) fn parse_ty_no_plus(&mut self) -> PResult<'a, P<Ty>> {
             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 @@ pub(super) fn parse_ty_for_where_clause(&mut self) -> PResult<'a, P<Ty>> {
             RecoverQPath::Yes,
             RecoverReturnSign::OnlyFatArrow,
             None,
+            IsAsCast::No,
         )
     }
 
@@ -171,6 +193,7 @@ pub(super) fn parse_ret_ty(
                 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 @@ pub(super) fn parse_ret_ty(
                 recover_qpath,
                 recover_return_sign,
                 None,
+                IsAsCast::No,
             )?;
             FnRetTy::Ty(ty)
         } else {
@@ -205,6 +229,7 @@ fn parse_ty_common(
         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 @@ fn parse_ty_common(
         // 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)
     }
 
index e725aa5d73d1f97d00f6f2a0c924826ffc1c7922..23f245a51681b559cdfc627ae0a7cd7fd1420030 100644 (file)
@@ -117,9 +117,9 @@ pub fn inside_block() {
 
 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)
 }
 
index 19b68556d79ad27cfb40022ee21d2bdc66367df5..e96b67da3364df8b9204fb8ff2a6c75515a1a8b5 100644 (file)
@@ -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>?;
index 25f7ba4d1f88e8920b6beb754c9fb3d3ad97b24c..9fa8086c2c9bf4af8b6fe37ae05539cbae98d8a0 100644 (file)
@@ -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
index 77f0896e9c155d54462a4c5e416b14eb99b1c304..9261067c22158c032256790ebe7e93eaf29065fe 100644 (file)
@@ -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
 
index 257a3fd67207ec2bd1a045fc7d88117eb787d5fe..2f6a7facfb271d8653c41957b67b06666a93f9bf 100644 (file)
@@ -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?
index 396208316df677e32a085508eba833fd14217089..71d543f9b73447804f775612ac98fc8050996fcf 100644 (file)
@@ -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 (file)
index 0000000..6ea2448
--- /dev/null
@@ -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 (file)
index 0000000..b1c5083
--- /dev/null
@@ -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 (file)
index 0000000..a3cd419
--- /dev/null
@@ -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
+