From eb678ff87f0cdbf523b26fe9255cff684b4091e5 Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Sat, 6 Sep 2014 15:23:55 -0700 Subject: [PATCH] librustc: Change the syntax of subslice matching to use postfix `..` instead of prefix `..`. This breaks code that looked like: match foo { [ first, ..middle, last ] => { ... } } Change this code to: match foo { [ first, middle.., last ] => { ... } } RFC #55. Closes #16967. [breaking-change] --- src/doc/rust.md | 2 +- src/doc/tutorial.md | 2 +- src/librustc/middle/resolve.rs | 19 ++---- src/libsyntax/parse/obsolete.rs | 5 ++ src/libsyntax/parse/parser.rs | 68 ++++++++++--------- src/libsyntax/print/pprust.rs | 2 +- .../borrowck-move-out-of-vec-tail.rs | 2 +- .../borrowck-vec-pattern-element-loan.rs | 6 +- .../borrowck-vec-pattern-loan-from-mut.rs | 2 +- .../borrowck-vec-pattern-move-tail.rs | 2 +- .../borrowck-vec-pattern-nesting.rs | 6 +- .../borrowck-vec-pattern-tail-element-loan.rs | 2 +- src/test/compile-fail/issue-12369.rs | 4 +- src/test/compile-fail/issue-12567.rs | 4 +- src/test/compile-fail/match-vec-invalid.rs | 2 +- .../compile-fail/match-vec-unreachable.rs | 2 +- src/test/compile-fail/non-exhaustive-match.rs | 14 ++-- .../non-exhaustive-pattern-witness.rs | 2 +- .../vec-matching-obsolete-syntax.rs | 22 ++++++ src/test/run-pass/issue-15080.rs | 4 +- src/test/run-pass/issue-15104.rs | 2 +- src/test/run-pass/issue-7784.rs | 2 +- src/test/run-pass/vec-matching-fold.rs | 4 +- .../vec-matching-legal-tail-element-borrow.rs | 2 +- src/test/run-pass/vec-matching.rs | 6 +- src/test/run-pass/vec-tail-matching.rs | 4 +- 26 files changed, 110 insertions(+), 82 deletions(-) create mode 100644 src/test/compile-fail/vec-matching-obsolete-syntax.rs diff --git a/src/doc/rust.md b/src/doc/rust.md index 3fd48d4532499..af8020babf209 100644 --- a/src/doc/rust.md +++ b/src/doc/rust.md @@ -3300,7 +3300,7 @@ it will bind the corresponding slice to the variable. Example: fn is_symmetric(list: &[uint]) -> bool { match list { [] | [_] => true, - [x, ..inside, y] if x == y => is_symmetric(inside), + [x, inside.., y] if x == y => is_symmetric(inside), _ => false } } diff --git a/src/doc/tutorial.md b/src/doc/tutorial.md index 0db25c4090ebc..0e5a624b27333 100644 --- a/src/doc/tutorial.md +++ b/src/doc/tutorial.md @@ -1707,7 +1707,7 @@ let score = match numbers { [] => 0, [a] => a * 10, [a, b] => a * 6 + b * 4, - [a, b, c, ..rest] => a * 5 + b * 3 + c * 2 + rest.len() as int + [a, b, c, rest..] => a * 5 + b * 3 + c * 2 + rest.len() as int }; ~~~~ diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs index 0c8697d31f3c9..3026b5c24c1a4 100644 --- a/src/librustc/middle/resolve.rs +++ b/src/librustc/middle/resolve.rs @@ -1549,18 +1549,13 @@ impl<'a> Resolver<'a> { PathListMod { .. } => Some(item.span), _ => None }).collect::>(); - match mod_spans.as_slice() { - [first, second, ..other] => { - self.resolve_error(first, - "`mod` import can only appear once in the list"); - self.session.span_note(second, - "another `mod` import appears here"); - for &other_span in other.iter() { - self.session.span_note(other_span, - "another `mod` import appears here"); - } - }, - [_] | [] => () + if mod_spans.len() > 1 { + self.resolve_error(mod_spans[0], + "`mod` import can only appear once in the list"); + for other_span in mod_spans.iter().skip(1) { + self.session.span_note(*other_span, + "another `mod` import appears here"); + } } for source_item in source_items.iter() { diff --git a/src/libsyntax/parse/obsolete.rs b/src/libsyntax/parse/obsolete.rs index 5273addf4f57d..ec6fd013d08ae 100644 --- a/src/libsyntax/parse/obsolete.rs +++ b/src/libsyntax/parse/obsolete.rs @@ -35,6 +35,7 @@ pub enum ObsoleteSyntax { ObsoleteManagedType, ObsoleteManagedExpr, ObsoleteImportRenaming, + ObsoleteSubsliceMatch, } pub trait ParserObsoleteMethods { @@ -87,6 +88,10 @@ impl<'a> ParserObsoleteMethods for parser::Parser<'a> { ObsoleteImportRenaming => ( "`use foo = bar` syntax", "write `use bar as foo` instead" + ), + ObsoleteSubsliceMatch => ( + "subslice match syntax", + "instead of `..xs`, write `xs..` in a pattern" ) }; diff --git a/src/libsyntax/parse/parser.rs b/src/libsyntax/parse/parser.rs index 936cabc54d178..6aff1152f7e1d 100644 --- a/src/libsyntax/parse/parser.rs +++ b/src/libsyntax/parse/parser.rs @@ -2858,43 +2858,42 @@ impl<'a> Parser<'a> { let mut before_slice = true; while self.token != token::RBRACKET { - if first { first = false; } - else { self.expect(&token::COMMA); } + if first { + first = false; + } else { + self.expect(&token::COMMA); + } - let mut is_slice = false; if before_slice { if self.token == token::DOTDOT { self.bump(); - is_slice = true; - before_slice = false; - } - } - if is_slice { - if self.token == token::COMMA || self.token == token::RBRACKET { - slice = Some(box(GC) ast::Pat { - id: ast::DUMMY_NODE_ID, - node: PatWild(PatWildMulti), - span: self.span, - }) - } else { - let subpat = self.parse_pat(); - match *subpat { - ast::Pat { node: PatIdent(_, _, _), .. } => { - slice = Some(subpat); - } - ast::Pat { span, .. } => self.span_fatal( - span, "expected an identifier or nothing" - ) + if self.token == token::COMMA || + self.token == token::RBRACKET { + slice = Some(box(GC) ast::Pat { + id: ast::DUMMY_NODE_ID, + node: PatWild(PatWildMulti), + span: self.span, + }); + before_slice = false; + } else { + let _ = self.parse_pat(); + let span = self.span; + self.obsolete(span, ObsoleteSubsliceMatch); } + continue } + } + + let subpat = self.parse_pat(); + if before_slice && self.token == token::DOTDOT { + self.bump(); + slice = Some(subpat); + before_slice = false; + } else if before_slice { + before.push(subpat); } else { - let subpat = self.parse_pat(); - if before_slice { - before.push(subpat); - } else { - after.push(subpat); - } + after.push(subpat); } } @@ -3065,7 +3064,11 @@ impl<'a> Parser<'a> { // These expressions are limited to literals (possibly // preceded by unary-minus) or identifiers. let val = self.parse_literal_maybe_minus(); - if self.eat(&token::DOTDOT) { + if self.token == token::DOTDOT && + self.look_ahead(1, |t| { + *t != token::COMMA && *t != token::RBRACKET + }) { + self.bump(); let end = if is_ident_or_path(&self.token) { let path = self.parse_path(LifetimeAndTypesWithColons) .path; @@ -3106,7 +3109,10 @@ impl<'a> Parser<'a> { } }); - if self.look_ahead(1, |t| *t == token::DOTDOT) { + if self.look_ahead(1, |t| *t == token::DOTDOT) && + self.look_ahead(2, |t| { + *t != token::COMMA && *t != token::RBRACKET + }) { let start = self.parse_expr_res(RESTRICT_NO_BAR_OP); self.eat(&token::DOTDOT); let end = self.parse_expr_res(RESTRICT_NO_BAR_OP); diff --git a/src/libsyntax/print/pprust.rs b/src/libsyntax/print/pprust.rs index d5bc1bfe956fb..eaeb6aaab8a75 100644 --- a/src/libsyntax/print/pprust.rs +++ b/src/libsyntax/print/pprust.rs @@ -1912,13 +1912,13 @@ impl<'a> State<'a> { |s, p| s.print_pat(&**p))); for p in slice.iter() { if !before.is_empty() { try!(self.word_space(",")); } + try!(self.print_pat(&**p)); match **p { ast::Pat { node: ast::PatWild(ast::PatWildMulti), .. } => { // this case is handled by print_pat } _ => try!(word(&mut self.s, "..")), } - try!(self.print_pat(&**p)); if !after.is_empty() { try!(self.word_space(",")); } } try!(self.commasep(Inconsistent, diff --git a/src/test/compile-fail/borrowck-move-out-of-vec-tail.rs b/src/test/compile-fail/borrowck-move-out-of-vec-tail.rs index d87557a46f753..d3c6a280e8c2a 100644 --- a/src/test/compile-fail/borrowck-move-out-of-vec-tail.rs +++ b/src/test/compile-fail/borrowck-move-out-of-vec-tail.rs @@ -25,7 +25,7 @@ pub fn main() { ); let x: &[Foo] = x.as_slice(); match x { - [_, ..tail] => { + [_, tail..] => { match tail { [Foo { string: a }, //~ ERROR cannot move out of dereference of `&`-pointer Foo { string: b }] => { diff --git a/src/test/compile-fail/borrowck-vec-pattern-element-loan.rs b/src/test/compile-fail/borrowck-vec-pattern-element-loan.rs index 53ebaa38fddba..912441a38cfda 100644 --- a/src/test/compile-fail/borrowck-vec-pattern-element-loan.rs +++ b/src/test/compile-fail/borrowck-vec-pattern-element-loan.rs @@ -12,7 +12,7 @@ fn a<'a>() -> &'a [int] { let vec = vec!(1, 2, 3, 4); let vec: &[int] = vec.as_slice(); //~ ERROR does not live long enough let tail = match vec { - [_, ..tail] => tail, + [_, tail..] => tail, _ => fail!("a") }; tail @@ -22,7 +22,7 @@ fn b<'a>() -> &'a [int] { let vec = vec!(1, 2, 3, 4); let vec: &[int] = vec.as_slice(); //~ ERROR does not live long enough let init = match vec { - [..init, _] => init, + [init.., _] => init, _ => fail!("b") }; init @@ -32,7 +32,7 @@ fn c<'a>() -> &'a [int] { let vec = vec!(1, 2, 3, 4); let vec: &[int] = vec.as_slice(); //~ ERROR does not live long enough let slice = match vec { - [_, ..slice, _] => slice, + [_, slice.., _] => slice, _ => fail!("c") }; slice diff --git a/src/test/compile-fail/borrowck-vec-pattern-loan-from-mut.rs b/src/test/compile-fail/borrowck-vec-pattern-loan-from-mut.rs index 393ec8b0b1b3b..cc1dbc8195543 100644 --- a/src/test/compile-fail/borrowck-vec-pattern-loan-from-mut.rs +++ b/src/test/compile-fail/borrowck-vec-pattern-loan-from-mut.rs @@ -12,7 +12,7 @@ fn a() { let mut v = vec!(1, 2, 3); let vb: &mut [int] = v.as_mut_slice(); match vb { - [_a, ..tail] => { + [_a, tail..] => { v.push(tail[0] + tail[1]); //~ ERROR cannot borrow } _ => {} diff --git a/src/test/compile-fail/borrowck-vec-pattern-move-tail.rs b/src/test/compile-fail/borrowck-vec-pattern-move-tail.rs index 7b092d16eec69..cb8762f44fb79 100644 --- a/src/test/compile-fail/borrowck-vec-pattern-move-tail.rs +++ b/src/test/compile-fail/borrowck-vec-pattern-move-tail.rs @@ -11,7 +11,7 @@ fn main() { let mut a = [1i, 2, 3, 4]; let t = match a { - [1, 2, ..tail] => tail, + [1, 2, tail..] => tail, _ => unreachable!() }; a[0] = 0; //~ ERROR cannot assign to `a[..]` because it is borrowed diff --git a/src/test/compile-fail/borrowck-vec-pattern-nesting.rs b/src/test/compile-fail/borrowck-vec-pattern-nesting.rs index 4a56f9821065b..58932d1b4228a 100644 --- a/src/test/compile-fail/borrowck-vec-pattern-nesting.rs +++ b/src/test/compile-fail/borrowck-vec-pattern-nesting.rs @@ -22,7 +22,7 @@ fn b() { let mut vec = vec!(box 1i, box 2, box 3); let vec: &mut [Box] = vec.as_mut_slice(); match vec { - [.._b] => { + [_b..] => { vec[0] = box 4; //~ ERROR cannot assign } } @@ -33,7 +33,7 @@ fn c() { let vec: &mut [Box] = vec.as_mut_slice(); match vec { [_a, //~ ERROR cannot move out - .._b] => { //~^ NOTE attempting to move value to here + _b..] => { //~^ NOTE attempting to move value to here // Note: `_a` is *moved* here, but `b` is borrowing, // hence illegal. @@ -50,7 +50,7 @@ fn d() { let mut vec = vec!(box 1i, box 2, box 3); let vec: &mut [Box] = vec.as_mut_slice(); match vec { - [.._a, //~ ERROR cannot move out + [_a.., //~ ERROR cannot move out _b] => {} //~ NOTE attempting to move value to here _ => {} } diff --git a/src/test/compile-fail/borrowck-vec-pattern-tail-element-loan.rs b/src/test/compile-fail/borrowck-vec-pattern-tail-element-loan.rs index 57a276bec81bc..2c9cf7d1b65be 100644 --- a/src/test/compile-fail/borrowck-vec-pattern-tail-element-loan.rs +++ b/src/test/compile-fail/borrowck-vec-pattern-tail-element-loan.rs @@ -12,7 +12,7 @@ fn a<'a>() -> &'a int { let vec = vec!(1, 2, 3, 4); let vec: &[int] = vec.as_slice(); //~ ERROR `vec` does not live long enough let tail = match vec { - [_a, ..tail] => &tail[0], + [_a, tail..] => &tail[0], _ => fail!("foo") }; tail diff --git a/src/test/compile-fail/issue-12369.rs b/src/test/compile-fail/issue-12369.rs index 7d800899e5209..4522b536ffd34 100644 --- a/src/test/compile-fail/issue-12369.rs +++ b/src/test/compile-fail/issue-12369.rs @@ -13,7 +13,7 @@ fn main() { let v: int = match sl.as_slice() { [] => 0, [a,b,c] => 3, - [a, ..rest] => a, - [10,a, ..rest] => 10 //~ ERROR: unreachable pattern + [a, rest..] => a, + [10,a, rest..] => 10 //~ ERROR: unreachable pattern }; } diff --git a/src/test/compile-fail/issue-12567.rs b/src/test/compile-fail/issue-12567.rs index d5a8339ba1938..26866cbbc6033 100644 --- a/src/test/compile-fail/issue-12567.rs +++ b/src/test/compile-fail/issue-12567.rs @@ -11,10 +11,10 @@ fn match_vecs<'a, T>(l1: &'a [T], l2: &'a [T]) { match (l1, l2) { ([], []) => println!("both empty"), - ([], [hd, ..tl]) | ([hd, ..tl], []) => println!("one empty"), + ([], [hd, tl..]) | ([hd, tl..], []) => println!("one empty"), //~^ ERROR: cannot move out of dereference //~^^ ERROR: cannot move out of dereference - ([hd1, ..tl1], [hd2, ..tl2]) => println!("both nonempty"), + ([hd1, tl1..], [hd2, tl2..]) => println!("both nonempty"), //~^ ERROR: cannot move out of dereference //~^^ ERROR: cannot move out of dereference } diff --git a/src/test/compile-fail/match-vec-invalid.rs b/src/test/compile-fail/match-vec-invalid.rs index 389e26aa400dc..51e83c14aa008 100644 --- a/src/test/compile-fail/match-vec-invalid.rs +++ b/src/test/compile-fail/match-vec-invalid.rs @@ -11,7 +11,7 @@ fn main() { let a = Vec::new(); match a { - [1, ..tail, ..tail] => {}, //~ ERROR: unexpected token: `..` + [1, tail.., tail..] => {}, //~ ERROR: expected `,`, found `..` _ => () } } diff --git a/src/test/compile-fail/match-vec-unreachable.rs b/src/test/compile-fail/match-vec-unreachable.rs index a94b070964638..a85ce660e8b1f 100644 --- a/src/test/compile-fail/match-vec-unreachable.rs +++ b/src/test/compile-fail/match-vec-unreachable.rs @@ -31,7 +31,7 @@ fn main() { let x: Vec = vec!('a', 'b', 'c'); let x: &[char] = x.as_slice(); match x { - ['a', 'b', 'c', .._tail] => {} + ['a', 'b', 'c', _tail..] => {} ['a', 'b', 'c'] => {} //~ ERROR unreachable pattern _ => {} } diff --git a/src/test/compile-fail/non-exhaustive-match.rs b/src/test/compile-fail/non-exhaustive-match.rs index 4de4af877127d..ae5f40d4874c9 100644 --- a/src/test/compile-fail/non-exhaustive-match.rs +++ b/src/test/compile-fail/non-exhaustive-match.rs @@ -38,14 +38,14 @@ fn main() { let vec = vec!(Some(42i), None, Some(21i)); let vec: &[Option] = vec.as_slice(); match vec { //~ ERROR non-exhaustive patterns: `[]` not covered - [Some(..), None, ..tail] => {} - [Some(..), Some(..), ..tail] => {} + [Some(..), None, tail..] => {} + [Some(..), Some(..), tail..] => {} [None] => {} } let vec = vec!(1i); let vec: &[int] = vec.as_slice(); match vec { - [_, ..tail] => (), + [_, tail..] => (), [] => () } let vec = vec!(0.5f32); @@ -59,10 +59,10 @@ fn main() { let vec = vec!(Some(42i), None, Some(21i)); let vec: &[Option] = vec.as_slice(); match vec { - [Some(..), None, ..tail] => {} - [Some(..), Some(..), ..tail] => {} - [None, None, ..tail] => {} - [None, Some(..), ..tail] => {} + [Some(..), None, tail..] => {} + [Some(..), Some(..), tail..] => {} + [None, None, tail..] => {} + [None, Some(..), tail..] => {} [Some(_)] => {} [None] => {} [] => {} diff --git a/src/test/compile-fail/non-exhaustive-pattern-witness.rs b/src/test/compile-fail/non-exhaustive-pattern-witness.rs index 6dc5ad8b606c3..d7f95f20d9f7a 100644 --- a/src/test/compile-fail/non-exhaustive-pattern-witness.rs +++ b/src/test/compile-fail/non-exhaustive-pattern-witness.rs @@ -63,7 +63,7 @@ fn vectors_with_nested_enums() { [Second(true), First] => (), [Second(true), Second(true)] => (), [Second(false), _] => (), - [_, _, ..tail, _] => () + [_, _, tail.., _] => () } } diff --git a/src/test/compile-fail/vec-matching-obsolete-syntax.rs b/src/test/compile-fail/vec-matching-obsolete-syntax.rs new file mode 100644 index 0000000000000..6330aac2d8b75 --- /dev/null +++ b/src/test/compile-fail/vec-matching-obsolete-syntax.rs @@ -0,0 +1,22 @@ +// Copyright 2014 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +fn main() { + let x = [1i, 2, 3]; + match x { + [a, b, ..c] => { //~ ERROR obsolete syntax + assert_eq!(a, 1); + assert_eq!(b, 2); + let expected: &[_] = &[3]; + assert_eq!(c, expected); + } + } +} + diff --git a/src/test/run-pass/issue-15080.rs b/src/test/run-pass/issue-15080.rs index 444e8bd37707e..1709321a71cfd 100644 --- a/src/test/run-pass/issue-15080.rs +++ b/src/test/run-pass/issue-15080.rs @@ -14,11 +14,11 @@ fn main() { let mut result = vec!(); loop { x = match x { - [1, n, 3, ..rest] => { + [1, n, 3, rest..] => { result.push(n); rest } - [n, ..rest] => { + [n, rest..] => { result.push(n); rest } diff --git a/src/test/run-pass/issue-15104.rs b/src/test/run-pass/issue-15104.rs index d2711339ccbd4..c6c9e8004558c 100644 --- a/src/test/run-pass/issue-15104.rs +++ b/src/test/run-pass/issue-15104.rs @@ -16,6 +16,6 @@ fn count_members(v: &[uint]) -> uint { match v { [] => 0, [_] => 1, - [_x, ..xs] => 1 + count_members(xs) + [_x, xs..] => 1 + count_members(xs) } } diff --git a/src/test/run-pass/issue-7784.rs b/src/test/run-pass/issue-7784.rs index d307a05703843..817f1ee13933b 100644 --- a/src/test/run-pass/issue-7784.rs +++ b/src/test/run-pass/issue-7784.rs @@ -29,7 +29,7 @@ fn main() { assert_eq!(d, "baz"); let out = bar("baz", "foo"); - let [a, ..xs, d] = out; + let [a, xs.., d] = out; assert_eq!(a, "baz"); assert!(xs == ["foo", "foo"]); assert_eq!(d, "baz"); diff --git a/src/test/run-pass/vec-matching-fold.rs b/src/test/run-pass/vec-matching-fold.rs index 07ee5f535e9ba..519d6987a7eb3 100644 --- a/src/test/run-pass/vec-matching-fold.rs +++ b/src/test/run-pass/vec-matching-fold.rs @@ -13,7 +13,7 @@ fn foldl(values: &[T], function: |partial: U, element: &T| -> U) -> U { match values { - [ref head, ..tail] => + [ref head, tail..] => foldl(tail, function(initial, head), function), [] => initial.clone() } @@ -24,7 +24,7 @@ fn foldr(values: &[T], function: |element: &T, partial: U| -> U) -> U { match values { - [..head, ref tail] => + [head.., ref tail] => foldr(head, function(tail, initial), function), [] => initial.clone() } diff --git a/src/test/run-pass/vec-matching-legal-tail-element-borrow.rs b/src/test/run-pass/vec-matching-legal-tail-element-borrow.rs index 2fd8a4ab256fd..a140399447b3b 100644 --- a/src/test/run-pass/vec-matching-legal-tail-element-borrow.rs +++ b/src/test/run-pass/vec-matching-legal-tail-element-borrow.rs @@ -13,7 +13,7 @@ pub fn main() { let x: &[int] = &[1, 2, 3, 4, 5]; if !x.is_empty() { let el = match x { - [1, ..ref tail] => &tail[0], + [1, ref tail..] => &tail[0], _ => unreachable!() }; println!("{}", *el); diff --git a/src/test/run-pass/vec-matching.rs b/src/test/run-pass/vec-matching.rs index e95495a42d282..bb636d6a6731e 100644 --- a/src/test/run-pass/vec-matching.rs +++ b/src/test/run-pass/vec-matching.rs @@ -20,7 +20,7 @@ fn a() { fn b() { let x = [1i, 2, 3]; match x { - [a, b, ..c] => { + [a, b, c..] => { assert_eq!(a, 1); assert_eq!(b, 2); let expected: &[_] = &[3]; @@ -28,7 +28,7 @@ fn b() { } } match x { - [..a, b, c] => { + [a.., b, c] => { let expected: &[_] = &[1]; assert_eq!(a, expected); assert_eq!(b, 2); @@ -36,7 +36,7 @@ fn b() { } } match x { - [a, ..b, c] => { + [a, b.., c] => { assert_eq!(a, 1); let expected: &[_] = &[2]; assert_eq!(b, expected); diff --git a/src/test/run-pass/vec-tail-matching.rs b/src/test/run-pass/vec-tail-matching.rs index e58aebcddfe0e..a1a222549a086 100644 --- a/src/test/run-pass/vec-tail-matching.rs +++ b/src/test/run-pass/vec-tail-matching.rs @@ -20,14 +20,14 @@ pub fn main() { Foo { string: "baz".to_string() } ]; match x { - [ref first, ..tail] => { + [ref first, tail..] => { assert!(first.string == "foo".to_string()); assert_eq!(tail.len(), 2); assert!(tail[0].string == "bar".to_string()); assert!(tail[1].string == "baz".to_string()); match tail { - [Foo { .. }, _, Foo { .. }, .. _tail] => { + [Foo { .. }, _, Foo { .. }, _tail..] => { unreachable!(); } [Foo { string: ref a }, Foo { string: ref b }] => {