Skip to content
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

Implement empty structs with braces (RFC 218) #28336

Merged
merged 3 commits into from
Sep 18, 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
16 changes: 14 additions & 2 deletions src/doc/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1178,11 +1178,22 @@ let px: i32 = match p { Point(x, _) => x };
```

A _unit-like struct_ is a structure without any fields, defined by leaving off
the list of fields entirely. Such types will have a single value. For example:
the list of fields entirely. Such a structure implicitly defines a constant of
its type with the same name. For example:

```
# #![feature(braced_empty_structs)]
struct Cookie;
let c = [Cookie, Cookie, Cookie, Cookie];
let c = [Cookie, Cookie {}, Cookie, Cookie {}];
```

is equivalent to

```
# #![feature(braced_empty_structs)]
struct Cookie {}
const Cookie: Cookie = Cookie {};
let c = [Cookie, Cookie {}, Cookie, Cookie {}];
```

The precise memory layout of a structure is not specified. One can specify a
Expand Down Expand Up @@ -2411,6 +2422,7 @@ The currently implemented features of the reference compiler are:
terms of encapsulation).
* - `default_type_parameter_fallback` - Allows type parameter defaults to
influence type inference.
* - `braced_empty_structs` - Allows use of empty structs with braces.

If a feature is promoted to a language feature, then all existing programs will
start to receive compilation warnings about `#![feature]` directives which enabled
Expand Down
50 changes: 25 additions & 25 deletions src/librustc_front/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1218,34 +1218,34 @@ impl<'a> State<'a> {
fields: &[hir::Field],
wth: &Option<P<hir::Expr>>) -> io::Result<()> {
try!(self.print_path(path, true, 0));
if !(fields.is_empty() && wth.is_none()) {
try!(word(&mut self.s, "{"));
try!(self.commasep_cmnt(
Consistent,
&fields[..],
|s, field| {
try!(s.ibox(indent_unit));
try!(s.print_ident(field.ident.node));
try!(s.word_space(":"));
try!(s.print_expr(&*field.expr));
s.end()
},
|f| f.span));
match *wth {
Some(ref expr) => {
try!(self.ibox(indent_unit));
if !fields.is_empty() {
try!(word(&mut self.s, ","));
try!(space(&mut self.s));
}
try!(word(&mut self.s, ".."));
try!(self.print_expr(&**expr));
try!(self.end());
try!(word(&mut self.s, "{"));
Copy link
Member

Choose a reason for hiding this comment

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

You might consider explicitly saying here in a comment that this is unconditionally injecting trailing braces { } for source code that may not necessarily have had them. (I do not object to this, especially since the goals for rustc's pretty printer can be considered independently from standalone tools like rustfmt.)

((Again, this nit won't block this PR.))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it can inject new braces. This function triggers only for ExprStructs and braceless "struct literals" are just ExprPaths.

Copy link
Member

Choose a reason for hiding this comment

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

hmm okay great then

try!(self.commasep_cmnt(
Consistent,
&fields[..],
|s, field| {
try!(s.ibox(indent_unit));
try!(s.print_ident(field.ident.node));
try!(s.word_space(":"));
try!(s.print_expr(&*field.expr));
s.end()
},
|f| f.span));
match *wth {
Some(ref expr) => {
try!(self.ibox(indent_unit));
if !fields.is_empty() {
try!(word(&mut self.s, ","));
try!(space(&mut self.s));
}
_ => try!(word(&mut self.s, ",")),
try!(word(&mut self.s, ".."));
try!(self.print_expr(&**expr));
try!(self.end());
}
_ => if !fields.is_empty() {
try!(word(&mut self.s, ","))
}
try!(word(&mut self.s, "}"));
}
try!(word(&mut self.s, "}"));
Ok(())
}

Expand Down
4 changes: 2 additions & 2 deletions src/librustc_typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1473,14 +1473,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
_ => return None
};

if let ty::VariantKind::Dict = variant.kind() {
let var_kind = variant.kind();
if var_kind == ty::VariantKind::Dict || var_kind == ty::VariantKind::Unit {
Some((adt, variant))
} else {
None
}
}


pub fn write_nil(&self, node_id: ast::NodeId) {
self.write_ty(node_id, self.tcx().mk_nil());
}
Expand Down
21 changes: 20 additions & 1 deletion src/libsyntax/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option<u32>, Status

// allow `#[unwind]`
("unwind_attributes", "1.4.0", None, Active),

// allow empty structs/enum variants with braces
("braced_empty_structs", "1.5.0", None, Active),
];
// (changing above list without updating src/doc/reference.md makes @cmr sad)

Expand Down Expand Up @@ -775,7 +778,7 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
}
}

ast::ItemStruct(..) => {
ast::ItemStruct(ref def, _) => {
if attr::contains_name(&i.attrs[..], "simd") {
self.gate_feature("simd", i.span,
"SIMD types are experimental and possibly buggy");
Expand All @@ -794,6 +797,10 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
}
}
}
if def.fields.is_empty() && def.ctor_id.is_none() {
self.gate_feature("braced_empty_structs", i.span,
"empty structs with braces are unstable");
}
}

ast::ItemDefaultImpl(..) => {
Expand Down Expand Up @@ -843,6 +850,12 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
"box expression syntax is experimental; \
you can call `Box::new` instead.");
}
ast::ExprStruct(_, ref fields, ref expr) => {
if fields.is_empty() && expr.is_none() {
self.gate_feature("braced_empty_structs", e.span,
"empty structs with braces are unstable");
}
}
_ => {}
}
visit::walk_expr(self, e);
Expand All @@ -867,6 +880,12 @@ impl<'a, 'v> Visitor<'v> for PostExpansionVisitor<'a> {
pattern.span,
"box pattern syntax is experimental");
}
ast::PatStruct(_, ref fields, dotdot) => {
if fields.is_empty() && !dotdot {
self.gate_feature("braced_empty_structs", pattern.span,
"empty structs with braces are unstable");
}
}
_ => {}
}
visit::walk_pat(self, pattern)
Expand Down
21 changes: 3 additions & 18 deletions src/libsyntax/parse/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2231,14 +2231,6 @@ impl<'a> Parser<'a> {
&[token::CloseDelim(token::Brace)]));
}

if fields.is_empty() && base.is_none() {
let last_span = self.last_span;
self.span_err(last_span,
"structure literal must either \
have at least one field or use \
structure update syntax");
}

hi = self.span.hi;
try!(self.expect(&token::CloseDelim(token::Brace)));
ex = ExprStruct(pth, fields, base);
Expand Down Expand Up @@ -4713,14 +4705,14 @@ impl<'a> Parser<'a> {
(Vec::new(), Some(ast::DUMMY_NODE_ID))
} else {
// If we see: `struct Foo<T> where T: Copy { ... }`
(try!(self.parse_record_struct_body(&class_name)), None)
(try!(self.parse_record_struct_body()), None)
}
// No `where` so: `struct Foo<T>;`
} else if try!(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 = try!(self.parse_record_struct_body(&class_name));
let fields = try!(self.parse_record_struct_body());
(fields, None)
// Tuple-style struct definition with optional where-clause.
} else if self.token == token::OpenDelim(token::Paren) {
Expand All @@ -4740,20 +4732,13 @@ impl<'a> Parser<'a> {
None))
}

pub fn parse_record_struct_body(&mut self,
class_name: &ast::Ident) -> PResult<Vec<StructField>> {
pub fn parse_record_struct_body(&mut self) -> PResult<Vec<StructField>> {
let mut fields = Vec::new();
if try!(self.eat(&token::OpenDelim(token::Brace)) ){
while self.token != token::CloseDelim(token::Brace) {
fields.push(try!(self.parse_struct_decl_field(true)));
}

if fields.is_empty() {
return Err(self.fatal(&format!("unit-like struct definition should be \
written as `struct {};`",
class_name)));
}

try!(self.bump());
} else {
let token_str = self.this_token_to_string();
Expand Down
50 changes: 25 additions & 25 deletions src/libsyntax/print/pprust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1855,34 +1855,34 @@ impl<'a> State<'a> {
fields: &[ast::Field],
wth: &Option<P<ast::Expr>>) -> io::Result<()> {
try!(self.print_path(path, true, 0));
if !(fields.is_empty() && wth.is_none()) {
try!(word(&mut self.s, "{"));
try!(self.commasep_cmnt(
Consistent,
&fields[..],
|s, field| {
try!(s.ibox(indent_unit));
try!(s.print_ident(field.ident.node));
try!(s.word_space(":"));
try!(s.print_expr(&*field.expr));
s.end()
},
|f| f.span));
match *wth {
Some(ref expr) => {
try!(self.ibox(indent_unit));
if !fields.is_empty() {
try!(word(&mut self.s, ","));
try!(space(&mut self.s));
}
try!(word(&mut self.s, ".."));
try!(self.print_expr(&**expr));
try!(self.end());
try!(word(&mut self.s, "{"));
try!(self.commasep_cmnt(
Consistent,
&fields[..],
|s, field| {
try!(s.ibox(indent_unit));
try!(s.print_ident(field.ident.node));
try!(s.word_space(":"));
try!(s.print_expr(&*field.expr));
s.end()
},
|f| f.span));
match *wth {
Some(ref expr) => {
try!(self.ibox(indent_unit));
if !fields.is_empty() {
try!(word(&mut self.s, ","));
try!(space(&mut self.s));
}
_ => try!(word(&mut self.s, ",")),
try!(word(&mut self.s, ".."));
try!(self.print_expr(&**expr));
try!(self.end());
}
_ => if !fields.is_empty() {
try!(word(&mut self.s, ","))
}
try!(word(&mut self.s, "}"));
}
try!(word(&mut self.s, "}"));
Ok(())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
Expand All @@ -8,13 +8,12 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// compile-flags: -Z parse-only
// Empty struct defined with braces shouldn't add names into value namespace
Copy link
Member

Choose a reason for hiding this comment

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

Great to have a test for it, but.

Isn't this a surprising gotcha? struct S {} and struct S; being noticeably different?.

Copy link
Member

Choose a reason for hiding this comment

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

I see the RFC is quite explicit about this design, so that was just something new to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a part of the RFC and a result of long discussion.
Personally, I find this quite logical - struct S {} is a usual struct and it doesn't add anything to the value namespace, it just happens to have an empty list of fields. struct S; in its turn is syntactically special and represents usual struct + some special magic.


struct Foo;
#![feature(braced_empty_structs)]

fn f2() {
let _end_stmt = Foo { };
//~^ ERROR: structure literal must either have at least one field
}
struct Empty {}

fn main() {}
fn main() {
let e = Empty; //~ ERROR `Empty` is the name of a struct or struct variant
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
Expand All @@ -8,13 +8,18 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// compile-flags: -Z parse-only
// Empty struct defined with braces shouldn't add names into value namespace

struct Foo;
#![feature(braced_empty_structs)]
#![deny(warnings)]

fn h4() {
let _end_of_tuple = (3, Foo { });
//~^ ERROR: structure literal must either have at least one field
}
struct Empty {}

fn main() {
let e = Empty {};

fn main() {}
match e {
Empty => () //~ ERROR unused variable: `Empty`
//~^ ERROR variable `Empty` should have a snake case name such as `empty`
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
Expand All @@ -8,13 +8,14 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

// compile-flags: -Z parse-only
// Feature gate test for empty struct with braces

struct Foo;
struct Empty {} //~ ERROR empty structs with braces are unstable

fn g3() {
let _mid_tuple = (Foo { }, 2);
//~^ ERROR: structure literal must either have at least one field
}
fn main() {
let e = Empty {}; //~ ERROR empty structs with braces are unstable

fn main() {}
match e {
Empty {} => {} //~ ERROR empty structs with braces are unstable
}
}
4 changes: 2 additions & 2 deletions src/test/compile-fail/issue-27831.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ fn main() {
let Foo { .. } = x; //~ ERROR `Foo` does not name a struct

let x = Bar;
Bar { ..x }; //~ ERROR `Bar` does not name a structure
let Bar { .. } = x; //~ ERROR `Bar` does not name a struct
Bar { ..x };
let Bar { .. } = x;

match Enum::Bar {
Enum::Bar { .. } //~ ERROR `Enum::Bar` does not name a struct
Expand Down
20 changes: 0 additions & 20 deletions src/test/parse-fail/struct-no-fields-5.rs

This file was deleted.

Loading