Skip to content

Commit

Permalink
Auto merge of #68847 - ecstatic-morse:const-impl, r=oli-obk
Browse files Browse the repository at this point in the history
Allow trait methods to be called on concrete types in a const context

This partially implements [RFC 2632](rust-lang/rfcs#2632) by const-checking methods inside an `impl const` block and allowing those methods to be called on concrete types. Calling trait methods on type parameters in a const context is not yet allowed. Implementing this will require much more work. Since we are only concerned with methods on concrete types, we are able to take advantage of the machinery in `Instance::resolve`, which is doing most of the work.

This also propagates `#[rustc_const_unstable]` from parent items to child items, making that attribute behave like `#[stable]` and `#[unstable]` do. This allows trait methods to be marked as unstably const.

cc #67792 #57563
cc @rust-lang/wg-const-eval
r? @oli-obk
  • Loading branch information
bors committed Feb 20, 2020
2 parents de362d8 + 19801b1 commit 6af388b
Show file tree
Hide file tree
Showing 22 changed files with 597 additions and 221 deletions.
8 changes: 8 additions & 0 deletions src/librustc/query/mod.rs
Expand Up @@ -279,6 +279,14 @@ rustc_queries! {
desc { |tcx| "checking if item is const fn: `{}`", tcx.def_path_str(key) }
}

/// Returns `true` if this is a const `impl`. **Do not call this function manually.**
///
/// This query caches the base data for the `is_const_impl` helper function, which also
/// takes into account stability attributes (e.g., `#[rustc_const_unstable]`).
query is_const_impl_raw(key: DefId) -> bool {
desc { |tcx| "checking if item is const impl: `{}`", tcx.def_path_str(key) }
}

query asyncness(key: DefId) -> hir::IsAsync {
desc { |tcx| "checking if the function is async: `{}`", tcx.def_path_str(key) }
}
Expand Down
10 changes: 1 addition & 9 deletions src/librustc_ast_lowering/item.rs
Expand Up @@ -66,15 +66,7 @@ impl<'a> Visitor<'a> for ItemLowerer<'a, '_, '_> {
if let Some(hir_id) = item_hir_id {
self.lctx.with_parent_item_lifetime_defs(hir_id, |this| {
let this = &mut ItemLowerer { lctx: this };
if let ItemKind::Impl { constness, ref of_trait, .. } = item.kind {
if let Const::Yes(span) = constness {
this.lctx
.diagnostic()
.struct_span_err(item.span, "const trait impls are not yet implemented")
.span_label(span, "const because of this")
.emit();
}

if let ItemKind::Impl { ref of_trait, .. } = item.kind {
this.with_trait_impl_ref(of_trait, |this| visit::walk_item(this, item));
} else {
visit::walk_item(this, item);
Expand Down
130 changes: 77 additions & 53 deletions src/librustc_mir/const_eval/fn_queries.rs
Expand Up @@ -3,7 +3,7 @@ use rustc::ty::query::Providers;
use rustc::ty::TyCtxt;
use rustc_attr as attr;
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_span::symbol::Symbol;
use rustc_target::spec::abi::Abi;

Expand Down Expand Up @@ -82,72 +82,96 @@ pub fn is_min_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
}
}

pub fn provide(providers: &mut Providers<'_>) {
/// Const evaluability whitelist is here to check evaluability at the
/// top level beforehand.
fn is_const_intrinsic(tcx: TyCtxt<'_>, def_id: DefId) -> Option<bool> {
if tcx.is_closure(def_id) {
return None;
}
pub fn is_parent_const_impl_raw(tcx: TyCtxt<'_>, hir_id: hir::HirId) -> bool {
let parent_id = tcx.hir().get_parent_did(hir_id);
if !parent_id.is_top_level_module() {
is_const_impl_raw(tcx, LocalDefId::from_def_id(parent_id))
} else {
false
}
}

match tcx.fn_sig(def_id).abi() {
Abi::RustIntrinsic | Abi::PlatformIntrinsic => {
Some(tcx.lookup_const_stability(def_id).is_some())
}
_ => None,
/// Checks whether the function has a `const` modifier or, in case it is an intrinsic, whether
/// said intrinsic is on the whitelist for being const callable.
fn is_const_fn_raw(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
let hir_id =
tcx.hir().as_local_hir_id(def_id).expect("Non-local call to local provider is_const_fn");

let node = tcx.hir().get(hir_id);

if let Some(whitelisted) = is_const_intrinsic(tcx, def_id) {
whitelisted
} else if let Some(fn_like) = FnLikeNode::from_node(node) {
if fn_like.constness() == hir::Constness::Const {
return true;
}
}

/// Checks whether the function has a `const` modifier or, in case it is an intrinsic, whether
/// said intrinsic is on the whitelist for being const callable.
fn is_const_fn_raw(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
let hir_id = tcx
.hir()
.as_local_hir_id(def_id)
.expect("Non-local call to local provider is_const_fn");
// If the function itself is not annotated with `const`, it may still be a `const fn`
// if it resides in a const trait impl.
is_parent_const_impl_raw(tcx, hir_id)
} else if let hir::Node::Ctor(_) = node {
true
} else {
false
}
}

let node = tcx.hir().get(hir_id);
/// Const evaluability whitelist is here to check evaluability at the
/// top level beforehand.
fn is_const_intrinsic(tcx: TyCtxt<'_>, def_id: DefId) -> Option<bool> {
if tcx.is_closure(def_id) {
return None;
}

if let Some(whitelisted) = is_const_intrinsic(tcx, def_id) {
whitelisted
} else if let Some(fn_like) = FnLikeNode::from_node(node) {
fn_like.constness() == hir::Constness::Const
} else if let hir::Node::Ctor(_) = node {
true
} else {
false
match tcx.fn_sig(def_id).abi() {
Abi::RustIntrinsic | Abi::PlatformIntrinsic => {
Some(tcx.lookup_const_stability(def_id).is_some())
}
_ => None,
}
}

fn is_promotable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
is_const_fn(tcx, def_id)
&& match tcx.lookup_const_stability(def_id) {
Some(stab) => {
if cfg!(debug_assertions) && stab.promotable {
let sig = tcx.fn_sig(def_id);
assert_eq!(
sig.unsafety(),
hir::Unsafety::Normal,
"don't mark const unsafe fns as promotable",
// https://github.com/rust-lang/rust/pull/53851#issuecomment-418760682
);
}
stab.promotable
/// Checks whether the given item is an `impl` that has a `const` modifier.
fn is_const_impl_raw(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool {
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id);
let node = tcx.hir().get(hir_id);
matches!(
node,
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl { constness: hir::Constness::Const, .. },
..
})
)
}

fn is_promotable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
is_const_fn(tcx, def_id)
&& match tcx.lookup_const_stability(def_id) {
Some(stab) => {
if cfg!(debug_assertions) && stab.promotable {
let sig = tcx.fn_sig(def_id);
assert_eq!(
sig.unsafety(),
hir::Unsafety::Normal,
"don't mark const unsafe fns as promotable",
// https://github.com/rust-lang/rust/pull/53851#issuecomment-418760682
);
}
None => false,
stab.promotable
}
}
None => false,
}
}

fn const_fn_is_allowed_fn_ptr(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
is_const_fn(tcx, def_id)
&& tcx
.lookup_const_stability(def_id)
.map(|stab| stab.allow_const_fn_ptr)
.unwrap_or(false)
}
fn const_fn_is_allowed_fn_ptr(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
is_const_fn(tcx, def_id)
&& tcx.lookup_const_stability(def_id).map(|stab| stab.allow_const_fn_ptr).unwrap_or(false)
}

pub fn provide(providers: &mut Providers<'_>) {
*providers = Providers {
is_const_fn_raw,
is_const_impl_raw: |tcx, def_id| is_const_impl_raw(tcx, LocalDefId::from_def_id(def_id)),
is_promotable_const_fn,
const_fn_is_allowed_fn_ptr,
..*providers
Expand Down
20 changes: 17 additions & 3 deletions src/librustc_mir/transform/check_consts/validation.rs
Expand Up @@ -4,7 +4,7 @@ use rustc::middle::lang_items;
use rustc::mir::visit::{MutatingUseContext, NonMutatingUseContext, PlaceContext, Visitor};
use rustc::mir::*;
use rustc::ty::cast::CastTy;
use rustc::ty::{self, TyCtxt};
use rustc::ty::{self, Instance, InstanceDef, TyCtxt};
use rustc_errors::struct_span_err;
use rustc_hir::{def_id::DefId, HirId};
use rustc_index::bit_set::BitSet;
Expand Down Expand Up @@ -502,8 +502,8 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
TerminatorKind::Call { func, .. } => {
let fn_ty = func.ty(*self.body, self.tcx);

let def_id = match fn_ty.kind {
ty::FnDef(def_id, _) => def_id,
let (def_id, substs) = match fn_ty.kind {
ty::FnDef(def_id, substs) => (def_id, substs),

ty::FnPtr(_) => {
self.check_op(ops::FnCallIndirect);
Expand All @@ -520,6 +520,20 @@ impl Visitor<'tcx> for Validator<'_, 'mir, 'tcx> {
return;
}

// See if this is a trait method for a concrete type whose impl of that trait is
// `const`.
if self.tcx.features().const_trait_impl {
let instance = Instance::resolve(self.tcx, self.param_env, def_id, substs);
debug!("Resolving ({:?}) -> {:?}", def_id, instance);
if let Some(func) = instance {
if let InstanceDef::Item(def_id) = func.def {
if is_const_fn(self.tcx, def_id) {
return;
}
}
}
}

if is_lang_panic_fn(self.tcx, def_id) {
self.check_op(ops::Panic);
} else if let Some(feature) = is_unstable_const_fn(self.tcx, def_id) {
Expand Down
8 changes: 8 additions & 0 deletions src/librustc_mir/transform/qualify_min_const_fn.rs
Expand Up @@ -10,6 +10,14 @@ use std::borrow::Cow;
type McfResult = Result<(), (Span, Cow<'static, str>)>;

pub fn is_min_const_fn(tcx: TyCtxt<'tcx>, def_id: DefId, body: &'a Body<'tcx>) -> McfResult {
// Prevent const trait methods from being annotated as `stable`.
if tcx.features().staged_api {
let hir_id = tcx.hir().as_local_hir_id(def_id).unwrap();
if crate::const_eval::is_parent_const_impl_raw(tcx, hir_id) {
return Err((body.span, "trait methods cannot be stable const fn".into()));
}
}

let mut current = def_id;
loop {
let predicates = tcx.predicates_of(current);
Expand Down

0 comments on commit 6af388b

Please sign in to comment.