diff options
author | David Wood <david@davidtw.co> | 2017-11-03 19:17:54 +0000 |
---|---|---|
committer | David Wood <david@davidtw.co> | 2017-11-03 19:36:18 +0000 |
commit | 059eccb07f35cd3dd1119d116c593b247752d682 (patch) | |
tree | f46189cc9bbb529e87362c85d1ea1979c8a7b97c | |
parent | 6c19ebe1288c1fc6fa5d9b1c65da256eede7a77c (diff) | |
download | rust-059eccb07f35cd3dd1119d116c593b247752d682.tar.gz |
Implemented RFC 2008 for enums (not including variants) and structs.
-rw-r--r-- | src/librustc/ty/mod.rs | 14 | ||||
-rw-r--r-- | src/librustc_const_eval/_match.rs | 73 | ||||
-rw-r--r-- | src/librustc_metadata/encoder.rs | 9 | ||||
-rw-r--r-- | src/librustc_passes/ast_validation.rs | 10 | ||||
-rw-r--r-- | src/librustc_privacy/lib.rs | 10 | ||||
-rw-r--r-- | src/librustc_resolve/build_reduced_graph.rs | 16 | ||||
-rw-r--r-- | src/librustc_typeck/check/_match.rs | 12 | ||||
-rw-r--r-- | src/librustc_typeck/check/mod.rs | 9 | ||||
-rw-r--r-- | src/librustc_typeck/diagnostics.rs | 59 |
9 files changed, 203 insertions, 9 deletions
diff --git a/src/librustc/ty/mod.rs b/src/librustc/ty/mod.rs index ee1668d6fa2..861238c10ba 100644 --- a/src/librustc/ty/mod.rs +++ b/src/librustc/ty/mod.rs @@ -1326,6 +1326,12 @@ bitflags! { const IS_FUNDAMENTAL = 1 << 2; const IS_UNION = 1 << 3; const IS_BOX = 1 << 4; + /// Indicates whether this abstract data type will be expanded on in future (new + /// fields/variants) and as such, whether downstream crates must match exhaustively on the + /// fields/variants of this data type. + /// + /// See RFC 2008 (https://github.com/rust-lang/rfcs/pull/2008). + const IS_NON_EXHAUSTIVE = 1 << 5; } } @@ -1526,6 +1532,9 @@ impl<'a, 'gcx, 'tcx> AdtDef { if Some(did) == tcx.lang_items().owned_box() { flags = flags | AdtFlags::IS_BOX; } + if tcx.has_attr(did, "non_exhaustive") { + flags = flags | AdtFlags::IS_NON_EXHAUSTIVE; + } match kind { AdtKind::Enum => flags = flags | AdtFlags::IS_ENUM, AdtKind::Union => flags = flags | AdtFlags::IS_UNION, @@ -1554,6 +1563,11 @@ impl<'a, 'gcx, 'tcx> AdtDef { self.flags.intersects(AdtFlags::IS_ENUM) } + #[inline] + pub fn is_non_exhaustive(&self) -> bool { + self.flags.intersects(AdtFlags::IS_NON_EXHAUSTIVE) + } + /// Returns the kind of the ADT - Struct or Enum. #[inline] pub fn adt_kind(&self) -> AdtKind { diff --git a/src/librustc_const_eval/_match.rs b/src/librustc_const_eval/_match.rs index 08f3b0a4c5f..6ebe3c67966 100644 --- a/src/librustc_const_eval/_match.rs +++ b/src/librustc_const_eval/_match.rs @@ -208,6 +208,20 @@ impl<'a, 'tcx> MatchCheckCtxt<'a, 'tcx> { } } + fn is_non_exhaustive_enum(&self, ty: Ty<'tcx>) -> bool { + match ty.sty { + ty::TyAdt(adt_def, ..) => adt_def.is_enum() && adt_def.is_non_exhaustive(), + _ => false, + } + } + + fn is_local(&self, ty: Ty<'tcx>) -> bool { + match ty.sty { + ty::TyAdt(adt_def, ..) => adt_def.did.is_local(), + _ => false, + } + } + fn is_variant_uninhabited(&self, variant: &'tcx ty::VariantDef, substs: &'tcx ty::subst::Substs<'tcx>) @@ -628,9 +642,16 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>, let is_privately_empty = all_ctors.is_empty() && !cx.is_uninhabited(pcx.ty); - debug!("missing_ctors={:?} is_privately_empty={:?}", missing_ctors, - is_privately_empty); - if missing_ctors.is_empty() && !is_privately_empty { + let is_declared_nonexhaustive = + cx.is_non_exhaustive_enum(pcx.ty) && !cx.is_local(pcx.ty); + debug!("missing_ctors={:?} is_privately_empty={:?} is_declared_nonexhaustive={:?}", + missing_ctors, is_privately_empty, is_declared_nonexhaustive); + + // For privately empty and non-exhaustive enums, we work as if there were an "extra" + // `_` constructor for the type, so we can never match over all constructors. + let is_non_exhaustive = is_privately_empty || is_declared_nonexhaustive; + + if missing_ctors.is_empty() && !is_non_exhaustive { all_ctors.into_iter().map(|c| { is_useful_specialized(cx, matrix, v, c.clone(), pcx.ty, witness) }).find(|result| result.is_useful()).unwrap_or(NotUseful) @@ -645,7 +666,51 @@ pub fn is_useful<'p, 'a: 'p, 'tcx: 'a>(cx: &mut MatchCheckCtxt<'a, 'tcx>, match is_useful(cx, &matrix, &v[1..], witness) { UsefulWithWitness(pats) => { let cx = &*cx; - let new_witnesses = if used_ctors.is_empty() { + // In this case, there's at least one "free" + // constructor that is only matched against by + // wildcard patterns. + // + // There are 2 ways we can report a witness here. + // Commonly, we can report all the "free" + // constructors as witnesses, e.g. if we have: + // + // ``` + // enum Direction { N, S, E, W } + // let Direction::N = ...; + // ``` + // + // we can report 3 witnesses: `S`, `E`, and `W`. + // + // However, there are 2 cases where we don't want + // to do this and instead report a single `_` witness: + // + // 1) If the user is matching against a non-exhaustive + // enum, there is no point in enumerating all possible + // variants, because the user can't actually match + // against them himself, e.g. in an example like: + // ``` + // let err: io::ErrorKind = ...; + // match err { + // io::ErrorKind::NotFound => {}, + // } + // ``` + // we don't want to show every possible IO error, + // but instead have `_` as the witness (this is + // actually *required* if the user specified *all* + // IO errors, but is probably what we want in every + // case). + // + // 2) If the user didn't actually specify a constructor + // in this arm, e.g. in + // ``` + // let x: (Direction, Direction, bool) = ...; + // let (_, _, false) = x; + // ``` + // we don't want to show all 16 possible witnesses + // `(<direction-1>, <direction-2>, true)` - we are + // satisfied with `(_, _, true)`. In this case, + // `used_ctors` is empty. + let new_witnesses = if is_non_exhaustive || used_ctors.is_empty() { // All constructors are unused. Add wild patterns // rather than each individual constructor pats.into_iter().map(|mut witness| { diff --git a/src/librustc_metadata/encoder.rs b/src/librustc_metadata/encoder.rs index 725d6d8fad0..458eec37244 100644 --- a/src/librustc_metadata/encoder.rs +++ b/src/librustc_metadata/encoder.rs @@ -584,7 +584,8 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> { fn encode_struct_ctor(&mut self, (adt_def_id, def_id): (DefId, DefId)) -> Entry<'tcx> { debug!("IsolatedEncoder::encode_struct_ctor({:?})", def_id); let tcx = self.tcx; - let variant = tcx.adt_def(adt_def_id).struct_variant(); + let adt_def = tcx.adt_def(adt_def_id); + let variant = adt_def.struct_variant(); let data = VariantData { ctor_kind: variant.ctor_kind, @@ -606,6 +607,12 @@ impl<'a, 'b: 'a, 'tcx: 'b> IsolatedEncoder<'a, 'b, 'tcx> { } } + // If the structure is marked as non_exhaustive then lower the visibility + // to within the crate. + if adt_def.is_non_exhaustive() && ctor_vis == ty::Visibility::Public { + ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX)); + } + let repr_options = get_repr_options(&tcx, adt_def_id); Entry { diff --git a/src/librustc_passes/ast_validation.rs b/src/librustc_passes/ast_validation.rs index e44f3f39824..71630de6aa8 100644 --- a/src/librustc_passes/ast_validation.rs +++ b/src/librustc_passes/ast_validation.rs @@ -42,6 +42,15 @@ impl<'a> AstValidator<'a> { } } + fn invalid_non_exhaustive_attribute(&self, variant: &Variant) { + let has_non_exhaustive = variant.node.attrs.iter() + .any(|attr| attr.check_name("non_exhaustive")); + if has_non_exhaustive { + self.err_handler().span_err(variant.span, + "#[non_exhaustive] is not yet supported on variants"); + } + } + fn invalid_visibility(&self, vis: &Visibility, span: Span, note: Option<&str>) { if vis != &Visibility::Inherited { let mut err = struct_span_err!(self.session, @@ -224,6 +233,7 @@ impl<'a> Visitor<'a> for AstValidator<'a> { } ItemKind::Enum(ref def, _) => { for variant in &def.variants { + self.invalid_non_exhaustive_attribute(variant); for field in variant.node.data.fields() { self.invalid_visibility(&field.vis, field.span, None); } diff --git a/src/librustc_privacy/lib.rs b/src/librustc_privacy/lib.rs index 3beba03ee14..cc3a923f62c 100644 --- a/src/librustc_privacy/lib.rs +++ b/src/librustc_privacy/lib.rs @@ -627,6 +627,16 @@ impl<'a, 'tcx> TypePrivacyVisitor<'a, 'tcx> { ctor_vis = field_vis; } } + + // If the structure is marked as non_exhaustive then lower the + // visibility to within the crate. + let struct_def_id = self.tcx.hir.get_parent_did(node_id); + let adt_def = self.tcx.adt_def(struct_def_id); + if adt_def.is_non_exhaustive() && ctor_vis == ty::Visibility::Public { + ctor_vis = ty::Visibility::Restricted( + DefId::local(CRATE_DEF_INDEX)); + } + return ctor_vis; } node => bug!("unexpected node kind: {:?}", node) diff --git a/src/librustc_resolve/build_reduced_graph.rs b/src/librustc_resolve/build_reduced_graph.rs index 880b370c7f6..b2866a42c42 100644 --- a/src/librustc_resolve/build_reduced_graph.rs +++ b/src/librustc_resolve/build_reduced_graph.rs @@ -338,11 +338,22 @@ impl<'a> Resolver<'a> { // These items live in both the type and value namespaces. ItemKind::Struct(ref struct_def, _) => { // Define a name in the type namespace. - let def = Def::Struct(self.definitions.local_def_id(item.id)); + let def_id = self.definitions.local_def_id(item.id); + let def = Def::Struct(def_id); self.define(parent, ident, TypeNS, (def, vis, sp, expansion)); - // Record field names for error reporting. let mut ctor_vis = vis; + + let has_non_exhaustive = item.attrs.iter() + .any(|item| item.check_name("non_exhaustive")); + + // If the structure is marked as non_exhaustive then lower the visibility + // to within the crate. + if has_non_exhaustive && vis == ty::Visibility::Public { + ctor_vis = ty::Visibility::Restricted(DefId::local(CRATE_DEF_INDEX)); + } + + // Record field names for error reporting. let field_names = struct_def.fields().iter().filter_map(|field| { let field_vis = self.resolve_visibility(&field.vis); if ctor_vis.is_at_least(field_vis, &*self) { @@ -414,6 +425,7 @@ impl<'a> Resolver<'a> { // value namespace, they are reserved for possible future use. let ctor_kind = CtorKind::from_ast(&variant.node.data); let ctor_def = Def::VariantCtor(def_id, ctor_kind); + self.define(parent, ident, ValueNS, (ctor_def, vis, variant.span, expansion)); } diff --git a/src/librustc_typeck/check/_match.rs b/src/librustc_typeck/check/_match.rs index e25f7d79668..272f13b2803 100644 --- a/src/librustc_typeck/check/_match.rs +++ b/src/librustc_typeck/check/_match.rs @@ -825,10 +825,11 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { def_bm: ty::BindingMode) { let tcx = self.tcx; - let (substs, kind_name) = match adt_ty.sty { - ty::TyAdt(adt, substs) => (substs, adt.variant_descr()), + let (substs, adt) = match adt_ty.sty { + ty::TyAdt(adt, substs) => (substs, adt), _ => span_bug!(span, "struct pattern is not an ADT") }; + let kind_name = adt.variant_descr(); // Index the struct fields' types. let field_map = variant.fields @@ -882,6 +883,13 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { self.check_pat_walk(&field.pat, field_ty, def_bm, true); } + // Require `..` if struct has non_exhaustive attribute. + if adt.is_struct() && adt.is_non_exhaustive() && !adt.did.is_local() && !etc { + span_err!(tcx.sess, span, E0638, + "`..` required with {} marked as non-exhaustive", + kind_name); + } + // Report an error if incorrect number of the fields were specified. if kind_name == "union" { if fields.len() != 1 { diff --git a/src/librustc_typeck/check/mod.rs b/src/librustc_typeck/check/mod.rs index d942c1176aa..82d59ecfc92 100644 --- a/src/librustc_typeck/check/mod.rs +++ b/src/librustc_typeck/check/mod.rs @@ -3448,6 +3448,15 @@ impl<'a, 'gcx, 'tcx> FnCtxt<'a, 'gcx, 'tcx> { hir::QPath::TypeRelative(ref qself, _) => qself.span }; + // Prohibit struct expressions when non exhaustive flag is set. + if let ty::TyAdt(adt, _) = struct_ty.sty { + if !adt.did.is_local() && adt.is_non_exhaustive() { + span_err!(self.tcx.sess, expr.span, E0639, + "cannot create non-exhaustive {} using struct expression", + adt.variant_descr()); + } + } + self.check_expr_struct_fields(struct_ty, expected, expr.id, path_span, variant, fields, base_expr.is_none()); if let &Some(ref base_expr) = base_expr { diff --git a/src/librustc_typeck/diagnostics.rs b/src/librustc_typeck/diagnostics.rs index 594cd0878cb..0f273d14603 100644 --- a/src/librustc_typeck/diagnostics.rs +++ b/src/librustc_typeck/diagnostics.rs @@ -4606,6 +4606,65 @@ foo.method(); // Ok! ``` "##, +E0638: r##" +This error indicates that the struct or enum must be matched non-exhaustively +as it has been marked as `non_exhaustive`. + +When applied within a crate, downstream users of the crate will need to use the +`_` pattern when matching enums and use the `..` pattern when matching structs. + +For example, in the below example, since the enum is marked as +`non_exhaustive`, it is required that downstream crates match non-exhaustively +on it. + +```rust,ignore (pseudo-Rust) +use std::error::Error as StdError; + +#[non_exhaustive] pub enum Error { + Message(String), + Other, +} + +impl StdError for Error { + fn description(&self) -> &str { + // This will not error, despite being marked as non_exhaustive, as this + // enum is defined within the current crate, it can be matched + // exhaustively. + match *self { + Message(ref s) => s, + Other => "other or unknown error", + } + } +} +``` + +An example of matching non-exhaustively on the above enum is provided below: + +```rust,ignore (pseudo-Rust) +use mycrate::Error; + +// This will not error as the non_exhaustive Error enum has been matched with a +// wildcard. +match error { + Message(ref s) => ..., + Other => ..., + _ => ..., +} +``` + +Similarly, for structs, match with `..` to avoid this error. +"##, + +E0639: r##" +This error indicates that the struct or enum cannot be instantiated from +outside of the defining crate as it has been marked as `non_exhaustive` and as +such more fields/variants may be added in future that could cause adverse side +effects for this code. + +It is recommended that you look for a `new` function or equivalent in the +crate's documentation. +"##, + } register_diagnostics! { |