Skip to content

Commit

Permalink
rustc: Switch tuple structs to have private fields
Browse files Browse the repository at this point in the history
This is a continuation of the work done in rust-lang#13184 to make struct fields private
by default. This commit finishes RFC 4 by making all tuple structs have private
fields by default. Note that enum variants are not affected.

A tuple struct having a private field means that it cannot be matched on in a
pattern match (both refutable and irrefutable), and it also cannot have a value
specified to be constructed. Similarly to private fields, switching the type of
a private field in a tuple struct should be able to be done in a backwards
compatible way.

The one snag that I ran into which wasn't mentioned in the RFC is that this
commit also forbids taking the value of a tuple struct constructor. For example,
this code now fails to compile:

    mod a {
        pub struct A(int);
    }

    let a: fn(int) -> a::A = a::A; //~ ERROR: first field is private

Although no fields are bound in this example, it exposes implementation details
through the type itself. For this reason, taking the value of a struct
constructor with private fields is forbidden (outside the containing module).

RFC: 0004-private-fields
  • Loading branch information
alexcrichton committed Apr 1, 2014
1 parent b8ef9fd commit 6831979
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 31 deletions.
8 changes: 8 additions & 0 deletions src/librustc/metadata/csearch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,3 +311,11 @@ pub fn get_exported_macros(cstore: &cstore::CStore,
let cdata = cstore.get_crate_data(crate_num);
decoder::get_exported_macros(cdata)
}

pub fn get_tuple_struct_definition_if_ctor(cstore: &cstore::CStore,
def_id: ast::DefId)
-> Option<ast::DefId>
{
let cdata = cstore.get_crate_data(def_id.krate);
decoder::get_tuple_struct_definition_if_ctor(cdata, def_id.node)
}
11 changes: 7 additions & 4 deletions src/librustc/metadata/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -962,23 +962,26 @@ pub fn get_static_methods_if_impl(intr: Rc<IdentInterner>,
/// If node_id is the constructor of a tuple struct, retrieve the NodeId of
/// the actual type definition, otherwise, return None
pub fn get_tuple_struct_definition_if_ctor(cdata: Cmd,
node_id: ast::NodeId) -> Option<ast::NodeId> {
node_id: ast::NodeId)
-> Option<ast::DefId>
{
let item = lookup_item(node_id, cdata.data());
let mut ret = None;
reader::tagged_docs(item, tag_items_data_item_is_tuple_struct_ctor, |_| {
ret = Some(item_reqd_and_translated_parent_item(cdata.cnum, item));
false
});
ret.map(|x| x.node)
ret
}

pub fn get_item_attrs(cdata: Cmd,
node_id: ast::NodeId,
orig_node_id: ast::NodeId,
f: |Vec<@ast::MetaItem> |) {
// The attributes for a tuple struct are attached to the definition, not the ctor;
// we assume that someone passing in a tuple struct ctor is actually wanting to
// look at the definition
let node_id = get_tuple_struct_definition_if_ctor(cdata, node_id).unwrap_or(node_id);
let node_id = get_tuple_struct_definition_if_ctor(cdata, orig_node_id);
let node_id = node_id.map(|x| x.node).unwrap_or(orig_node_id);
let item = lookup_item(node_id, cdata.data());
reader::tagged_docs(item, tag_attributes, |attributes| {
reader::tagged_docs(attributes, tag_attribute, |attribute| {
Expand Down
119 changes: 98 additions & 21 deletions src/librustc/middle/privacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use std::mem::replace;

use metadata::csearch;
use middle::lint;
use middle::resolve;
use middle::ty;
Expand Down Expand Up @@ -358,6 +359,12 @@ enum PrivacyResult {
DisallowedBy(ast::NodeId),
}

enum FieldName {
UnnamedField(uint), // index
// FIXME #6993: change type (and name) from Ident to Name
NamedField(ast::Ident),
}

impl<'a> PrivacyVisitor<'a> {
// used when debugging
fn nodestr(&self, id: ast::NodeId) -> ~str {
Expand Down Expand Up @@ -560,18 +567,23 @@ impl<'a> PrivacyVisitor<'a> {
}

// Checks that a field is in scope.
// FIXME #6993: change type (and name) from Ident to Name
fn check_field(&mut self, span: Span, id: ast::DefId, ident: ast::Ident) {
for field in ty::lookup_struct_fields(self.tcx, id).iter() {
if field.name != ident.name { continue; }
if field.vis == ast::Public { break }
if !is_local(field.id) ||
!self.private_accessible(field.id.node) {
self.tcx.sess.span_err(span,
format!("field `{}` is private",
token::get_ident(ident)))
fn check_field(&mut self, span: Span, id: ast::DefId,
name: FieldName) {
let fields = ty::lookup_struct_fields(self.tcx, id);
let field = match name {
NamedField(ident) => {
fields.iter().find(|f| f.name == ident.name).unwrap()
}
break;
UnnamedField(idx) => fields.get(idx)
};
if field.vis == ast::Public { return }
if !is_local(field.id) || !self.private_accessible(field.id.node) {
let msg = match name {
NamedField(name) => format!("field `{}` is private",
token::get_ident(name)),
UnnamedField(idx) => format!("field \\#{} is private", idx + 1),
};
self.tcx.sess.span_err(span, msg);
}
}

Expand Down Expand Up @@ -634,10 +646,11 @@ impl<'a> PrivacyVisitor<'a> {
_ => {},
}
}
// If an import is not used in either namespace, we still want to check
// that it could be legal. Therefore we check in both namespaces and only
// report an error if both would be illegal. We only report one error,
// even if it is illegal to import from both namespaces.
// If an import is not used in either namespace, we still
// want to check that it could be legal. Therefore we check
// in both namespaces and only report an error if both would
// be illegal. We only report one error, even if it is
// illegal to import from both namespaces.
match (value_priv, check_value, type_priv, check_type) {
(Some(p), resolve::Unused, None, _) |
(None, _, Some(p), resolve::Unused) => {
Expand Down Expand Up @@ -701,7 +714,8 @@ impl<'a> PrivacyVisitor<'a> {
// is whether the trait itself is accessible or not.
MethodParam(MethodParam { trait_id: trait_id, .. }) |
MethodObject(MethodObject { trait_id: trait_id, .. }) => {
self.report_error(self.ensure_public(span, trait_id, None, "source trait"));
self.report_error(self.ensure_public(span, trait_id, None,
"source trait"));
}
}
}
Expand All @@ -726,7 +740,7 @@ impl<'a> Visitor<()> for PrivacyVisitor<'a> {
match ty::get(ty::expr_ty_adjusted(self.tcx, base,
&*self.method_map.borrow())).sty {
ty::ty_struct(id, _) => {
self.check_field(expr.span, id, ident);
self.check_field(expr.span, id, NamedField(ident));
}
_ => {}
}
Expand All @@ -749,15 +763,16 @@ impl<'a> Visitor<()> for PrivacyVisitor<'a> {
match ty::get(ty::expr_ty(self.tcx, expr)).sty {
ty::ty_struct(id, _) => {
for field in (*fields).iter() {
self.check_field(expr.span, id, field.ident.node);
self.check_field(expr.span, id,
NamedField(field.ident.node));
}
}
ty::ty_enum(_, _) => {
match self.tcx.def_map.borrow().get_copy(&expr.id) {
ast::DefVariant(_, variant_id, _) => {
for field in fields.iter() {
self.check_field(expr.span, variant_id,
field.ident.node);
NamedField(field.ident.node));
}
}
_ => self.tcx.sess.span_bug(expr.span,
Expand All @@ -772,6 +787,46 @@ impl<'a> Visitor<()> for PrivacyVisitor<'a> {
struct type?!"),
}
}
ast::ExprPath(..) => {
let guard = |did: ast::DefId| {
let fields = ty::lookup_struct_fields(self.tcx, did);
let any_priv = fields.iter().any(|f| {
f.vis != ast::Public && (
!is_local(f.id) ||
!self.private_accessible(f.id.node))
});
if any_priv {
self.tcx.sess.span_err(expr.span,
"cannot invoke tuple struct constructor \
with private fields");
}
};
match self.tcx.def_map.borrow().find(&expr.id) {
Some(&ast::DefStruct(did)) => {
guard(if is_local(did) {
local_def(self.tcx.map.get_parent(did.node))
} else {
// "tuple structs" with zero fields (such as
// `pub struct Foo;`) don't have a ctor_id, hence
// the unwrap_or to the same struct id.
let maybe_did =
csearch::get_tuple_struct_definition_if_ctor(
&self.tcx.sess.cstore, did);
maybe_did.unwrap_or(did)
})
}
// Tuple struct constructors across crates are identified as
// DefFn types, so we explicitly handle that case here.
Some(&ast::DefFn(did, _)) if !is_local(did) => {
match csearch::get_tuple_struct_definition_if_ctor(
&self.tcx.sess.cstore, did) {
Some(did) => guard(did),
None => {}
}
}
_ => {}
}
}
_ => {}
}

Expand Down Expand Up @@ -821,15 +876,16 @@ impl<'a> Visitor<()> for PrivacyVisitor<'a> {
match ty::get(ty::pat_ty(self.tcx, pattern)).sty {
ty::ty_struct(id, _) => {
for field in fields.iter() {
self.check_field(pattern.span, id, field.ident);
self.check_field(pattern.span, id,
NamedField(field.ident));
}
}
ty::ty_enum(_, _) => {
match self.tcx.def_map.borrow().find(&pattern.id) {
Some(&ast::DefVariant(_, variant_id, _)) => {
for field in fields.iter() {
self.check_field(pattern.span, variant_id,
field.ident);
NamedField(field.ident));
}
}
_ => self.tcx.sess.span_bug(pattern.span,
Expand All @@ -844,6 +900,27 @@ impl<'a> Visitor<()> for PrivacyVisitor<'a> {
struct type?!"),
}
}

// Patterns which bind no fields are allowable (the path is check
// elsewhere).
ast::PatEnum(_, Some(ref fields)) => {
match ty::get(ty::pat_ty(self.tcx, pattern)).sty {
ty::ty_struct(id, _) => {
for (i, field) in fields.iter().enumerate() {
match field.node {
ast::PatWild(..) => continue,
_ => {}
}
self.check_field(field.span, id, UnnamedField(i));
}
}
ty::ty_enum(..) => {
// enum fields have no privacy at this time
}
_ => {}
}

}
_ => {}
}

Expand Down
12 changes: 6 additions & 6 deletions src/libsyntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -493,10 +493,10 @@ pub enum Expr_ {
ExprVstore(@Expr, ExprVstore),
// First expr is the place; second expr is the value.
ExprBox(@Expr, @Expr),
ExprVec(Vec<@Expr> , Mutability),
ExprCall(@Expr, Vec<@Expr> ),
ExprMethodCall(Ident, Vec<P<Ty>> , Vec<@Expr> ),
ExprTup(Vec<@Expr> ),
ExprVec(Vec<@Expr>, Mutability),
ExprCall(@Expr, Vec<@Expr>),
ExprMethodCall(Ident, Vec<P<Ty>>, Vec<@Expr>),
ExprTup(Vec<@Expr>),
ExprBinary(BinOp, @Expr, @Expr),
ExprUnary(UnOp, @Expr),
ExprLit(@Lit),
Expand All @@ -508,14 +508,14 @@ pub enum Expr_ {
// Conditionless loop (can be exited with break, cont, or ret)
// FIXME #6993: change to Option<Name>
ExprLoop(P<Block>, Option<Ident>),
ExprMatch(@Expr, Vec<Arm> ),
ExprMatch(@Expr, Vec<Arm>),
ExprFnBlock(P<FnDecl>, P<Block>),
ExprProc(P<FnDecl>, P<Block>),
ExprBlock(P<Block>),

ExprAssign(@Expr, @Expr),
ExprAssignOp(BinOp, @Expr, @Expr),
ExprField(@Expr, Ident, Vec<P<Ty>> ),
ExprField(@Expr, Ident, Vec<P<Ty>>),
ExprIndex(@Expr, @Expr),

/// Expression that looks like a "name". For example,
Expand Down
14 changes: 14 additions & 0 deletions src/test/auxiliary/privacy-tuple-struct.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// 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.

pub struct A(());
pub struct B(int);
pub struct C(pub int, int);
pub struct D(pub int);
Loading

0 comments on commit 6831979

Please sign in to comment.