Skip to content

Commit b2a2869

Browse files
authored
Rollup merge of rust-lang#67258 - Centril:open-ended-ranges, r=oli-obk
Introduce `X..`, `..X`, and `..=X` range patterns Tracking issue: rust-lang#67264 Feature gate: `#![feature(half_open_range_patterns)]` --------------------------- In this PR, we introduce range-from (`X..`), range-to (`..X`), and range-to-inclusive (`..=X`) patterns. These correspond to the `RangeFrom`, `RangeTo`, and `RangeToInclusive` expression forms introduced with the same syntaxes. The correspondence is both syntactic and semantic (in the sense that e.g. a `X..` pattern matching on a scrutinee `s` holds exactly when `(X..).contains(&s)` holds). --------------------------- Noteworthy: - The compiler complexity added with this PR is around 10 lines (discounting new tests, which account for the large PR size). - `...X` is accepted syntactically with the same meaning as `..=X`. This is done primarily to simplify and unify the implementation & spec. If-and-when we decide to make `X...Y` a hard error on a new edition, we can do the same for `...X` patterns as well. - `X...` and `X..=` is rejected syntactically just like it is for the expression equivalents. We should perhaps make these into semantic restrictions (cc @petrochenkov). - In HAIR, these half-open ranges are represented by inserting the max/min values for the approprate types. That is, `X..` where `X: u8` would become `X..=u8::MAX` in HAIR (note the `..=` since `RangeFrom` includes the end). - Exhaustive integer / char matching does not (yet) allow for e.g. exhaustive matching on `0usize..` or `..5usize | 5..` (same idea for `isize`). This would be a substantially more invasive change, and could be added in some other PR. - The issues with slice pattern syntax has been resolved as we decided to use `..` to mean a "rest-pattern" and `[xs @ ..]` to bind the rest to a name in a slice pattern. - Like with rust-lang#35712, which provided `X..Y` range patterns, this is not yet backed up by an RFC. I'm providing this experimental implementation now to have something concrete to discuss. I would be happy to provide an RFC for this PR as well as for rust-lang#35712 to finalize and confirm the ideas with the larger community. Closes rust-lang/rfcs#947. --------------------------- r? @varkor cc @matthewjasper @oli-obk I would recommend reviewing this (in particular HAIR-lowering and pattern parsing changes) with whitespace changes ignored.
2 parents 2d8d559 + d5598aa commit b2a2869

File tree

56 files changed

+2183
-849
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+2183
-849
lines changed

src/librustc/ty/util.rs

+73-22
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,18 @@
33
use crate::hir::map::DefPathData;
44
use crate::ich::NodeIdHashingMode;
55
use crate::mir::interpret::{sign_extend, truncate};
6-
use crate::ty::layout::{Integer, IntegerExt};
6+
use crate::ty::layout::{Integer, IntegerExt, Size};
77
use crate::ty::query::TyCtxtAt;
88
use crate::ty::subst::{GenericArgKind, InternalSubsts, Subst, SubstsRef};
99
use crate::ty::TyKind::*;
1010
use crate::ty::{self, DefIdTree, GenericParamDefKind, Ty, TyCtxt, TypeFoldable};
1111
use crate::util::common::ErrorReported;
12+
use rustc_apfloat::Float as _;
13+
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
14+
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
1215
use rustc_hir as hir;
1316
use rustc_hir::def::DefKind;
1417
use rustc_hir::def_id::DefId;
15-
16-
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
17-
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
1818
use rustc_macros::HashStable;
1919
use rustc_span::Span;
2020
use std::{cmp, fmt};
@@ -43,41 +43,54 @@ impl<'tcx> fmt::Display for Discr<'tcx> {
4343
}
4444
}
4545

46+
fn signed_min(size: Size) -> i128 {
47+
sign_extend(1_u128 << (size.bits() - 1), size) as i128
48+
}
49+
50+
fn signed_max(size: Size) -> i128 {
51+
i128::max_value() >> (128 - size.bits())
52+
}
53+
54+
fn unsigned_max(size: Size) -> u128 {
55+
u128::max_value() >> (128 - size.bits())
56+
}
57+
58+
fn int_size_and_signed<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> (Size, bool) {
59+
let (int, signed) = match ty.kind {
60+
Int(ity) => (Integer::from_attr(&tcx, SignedInt(ity)), true),
61+
Uint(uty) => (Integer::from_attr(&tcx, UnsignedInt(uty)), false),
62+
_ => bug!("non integer discriminant"),
63+
};
64+
(int.size(), signed)
65+
}
66+
4667
impl<'tcx> Discr<'tcx> {
4768
/// Adds `1` to the value and wraps around if the maximum for the type is reached.
4869
pub fn wrap_incr(self, tcx: TyCtxt<'tcx>) -> Self {
4970
self.checked_add(tcx, 1).0
5071
}
5172
pub fn checked_add(self, tcx: TyCtxt<'tcx>, n: u128) -> (Self, bool) {
52-
let (int, signed) = match self.ty.kind {
53-
Int(ity) => (Integer::from_attr(&tcx, SignedInt(ity)), true),
54-
Uint(uty) => (Integer::from_attr(&tcx, UnsignedInt(uty)), false),
55-
_ => bug!("non integer discriminant"),
56-
};
57-
58-
let size = int.size();
59-
let bit_size = int.size().bits();
60-
let shift = 128 - bit_size;
61-
if signed {
62-
let sext = |u| sign_extend(u, size) as i128;
63-
let min = sext(1_u128 << (bit_size - 1));
64-
let max = i128::max_value() >> shift;
65-
let val = sext(self.val);
73+
let (size, signed) = int_size_and_signed(tcx, self.ty);
74+
let (val, oflo) = if signed {
75+
let min = signed_min(size);
76+
let max = signed_max(size);
77+
let val = sign_extend(self.val, size) as i128;
6678
assert!(n < (i128::max_value() as u128));
6779
let n = n as i128;
6880
let oflo = val > max - n;
6981
let val = if oflo { min + (n - (max - val) - 1) } else { val + n };
7082
// zero the upper bits
7183
let val = val as u128;
7284
let val = truncate(val, size);
73-
(Self { val: val as u128, ty: self.ty }, oflo)
85+
(val, oflo)
7486
} else {
75-
let max = u128::max_value() >> shift;
87+
let max = unsigned_max(size);
7688
let val = self.val;
7789
let oflo = val > max - n;
7890
let val = if oflo { n - (max - val) - 1 } else { val + n };
79-
(Self { val: val, ty: self.ty }, oflo)
80-
}
91+
(val, oflo)
92+
};
93+
(Self { val, ty: self.ty }, oflo)
8194
}
8295
}
8396

@@ -621,6 +634,44 @@ impl<'tcx> TyCtxt<'tcx> {
621634
}
622635

623636
impl<'tcx> ty::TyS<'tcx> {
637+
/// Returns the maximum value for the given numeric type (including `char`s)
638+
/// or returns `None` if the type is not numeric.
639+
pub fn numeric_max_val(&'tcx self, tcx: TyCtxt<'tcx>) -> Option<&'tcx ty::Const<'tcx>> {
640+
let val = match self.kind {
641+
ty::Int(_) | ty::Uint(_) => {
642+
let (size, signed) = int_size_and_signed(tcx, self);
643+
let val = if signed { signed_max(size) as u128 } else { unsigned_max(size) };
644+
Some(val)
645+
}
646+
ty::Char => Some(std::char::MAX as u128),
647+
ty::Float(fty) => Some(match fty {
648+
ast::FloatTy::F32 => ::rustc_apfloat::ieee::Single::INFINITY.to_bits(),
649+
ast::FloatTy::F64 => ::rustc_apfloat::ieee::Double::INFINITY.to_bits(),
650+
}),
651+
_ => None,
652+
};
653+
val.map(|v| ty::Const::from_bits(tcx, v, ty::ParamEnv::empty().and(self)))
654+
}
655+
656+
/// Returns the minimum value for the given numeric type (including `char`s)
657+
/// or returns `None` if the type is not numeric.
658+
pub fn numeric_min_val(&'tcx self, tcx: TyCtxt<'tcx>) -> Option<&'tcx ty::Const<'tcx>> {
659+
let val = match self.kind {
660+
ty::Int(_) | ty::Uint(_) => {
661+
let (size, signed) = int_size_and_signed(tcx, self);
662+
let val = if signed { truncate(signed_min(size) as u128, size) } else { 0 };
663+
Some(val)
664+
}
665+
ty::Char => Some(0),
666+
ty::Float(fty) => Some(match fty {
667+
ast::FloatTy::F32 => (-::rustc_apfloat::ieee::Single::INFINITY).to_bits(),
668+
ast::FloatTy::F64 => (-::rustc_apfloat::ieee::Double::INFINITY).to_bits(),
669+
}),
670+
_ => None,
671+
};
672+
val.map(|v| ty::Const::from_bits(tcx, v, ty::ParamEnv::empty().and(self)))
673+
}
674+
624675
/// Checks whether values of this type `T` are *moved* or *copied*
625676
/// when referenced -- this amounts to a check for whether `T:
626677
/// Copy`, but note that we **don't** consider lifetimes when

src/librustc_ast_lowering/pat.rs

+7-6
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
6565
PatKind::Box(ref inner) => hir::PatKind::Box(self.lower_pat(inner)),
6666
PatKind::Ref(ref inner, mutbl) => hir::PatKind::Ref(self.lower_pat(inner), mutbl),
6767
PatKind::Range(ref e1, ref e2, Spanned { node: ref end, .. }) => hir::PatKind::Range(
68-
self.lower_expr(e1),
69-
self.lower_expr(e2),
70-
self.lower_range_end(end),
68+
e1.as_deref().map(|e| self.lower_expr(e)),
69+
e2.as_deref().map(|e| self.lower_expr(e)),
70+
self.lower_range_end(end, e2.is_some()),
7171
),
7272
PatKind::Slice(ref pats) => self.lower_pat_slice(pats),
7373
PatKind::Rest => {
@@ -253,10 +253,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
253253
hir::PatKind::Wild
254254
}
255255

256-
fn lower_range_end(&mut self, e: &RangeEnd) -> hir::RangeEnd {
256+
fn lower_range_end(&mut self, e: &RangeEnd, has_end: bool) -> hir::RangeEnd {
257257
match *e {
258-
RangeEnd::Included(_) => hir::RangeEnd::Included,
259-
RangeEnd::Excluded => hir::RangeEnd::Excluded,
258+
RangeEnd::Excluded if has_end => hir::RangeEnd::Excluded,
259+
// No end; so `X..` behaves like `RangeFrom`.
260+
RangeEnd::Excluded | RangeEnd::Included(_) => hir::RangeEnd::Included,
260261
}
261262
}
262263
}

src/librustc_feature/active.rs

+3
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,9 @@ declare_features! (
534534
/// Allows the use of `#[cfg(sanitize = "option")]`; set when -Zsanitizer is used.
535535
(active, cfg_sanitize, "1.41.0", Some(39699), None),
536536

537+
/// Allows using `..X`, `..=X`, `...X`, and `X..` as a pattern.
538+
(active, half_open_range_patterns, "1.41.0", Some(67264), None),
539+
537540
/// Allows using `&mut` in constant functions.
538541
(active, const_mut_refs, "1.41.0", Some(57349), None),
539542

src/librustc_hir/hir.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,7 @@ pub enum PatKind<'hir> {
905905
Lit(&'hir Expr<'hir>),
906906

907907
/// A range pattern (e.g., `1..=2` or `1..2`).
908-
Range(&'hir Expr<'hir>, &'hir Expr<'hir>, RangeEnd),
908+
Range(Option<&'hir Expr<'hir>>, Option<&'hir Expr<'hir>>, RangeEnd),
909909

910910
/// A slice pattern, `[before_0, ..., before_n, (slice, after_0, ..., after_n)?]`.
911911
///

src/librustc_hir/intravisit.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -766,8 +766,8 @@ pub fn walk_pat<'v, V: Visitor<'v>>(visitor: &mut V, pattern: &'v Pat<'v>) {
766766
}
767767
PatKind::Lit(ref expression) => visitor.visit_expr(expression),
768768
PatKind::Range(ref lower_bound, ref upper_bound, _) => {
769-
visitor.visit_expr(lower_bound);
770-
visitor.visit_expr(upper_bound)
769+
walk_list!(visitor, visit_expr, lower_bound);
770+
walk_list!(visitor, visit_expr, upper_bound);
771771
}
772772
PatKind::Wild => (),
773773
PatKind::Slice(prepatterns, ref slice_pattern, postpatterns) => {

src/librustc_hir/print.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -1767,13 +1767,17 @@ impl<'a> State<'a> {
17671767
}
17681768
PatKind::Lit(ref e) => self.print_expr(&e),
17691769
PatKind::Range(ref begin, ref end, ref end_kind) => {
1770-
self.print_expr(&begin);
1771-
self.s.space();
1770+
if let Some(expr) = begin {
1771+
self.print_expr(expr);
1772+
self.s.space();
1773+
}
17721774
match *end_kind {
17731775
RangeEnd::Included => self.s.word("..."),
17741776
RangeEnd::Excluded => self.s.word(".."),
17751777
}
1776-
self.print_expr(&end);
1778+
if let Some(expr) = end {
1779+
self.print_expr(expr);
1780+
}
17771781
}
17781782
PatKind::Slice(ref before, ref slice, ref after) => {
17791783
self.s.word("[");

src/librustc_lint/builtin.rs

+12-6
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ use syntax::ast::{self, Expr};
4646
use syntax::attr::{self, HasAttrs};
4747
use syntax::errors::{Applicability, DiagnosticBuilder};
4848
use syntax::print::pprust::{self, expr_to_string};
49-
use syntax::ptr::P;
5049
use syntax::tokenstream::{TokenStream, TokenTree};
5150
use syntax::visit::FnKind;
5251

@@ -1309,11 +1308,13 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
13091308

13101309
/// If `pat` is a `...` pattern, return the start and end of the range, as well as the span
13111310
/// corresponding to the ellipsis.
1312-
fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(&P<Expr>, &P<Expr>, Span)> {
1311+
fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> {
13131312
match &pat.kind {
1314-
PatKind::Range(a, b, Spanned { span, node: RangeEnd::Included(DotDotDot), .. }) => {
1315-
Some((a, b, *span))
1316-
}
1313+
PatKind::Range(
1314+
a,
1315+
Some(b),
1316+
Spanned { span, node: RangeEnd::Included(DotDotDot) },
1317+
) => Some((a.as_deref(), b, *span)),
13171318
_ => None,
13181319
}
13191320
}
@@ -1328,11 +1329,16 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
13281329
let suggestion = "use `..=` for an inclusive range";
13291330
if parenthesise {
13301331
self.node_id = Some(pat.id);
1332+
let end = expr_to_string(&end);
1333+
let replace = match start {
1334+
Some(start) => format!("&({}..={})", expr_to_string(&start), end),
1335+
None => format!("&(..={})", end),
1336+
};
13311337
let mut err = cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, msg);
13321338
err.span_suggestion(
13331339
pat.span,
13341340
suggestion,
1335-
format!("&({}..={})", expr_to_string(&start), expr_to_string(&end)),
1341+
replace,
13361342
Applicability::MachineApplicable,
13371343
);
13381344
err.emit();

0 commit comments

Comments
 (0)