Skip to content

Fix the parsing of where-clauses for tuple-structs #20424

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jan 6, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 89 additions & 40 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4875,67 +4875,116 @@ impl<'a> Parser<'a> {
self.span_err(ty.span, "`virtual` structs have been removed from the language");
}

self.parse_where_clause(&mut generics);
// There is a special case worth noting here, as reported in issue #17904.
// If we are parsing a tuple struct it is the case that the where clause
// should follow the field list. Like so:
//
// struct Foo<T>(T) where T: Copy;
//
// If we are parsing a normal record-style struct it is the case
// that the where clause comes before the body, and after the generics.
// So if we look ahead and see a brace or a where-clause we begin
// parsing a record style struct.
//
// Otherwise if we look ahead and see a paren we parse a tuple-style
// struct.

let (fields, ctor_id) = if self.token.is_keyword(keywords::Where) {
self.parse_where_clause(&mut generics);
if self.eat(&token::Semi) {
// If we see a: `struct Foo<T> where T: Copy;` style decl.
(Vec::new(), Some(ast::DUMMY_NODE_ID))
} else {
// If we see: `struct Foo<T> where T: Copy { ... }`
(self.parse_record_struct_body(&class_name), None)
}
// No `where` so: `struct Foo<T>;`
} else if self.eat(&token::Semi) {
(Vec::new(), Some(ast::DUMMY_NODE_ID))
// Record-style struct definition
} else if self.token == token::OpenDelim(token::Brace) {
let fields = self.parse_record_struct_body(&class_name);
(fields, None)
// Tuple-style struct definition with optional where-clause.
} else {
let fields = self.parse_tuple_struct_body(&class_name, &mut generics);
(fields, Some(ast::DUMMY_NODE_ID))
};

let mut fields: Vec<StructField>;
let is_tuple_like;
(class_name,
ItemStruct(P(ast::StructDef {
fields: fields,
ctor_id: ctor_id,
}), generics),
None)
}

pub fn parse_record_struct_body(&mut self, class_name: &ast::Ident) -> Vec<StructField> {
let mut fields = Vec::new();
if self.eat(&token::OpenDelim(token::Brace)) {
// It's a record-like struct.
is_tuple_like = false;
fields = Vec::new();
while self.token != token::CloseDelim(token::Brace) {
fields.push(self.parse_struct_decl_field(true));
}

if fields.len() == 0 {
self.fatal(format!("unit-like struct definition should be \
written as `struct {};`",
token::get_ident(class_name))[]);
written as `struct {};`",
token::get_ident(class_name.clone()))[]);
}

self.bump();
} else if self.check(&token::OpenDelim(token::Paren)) {
// It's a tuple-like struct.
is_tuple_like = true;
fields = self.parse_unspanned_seq(
} else {
let token_str = self.this_token_to_string();
self.fatal(format!("expected `where`, or `{}` after struct \
name, found `{}`", "{",
token_str)[]);
}

fields
}

pub fn parse_tuple_struct_body(&mut self,
class_name: &ast::Ident,
generics: &mut ast::Generics)
-> Vec<StructField> {
// This is the case where we find `struct Foo<T>(T) where T: Copy;`
if self.check(&token::OpenDelim(token::Paren)) {
let fields = self.parse_unspanned_seq(
&token::OpenDelim(token::Paren),
&token::CloseDelim(token::Paren),
seq_sep_trailing_allowed(token::Comma),
|p| {
let attrs = p.parse_outer_attributes();
let lo = p.span.lo;
let struct_field_ = ast::StructField_ {
kind: UnnamedField(p.parse_visibility()),
id: ast::DUMMY_NODE_ID,
ty: p.parse_ty_sum(),
attrs: attrs,
};
spanned(lo, p.span.hi, struct_field_)
});
let attrs = p.parse_outer_attributes();
let lo = p.span.lo;
let struct_field_ = ast::StructField_ {
kind: UnnamedField(p.parse_visibility()),
id: ast::DUMMY_NODE_ID,
ty: p.parse_ty_sum(),
attrs: attrs,
};
spanned(lo, p.span.hi, struct_field_)
});

if fields.len() == 0 {
self.fatal(format!("unit-like struct definition should be \
written as `struct {};`",
token::get_ident(class_name))[]);
written as `struct {};`",
token::get_ident(class_name.clone()))[]);
}

self.parse_where_clause(generics);
self.expect(&token::Semi);
} else if self.eat(&token::Semi) {
// It's a unit-like struct.
is_tuple_like = true;
fields = Vec::new();
fields
// This is the case where we just see struct Foo<T> where T: Copy;
} else if self.token.is_keyword(keywords::Where) {
self.parse_where_clause(generics);
self.expect(&token::Semi);
Vec::new()
// This case is where we see: `struct Foo<T>;`
} else {
let token_str = self.this_token_to_string();
self.fatal(format!("expected `{}`, `(`, or `;` after struct \
name, found `{}`", "{",
token_str)[])
self.fatal(format!("expected `where`, `{}`, `(`, or `;` after struct \
name, found `{}`", "{", token_str)[]);
}

let _ = ast::DUMMY_NODE_ID; // FIXME: Workaround for crazy bug.
let new_id = ast::DUMMY_NODE_ID;
(class_name,
ItemStruct(P(ast::StructDef {
fields: fields,
ctor_id: if is_tuple_like { Some(new_id) } else { None },
}), generics),
None)
}

/// Parse a structure field declaration
Expand Down
3 changes: 2 additions & 1 deletion src/libsyntax/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1067,7 +1067,6 @@ impl<'a> State<'a> {
span: codemap::Span) -> IoResult<()> {
try!(self.print_ident(ident));
try!(self.print_generics(generics));
try!(self.print_where_clause(generics));
if ast_util::struct_def_is_tuple_like(struct_def) {
if !struct_def.fields.is_empty() {
try!(self.popen());
Expand All @@ -1086,10 +1085,12 @@ impl<'a> State<'a> {
));
try!(self.pclose());
}
try!(self.print_where_clause(generics));
try!(word(&mut self.s, ";"));
try!(self.end());
self.end() // close the outer-box
} else {
try!(self.print_where_clause(generics));
try!(self.nbsp());
try!(self.bopen());
try!(self.hardbreak_if_not_bol());
Expand Down
17 changes: 17 additions & 0 deletions src/test/compile-fail/issue-17904.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 <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.

struct Baz<U> where U: Eq(U); //This is parsed as the new Fn* style parenthesis syntax.
struct Baz<U> where U: Eq(U) -> R; // Notice this parses as well.
struct Baz<U>(U) where U: Eq; // This rightfully signals no error as well.
struct Foo<T> where T: Copy, (T); //~ ERROR unexpected token in `where` clause
struct Bar<T> { x: T } where T: Copy //~ ERROR expected item, found `where`

fn main() {}
2 changes: 1 addition & 1 deletion src/test/compile-fail/unsized.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

// Test syntax checks for `type` keyword.

struct S1 for type; //~ ERROR expected `{`, `(`, or `;` after struct name, found `for`
struct S1 for type; //~ ERROR expected `where`, `{`, `(`, or `;` after struct name, found `for`

pub fn main() {
}
18 changes: 18 additions & 0 deletions src/test/run-pass/issue-17904.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 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 <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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently this file doesn't pretty print correctly: alexcrichton@acef292

(it's fine to ignore pretty printing for now)

struct Foo<T> where T: Copy;
struct Bar<T>(T) where T: Copy;
struct Bleh<T, U>(T, U) where T: Copy, U: Sized;
struct Baz<T> where T: Copy {
field: T
}

fn main() {}