auto merge of #8141 : graydon/rust/foreach-in-sketch, r=brson

This is a preliminary implementation of `for ... in ... { ...}` using a transitionary keyword `foreach`. Codesize seems to be a little bit down (10% or less non-opt) and otherwise it seems quite trivial to rewrite lambda-based loops to use it. Once we've rewritten the codebase away from lambda-based `for` we can retarget that word at the same production, snapshot, rewrite the keywords in one go, and expire `foreach`.

Feedback welcome. It's a desugaring-based approach which is arguably something we should have been doing for other constructs before. I apologize both for the laziness associated with doing it this way and with any sense that I'm bending rules I put in place previously concerning "never doing desugarings". I put the expansion in `expand.rs` and would be amenable to the argument that the code there needs better factoring / more helpers / to move to a submodule or helper function. It does seem to work at this point, though, and I gather we'd like to get the shift done relatively quickly.
This commit is contained in:
bors 2013-07-31 03:58:21 -07:00
commit 6296dc0d73
25 changed files with 374 additions and 4 deletions

View File

@ -239,6 +239,8 @@ impl CFGBuilder {
expr_exit
}
ast::expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
ast::expr_loop(ref body, _) => {
//
// [pred]

View File

@ -583,6 +583,8 @@ impl<'self, O:DataFlowOperator> PropagationContext<'self, O> {
copy_bits(new_loop_scope.break_bits, in_out);
}
ast::expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
ast::expr_loop(ref blk, _) => {
//
// (expr) <--+

View File

@ -503,6 +503,7 @@ fn visit_expr(expr: @expr, (this, vt): (@mut IrMaps, vt<@mut IrMaps>)) {
this.add_live_node_for_node(expr.id, ExprNode(expr.span));
visit::visit_expr(expr, (this, vt));
}
expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
expr_binary(_, op, _, _) if ast_util::lazy_binop(op) => {
this.add_live_node_for_node(expr.id, ExprNode(expr.span));
visit::visit_expr(expr, (this, vt));
@ -1057,6 +1058,8 @@ impl Liveness {
self.propagate_through_loop(expr, Some(cond), blk, succ)
}
expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
// Note that labels have been resolved, so we don't need to look
// at the label ident
expr_loop(ref blk, _) => {
@ -1487,6 +1490,7 @@ fn check_expr(expr: @expr, (this, vt): (@Liveness, vt<@Liveness>)) {
expr_paren(*) | expr_fn_block(*) | expr_path(*) | expr_self(*) => {
visit::visit_expr(expr, (this, vt));
}
expr_for_loop(*) => fail!("non-desugared expr_for_loop")
}
}

View File

@ -435,6 +435,8 @@ impl mem_categorization_ctxt {
ast::expr_inline_asm(*) => {
return self.cat_rvalue_node(expr, expr_ty);
}
ast::expr_for_loop(*) => fail!("non-desugared expr_for_loop")
}
}

View File

@ -487,6 +487,8 @@ impl VisitContext {
self.consume_block(blk, visitor);
}
expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
expr_unary(_, _, lhs) => {
if !self.use_overloaded_operator(
expr, lhs, [], visitor)

View File

@ -5016,6 +5016,8 @@ impl Resolver {
}
}
expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
expr_break(Some(label)) | expr_again(Some(label)) => {
match self.search_ribs(self.label_ribs, label, expr.span,
DontAllowCapturingSelf) {

View File

@ -2266,7 +2266,7 @@ pub fn register_fn_fuller(ccx: @mut CrateContext,
sp: span,
sym: ~str,
node_id: ast::NodeId,
node_type: ty::t,
_node_type: ty::t,
cc: lib::llvm::CallConv,
fn_ty: Type)
-> ValueRef {

View File

@ -401,7 +401,9 @@ pub fn mark_for_expr(cx: &Context, e: &expr) {
expr_match(*) | expr_block(_) | expr_if(*) | expr_while(*) |
expr_break(_) | expr_again(_) | expr_unary(*) | expr_lit(_) |
expr_mac(_) | expr_addr_of(*) | expr_ret(_) | expr_loop(*) |
expr_loop_body(_) | expr_do_body(_) => ()
expr_loop_body(_) | expr_do_body(_) => (),
expr_for_loop(*) => fail!("non-desugared expr_for_loop")
}
}

View File

@ -3240,6 +3240,8 @@ pub fn expr_kind(tcx: ctxt,
RvalueStmtExpr
}
ast::expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
ast::expr_lit(_) | // Note: lit_str is carved out above
ast::expr_unary(*) |
ast::expr_addr_of(*) |

View File

@ -2559,6 +2559,8 @@ pub fn check_expr_with_unifier(fcx: @mut FnCtxt,
fcx.write_nil(id);
}
}
ast::expr_for_loop(*) =>
fail!("non-desugared expr_for_loop"),
ast::expr_loop(ref body, _) => {
check_block_no_value(fcx, (body));
if !may_break(tcx, expr.id, body) {

View File

@ -1041,6 +1041,7 @@ pub mod guarantor {
rcx.fcx.tcx(), rcx.fcx.inh.method_map, expr));
None
}
ast::expr_for_loop(*) => fail!("non-desugared expr_for_loop"),
}
}

View File

@ -465,6 +465,7 @@ pub enum expr_ {
expr_cast(@expr, Ty),
expr_if(@expr, Block, Option<@expr>),
expr_while(@expr, Block),
expr_for_loop(@pat, @expr, Block),
/* Conditionless loop (can be exited with break, cont, or ret)
Same semantics as while(true) { body }, but typestate knows that the
(implicit) condition is always true. */

View File

@ -16,11 +16,12 @@ use ast_util::{new_rename, new_mark, resolve};
use attr;
use attr::AttrMetaMethods;
use codemap;
use codemap::{span, ExpnInfo, NameAndSpan};
use codemap::{span, spanned, ExpnInfo, NameAndSpan};
use ext::base::*;
use fold::*;
use parse;
use parse::{parse_item_from_source_str};
use parse::token;
use parse::token::{ident_to_str, intern};
use visit;
use visit::Visitor;
@ -99,6 +100,159 @@ pub fn expand_expr(extsbox: @mut SyntaxEnv,
}
}
}
// Desugar expr_for_loop
// From: `foreach <src_pat> in <src_expr> <src_loop_block>`
ast::expr_for_loop(src_pat, src_expr, ref src_loop_block) => {
let src_pat = src_pat.clone();
let src_expr = src_expr.clone();
// Expand any interior macros etc.
// NB: we don't fold pats yet. Curious.
let src_expr = fld.fold_expr(src_expr).clone();
let src_loop_block = fld.fold_block(src_loop_block).clone();
let span = s;
let lo = s.lo;
let hi = s.hi;
pub fn mk_expr(cx: @ExtCtxt, span: span,
node: expr_) -> @ast::expr {
@ast::expr {
id: cx.next_id(),
node: node,
span: span,
}
}
fn mk_block(cx: @ExtCtxt,
stmts: &[@ast::stmt],
expr: Option<@ast::expr>,
span: span) -> ast::Block {
ast::Block {
view_items: ~[],
stmts: stmts.to_owned(),
expr: expr,
id: cx.next_id(),
rules: ast::DefaultBlock,
span: span,
}
}
fn mk_simple_path(ident: ast::ident, span: span) -> ast::Path {
ast::Path {
span: span,
global: false,
idents: ~[ident],
rp: None,
types: ~[]
}
}
// to:
//
// {
// let _i = &mut <src_expr>;
// loop {
// match i.next() {
// None => break,
// Some(<src_pat>) => <src_loop_block>
// }
// }
// }
let local_ident = token::gensym_ident("i");
let some_ident = token::str_to_ident("Some");
let none_ident = token::str_to_ident("None");
let next_ident = token::str_to_ident("next");
let local_path_1 = mk_simple_path(local_ident, span);
let local_path_2 = mk_simple_path(local_ident, span);
let some_path = mk_simple_path(some_ident, span);
let none_path = mk_simple_path(none_ident, span);
// `let i = &mut <src_expr>`
let iter_decl_stmt = {
let ty = ast::Ty {
id: cx.next_id(),
node: ast::ty_infer,
span: span
};
let local = @ast::Local {
is_mutbl: false,
ty: ty,
pat: @ast::pat {
id: cx.next_id(),
node: ast::pat_ident(ast::bind_infer, local_path_1, None),
span: src_expr.span
},
init: Some(mk_expr(cx, src_expr.span,
ast::expr_addr_of(ast::m_mutbl, src_expr))),
id: cx.next_id(),
span: src_expr.span,
};
let e = @spanned(src_expr.span.lo,
src_expr.span.hi,
ast::decl_local(local));
@spanned(lo, hi, ast::stmt_decl(e, cx.next_id()))
};
// `None => break;`
let none_arm = {
let break_expr = mk_expr(cx, span, ast::expr_break(None));
let break_stmt = @spanned(lo, hi, ast::stmt_expr(break_expr, cx.next_id()));
let none_block = mk_block(cx, [break_stmt], None, span);
let none_pat = @ast::pat {
id: cx.next_id(),
node: ast::pat_ident(ast::bind_infer, none_path, None),
span: span
};
ast::arm {
pats: ~[none_pat],
guard: None,
body: none_block
}
};
// `Some(<src_pat>) => <src_loop_block>`
let some_arm = {
let pat = @ast::pat {
id: cx.next_id(),
node: ast::pat_enum(some_path, Some(~[src_pat])),
span: src_pat.span
};
ast::arm {
pats: ~[pat],
guard: None,
body: src_loop_block
}
};
// `match i.next() { ... }`
let match_stmt = {
let local_expr = mk_expr(cx, span, ast::expr_path(local_path_2));
let next_call_expr = mk_expr(cx, span,
ast::expr_method_call(cx.next_id(),
local_expr, next_ident,
~[], ~[], ast::NoSugar));
let match_expr = mk_expr(cx, span, ast::expr_match(next_call_expr,
~[none_arm, some_arm]));
@spanned(lo, hi, ast::stmt_expr(match_expr, cx.next_id()))
};
// `loop { ... }`
let loop_block = {
let loop_body_block = mk_block(cx, [match_stmt], None, span);
let loop_body_expr = mk_expr(cx, span, ast::expr_loop(loop_body_block, None));
let loop_body_stmt = @spanned(lo, hi, ast::stmt_expr(loop_body_expr, cx.next_id()));
mk_block(cx, [iter_decl_stmt,
loop_body_stmt],
None, span)
};
(ast::expr_block(loop_block), span)
}
_ => orig(e, s, fld)
}
}

View File

@ -559,6 +559,11 @@ pub fn noop_fold_expr(e: &expr_, fld: @ast_fold) -> expr_ {
expr_while(cond, ref body) => {
expr_while(fld.fold_expr(cond), fld.fold_block(body))
}
expr_for_loop(pat, iter, ref body) => {
expr_for_loop(fld.fold_pat(pat),
fld.fold_expr(iter),
fld.fold_block(body))
}
expr_loop(ref body, opt_ident) => {
expr_loop(
fld.fold_block(body),

View File

@ -28,6 +28,7 @@ pub fn expr_requires_semi_to_be_stmt(e: @ast::expr) -> bool {
| ast::expr_block(_)
| ast::expr_while(*)
| ast::expr_loop(*)
| ast::expr_for_loop(*)
| ast::expr_call(_, _, ast::DoSugar)
| ast::expr_call(_, _, ast::ForSugar)
| ast::expr_method_call(_, _, _, _, _, ast::DoSugar)

View File

@ -29,7 +29,7 @@ use ast::{expr_method_call, expr_paren, expr_path, expr_repeat};
use ast::{expr_ret, expr_self, expr_struct, expr_tup, expr_unary};
use ast::{expr_vec, expr_vstore, expr_vstore_mut_box};
use ast::{expr_vstore_slice, expr_vstore_box};
use ast::{expr_vstore_mut_slice, expr_while, extern_fn, Field, fn_decl};
use ast::{expr_vstore_mut_slice, expr_while, expr_for_loop, extern_fn, Field, fn_decl};
use ast::{expr_vstore_uniq, Onceness, Once, Many};
use ast::{foreign_item, foreign_item_static, foreign_item_fn, foreign_mod};
use ast::{ident, impure_fn, inherited, item, item_, item_static};
@ -1622,6 +1622,8 @@ impl Parser {
hi = self.span.hi;
} else if self.eat_keyword(keywords::If) {
return self.parse_if_expr();
} else if self.eat_keyword(keywords::ForEach) {
return self.parse_for_expr();
} else if self.eat_keyword(keywords::For) {
return self.parse_sugary_call_expr(lo, ~"for", ForSugar,
expr_loop_body);
@ -2323,6 +2325,21 @@ impl Parser {
}
}
// parse a 'foreach' .. 'in' expression ('foreach' token already eaten)
pub fn parse_for_expr(&self) -> @expr {
// Parse: `foreach <src_pat> in <src_expr> <src_loop_block>`
let lo = self.last_span.lo;
let pat = self.parse_pat();
self.expect_keyword(keywords::In);
let expr = self.parse_expr();
let loop_block = self.parse_block();
let hi = self.span.hi;
self.mk_expr(lo, hi, expr_for_loop(pat, expr, loop_block))
}
// parse a 'for' or 'do'.
// the 'for' and 'do' expressions parse as calls, but look like
// function calls followed by a closure expression.

View File

@ -474,6 +474,8 @@ fn mk_fresh_ident_interner() -> @ident_interner {
"while", // 64
"be", // 65
"in", // 66
"foreach", // 67
];
@ident_interner {
@ -570,8 +572,10 @@ pub mod keywords {
False,
Fn,
For,
ForEach,
If,
Impl,
In,
Let,
__Log,
Loop,
@ -612,8 +616,10 @@ pub mod keywords {
False => ident { name: 40, ctxt: 0 },
Fn => ident { name: 41, ctxt: 0 },
For => ident { name: 42, ctxt: 0 },
ForEach => ident { name: 67, ctxt: 0 },
If => ident { name: 43, ctxt: 0 },
Impl => ident { name: 44, ctxt: 0 },
In => ident { name: 66, ctxt: 0 },
Let => ident { name: 45, ctxt: 0 },
__Log => ident { name: 46, ctxt: 0 },
Loop => ident { name: 47, ctxt: 0 },

View File

@ -1228,6 +1228,15 @@ pub fn print_expr(s: @ps, expr: &ast::expr) {
space(s.s);
print_block(s, blk);
}
ast::expr_for_loop(pat, iter, ref blk) => {
head(s, "foreach");
print_pat(s, pat);
space(s.s);
word_space(s, "in");
print_expr(s, iter);
space(s.s);
print_block(s, blk);
}
ast::expr_loop(ref blk, opt_ident) => {
for opt_ident.iter().advance |ident| {
word(s.s, "'");

View File

@ -512,6 +512,11 @@ pub fn visit_expr<E:Clone>(ex: @expr, (e, v): (E, vt<E>)) {
(v.visit_expr)(x, (e.clone(), v));
(v.visit_block)(b, (e.clone(), v));
}
expr_for_loop(pat, iter, ref b) => {
(v.visit_pat)(pat, (e.clone(), v));
(v.visit_expr)(iter, (e.clone(), v));
(v.visit_block)(b, (e.clone(), v));
}
expr_loop(ref b, _) => (v.visit_block)(b, (e.clone(), v)),
expr_match(x, ref arms) => {
(v.visit_expr)(x, (e.clone(), v));

View File

@ -0,0 +1,21 @@
// Copyright 2012 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.
fn main() {
let x = [1,..100];
let mut y = 0;
foreach i in x.iter() {
if y > 10 {
break;
}
y += *i;
}
assert!(y == 11);
}

View File

@ -0,0 +1,41 @@
// Copyright 2012 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.
use std::hashmap::HashMap;
// This is a fancy one: it uses an external iterator established
// outside the loop, breaks, then _picks back up_ and continues
// iterating with it.
fn main() {
let mut h = HashMap::new();
let kvs = [(1, 10), (2, 20), (3, 30)];
foreach &(k,v) in kvs.iter() {
h.insert(k,v);
}
let mut x = 0;
let mut y = 0;
let mut i = h.iter();
foreach (&k,&v) in i {
x += k;
y += v;
break;
}
foreach (&k,&v) in i {
x += k;
y += v;
}
assert_eq!(x, 6);
assert_eq!(y, 60);
}

View File

@ -0,0 +1,27 @@
// Copyright 2012 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.
use std::hashmap::HashMap;
fn main() {
let mut h = HashMap::new();
let kvs = [(1, 10), (2, 20), (3, 30)];
foreach &(k,v) in kvs.iter() {
h.insert(k,v);
}
let mut x = 0;
let mut y = 0;
foreach (&k,&v) in h.iter() {
x += k;
y += v;
}
assert_eq!(x, 6);
assert_eq!(y, 60);
}

View File

@ -0,0 +1,21 @@
// Copyright 2012 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.
fn main() {
let x = [1,..100];
let mut y = 0;
foreach (n,i) in x.iter().enumerate() {
if n < 10 {
loop;
}
y += *i;
}
assert_eq!(y, 90);
}

View File

@ -0,0 +1,23 @@
// Copyright 2012 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.
fn main() {
let x = [1,..100];
let y = [2,..100];
let mut p = 0;
let mut q = 0;
foreach i in x.iter() {
foreach j in y.iter() {
p += *j;
}
q += *i + p;
}
assert!(q == 1010100);
}

View File

@ -0,0 +1,18 @@
// Copyright 2012 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.
fn main() {
let x = [1,..100];
let mut y = 0;
foreach i in x.iter() {
y += *i
}
assert!(y == 100);
}