]> git.lizzy.rs Git - rust.git/commitdiff
implement the `?` operator
authorJorge Aparicio <japaricious@gmail.com>
Sun, 28 Feb 2016 22:38:48 +0000 (17:38 -0500)
committerJorge Aparicio <japaricious@gmail.com>
Mon, 7 Mar 2016 19:39:39 +0000 (14:39 -0500)
The `?` postfix operator is sugar equivalent to the try! macro, but is more amenable to chaining:
`File::open("foo")?.metadata()?.is_dir()`.

`?` is accepted on any *expression* that can return a `Result`, e.g. `x()?`, `y!()?`, `{z}?`,
`(w)?`, etc. And binds more tightly than unary operators, e.g. `!x?` is parsed as `!(x?)`.

cc #31436

12 files changed:
src/librustc/middle/check_match.rs
src/librustc_front/hir.rs
src/librustc_front/lowering.rs
src/libsyntax/ast.rs
src/libsyntax/feature_gate.rs
src/libsyntax/fold.rs
src/libsyntax/parse/parser.rs
src/libsyntax/print/pprust.rs
src/libsyntax/visit.rs
src/test/compile-fail/feature-gate-try-operator.rs [new file with mode: 0644]
src/test/run-pass/try-operator-hygiene.rs [new file with mode: 0644]
src/test/run-pass/try-operator.rs [new file with mode: 0644]

index be8793bac5dbfb5c7a7bc3a62904f53e8ff0de32..94b2b12dca31c298fcd031de94b2d9367408cc90 100644 (file)
@@ -344,6 +344,10 @@ fn check_arms(cx: &MatchCheckCtxt,
                         hir::MatchSource::Normal => {
                             span_err!(cx.tcx.sess, pat.span, E0001, "unreachable pattern")
                         },
+
+                        hir::MatchSource::TryDesugar => {
+                            cx.tcx.sess.span_bug(pat.span, "unreachable try pattern")
+                        },
                     }
                 }
                 Useful => (),
index 44e7fa05073a1d195e194f48aef64db783ce6fc1..ece62364376fc75b0ecd911c6dadc009870cd70f 100644 (file)
@@ -835,6 +835,7 @@ pub enum MatchSource {
     },
     WhileLetDesugar,
     ForLoopDesugar,
+    TryDesugar,
 }
 
 #[derive(Clone, PartialEq, Eq, RustcEncodable, RustcDecodable, Hash, Debug, Copy)]
index acc7c6164b54ab9a3d0ed36443e7a07f7da2152a..291df66755e7d80359f00920936b6357318b2b6d 100644 (file)
@@ -1605,6 +1605,63 @@ fn make_struct(lctx: &LoweringContext,
                 });
             }
 
+            // Desugar ExprKind::Try
+            // From: `<expr>?`
+            ExprKind::Try(ref sub_expr) => {
+                // to:
+                //
+                // {
+                //     match <expr> {
+                //         Ok(val) => val,
+                //         Err(err) => {
+                //             return Err(From::from(err))
+                //         }
+                //     }
+                // }
+
+                return cache_ids(lctx, e.id, |lctx| {
+                    // expand <expr>
+                    let sub_expr = lower_expr(lctx, sub_expr);
+
+                    // Ok(val) => val
+                    let ok_arm = {
+                        let val_ident = lctx.str_to_ident("val");
+                        let val_pat = pat_ident(lctx, e.span, val_ident);
+                        let val_expr = expr_ident(lctx, e.span, val_ident, None);
+                        let ok_pat = pat_ok(lctx, e.span, val_pat);
+
+                        arm(hir_vec![ok_pat], val_expr)
+                    };
+
+                    // Err(err) => return Err(From::from(err))
+                    let err_arm = {
+                        let err_ident = lctx.str_to_ident("err");
+                        let from_expr = {
+                            let path = std_path(lctx, &["convert", "From", "from"]);
+                            let path = path_global(e.span, path);
+                            let from = expr_path(lctx, path, None);
+                            let err_expr = expr_ident(lctx, e.span, err_ident, None);
+
+                            expr_call(lctx, e.span, from, hir_vec![err_expr], None)
+                        };
+                        let err_expr = {
+                            let path = std_path(lctx, &["result", "Result", "Err"]);
+                            let path = path_global(e.span, path);
+                            let err_ctor = expr_path(lctx, path, None);
+                            expr_call(lctx, e.span, err_ctor, hir_vec![from_expr], None)
+                        };
+                        let err_pat = pat_err(lctx, e.span, pat_ident(lctx, e.span, err_ident));
+                        let ret_expr = expr(lctx, e.span,
+                                            hir::Expr_::ExprRet(Some(err_expr)), None);
+
+                        arm(hir_vec![err_pat], ret_expr)
+                    };
+
+                    expr_match(lctx, e.span, sub_expr, hir_vec![err_arm, ok_arm],
+                               hir::MatchSource::TryDesugar, None)
+                })
+            }
+
             ExprKind::Mac(_) => panic!("Shouldn't exist here"),
         },
         span: e.span,
@@ -1819,6 +1876,18 @@ fn block_all(lctx: &LoweringContext,
     })
 }
 
+fn pat_ok(lctx: &LoweringContext, span: Span, pat: P<hir::Pat>) -> P<hir::Pat> {
+    let ok = std_path(lctx, &["result", "Result", "Ok"]);
+    let path = path_global(span, ok);
+    pat_enum(lctx, span, path, hir_vec![pat])
+}
+
+fn pat_err(lctx: &LoweringContext, span: Span, pat: P<hir::Pat>) -> P<hir::Pat> {
+    let err = std_path(lctx, &["result", "Result", "Err"]);
+    let path = path_global(span, err);
+    pat_enum(lctx, span, path, hir_vec![pat])
+}
+
 fn pat_some(lctx: &LoweringContext, span: Span, pat: P<hir::Pat>) -> P<hir::Pat> {
     let some = std_path(lctx, &["option", "Option", "Some"]);
     let path = path_global(span, some);
index 0dbfb2c7be65470ee70d7a0f22d771bfd6f0071a..342ba60e553b0012e29b009286ad97ae2205d699 100644 (file)
@@ -1022,6 +1022,9 @@ pub enum ExprKind {
 
     /// No-op: used solely so we can pretty-print faithfully
     Paren(P<Expr>),
+
+    /// `expr?`
+    Try(P<Expr>),
 }
 
 /// The explicit Self type in a "qualified path". The actual
index 2302548914223c21ddd9091b83c9fa1b918dae19..14a3f93738a32a9aa6dc7d43595593d7ba661830 100644 (file)
 
     // a...b and ...b
     ("inclusive_range_syntax", "1.7.0", Some(28237), Active),
+
+    // `expr?`
+    ("question_mark", "1.9.0", Some(31436), Active)
 ];
 // (changing above list without updating src/doc/reference.md makes @cmr sad)
 
@@ -570,6 +573,7 @@ pub struct Features {
     pub staged_api: bool,
     pub stmt_expr_attributes: bool,
     pub deprecated: bool,
+    pub question_mark: bool,
 }
 
 impl Features {
@@ -603,6 +607,7 @@ pub fn new() -> Features {
             staged_api: false,
             stmt_expr_attributes: false,
             deprecated: false,
+            question_mark: false,
         }
     }
 }
@@ -1001,6 +1006,9 @@ fn visit_expr(&mut self, e: &ast::Expr) {
                                   e.span,
                                   "inclusive range syntax is experimental");
             }
+            ast::ExprKind::Try(..) => {
+                self.gate_feature("question_mark", e.span, "the `?` operator is not stable");
+            }
             _ => {}
         }
         visit::walk_expr(self, e);
@@ -1203,6 +1211,7 @@ fn check_crate_inner<F>(cm: &CodeMap, span_handler: &Handler,
         staged_api: cx.has_feature("staged_api"),
         stmt_expr_attributes: cx.has_feature("stmt_expr_attributes"),
         deprecated: cx.has_feature("deprecated"),
+        question_mark: cx.has_feature("question_mark"),
     }
 }
 
index 591c1295d66412746f56666c518b54af33b89441..9056103d30086bbdd7e1bd6f9f45a355e4dae187 100644 (file)
@@ -1332,7 +1332,8 @@ pub fn noop_fold_expr<T: Folder>(Expr {id, node, span, attrs}: Expr, folder: &mu
                         fields.move_map(|x| folder.fold_field(x)),
                         maybe_expr.map(|x| folder.fold_expr(x)))
             },
-            ExprKind::Paren(ex) => ExprKind::Paren(folder.fold_expr(ex))
+            ExprKind::Paren(ex) => ExprKind::Paren(folder.fold_expr(ex)),
+            ExprKind::Try(ex) => ExprKind::Try(folder.fold_expr(ex)),
         },
         span: folder.new_span(span),
         attrs: attrs.map_thin_attrs(|v| fold_attrs(v, folder)),
index d9714cc1e25e0630ee71c51253f8063e8ca94e41..53b5341542987a0f03cede810a3cf66b03730881 100644 (file)
@@ -2534,6 +2534,12 @@ fn parse_dot_or_call_expr_with_(&mut self, e0: P<Expr>, lo: BytePos) -> PResult<
         let mut e = e0;
         let mut hi;
         loop {
+            // expr?
+            while self.eat(&token::Question) {
+                let hi = self.span.hi;
+                e = self.mk_expr(lo, hi, ExprKind::Try(e), None);
+            }
+
             // expr.f
             if self.eat(&token::Dot) {
                 match self.token {
@@ -2907,7 +2913,6 @@ pub fn parse_assoc_expr_with(&mut self,
             }
         };
 
-
         if self.expr_is_complete(&lhs) {
             // Semi-statement forms are odd. See https://github.com/rust-lang/rust/issues/29071
             return Ok(lhs);
index 55c1af44cab856aa197d8b9bdd75baa2690e3fc5..2cfed1f82f7ecd455788845cfa85db68f1ba2043 100644 (file)
@@ -2277,6 +2277,10 @@ fn print_expr_outer_attr_style(&mut self,
                 try!(self.print_inner_attributes_inline(attrs));
                 try!(self.print_expr(&e));
                 try!(self.pclose());
+            },
+            ast::ExprKind::Try(ref e) => {
+                try!(self.print_expr(e));
+                try!(word(&mut self.s, "?"))
             }
         }
         try!(self.ann.post(self, NodeExpr(expr)));
index 73ad488e55c931d1e6e4a87d695a76cc541a3e0b..25aee09e26c8efb81a09fc32b74f05d613387c80 100644 (file)
@@ -793,6 +793,9 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr) {
                 visitor.visit_expr(&output.expr)
             }
         }
+        ExprKind::Try(ref subexpression) => {
+            visitor.visit_expr(subexpression)
+        }
     }
 
     visitor.visit_expr_post(expression)
diff --git a/src/test/compile-fail/feature-gate-try-operator.rs b/src/test/compile-fail/feature-gate-try-operator.rs
new file mode 100644 (file)
index 0000000..184aa63
--- /dev/null
@@ -0,0 +1,20 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+macro_rules! id {
+    ($e:expr) => { $e }
+}
+
+fn main() {
+    id!(x?);  //~ error: the `?` operator is not stable (see issue #31436)
+    //~^ help: add #![feature(question_mark)] to the crate attributes to enable
+    y?;  //~ error: the `?` operator is not stable (see issue #31436)
+    //~^ help: add #![feature(question_mark)] to the crate attributes to enable
+}
diff --git a/src/test/run-pass/try-operator-hygiene.rs b/src/test/run-pass/try-operator-hygiene.rs
new file mode 100644 (file)
index 0000000..233c03d
--- /dev/null
@@ -0,0 +1,34 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+// `expr?` expands to:
+//
+// match expr {
+//     Ok(val) => val,
+//     Err(err) => return From::from(err),
+// }
+//
+// This test verifies that the expansion is hygienic, i.e. it's not affected by other `val` and
+// `err` bindings that may be in scope.
+
+#![feature(question_mark)]
+
+use std::num::ParseIntError;
+
+fn main() {
+    assert_eq!(parse(), Ok(1));
+}
+
+fn parse() -> Result<i32, ParseIntError> {
+    const val: char = 'a';
+    const err: char = 'b';
+
+    Ok("1".parse::<i32>()?)
+}
diff --git a/src/test/run-pass/try-operator.rs b/src/test/run-pass/try-operator.rs
new file mode 100644 (file)
index 0000000..de5ccf0
--- /dev/null
@@ -0,0 +1,200 @@
+// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
+// file at the top-level directory of this distribution and at
+// http://rust-lang.org/COPYRIGHT.
+//
+// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
+// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
+// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
+// option. This file may not be copied, modified, or distributed
+// except according to those terms.
+
+#![feature(question_mark)]
+
+use std::fs::File;
+use std::io::{Read, self};
+use std::num::ParseIntError;
+use std::str::FromStr;
+
+fn on_method() -> Result<i32, ParseIntError> {
+    Ok("1".parse::<i32>()? + "2".parse::<i32>()?)
+}
+
+fn in_chain() -> Result<String, ParseIntError> {
+    Ok("3".parse::<i32>()?.to_string())
+}
+
+fn on_call() -> Result<i32, ParseIntError> {
+    fn parse<T: FromStr>(s: &str) -> Result<T, T::Err> {
+        s.parse()
+    }
+
+    Ok(parse("4")?)
+}
+
+fn nested() -> Result<i32, ParseIntError> {
+    Ok("5".parse::<i32>()?.to_string().parse()?)
+}
+
+fn on_path() -> Result<i32, ParseIntError> {
+    let x = "6".parse::<i32>();
+
+    Ok(x?)
+}
+
+fn on_macro() -> Result<i32, ParseIntError> {
+    macro_rules! id {
+        ($e:expr) => { $e }
+    }
+
+    Ok(id!("7".parse::<i32>())?)
+}
+
+fn on_parens() -> Result<i32, ParseIntError> {
+    let x = "8".parse::<i32>();
+
+    Ok((x)?)
+}
+
+fn on_block() -> Result<i32, ParseIntError> {
+    let x = "9".parse::<i32>();
+
+    Ok({x}?)
+}
+
+fn on_field() -> Result<i32, ParseIntError> {
+    struct Pair<A, B> { a: A, b: B }
+
+    let x = Pair { a: "10".parse::<i32>(), b: 0 };
+
+    Ok(x.a?)
+}
+
+fn on_tuple_field() -> Result<i32, ParseIntError> {
+    let x = ("11".parse::<i32>(), 0);
+
+    Ok(x.0?)
+}
+
+fn on_try() -> Result<i32, ParseIntError> {
+    let x = "12".parse::<i32>().map(|i| i.to_string().parse::<i32>());
+
+    Ok(x??)
+}
+
+fn on_binary_op() -> Result<i32, ParseIntError> {
+    let x = 13 - "14".parse::<i32>()?;
+    let y = "15".parse::<i32>()? - 16;
+    let z = "17".parse::<i32>()? - "18".parse::<i32>()?;
+
+    Ok(x + y + z)
+}
+
+fn on_index() -> Result<i32, ParseIntError> {
+    let x = [19];
+    let y = "0".parse::<usize>();
+
+    Ok(x[y?])
+}
+
+fn on_args() -> Result<i32, ParseIntError> {
+    fn sub(x: i32, y: i32) -> i32 { x - y }
+
+    let x = "20".parse();
+    let y = "21".parse();
+
+    Ok(sub(x?, y?))
+}
+
+fn on_if() -> Result<i32, ParseIntError> {
+    Ok(if true {
+        "22".parse::<i32>()
+    } else {
+        "23".parse::<i32>()
+    }?)
+}
+
+fn on_if_let() -> Result<i32, ParseIntError> {
+    Ok(if let Ok(..) = "24".parse::<i32>() {
+        "25".parse::<i32>()
+    } else {
+        "26".parse::<i32>()
+    }?)
+}
+
+fn on_match() -> Result<i32, ParseIntError> {
+    Ok(match "27".parse::<i32>() {
+        Err(..) => "28".parse::<i32>(),
+        Ok(..) => "29".parse::<i32>(),
+    }?)
+}
+
+fn tight_binding() -> Result<bool, ()> {
+    fn ok<T>(x: T) -> Result<T, ()> { Ok(x) }
+
+    let x = ok(true);
+    Ok(!x?)
+}
+
+// just type check
+fn merge_error() -> Result<i32, Error> {
+    let mut s = String::new();
+
+    File::open("foo.txt")?.read_to_string(&mut s)?;
+
+    Ok(s.parse::<i32>()? + 1)
+}
+
+fn main() {
+    assert_eq!(Ok(3), on_method());
+
+    assert_eq!(Ok("3".to_string()), in_chain());
+
+    assert_eq!(Ok(4), on_call());
+
+    assert_eq!(Ok(5), nested());
+
+    assert_eq!(Ok(6), on_path());
+
+    assert_eq!(Ok(7), on_macro());
+
+    assert_eq!(Ok(8), on_parens());
+
+    assert_eq!(Ok(9), on_block());
+
+    assert_eq!(Ok(10), on_field());
+
+    assert_eq!(Ok(11), on_tuple_field());
+
+    assert_eq!(Ok(12), on_try());
+
+    assert_eq!(Ok(-3), on_binary_op());
+
+    assert_eq!(Ok(19), on_index());
+
+    assert_eq!(Ok(-1), on_args());
+
+    assert_eq!(Ok(22), on_if());
+
+    assert_eq!(Ok(25), on_if_let());
+
+    assert_eq!(Ok(29), on_match());
+
+    assert_eq!(Ok(false), tight_binding());
+}
+
+enum Error {
+    Io(io::Error),
+    Parse(ParseIntError),
+}
+
+impl From<io::Error> for Error {
+    fn from(e: io::Error) -> Error {
+        Error::Io(e)
+    }
+}
+
+impl From<ParseIntError> for Error {
+    fn from(e: ParseIntError) -> Error {
+        Error::Parse(e)
+    }
+}