Skip to content

Commit

Permalink
Auto merge of #63770 - oli-obk:allow_internal_unstable, r=Centril
Browse files Browse the repository at this point in the history
Stabilize `str::len`, `[T]::len` and `str::as_bytes` as const fn

r? @Centril

cc @RalfJung

This also introduces a scheme for making certain feature gates legal in stabilized const fns
  • Loading branch information
bors committed Sep 24, 2019
2 parents 7fdea7a + 7767e7f commit 6ef275e
Show file tree
Hide file tree
Showing 12 changed files with 114 additions and 78 deletions.
6 changes: 3 additions & 3 deletions src/libcore/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,9 @@
#![feature(rtm_target_feature)]
#![feature(f16c_target_feature)]
#![feature(hexagon_target_feature)]
#![feature(const_slice_len)]
#![feature(const_str_as_bytes)]
#![feature(const_str_len)]
#![cfg_attr(bootstrap, feature(const_slice_len))]
#![cfg_attr(bootstrap, feature(const_str_as_bytes))]
#![cfg_attr(bootstrap, feature(const_str_len))]
#![feature(const_int_conversion)]
#![feature(const_transmute)]
#![feature(non_exhaustive)]
Expand Down
6 changes: 4 additions & 2 deletions src/libcore/slice/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ impl<T> [T] {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[rustc_const_unstable(feature = "const_slice_len")]
#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_slice_len"))]
// SAFETY: const sound because we transmute out the length field as a usize (which it must be)
#[cfg_attr(not(bootstrap), allow_internal_unstable(const_fn_union))]
pub const fn len(&self) -> usize {
unsafe {
crate::ptr::Repr { rust: self }.raw.len
Expand All @@ -79,7 +81,7 @@ impl<T> [T] {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[rustc_const_unstable(feature = "const_slice_len")]
#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_slice_len"))]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
Expand Down
8 changes: 5 additions & 3 deletions src/libcore/str/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2090,7 +2090,7 @@ impl str {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[inline]
#[rustc_const_unstable(feature = "const_str_len")]
#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_str_len"))]
pub const fn len(&self) -> usize {
self.as_bytes().len()
}
Expand All @@ -2110,7 +2110,7 @@ impl str {
/// ```
#[inline]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_str_len")]
#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_str_len"))]
pub const fn is_empty(&self) -> bool {
self.len() == 0
}
Expand Down Expand Up @@ -2168,7 +2168,9 @@ impl str {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
#[inline(always)]
#[rustc_const_unstable(feature="const_str_as_bytes")]
#[cfg_attr(bootstrap, rustc_const_unstable(feature = "const_str_as_bytes"))]
// SAFETY: const sound because we transmute two types with the same layout
#[cfg_attr(not(bootstrap), allow_internal_unstable(const_fn_union))]
pub const fn as_bytes(&self) -> &[u8] {
#[repr(C)]
union Slices<'a> {
Expand Down
4 changes: 2 additions & 2 deletions src/librustc_mir/transform/check_unsafety.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,15 +305,15 @@ impl<'a, 'tcx> Visitor<'tcx> for UnsafetyChecker<'a, 'tcx> {
"assignment to non-`Copy` union field",
"the previous content of the field will be dropped, which \
causes undefined behavior if the field was not properly \
initialized", UnsafetyViolationKind::General)
initialized", UnsafetyViolationKind::GeneralAndConstFn)
} else {
// write to non-move union, safe
}
} else {
self.require_unsafe("access to union field",
"the field may not be properly initialized: using \
uninitialized data will cause undefined behavior",
UnsafetyViolationKind::General)
UnsafetyViolationKind::GeneralAndConstFn)
}
}
}
Expand Down
81 changes: 57 additions & 24 deletions src/librustc_mir/transform/qualify_min_const_fn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ use rustc::ty::{self, Predicate, Ty, TyCtxt, adjustment::{PointerCast}};
use rustc_target::spec::abi;
use std::borrow::Cow;
use syntax_pos::Span;
use syntax::symbol::{sym, Symbol};
use syntax::attr;

type McfResult = Result<(), (Span, Cow<'static, str>)>;

Expand Down Expand Up @@ -67,9 +69,9 @@ pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -
)?;

for bb in body.basic_blocks() {
check_terminator(tcx, body, bb.terminator())?;
check_terminator(tcx, body, def_id, bb.terminator())?;
for stmt in &bb.statements {
check_statement(tcx, body, stmt)?;
check_statement(tcx, body, def_id, stmt)?;
}
}
Ok(())
Expand Down Expand Up @@ -121,16 +123,17 @@ fn check_ty(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>, span: Span, fn_def_id: DefId) -> Mc

fn check_rvalue(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
body: &Body<'tcx>,
def_id: DefId,
rvalue: &Rvalue<'tcx>,
span: Span,
) -> McfResult {
match rvalue {
Rvalue::Repeat(operand, _) | Rvalue::Use(operand) => {
check_operand(operand, span)
check_operand(tcx, operand, span, def_id, body)
}
Rvalue::Len(place) | Rvalue::Discriminant(place) | Rvalue::Ref(_, _, place) => {
check_place(place, span)
check_place(tcx, place, span, def_id, body)
}
Rvalue::Cast(CastKind::Misc, operand, cast_ty) => {
use rustc::ty::cast::CastTy;
Expand All @@ -144,11 +147,11 @@ fn check_rvalue(
(CastTy::RPtr(_), CastTy::Float) => bug!(),
(CastTy::RPtr(_), CastTy::Int(_)) => bug!(),
(CastTy::Ptr(_), CastTy::RPtr(_)) => bug!(),
_ => check_operand(operand, span),
_ => check_operand(tcx, operand, span, def_id, body),
}
}
Rvalue::Cast(CastKind::Pointer(PointerCast::MutToConstPointer), operand, _) => {
check_operand(operand, span)
check_operand(tcx, operand, span, def_id, body)
}
Rvalue::Cast(CastKind::Pointer(PointerCast::UnsafeFnPointer), _, _) |
Rvalue::Cast(CastKind::Pointer(PointerCast::ClosureFnPointer(_)), _, _) |
Expand All @@ -162,8 +165,8 @@ fn check_rvalue(
)),
// binops are fine on integers
Rvalue::BinaryOp(_, lhs, rhs) | Rvalue::CheckedBinaryOp(_, lhs, rhs) => {
check_operand(lhs, span)?;
check_operand(rhs, span)?;
check_operand(tcx, lhs, span, def_id, body)?;
check_operand(tcx, rhs, span, def_id, body)?;
let ty = lhs.ty(body, tcx);
if ty.is_integral() || ty.is_bool() || ty.is_char() {
Ok(())
Expand All @@ -182,7 +185,7 @@ fn check_rvalue(
Rvalue::UnaryOp(_, operand) => {
let ty = operand.ty(body, tcx);
if ty.is_integral() || ty.is_bool() {
check_operand(operand, span)
check_operand(tcx, operand, span, def_id, body)
} else {
Err((
span,
Expand All @@ -192,7 +195,7 @@ fn check_rvalue(
}
Rvalue::Aggregate(_, operands) => {
for operand in operands {
check_operand(operand, span)?;
check_operand(tcx, operand, span, def_id, body)?;
}
Ok(())
}
Expand All @@ -201,21 +204,22 @@ fn check_rvalue(

fn check_statement(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
body: &Body<'tcx>,
def_id: DefId,
statement: &Statement<'tcx>,
) -> McfResult {
let span = statement.source_info.span;
match &statement.kind {
StatementKind::Assign(box(place, rval)) => {
check_place(place, span)?;
check_rvalue(tcx, body, rval, span)
check_place(tcx, place, span, def_id, body)?;
check_rvalue(tcx, body, def_id, rval, span)
}

StatementKind::FakeRead(FakeReadCause::ForMatchedPlace, _) => {
Err((span, "loops and conditional expressions are not stable in const fn".into()))
}

StatementKind::FakeRead(_, place) => check_place(place, span),
StatementKind::FakeRead(_, place) => check_place(tcx, place, span, def_id, body),

// just an assignment
StatementKind::SetDiscriminant { .. } => Ok(()),
Expand All @@ -234,30 +238,48 @@ fn check_statement(
}

fn check_operand(
tcx: TyCtxt<'tcx>,
operand: &Operand<'tcx>,
span: Span,
def_id: DefId,
body: &Body<'tcx>
) -> McfResult {
match operand {
Operand::Move(place) | Operand::Copy(place) => {
check_place(place, span)
check_place(tcx, place, span, def_id, body)
}
Operand::Constant(_) => Ok(()),
}
}

fn check_place(
tcx: TyCtxt<'tcx>,
place: &Place<'tcx>,
span: Span,
def_id: DefId,
body: &Body<'tcx>
) -> McfResult {
for elem in place.projection.iter() {
let mut cursor = &*place.projection;
while let [proj_base @ .., elem] = cursor {
cursor = proj_base;
match elem {
ProjectionElem::Downcast(..) => {
return Err((span, "`match` or `if let` in `const fn` is unstable".into()));
}
ProjectionElem::Field(..) => {
let base_ty = Place::ty_from(&place.base, &proj_base, body, tcx).ty;
if let Some(def) = base_ty.ty_adt_def() {
// No union field accesses in `const fn`
if def.is_union() {
if !feature_allowed(tcx, def_id, sym::const_fn_union) {
return Err((span, "accessing union fields is unstable".into()));
}
}
}
}
ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Subslice { .. }
| ProjectionElem::Deref
| ProjectionElem::Field(..)
| ProjectionElem::Index(_) => {}
}
}
Expand All @@ -271,9 +293,20 @@ fn check_place(
}
}

/// Returns whether `allow_internal_unstable(..., <feature_gate>, ...)` is present.
fn feature_allowed(
tcx: TyCtxt<'tcx>,
def_id: DefId,
feature_gate: Symbol,
) -> bool {
attr::allow_internal_unstable(&tcx.get_attrs(def_id), &tcx.sess.diagnostic())
.map_or(false, |mut features| features.any(|name| name == feature_gate))
}

fn check_terminator(
tcx: TyCtxt<'tcx>,
body: &'a Body<'tcx>,
def_id: DefId,
terminator: &Terminator<'tcx>,
) -> McfResult {
let span = terminator.source_info.span;
Expand All @@ -283,11 +316,11 @@ fn check_terminator(
| TerminatorKind::Resume => Ok(()),

TerminatorKind::Drop { location, .. } => {
check_place(location, span)
check_place(tcx, location, span, def_id, body)
}
TerminatorKind::DropAndReplace { location, value, .. } => {
check_place(location, span)?;
check_operand(value, span)
check_place(tcx, location, span, def_id, body)?;
check_operand(tcx, value, span, def_id, body)
},

TerminatorKind::FalseEdges { .. } | TerminatorKind::SwitchInt { .. } => Err((
Expand Down Expand Up @@ -339,10 +372,10 @@ fn check_terminator(
)),
}

check_operand(func, span)?;
check_operand(tcx, func, span, def_id, body)?;

for arg in args {
check_operand(arg, span)?;
check_operand(tcx, arg, span, def_id, body)?;
}
Ok(())
} else {
Expand All @@ -356,7 +389,7 @@ fn check_terminator(
msg: _,
target: _,
cleanup: _,
} => check_operand(cond, span),
} => check_operand(tcx, cond, span, def_id, body),

TerminatorKind::FalseUnwind { .. } => {
Err((span, "loops are not allowed in const fn".into()))
Expand Down
24 changes: 24 additions & 0 deletions src/libsyntax/attr/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,30 @@ pub fn find_by_name(attrs: &[Attribute], name: Symbol) -> Option<&Attribute> {
attrs.iter().find(|attr| attr.check_name(name))
}

pub fn allow_internal_unstable<'a>(
attrs: &[Attribute],
span_diagnostic: &'a errors::Handler,
) -> Option<impl Iterator<Item = Symbol> + 'a> {
find_by_name(attrs, sym::allow_internal_unstable).and_then(|attr| {
attr.meta_item_list().or_else(|| {
span_diagnostic.span_err(
attr.span,
"allow_internal_unstable expects list of feature names"
);
None
}).map(|features| features.into_iter().filter_map(move |it| {
let name = it.ident().map(|ident| ident.name);
if name.is_none() {
span_diagnostic.span_err(
it.span(),
"`allow_internal_unstable` expects feature names",
)
}
name
}))
})
}

pub fn filter_by_name(attrs: &[Attribute], name: Symbol)
-> impl Iterator<Item=&Attribute> {
attrs.iter().filter(move |attr| attr.check_name(name))
Expand Down
30 changes: 3 additions & 27 deletions src/libsyntax/ext/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -762,33 +762,9 @@ impl SyntaxExtension {
name: Name,
attrs: &[ast::Attribute],
) -> SyntaxExtension {
let allow_internal_unstable =
attr::find_by_name(attrs, sym::allow_internal_unstable).map(|attr| {
attr.meta_item_list()
.map(|list| {
list.iter()
.filter_map(|it| {
let name = it.ident().map(|ident| ident.name);
if name.is_none() {
sess.span_diagnostic.span_err(
it.span(), "allow internal unstable expects feature names"
)
}
name
})
.collect::<Vec<Symbol>>()
.into()
})
.unwrap_or_else(|| {
sess.span_diagnostic.span_warn(
attr.span,
"allow_internal_unstable expects list of feature names. In the future \
this will become a hard error. Please use `allow_internal_unstable(\
foo, bar)` to only allow the `foo` and `bar` features",
);
vec![sym::allow_internal_unstable_backcompat_hack].into()
})
});
let allow_internal_unstable = attr::allow_internal_unstable(
&attrs, &sess.span_diagnostic,
).map(|features| features.collect::<Vec<Symbol>>().into());

let mut local_inner_macros = false;
if let Some(macro_export) = attr::find_by_name(attrs, sym::macro_export) {
Expand Down
12 changes: 10 additions & 2 deletions src/test/ui/consts/const-eval/strlen.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
// run-pass

#![feature(const_str_len, const_str_as_bytes)]

const S: &str = "foo";
pub const B: &[u8] = S.as_bytes();
pub const C: usize = B.len();
pub const D: bool = B.is_empty();
pub const E: bool = S.is_empty();
pub const F: usize = S.len();

pub fn foo() -> [u8; S.len()] {
let mut buf = [0; S.len()];
Expand All @@ -20,4 +22,10 @@ fn main() {
assert_eq!(LEN, S.len());
assert_eq!(B, foo());
assert_eq!(B, b"foo");
assert_eq!(C, 3);
assert_eq!(F, 3);
assert!(!D);
assert!(!E);
const EMPTY: bool = "".is_empty();
assert!(EMPTY);
}
Loading

0 comments on commit 6ef275e

Please sign in to comment.