Skip to content

Commit 1bf8d69

Browse files
committed
Rollup merge of rust-lang#32199 - nikomatsakis:limiting-constants-in-patterns-2, r=pnkfelix
Restrict constants in patterns This implements [RFC 1445](https://github.com/rust-lang/rfcs/blob/master/text/1445-restrict-constants-in-patterns.md). The primary change is to limit the types of constants used in patterns to those that *derive* `Eq` (note that implementing `Eq` is not sufficient). This has two main effects: 1. Floating point constants are linted, and will eventually be disallowed. This is because floating point constants do not implement `Eq` but only `PartialEq`. This check replaces the existing special case code that aimed to detect the use of `NaN`. 2. Structs and enums must derive `Eq` to be usable within a match. This is a [breaking-change]: if you encounter a problem, you are most likely using a constant in an expression where the type of the constant is some struct that does not currently implement `Eq`. Something like the following: ```rust struct SomeType { ... } const SOME_CONST: SomeType = ...; match foo { SOME_CONST => ... } ``` The easiest and most future compatible fix is to annotate the type in question with `#[derive(Eq)]` (note that merely *implementing* `Eq` is not enough, it must be *derived*): ```rust struct SomeType { ... } const SOME_CONST: SomeType = ...; match foo { SOME_CONST => ... } ``` Another good option is to rewrite the match arm to use an `if` condition (this is also particularly good for floating point types, which implement `PartialEq` but not `Eq`): ```rust match foo { c if c == SOME_CONST => ... } ``` Finally, a third alternative is to tag the type with `#[structural_match]`; but this is not recommended, as the attribute is never expected to be stabilized. Please see RFC rust-lang#1445 for more details. cc rust-lang#31434 r? @pnkfelix
2 parents eac55c2 + 2536ae5 commit 1bf8d69

25 files changed

+438
-46
lines changed

src/librustc/lint/builtin.rs

+15
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,19 @@ declare_lint! {
136136
"type parameter default erroneously allowed in invalid location"
137137
}
138138

139+
declare_lint! {
140+
pub ILLEGAL_FLOATING_POINT_CONSTANT_PATTERN,
141+
Warn,
142+
"floating-point constants cannot be used in patterns"
143+
}
144+
145+
declare_lint! {
146+
pub ILLEGAL_STRUCT_OR_ENUM_CONSTANT_PATTERN,
147+
Deny,
148+
"constants of struct or enum type can only be used in a pattern if \
149+
the struct or enum has `#[derive(PartialEq, Eq)]`"
150+
}
151+
139152
declare_lint! {
140153
pub MATCH_OF_UNIT_VARIANT_VIA_PAREN_DOTDOT,
141154
Deny,
@@ -193,6 +206,8 @@ impl LintPass for HardwiredLints {
193206
PRIVATE_IN_PUBLIC,
194207
INACCESSIBLE_EXTERN_CRATE,
195208
INVALID_TYPE_PARAM_DEFAULT,
209+
ILLEGAL_FLOATING_POINT_CONSTANT_PATTERN,
210+
ILLEGAL_STRUCT_OR_ENUM_CONSTANT_PATTERN,
196211
MATCH_OF_UNIT_VARIANT_VIA_PAREN_DOTDOT,
197212
CONST_ERR,
198213
RAW_POINTER_DERIVE,

src/librustc/middle/check_match.rs

+17-8
Original file line numberDiff line numberDiff line change
@@ -478,15 +478,24 @@ impl<'a, 'tcx> Folder for StaticInliner<'a, 'tcx> {
478478
Some(Def::Const(did)) => {
479479
let substs = Some(self.tcx.node_id_item_substs(pat.id).substs);
480480
if let Some((const_expr, _)) = lookup_const_by_id(self.tcx, did, substs) {
481-
const_expr_to_pat(self.tcx, const_expr, pat.span).map(|new_pat| {
482-
483-
if let Some(ref mut renaming_map) = self.renaming_map {
484-
// Record any renamings we do here
485-
record_renamings(const_expr, &pat, renaming_map);
481+
match const_expr_to_pat(self.tcx, const_expr, pat.id, pat.span) {
482+
Ok(new_pat) => {
483+
if let Some(ref mut map) = self.renaming_map {
484+
// Record any renamings we do here
485+
record_renamings(const_expr, &pat, map);
486+
}
487+
new_pat
486488
}
487-
488-
new_pat
489-
})
489+
Err(def_id) => {
490+
self.failed = true;
491+
self.tcx.sess.span_err(
492+
pat.span,
493+
&format!("constants of the type `{}` \
494+
cannot be used in patterns",
495+
self.tcx.item_path_str(def_id)));
496+
pat
497+
}
498+
}
490499
} else {
491500
self.failed = true;
492501
span_err!(self.tcx.sess, pat.span, E0158,

src/librustc/middle/const_eval.rs

+58-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use self::EvalHint::*;
1616

1717
use front::map as ast_map;
1818
use front::map::blocks::FnLikeNode;
19+
use lint;
1920
use middle::cstore::{self, CrateStore, InlinedItem};
2021
use middle::{infer, subst, traits};
2122
use middle::def::Def;
@@ -323,10 +324,41 @@ impl ConstVal {
323324
}
324325
}
325326

326-
pub fn const_expr_to_pat(tcx: &TyCtxt, expr: &Expr, span: Span) -> P<hir::Pat> {
327+
pub fn const_expr_to_pat(tcx: &ty::TyCtxt, expr: &Expr, pat_id: ast::NodeId, span: Span)
328+
-> Result<P<hir::Pat>, DefId> {
329+
let pat_ty = tcx.expr_ty(expr);
330+
debug!("expr={:?} pat_ty={:?} pat_id={}", expr, pat_ty, pat_id);
331+
match pat_ty.sty {
332+
ty::TyFloat(_) => {
333+
tcx.sess.add_lint(
334+
lint::builtin::ILLEGAL_FLOATING_POINT_CONSTANT_PATTERN,
335+
pat_id,
336+
span,
337+
format!("floating point constants cannot be used in patterns"));
338+
}
339+
ty::TyEnum(adt_def, _) |
340+
ty::TyStruct(adt_def, _) => {
341+
if !tcx.has_attr(adt_def.did, "structural_match") {
342+
tcx.sess.add_lint(
343+
lint::builtin::ILLEGAL_STRUCT_OR_ENUM_CONSTANT_PATTERN,
344+
pat_id,
345+
span,
346+
format!("to use a constant of type `{}` \
347+
in a pattern, \
348+
`{}` must be annotated with `#[derive(PartialEq, Eq)]`",
349+
tcx.item_path_str(adt_def.did),
350+
tcx.item_path_str(adt_def.did)));
351+
}
352+
}
353+
_ => { }
354+
}
355+
327356
let pat = match expr.node {
328357
hir::ExprTup(ref exprs) =>
329-
PatKind::Tup(exprs.iter().map(|expr| const_expr_to_pat(tcx, &expr, span)).collect()),
358+
PatKind::Tup(try!(exprs.iter()
359+
.map(|expr| const_expr_to_pat(tcx, &expr,
360+
pat_id, span))
361+
.collect())),
330362

331363
hir::ExprCall(ref callee, ref args) => {
332364
let def = *tcx.def_map.borrow().get(&callee.id).unwrap();
@@ -336,31 +368,41 @@ pub fn const_expr_to_pat(tcx: &TyCtxt, expr: &Expr, span: Span) -> P<hir::Pat> {
336368
let path = match def.full_def() {
337369
Def::Struct(def_id) => def_to_path(tcx, def_id),
338370
Def::Variant(_, variant_did) => def_to_path(tcx, variant_did),
339-
Def::Fn(..) => return P(hir::Pat {
371+
Def::Fn(..) => return Ok(P(hir::Pat {
340372
id: expr.id,
341373
node: PatKind::Lit(P(expr.clone())),
342374
span: span,
343-
}),
375+
})),
344376
_ => unreachable!()
345377
};
346-
let pats = args.iter().map(|expr| const_expr_to_pat(tcx, &expr, span)).collect();
378+
let pats = try!(args.iter()
379+
.map(|expr| const_expr_to_pat(tcx, &**expr,
380+
pat_id, span))
381+
.collect());
347382
PatKind::TupleStruct(path, Some(pats))
348383
}
349384

350385
hir::ExprStruct(ref path, ref fields, None) => {
351-
let field_pats = fields.iter().map(|field| codemap::Spanned {
352-
span: codemap::DUMMY_SP,
353-
node: hir::FieldPat {
354-
name: field.name.node,
355-
pat: const_expr_to_pat(tcx, &field.expr, span),
356-
is_shorthand: false,
357-
},
358-
}).collect();
386+
let field_pats =
387+
try!(fields.iter()
388+
.map(|field| Ok(codemap::Spanned {
389+
span: codemap::DUMMY_SP,
390+
node: hir::FieldPat {
391+
name: field.name.node,
392+
pat: try!(const_expr_to_pat(tcx, &field.expr,
393+
pat_id, span)),
394+
is_shorthand: false,
395+
},
396+
}))
397+
.collect());
359398
PatKind::Struct(path.clone(), field_pats, false)
360399
}
361400

362401
hir::ExprVec(ref exprs) => {
363-
let pats = exprs.iter().map(|expr| const_expr_to_pat(tcx, &expr, span)).collect();
402+
let pats = try!(exprs.iter()
403+
.map(|expr| const_expr_to_pat(tcx, &expr,
404+
pat_id, span))
405+
.collect());
364406
PatKind::Vec(pats, None, hir::HirVec::new())
365407
}
366408

@@ -373,15 +415,15 @@ pub fn const_expr_to_pat(tcx: &TyCtxt, expr: &Expr, span: Span) -> P<hir::Pat> {
373415
Some(Def::AssociatedConst(def_id)) => {
374416
let substs = Some(tcx.node_id_item_substs(expr.id).substs);
375417
let (expr, _ty) = lookup_const_by_id(tcx, def_id, substs).unwrap();
376-
return const_expr_to_pat(tcx, expr, span);
418+
return const_expr_to_pat(tcx, expr, pat_id, span);
377419
},
378420
_ => unreachable!(),
379421
}
380422
}
381423

382424
_ => PatKind::Lit(P(expr.clone()))
383425
};
384-
P(hir::Pat { id: expr.id, node: pat, span: span })
426+
Ok(P(hir::Pat { id: expr.id, node: pat, span: span }))
385427
}
386428

387429
pub fn eval_const_expr(tcx: &TyCtxt, e: &Expr) -> ConstVal {

src/librustc/session/mod.rs

+7-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,13 @@ impl Session {
246246
let lint_id = lint::LintId::of(lint);
247247
let mut lints = self.lints.borrow_mut();
248248
match lints.get_mut(&id) {
249-
Some(arr) => { arr.push((lint_id, sp, msg)); return; }
249+
Some(arr) => {
250+
let tuple = (lint_id, sp, msg);
251+
if !arr.contains(&tuple) {
252+
arr.push(tuple);
253+
}
254+
return;
255+
}
250256
None => {}
251257
}
252258
lints.insert(id, vec!((lint_id, sp, msg)));

src/librustc_lint/lib.rs

+8
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,14 @@ pub fn register_builtins(store: &mut lint::LintStore, sess: Option<&Session>) {
179179
id: LintId::of(OVERLAPPING_INHERENT_IMPLS),
180180
reference: "issue #22889 <https://github.com/rust-lang/rust/issues/22889>",
181181
},
182+
FutureIncompatibleInfo {
183+
id: LintId::of(ILLEGAL_FLOATING_POINT_CONSTANT_PATTERN),
184+
reference: "RFC 1445 <https://github.com/rust-lang/rfcs/pull/1445>",
185+
},
186+
FutureIncompatibleInfo {
187+
id: LintId::of(ILLEGAL_STRUCT_OR_ENUM_CONSTANT_PATTERN),
188+
reference: "RFC 1445 <https://github.com/rust-lang/rfcs/pull/1445>",
189+
},
182190
]);
183191

184192
// We have one lint pass defined specially

src/librustc_mir/hair/cx/pattern.rs

+10-3
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,16 @@ impl<'patcx, 'cx, 'tcx> PatCx<'patcx, 'cx, 'tcx> {
9090
let substs = Some(self.cx.tcx.node_id_item_substs(pat.id).substs);
9191
match const_eval::lookup_const_by_id(self.cx.tcx, def_id, substs) {
9292
Some((const_expr, _const_ty)) => {
93-
let pat = const_eval::const_expr_to_pat(self.cx.tcx, const_expr,
94-
pat.span);
95-
return self.to_pattern(&pat);
93+
match const_eval::const_expr_to_pat(self.cx.tcx,
94+
const_expr,
95+
pat.id,
96+
pat.span) {
97+
Ok(pat) =>
98+
return self.to_pattern(&pat),
99+
Err(_) =>
100+
self.cx.tcx.sess.span_bug(
101+
pat.span, "illegal constant"),
102+
}
96103
}
97104
None => {
98105
self.cx.tcx.sess.span_bug(

src/libstd/num/f32.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1152,9 +1152,10 @@ impl f32 {
11521152
#[stable(feature = "rust1", since = "1.0.0")]
11531153
#[inline]
11541154
pub fn asinh(self) -> f32 {
1155-
match self {
1156-
NEG_INFINITY => NEG_INFINITY,
1157-
x => (x + ((x * x) + 1.0).sqrt()).ln(),
1155+
if self == NEG_INFINITY {
1156+
NEG_INFINITY
1157+
} else {
1158+
(self + ((self * self) + 1.0).sqrt()).ln()
11581159
}
11591160
}
11601161

src/libstd/num/f64.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1023,9 +1023,10 @@ impl f64 {
10231023
#[stable(feature = "rust1", since = "1.0.0")]
10241024
#[inline]
10251025
pub fn asinh(self) -> f64 {
1026-
match self {
1027-
NEG_INFINITY => NEG_INFINITY,
1028-
x => (x + ((x * x) + 1.0).sqrt()).ln(),
1026+
if self == NEG_INFINITY {
1027+
NEG_INFINITY
1028+
} else {
1029+
(self + ((self * self) + 1.0).sqrt()).ln()
10291030
}
10301031
}
10311032

src/libsyntax/codemap.rs

+25
Original file line numberDiff line numberDiff line change
@@ -1304,6 +1304,31 @@ impl CodeMap {
13041304
return a;
13051305
}
13061306

1307+
/// Check if the backtrace `subtrace` contains `suptrace` as a prefix.
1308+
pub fn more_specific_trace(&self,
1309+
mut subtrace: ExpnId,
1310+
suptrace: ExpnId)
1311+
-> bool {
1312+
loop {
1313+
if subtrace == suptrace {
1314+
return true;
1315+
}
1316+
1317+
let stop = self.with_expn_info(subtrace, |opt_expn_info| {
1318+
if let Some(expn_info) = opt_expn_info {
1319+
subtrace = expn_info.call_site.expn_id;
1320+
false
1321+
} else {
1322+
true
1323+
}
1324+
});
1325+
1326+
if stop {
1327+
return false;
1328+
}
1329+
}
1330+
}
1331+
13071332
pub fn record_expansion(&self, expn_info: ExpnInfo) -> ExpnId {
13081333
let mut expansions = self.expansions.borrow_mut();
13091334
expansions.push(expn_info);

src/libsyntax/ext/expand.rs

+36-6
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ use visit::Visitor;
3333
use std_inject;
3434

3535
use std::collections::HashSet;
36-
36+
use std::env;
3737

3838
pub fn expand_expr(e: P<ast::Expr>, fld: &mut MacroExpander) -> P<ast::Expr> {
3939
let expr_span = e.span;
@@ -1275,11 +1275,41 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
12751275
}
12761276

12771277
fn new_span(cx: &ExtCtxt, sp: Span) -> Span {
1278-
/* this discards information in the case of macro-defining macros */
1279-
Span {
1280-
lo: sp.lo,
1281-
hi: sp.hi,
1282-
expn_id: cx.backtrace(),
1278+
debug!("new_span(sp={:?})", sp);
1279+
1280+
if cx.codemap().more_specific_trace(sp.expn_id, cx.backtrace()) {
1281+
// If the span we are looking at has a backtrace that has more
1282+
// detail than our current backtrace, then we keep that
1283+
// backtrace. Honestly, I have no idea if this makes sense,
1284+
// because I have no idea why we are stripping the backtrace
1285+
// below. But the reason I made this change is because, in
1286+
// deriving, we were generating attributes with a specific
1287+
// backtrace, which was essential for `#[structural_match]` to
1288+
// be properly supported, but these backtraces were being
1289+
// stripped and replaced with a null backtrace. Sort of
1290+
// unclear why this is the case. --nmatsakis
1291+
debug!("new_span: keeping trace from {:?} because it is more specific",
1292+
sp.expn_id);
1293+
sp
1294+
} else {
1295+
// This discards information in the case of macro-defining macros.
1296+
//
1297+
// The comment above was originally added in
1298+
// b7ec2488ff2f29681fe28691d20fd2c260a9e454 in Feb 2012. I
1299+
// *THINK* the reason we are doing this is because we want to
1300+
// replace the backtrace of the macro contents with the
1301+
// backtrace that contains the macro use. But it's pretty
1302+
// unclear to me. --nmatsakis
1303+
let sp1 = Span {
1304+
lo: sp.lo,
1305+
hi: sp.hi,
1306+
expn_id: cx.backtrace(),
1307+
};
1308+
debug!("new_span({:?}) = {:?}", sp, sp1);
1309+
if sp.expn_id.into_u32() == 0 && env::var_os("NDM").is_some() {
1310+
panic!("NDM");
1311+
}
1312+
sp1
12831313
}
12841314
}
12851315

src/libsyntax/feature_gate.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,8 @@ const KNOWN_FEATURES: &'static [(&'static str, &'static str, Option<u32>, Status
109109
// to bootstrap fix for #5723.
110110
("issue_5723_bootstrap", "1.0.0", None, Accepted),
111111

112+
("structural_match", "1.8.0", Some(31434), Active),
113+
112114
// A way to temporarily opt out of opt in copy. This will *never* be accepted.
113115
("opt_out_copy", "1.0.0", None, Removed),
114116

@@ -304,6 +306,11 @@ pub const KNOWN_ATTRIBUTES: &'static [(&'static str, AttributeType, AttributeGat
304306
("link_args", Normal, Ungated),
305307
("macro_escape", Normal, Ungated),
306308

309+
// RFC #1445.
310+
("structural_match", Whitelisted, Gated("structural_match",
311+
"the semantics of constant patterns is \
312+
not yet settled")),
313+
307314
// Not used any more, but we can't feature gate it
308315
("no_stack_check", Normal, Ungated),
309316

@@ -676,7 +683,7 @@ impl<'a> Context<'a> {
676683
fn gate_feature(&self, feature: &str, span: Span, explain: &str) {
677684
let has_feature = self.has_feature(feature);
678685
debug!("gate_feature(feature = {:?}, span = {:?}); has? {}", feature, span, has_feature);
679-
if !has_feature {
686+
if !has_feature && !self.cm.span_allows_unstable(span) {
680687
emit_feature_err(self.span_handler, feature, span, GateIssue::Language, explain);
681688
}
682689
}

src/libsyntax_ext/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ crate-type = ["dylib"]
1010

1111
[dependencies]
1212
fmt_macros = { path = "../libfmt_macros" }
13+
log = { path = "../liblog" }
1314
syntax = { path = "../libsyntax" }

0 commit comments

Comments
 (0)