Skip to content

Commit

Permalink
Rollup merge of rust-lang#52602 - scottmcm:tryblock-expr, r=nikomatsakis
Browse files Browse the repository at this point in the history
Implement try block expressions

I noticed that `try` wasn't a keyword yet in Rust 2018, so...

~~Fix​es rust-lang#52604 That was fixed by PR rust-lang#53135
cc rust-lang#31436 rust-lang#50412
  • Loading branch information
Mark-Simulacrum authored Aug 22, 2018
2 parents f1b506a + 0095471 commit 9146d03
Show file tree
Hide file tree
Showing 48 changed files with 320 additions and 224 deletions.
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
# `catch_expr`
# `try_blocks`

The tracking issue for this feature is: [#31436]

[#31436]: https://github.com/rust-lang/rust/issues/31436

------------------------

The `catch_expr` feature adds support for a `catch` expression. The `catch`
expression creates a new scope one can use the `?` operator in.
The `try_blocks` feature adds support for `try` blocks. A `try`
block creates a new scope one can use the `?` operator in.

```rust
#![feature(catch_expr)]
```rust,ignore
// This code needs the 2018 edition
#![feature(try_blocks)]
use std::num::ParseIntError;
let result: Result<i32, ParseIntError> = do catch {
let result: Result<i32, ParseIntError> = try {
"1".parse::<i32>()?
+ "2".parse::<i32>()?
+ "3".parse::<i32>()?
};
assert_eq!(result, Ok(6));
let result: Result<i32, ParseIntError> = do catch {
let result: Result<i32, ParseIntError> = try {
"1".parse::<i32>()?
+ "foo".parse::<i32>()?
+ "3".parse::<i32>()?
Expand Down
4 changes: 2 additions & 2 deletions src/librustc/hir/lowering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3613,10 +3613,10 @@ impl<'a> LoweringContext<'a> {
hir::LoopSource::Loop,
)
}),
ExprKind::Catch(ref body) => {
ExprKind::TryBlock(ref body) => {
self.with_catch_scope(body.id, |this| {
let unstable_span =
this.allow_internal_unstable(CompilerDesugaringKind::Catch, body.span);
this.allow_internal_unstable(CompilerDesugaringKind::TryBlock, body.span);
let mut block = this.lower_block(body, true).into_inner();
let tail = block.expr.take().map_or_else(
|| {
Expand Down
2 changes: 1 addition & 1 deletion src/librustc/ich/impls_syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ impl_stable_hash_for!(enum ::syntax_pos::hygiene::CompilerDesugaringKind {
QuestionMark,
ExistentialReturnType,
ForLoop,
Catch
TryBlock
});

impl_stable_hash_for!(enum ::syntax_pos::FileName {
Expand Down
1 change: 0 additions & 1 deletion src/librustc/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
#![feature(trace_macros)]
#![feature(trusted_len)]
#![feature(vec_remove_item)]
#![feature(catch_expr)]
#![feature(step_trait)]
#![feature(integer_atomics)]
#![feature(test)]
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_mir/borrow_check/nll/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,14 +312,14 @@ fn dump_mir_results<'a, 'gcx, 'tcx>(
);

// Also dump the inference graph constraints as a graphviz file.
let _: io::Result<()> = do catch {
let _: io::Result<()> = try_block! {
let mut file =
pretty::create_dump_file(infcx.tcx, "regioncx.all.dot", None, "nll", &0, source)?;
regioncx.dump_graphviz_raw_constraints(&mut file)?;
};

// Also dump the inference graph constraints as a graphviz file.
let _: io::Result<()> = do catch {
let _: io::Result<()> = try_block! {
let mut file =
pretty::create_dump_file(infcx.tcx, "regioncx.scc.dot", None, "nll", &0, source)?;
regioncx.dump_graphviz_scc_constraints(&mut file)?;
Expand Down
9 changes: 8 additions & 1 deletion src/librustc_mir/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ Rust MIR: a lowered representation of Rust. Also: an experiment!
#![feature(slice_sort_by_cached_key)]
#![feature(box_patterns)]
#![feature(box_syntax)]
#![feature(catch_expr)]
#![feature(crate_visibility_modifier)]
#![feature(const_fn)]
#![feature(core_intrinsics)]
Expand Down Expand Up @@ -63,6 +62,14 @@ extern crate rustc_apfloat;
extern crate byteorder;
extern crate core;

// Once we can use edition 2018 in the compiler,
// replace this with real try blocks.
macro_rules! try_block {
($($inside:tt)*) => (
(||{ ::std::ops::Try::from_ok({ $($inside)* }) })()
)
}

mod diagnostics;

mod borrow_check;
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_mir/util/pretty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ fn dump_matched_mir_node<'a, 'gcx, 'tcx, F>(
) where
F: FnMut(PassWhere, &mut dyn Write) -> io::Result<()>,
{
let _: io::Result<()> = do catch {
let _: io::Result<()> = try_block! {
let mut file = create_dump_file(tcx, "mir", pass_num, pass_name, disambiguator, source)?;
writeln!(file, "// MIR for `{}`", node_path)?;
writeln!(file, "// source = {:?}", source)?;
Expand All @@ -156,7 +156,7 @@ fn dump_matched_mir_node<'a, 'gcx, 'tcx, F>(
};

if tcx.sess.opts.debugging_opts.dump_mir_graphviz {
let _: io::Result<()> = do catch {
let _: io::Result<()> = try_block! {
let mut file =
create_dump_file(tcx, "dot", pass_num, pass_name, disambiguator, source)?;
write_mir_fn_graphviz(tcx, source.def_id, mir, &mut file)?;
Expand Down
2 changes: 1 addition & 1 deletion src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4471,7 +4471,7 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> {
// In some cases, blocks have just one exit, but other blocks
// can be targeted by multiple breaks. This can happen both
// with labeled blocks as well as when we desugar
// a `do catch { ... }` expression.
// a `try { ... }` expression.
//
// Example 1:
//
Expand Down
6 changes: 3 additions & 3 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,7 +987,7 @@ impl Expr {
ExprKind::Match(..) => ExprPrecedence::Match,
ExprKind::Closure(..) => ExprPrecedence::Closure,
ExprKind::Block(..) => ExprPrecedence::Block,
ExprKind::Catch(..) => ExprPrecedence::Catch,
ExprKind::TryBlock(..) => ExprPrecedence::TryBlock,
ExprKind::Async(..) => ExprPrecedence::Async,
ExprKind::Assign(..) => ExprPrecedence::Assign,
ExprKind::AssignOp(..) => ExprPrecedence::AssignOp,
Expand Down Expand Up @@ -1108,8 +1108,8 @@ pub enum ExprKind {
/// created during lowering cannot be made the parent of any other
/// preexisting defs.
Async(CaptureBy, NodeId, P<Block>),
/// A catch block (`catch { ... }`)
Catch(P<Block>),
/// A try block (`try { ... }`)
TryBlock(P<Block>),

/// An assignment (`a = foo()`)
Assign(P<Expr>, P<Expr>),
Expand Down
8 changes: 4 additions & 4 deletions src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,8 @@ declare_features! (
// `extern "x86-interrupt" fn()`
(active, abi_x86_interrupt, "1.17.0", Some(40180), None),

// Allows the `catch {...}` expression
(active, catch_expr, "1.17.0", Some(31436), None),
// Allows the `try {...}` expression
(active, try_blocks, "1.29.0", Some(31436), None),

// Used to preserve symbols (see llvm.used)
(active, used, "1.18.0", Some(40289), None),
Expand Down Expand Up @@ -1732,8 +1732,8 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
e.span,
"yield syntax is experimental");
}
ast::ExprKind::Catch(_) => {
gate_feature_post!(&self, catch_expr, e.span, "`catch` expression is experimental");
ast::ExprKind::TryBlock(_) => {
gate_feature_post!(&self, try_blocks, e.span, "`try` expression is experimental");
}
ast::ExprKind::IfLet(ref pats, ..) | ast::ExprKind::WhileLet(ref pats, ..) => {
if pats.len() > 1 {
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/fold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1351,7 +1351,7 @@ pub fn noop_fold_expr<T: Folder>(Expr {id, node, span, attrs}: Expr, folder: &mu
}
ExprKind::Yield(ex) => ExprKind::Yield(ex.map(|x| folder.fold_expr(x))),
ExprKind::Try(ex) => ExprKind::Try(folder.fold_expr(ex)),
ExprKind::Catch(body) => ExprKind::Catch(folder.fold_block(body)),
ExprKind::TryBlock(body) => ExprKind::TryBlock(folder.fold_block(body)),
},
id: folder.new_id(id),
span: folder.new_span(span),
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
#![feature(rustc_diagnostic_macros)]
#![feature(slice_sort_by_cached_key)]
#![feature(str_escape)]
#![feature(try_trait)]
#![feature(unicode_internals)]
#![feature(catch_expr)]

#![recursion_limit="256"]

Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/parse/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
ast::ExprKind::WhileLet(..) |
ast::ExprKind::Loop(..) |
ast::ExprKind::ForLoop(..) |
ast::ExprKind::Catch(..) => false,
ast::ExprKind::TryBlock(..) => false,
_ => true,
}
}
Expand Down
40 changes: 30 additions & 10 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1757,9 +1757,17 @@ impl<'a> Parser<'a> {

let parser_snapshot_before_pat = self.clone();

// Once we can use edition 2018 in the compiler,
// replace this with real try blocks.
macro_rules! try_block {
($($inside:tt)*) => (
(||{ ::std::ops::Try::from_ok({ $($inside)* }) })()
)
}

// We're going to try parsing the argument as a pattern (even though it's not
// allowed). This way we can provide better errors to the user.
let pat_arg: PResult<'a, _> = do catch {
let pat_arg: PResult<'a, _> = try_block! {
let pat = self.parse_pat()?;
self.expect(&token::Colon)?;
(pat, self.parse_ty()?)
Expand Down Expand Up @@ -2387,11 +2395,15 @@ impl<'a> Parser<'a> {
BlockCheckMode::Unsafe(ast::UserProvided),
attrs);
}
if self.is_catch_expr() {
if self.is_do_catch_block() {
let mut db = self.fatal("found removed `do catch` syntax");
db.help("Following RFC #2388, the new non-placeholder syntax is `try`");
return Err(db);
}
if self.is_try_block() {
let lo = self.span;
assert!(self.eat_keyword(keywords::Do));
assert!(self.eat_keyword(keywords::Catch));
return self.parse_catch_expr(lo, attrs);
assert!(self.eat_keyword(keywords::Try));
return self.parse_try_block(lo, attrs);
}
if self.eat_keyword(keywords::Return) {
if self.token.can_begin_expr() {
Expand Down Expand Up @@ -3453,13 +3465,13 @@ impl<'a> Parser<'a> {
ExprKind::Async(capture_clause, ast::DUMMY_NODE_ID, body), attrs))
}

/// Parse a `do catch {...}` expression (`do catch` token already eaten)
fn parse_catch_expr(&mut self, span_lo: Span, mut attrs: ThinVec<Attribute>)
/// Parse a `try {...}` expression (`try` token already eaten)
fn parse_try_block(&mut self, span_lo: Span, mut attrs: ThinVec<Attribute>)
-> PResult<'a, P<Expr>>
{
let (iattrs, body) = self.parse_inner_attrs_and_block()?;
attrs.extend(iattrs);
Ok(self.mk_expr(span_lo.to(body.span), ExprKind::Catch(body), attrs))
Ok(self.mk_expr(span_lo.to(body.span), ExprKind::TryBlock(body), attrs))
}

// `match` token already eaten
Expand Down Expand Up @@ -4408,12 +4420,20 @@ impl<'a> Parser<'a> {
)
}

fn is_catch_expr(&mut self) -> bool {
fn is_do_catch_block(&mut self) -> bool {
self.token.is_keyword(keywords::Do) &&
self.look_ahead(1, |t| t.is_keyword(keywords::Catch)) &&
self.look_ahead(2, |t| *t == token::OpenDelim(token::Brace)) &&
!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
}

fn is_try_block(&mut self) -> bool {
self.token.is_keyword(keywords::Try) &&
self.look_ahead(1, |t| *t == token::OpenDelim(token::Brace)) &&

self.span.edition() >= Edition::Edition2018 &&

// prevent `while catch {} {}`, `if catch {} {} else {}`, etc.
// prevent `while try {} {}`, `if try {} {} else {}`, etc.
!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
}

Expand Down
4 changes: 2 additions & 2 deletions src/libsyntax/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2379,8 +2379,8 @@ impl<'a> State<'a> {
self.print_expr_maybe_paren(e, parser::PREC_POSTFIX)?;
self.s.word("?")?
}
ast::ExprKind::Catch(ref blk) => {
self.head("do catch")?;
ast::ExprKind::TryBlock(ref blk) => {
self.head("try")?;
self.s.space()?;
self.print_block_with_attrs(blk, attrs)?
}
Expand Down
4 changes: 2 additions & 2 deletions src/libsyntax/util/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ pub enum ExprPrecedence {
Loop,
Match,
Block,
Catch,
TryBlock,
Struct,
Async,
}
Expand Down Expand Up @@ -332,7 +332,7 @@ impl ExprPrecedence {
ExprPrecedence::Loop |
ExprPrecedence::Match |
ExprPrecedence::Block |
ExprPrecedence::Catch |
ExprPrecedence::TryBlock |
ExprPrecedence::Async |
ExprPrecedence::Struct => PREC_PAREN,
}
Expand Down
2 changes: 1 addition & 1 deletion src/libsyntax/visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -809,7 +809,7 @@ pub fn walk_expr<'a, V: Visitor<'a>>(visitor: &mut V, expression: &'a Expr) {
ExprKind::Try(ref subexpression) => {
visitor.visit_expr(subexpression)
}
ExprKind::Catch(ref body) => {
ExprKind::TryBlock(ref body) => {
visitor.visit_block(body)
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/libsyntax_pos/hygiene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ impl ExpnFormat {
#[derive(Clone, Copy, Hash, Debug, PartialEq, Eq, RustcEncodable, RustcDecodable)]
pub enum CompilerDesugaringKind {
QuestionMark,
Catch,
TryBlock,
/// Desugaring of an `impl Trait` in return type position
/// to an `existential type Foo: Trait;` + replacing the
/// `impl Trait` with `Foo`.
Expand All @@ -609,7 +609,7 @@ impl CompilerDesugaringKind {
Symbol::intern(match self {
CompilerDesugaringKind::Async => "async",
CompilerDesugaringKind::QuestionMark => "?",
CompilerDesugaringKind::Catch => "do catch",
CompilerDesugaringKind::TryBlock => "try block",
CompilerDesugaringKind::ExistentialReturnType => "existential type",
CompilerDesugaringKind::ForLoop => "for loop",
})
Expand Down
20 changes: 11 additions & 9 deletions src/libsyntax_pos/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -415,23 +415,25 @@ declare_keywords! {

// Edition-specific keywords reserved for future use.
(51, Async, "async") // >= 2018 Edition Only
(52, Try, "try") // >= 2018 Edition Only

// Special lifetime names
(52, UnderscoreLifetime, "'_")
(53, StaticLifetime, "'static")
(53, UnderscoreLifetime, "'_")
(54, StaticLifetime, "'static")

// Weak keywords, have special meaning only in specific contexts.
(54, Auto, "auto")
(55, Catch, "catch")
(56, Default, "default")
(57, Dyn, "dyn")
(58, Union, "union")
(59, Existential, "existential")
(55, Auto, "auto")
(56, Catch, "catch")
(57, Default, "default")
(58, Dyn, "dyn")
(59, Union, "union")
(60, Existential, "existential")
}

impl Symbol {
fn is_unused_keyword_2018(self) -> bool {
self == keywords::Async.name()
self >= keywords::Async.name() &&
self <= keywords::Try.name()
}
}

Expand Down
17 changes: 17 additions & 0 deletions src/test/parse-fail/do-catch-suggests-try.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright 2018 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.

// compile-flags: -Z parse-only

fn main() {
let _: Option<()> = do catch {};
//~^ ERROR found removed `do catch` syntax
//~^^ HELP Following RFC #2388, the new non-placeholder syntax is `try`
}
Loading

0 comments on commit 9146d03

Please sign in to comment.