Skip to content

Commit

Permalink
Rollup merge of rust-lang#67258 - Centril:open-ended-ranges, r=oli-obk
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JohnTitor authored Jan 10, 2020
2 parents 2d8d559 + d5598aa commit 822296b
Show file tree
Hide file tree
Showing 56 changed files with 2,183 additions and 849 deletions.
95 changes: 73 additions & 22 deletions src/librustc/ty/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,18 @@
use crate::hir::map::DefPathData;
use crate::ich::NodeIdHashingMode;
use crate::mir::interpret::{sign_extend, truncate};
use crate::ty::layout::{Integer, IntegerExt};
use crate::ty::layout::{Integer, IntegerExt, Size};
use crate::ty::query::TyCtxtAt;
use crate::ty::subst::{GenericArgKind, InternalSubsts, Subst, SubstsRef};
use crate::ty::TyKind::*;
use crate::ty::{self, DefIdTree, GenericParamDefKind, Ty, TyCtxt, TypeFoldable};
use crate::util::common::ErrorReported;
use rustc_apfloat::Float as _;
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_hir as hir;
use rustc_hir::def::DefKind;
use rustc_hir::def_id::DefId;

use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
use rustc_macros::HashStable;
use rustc_span::Span;
use std::{cmp, fmt};
Expand Down Expand Up @@ -43,41 +43,54 @@ impl<'tcx> fmt::Display for Discr<'tcx> {
}
}

fn signed_min(size: Size) -> i128 {
sign_extend(1_u128 << (size.bits() - 1), size) as i128
}

fn signed_max(size: Size) -> i128 {
i128::max_value() >> (128 - size.bits())
}

fn unsigned_max(size: Size) -> u128 {
u128::max_value() >> (128 - size.bits())
}

fn int_size_and_signed<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> (Size, bool) {
let (int, signed) = match ty.kind {
Int(ity) => (Integer::from_attr(&tcx, SignedInt(ity)), true),
Uint(uty) => (Integer::from_attr(&tcx, UnsignedInt(uty)), false),
_ => bug!("non integer discriminant"),
};
(int.size(), signed)
}

impl<'tcx> Discr<'tcx> {
/// Adds `1` to the value and wraps around if the maximum for the type is reached.
pub fn wrap_incr(self, tcx: TyCtxt<'tcx>) -> Self {
self.checked_add(tcx, 1).0
}
pub fn checked_add(self, tcx: TyCtxt<'tcx>, n: u128) -> (Self, bool) {
let (int, signed) = match self.ty.kind {
Int(ity) => (Integer::from_attr(&tcx, SignedInt(ity)), true),
Uint(uty) => (Integer::from_attr(&tcx, UnsignedInt(uty)), false),
_ => bug!("non integer discriminant"),
};

let size = int.size();
let bit_size = int.size().bits();
let shift = 128 - bit_size;
if signed {
let sext = |u| sign_extend(u, size) as i128;
let min = sext(1_u128 << (bit_size - 1));
let max = i128::max_value() >> shift;
let val = sext(self.val);
let (size, signed) = int_size_and_signed(tcx, self.ty);
let (val, oflo) = if signed {
let min = signed_min(size);
let max = signed_max(size);
let val = sign_extend(self.val, size) as i128;
assert!(n < (i128::max_value() as u128));
let n = n as i128;
let oflo = val > max - n;
let val = if oflo { min + (n - (max - val) - 1) } else { val + n };
// zero the upper bits
let val = val as u128;
let val = truncate(val, size);
(Self { val: val as u128, ty: self.ty }, oflo)
(val, oflo)
} else {
let max = u128::max_value() >> shift;
let max = unsigned_max(size);
let val = self.val;
let oflo = val > max - n;
let val = if oflo { n - (max - val) - 1 } else { val + n };
(Self { val: val, ty: self.ty }, oflo)
}
(val, oflo)
};
(Self { val, ty: self.ty }, oflo)
}
}

Expand Down Expand Up @@ -621,6 +634,44 @@ impl<'tcx> TyCtxt<'tcx> {
}

impl<'tcx> ty::TyS<'tcx> {
/// Returns the maximum value for the given numeric type (including `char`s)
/// or returns `None` if the type is not numeric.
pub fn numeric_max_val(&'tcx self, tcx: TyCtxt<'tcx>) -> Option<&'tcx ty::Const<'tcx>> {
let val = match self.kind {
ty::Int(_) | ty::Uint(_) => {
let (size, signed) = int_size_and_signed(tcx, self);
let val = if signed { signed_max(size) as u128 } else { unsigned_max(size) };
Some(val)
}
ty::Char => Some(std::char::MAX as u128),
ty::Float(fty) => Some(match fty {
ast::FloatTy::F32 => ::rustc_apfloat::ieee::Single::INFINITY.to_bits(),
ast::FloatTy::F64 => ::rustc_apfloat::ieee::Double::INFINITY.to_bits(),
}),
_ => None,
};
val.map(|v| ty::Const::from_bits(tcx, v, ty::ParamEnv::empty().and(self)))
}

/// Returns the minimum value for the given numeric type (including `char`s)
/// or returns `None` if the type is not numeric.
pub fn numeric_min_val(&'tcx self, tcx: TyCtxt<'tcx>) -> Option<&'tcx ty::Const<'tcx>> {
let val = match self.kind {
ty::Int(_) | ty::Uint(_) => {
let (size, signed) = int_size_and_signed(tcx, self);
let val = if signed { truncate(signed_min(size) as u128, size) } else { 0 };
Some(val)
}
ty::Char => Some(0),
ty::Float(fty) => Some(match fty {
ast::FloatTy::F32 => (-::rustc_apfloat::ieee::Single::INFINITY).to_bits(),
ast::FloatTy::F64 => (-::rustc_apfloat::ieee::Double::INFINITY).to_bits(),
}),
_ => None,
};
val.map(|v| ty::Const::from_bits(tcx, v, ty::ParamEnv::empty().and(self)))
}

/// Checks whether values of this type `T` are *moved* or *copied*
/// when referenced -- this amounts to a check for whether `T:
/// Copy`, but note that we **don't** consider lifetimes when
Expand Down
13 changes: 7 additions & 6 deletions src/librustc_ast_lowering/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,9 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
PatKind::Box(ref inner) => hir::PatKind::Box(self.lower_pat(inner)),
PatKind::Ref(ref inner, mutbl) => hir::PatKind::Ref(self.lower_pat(inner), mutbl),
PatKind::Range(ref e1, ref e2, Spanned { node: ref end, .. }) => hir::PatKind::Range(
self.lower_expr(e1),
self.lower_expr(e2),
self.lower_range_end(end),
e1.as_deref().map(|e| self.lower_expr(e)),
e2.as_deref().map(|e| self.lower_expr(e)),
self.lower_range_end(end, e2.is_some()),
),
PatKind::Slice(ref pats) => self.lower_pat_slice(pats),
PatKind::Rest => {
Expand Down Expand Up @@ -253,10 +253,11 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::PatKind::Wild
}

fn lower_range_end(&mut self, e: &RangeEnd) -> hir::RangeEnd {
fn lower_range_end(&mut self, e: &RangeEnd, has_end: bool) -> hir::RangeEnd {
match *e {
RangeEnd::Included(_) => hir::RangeEnd::Included,
RangeEnd::Excluded => hir::RangeEnd::Excluded,
RangeEnd::Excluded if has_end => hir::RangeEnd::Excluded,
// No end; so `X..` behaves like `RangeFrom`.
RangeEnd::Excluded | RangeEnd::Included(_) => hir::RangeEnd::Included,
}
}
}
3 changes: 3 additions & 0 deletions src/librustc_feature/active.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,9 @@ declare_features! (
/// Allows the use of `#[cfg(sanitize = "option")]`; set when -Zsanitizer is used.
(active, cfg_sanitize, "1.41.0", Some(39699), None),

/// Allows using `..X`, `..=X`, `...X`, and `X..` as a pattern.
(active, half_open_range_patterns, "1.41.0", Some(67264), None),

/// Allows using `&mut` in constant functions.
(active, const_mut_refs, "1.41.0", Some(57349), None),

Expand Down
2 changes: 1 addition & 1 deletion src/librustc_hir/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,7 @@ pub enum PatKind<'hir> {
Lit(&'hir Expr<'hir>),

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

/// A slice pattern, `[before_0, ..., before_n, (slice, after_0, ..., after_n)?]`.
///
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_hir/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,8 +766,8 @@ pub fn walk_pat<'v, V: Visitor<'v>>(visitor: &mut V, pattern: &'v Pat<'v>) {
}
PatKind::Lit(ref expression) => visitor.visit_expr(expression),
PatKind::Range(ref lower_bound, ref upper_bound, _) => {
visitor.visit_expr(lower_bound);
visitor.visit_expr(upper_bound)
walk_list!(visitor, visit_expr, lower_bound);
walk_list!(visitor, visit_expr, upper_bound);
}
PatKind::Wild => (),
PatKind::Slice(prepatterns, ref slice_pattern, postpatterns) => {
Expand Down
10 changes: 7 additions & 3 deletions src/librustc_hir/print.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1767,13 +1767,17 @@ impl<'a> State<'a> {
}
PatKind::Lit(ref e) => self.print_expr(&e),
PatKind::Range(ref begin, ref end, ref end_kind) => {
self.print_expr(&begin);
self.s.space();
if let Some(expr) = begin {
self.print_expr(expr);
self.s.space();
}
match *end_kind {
RangeEnd::Included => self.s.word("..."),
RangeEnd::Excluded => self.s.word(".."),
}
self.print_expr(&end);
if let Some(expr) = end {
self.print_expr(expr);
}
}
PatKind::Slice(ref before, ref slice, ref after) => {
self.s.word("[");
Expand Down
18 changes: 12 additions & 6 deletions src/librustc_lint/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ use syntax::ast::{self, Expr};
use syntax::attr::{self, HasAttrs};
use syntax::errors::{Applicability, DiagnosticBuilder};
use syntax::print::pprust::{self, expr_to_string};
use syntax::ptr::P;
use syntax::tokenstream::{TokenStream, TokenTree};
use syntax::visit::FnKind;

Expand Down Expand Up @@ -1309,11 +1308,13 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {

/// If `pat` is a `...` pattern, return the start and end of the range, as well as the span
/// corresponding to the ellipsis.
fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(&P<Expr>, &P<Expr>, Span)> {
fn matches_ellipsis_pat(pat: &ast::Pat) -> Option<(Option<&Expr>, &Expr, Span)> {
match &pat.kind {
PatKind::Range(a, b, Spanned { span, node: RangeEnd::Included(DotDotDot), .. }) => {
Some((a, b, *span))
}
PatKind::Range(
a,
Some(b),
Spanned { span, node: RangeEnd::Included(DotDotDot) },
) => Some((a.as_deref(), b, *span)),
_ => None,
}
}
Expand All @@ -1328,11 +1329,16 @@ impl EarlyLintPass for EllipsisInclusiveRangePatterns {
let suggestion = "use `..=` for an inclusive range";
if parenthesise {
self.node_id = Some(pat.id);
let end = expr_to_string(&end);
let replace = match start {
Some(start) => format!("&({}..={})", expr_to_string(&start), end),
None => format!("&(..={})", end),
};
let mut err = cx.struct_span_lint(ELLIPSIS_INCLUSIVE_RANGE_PATTERNS, pat.span, msg);
err.span_suggestion(
pat.span,
suggestion,
format!("&({}..={})", expr_to_string(&start), expr_to_string(&end)),
replace,
Applicability::MachineApplicable,
);
err.emit();
Expand Down
Loading

0 comments on commit 822296b

Please sign in to comment.