Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add #[default_method_body_is_const] #86857

Merged
merged 12 commits into from
Jul 13, 2021
6 changes: 6 additions & 0 deletions compiler/rustc_feature/src/builtin_attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,12 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
),

gated!(cmse_nonsecure_entry, AssumedUsed, template!(Word), experimental!(cmse_nonsecure_entry)),
// RFC 2632
gated!(
default_method_body_is_const, AssumedUsed, template!(Word), const_trait_impl,
"`default_method_body_is_const` is a temporary placeholder for declaring default bodies \
as `const`, which may be removed or renamed in the future."
),

// ==========================================================================
// Internal attributes: Stability, deprecation, and unsafe:
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_metadata/src/rmeta/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,10 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
self.get_impl_data(id).defaultness
}

fn get_impl_constness(&self, id: DefIndex) -> hir::Constness {
self.get_impl_data(id).constness
}

fn get_coerce_unsized_info(&self, id: DefIndex) -> Option<ty::adjustment::CoerceUnsizedInfo> {
self.get_impl_data(id).coerce_unsized_info
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ provide! { <'tcx> tcx, def_id, other, cdata,
is_no_builtins => { cdata.root.no_builtins }
symbol_mangling_version => { cdata.root.symbol_mangling_version }
impl_defaultness => { cdata.get_impl_defaultness(def_id.index) }
impl_constness => { cdata.get_impl_constness(def_id.index) }
reachable_non_generics => {
let reachable_non_generics = tcx
.exported_symbols(cdata.cnum)
Expand Down
11 changes: 8 additions & 3 deletions compiler/rustc_metadata/src/rmeta/encoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1412,7 +1412,7 @@ impl EncodeContext<'a, 'tcx> {
adt_def.repr,
)
}
hir::ItemKind::Impl(hir::Impl { defaultness, .. }) => {
hir::ItemKind::Impl(hir::Impl { defaultness, constness, .. }) => {
let trait_ref = self.tcx.impl_trait_ref(def_id);
let polarity = self.tcx.impl_polarity(def_id);
let parent = if let Some(trait_ref) = trait_ref {
Expand All @@ -1437,8 +1437,13 @@ impl EncodeContext<'a, 'tcx> {
}
});

let data =
ImplData { polarity, defaultness, parent_impl: parent, coerce_unsized_info };
let data = ImplData {
polarity,
defaultness,
constness,
parent_impl: parent,
coerce_unsized_info,
};

EntryKind::Impl(self.lazy(data))
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_metadata/src/rmeta/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,7 @@ struct TraitData {
#[derive(TyEncodable, TyDecodable)]
struct ImplData {
polarity: ty::ImplPolarity,
constness: hir::Constness,
defaultness: hir::Defaultness,
parent_impl: Option<DefId>,

Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/dep_graph/dep_node.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ pub type DepNode = rustc_query_system::dep_graph::DepNode<DepKind>;
// required that their size stay the same, but we don't want to change
// it inadvertently. This assert just ensures we're aware of any change.
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
static_assert_size!(DepNode, 17);
static_assert_size!(DepNode, 18);

#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
static_assert_size!(DepNode, 24);
Expand Down
10 changes: 9 additions & 1 deletion compiler/rustc_middle/src/hir/map/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use rustc_index::vec::Idx;
use rustc_span::def_id::StableCrateId;
use rustc_span::hygiene::MacroKind;
use rustc_span::source_map::Spanned;
use rustc_span::symbol::{kw, Ident, Symbol};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_span::Span;
use rustc_target::spec::abi::Abi;

Expand Down Expand Up @@ -457,6 +457,9 @@ impl<'hir> Map<'hir> {
/// Returns the `ConstContext` of the body associated with this `LocalDefId`.
///
/// Panics if `LocalDefId` does not have an associated body.
///
/// This should only be used for determining the context of a body, a return
/// value of `Some` does not always suggest that the owner of the body is `const`.
pub fn body_const_context(&self, did: LocalDefId) -> Option<ConstContext> {
let hir_id = self.local_def_id_to_hir_id(did);
let ccx = match self.body_owner_kind(hir_id) {
Expand All @@ -465,6 +468,11 @@ impl<'hir> Map<'hir> {

BodyOwnerKind::Fn if self.tcx.is_constructor(did.to_def_id()) => return None,
BodyOwnerKind::Fn if self.tcx.is_const_fn_raw(did.to_def_id()) => ConstContext::ConstFn,
BodyOwnerKind::Fn
if self.tcx.has_attr(did.to_def_id(), sym::default_method_body_is_const) =>
{
ConstContext::ConstFn
}
BodyOwnerKind::Fn | BodyOwnerKind::Closure => return None,
};

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_middle/src/query/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,10 @@ rustc_queries! {
desc { |tcx| "looking up whether `{}` is a default impl", tcx.def_path_str(def_id) }
}

query impl_constness(def_id: DefId) -> hir::Constness {
desc { |tcx| "looking up whether `{}` is a const impl", tcx.def_path_str(def_id) }
}

query check_item_well_formed(key: LocalDefId) -> () {
desc { |tcx| "checking that `{}` is well-formed", tcx.def_path_str(key.to_def_id()) }
}
Expand Down
15 changes: 9 additions & 6 deletions compiler/rustc_mir/src/const_eval/machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -235,12 +235,15 @@ impl<'mir, 'tcx> interpret::Machine<'mir, 'tcx> for CompileTimeInterpreter<'mir,
// sensitive check here. But we can at least rule out functions that are not const
// at all.
if !ecx.tcx.is_const_fn_raw(def.did) {
// Some functions we support even if they are non-const -- but avoid testing
// that for const fn!
ecx.hook_panic_fn(instance, args)?;
// We certainly do *not* want to actually call the fn
// though, so be sure we return here.
throw_unsup_format!("calling non-const function `{}`", instance)
// allow calling functions marked with #[default_method_body_is_const].
if !ecx.tcx.has_attr(def.did, sym::default_method_body_is_const) {
// Some functions we support even if they are non-const -- but avoid testing
// that for const fn!
ecx.hook_panic_fn(instance, args)?;
// We certainly do *not* want to actually call the fn
// though, so be sure we return here.
throw_unsup_format!("calling non-const function `{}`", instance)
}
}
}
// This is a const fn. Call it.
Expand Down
30 changes: 28 additions & 2 deletions compiler/rustc_mir/src/transform/check_consts/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -886,8 +886,34 @@ impl Visitor<'tcx> for Validator<'mir, 'tcx> {
}

if !tcx.is_const_fn_raw(callee) {
self.check_op(ops::FnCallNonConst);
return;
let mut permitted = false;

let callee_trait = tcx.trait_of_item(callee);
if let Some(trait_id) = callee_trait {
if tcx.has_attr(caller, sym::default_method_body_is_const) {
// permit call to non-const fn when caller has default_method_body_is_const..
if tcx.trait_of_item(caller) == callee_trait {
// ..and caller and callee are in the same trait.
permitted = true;
}
}
let mut const_impls = true;
tcx.for_each_relevant_impl(trait_id, substs.type_at(0), |imp| {
if const_impls {
if let hir::Constness::NotConst = tcx.impl_constness(imp) {
const_impls = false;
}
}
});
Comment on lines +901 to +907
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is ok for now, it's exactly what i meant. in the future we should figure out whether we can "just" generate the appropriate bounds in typeck. basically i think in a perfect world, we'd get a "trait not implemented" error, with the nonconst impl listed and suggesting to make it const.

if const_impls {
permitted = true;
}
}

if !permitted {
self.check_op(ops::FnCallNonConst);
return;
}
}

// If the `const fn` we are trying to call is not const-stable, ensure that we have
Expand Down
26 changes: 26 additions & 0 deletions compiler/rustc_passes/src/check_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ impl CheckAttrVisitor<'tcx> {
| sym::rustc_if_this_changed
| sym::rustc_then_this_would_need => self.check_rustc_dirty_clean(&attr),
sym::cmse_nonsecure_entry => self.check_cmse_nonsecure_entry(attr, span, target),
sym::default_method_body_is_const => {
self.check_default_method_body_is_const(attr, span, target)
}
_ => true,
};
// lint-only checks
Expand Down Expand Up @@ -1465,6 +1468,29 @@ impl CheckAttrVisitor<'tcx> {
}
}
}

/// default_method_body_is_const should only be applied to trait methods with default bodies.
fn check_default_method_body_is_const(
&self,
attr: &Attribute,
span: &Span,
target: Target,
) -> bool {
match target {
Target::Method(MethodKind::Trait { body: true }) => true,
_ => {
self.tcx
.sess
.struct_span_err(
attr.span,
"attribute should be applied to a trait method with body",
)
.span_label(*span, "not a trait method or missing a body")
.emit();
false
}
}
}
}

impl Visitor<'tcx> for CheckAttrVisitor<'tcx> {
Expand Down
44 changes: 26 additions & 18 deletions compiler/rustc_passes/src/check_const.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! through, but errors for structured control flow in a `const` should be emitted here.

use rustc_attr as attr;
use rustc_data_structures::stable_set::FxHashSet;
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_hir::def_id::LocalDefId;
Expand Down Expand Up @@ -85,34 +86,41 @@ impl<'tcx> hir::itemlikevisit::ItemLikeVisitor<'tcx> for CheckConstTraitVisitor<
if let hir::ItemKind::Impl(ref imp) = item.kind {
if let hir::Constness::Const = imp.constness {
let did = imp.of_trait.as_ref()?.trait_def_id()?;
let trait_fn_cnt = self
.tcx
.associated_item_def_ids(did)
.iter()
.filter(|did| {
matches!(
self.tcx.associated_item(**did),
ty::AssocItem { kind: ty::AssocKind::Fn, .. }
)
})
.count();
let mut to_implement = FxHashSet::default();

for did in self.tcx.associated_item_def_ids(did) {
if let ty::AssocItem {
kind: ty::AssocKind::Fn, ident, defaultness, ..
} = self.tcx.associated_item(*did)
{
// we can ignore functions that do not have default bodies:
// if those are unimplemented it will be catched by typeck.
if defaultness.has_value()
&& !self.tcx.has_attr(*did, sym::default_method_body_is_const)
{
to_implement.insert(ident);
}
}
}

let impl_fn_cnt = imp
for it in imp
.items
.iter()
.filter(|it| matches!(it.kind, hir::AssocItemKind::Fn { .. }))
.count();
{
to_implement.remove(&it.ident);
}

// number of trait functions unequal to functions in impl,
// meaning that one or more provided/default functions of the
// trait are used.
if trait_fn_cnt != impl_fn_cnt {
// all nonconst trait functions (not marked with #[default_method_body_is_const])
// must be implemented
if !to_implement.is_empty() {
self.tcx
.sess
.struct_span_err(
item.span,
"const trait implementations may not use default functions",
"const trait implementations may not use non-const default functions",
)
.note(&format!("`{}` not implemented", to_implement.into_iter().map(|id| id.to_string()).collect::<Vec<_>>().join("`, `")))
.emit();
}
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ symbols! {
decode,
default_alloc_error_handler,
default_lib_allocator,
default_method_body_is_const,
default_type_parameter_fallback,
default_type_params,
delay_span_bug_from_inside_query,
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_ty_utils/src/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,16 @@ fn impl_defaultness(tcx: TyCtxt<'_>, def_id: DefId) -> hir::Defaultness {
}
}

fn impl_constness(tcx: TyCtxt<'_>, def_id: DefId) -> hir::Constness {
let hir_id = tcx.hir().local_def_id_to_hir_id(def_id.expect_local());
let item = tcx.hir().expect_item(hir_id);
if let hir::ItemKind::Impl(impl_) = &item.kind {
impl_.constness
} else {
bug!("`impl_constness` called on {:?}", item);
}
}

/// Calculates the `Sized` constraint.
///
/// In fact, there are only a few options for the types in the constraint:
Expand Down Expand Up @@ -535,6 +545,7 @@ pub fn provide(providers: &mut ty::query::Providers) {
instance_def_size_estimate,
issue33140_self_ty,
impl_defaultness,
impl_constness,
conservative_is_privately_uninhabited: conservative_is_privately_uninhabited_raw,
..*providers
};
Expand Down
14 changes: 14 additions & 0 deletions src/test/ui/rfc-2632-const-trait-impl/attr-misuse.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#![feature(const_trait_impl)]
#![allow(incomplete_features)]

#[default_method_body_is_const] //~ ERROR attribute should be applied
trait A {
#[default_method_body_is_const] //~ ERROR attribute should be applied
fn no_body(self);

#[default_method_body_is_const]
fn correct_use(&self) {}
}

#[default_method_body_is_const] //~ ERROR attribute should be applied
fn main() {}
32 changes: 32 additions & 0 deletions src/test/ui/rfc-2632-const-trait-impl/attr-misuse.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
error: attribute should be applied to a trait method with body
--> $DIR/attr-misuse.rs:4:1
|
LL | #[default_method_body_is_const]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | / trait A {
LL | | #[default_method_body_is_const]
LL | | fn no_body(self);
LL | |
LL | | #[default_method_body_is_const]
LL | | fn correct_use(&self) {}
LL | | }
| |_- not a trait method or missing a body

error: attribute should be applied to a trait method with body
--> $DIR/attr-misuse.rs:13:1
|
LL | #[default_method_body_is_const]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | fn main() {}
| ------------ not a trait method or missing a body

error: attribute should be applied to a trait method with body
--> $DIR/attr-misuse.rs:6:5
|
LL | #[default_method_body_is_const]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
LL | fn no_body(self);
| ----------------- not a trait method or missing a body

error: aborting due to 3 previous errors

Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#![feature(const_trait_impl)]
#![feature(const_fn_trait_bound)] // FIXME is this needed?
#![allow(incomplete_features)]

trait ConstDefaultFn: Sized {
fn b(self);

#[default_method_body_is_const]
fn a(self) {
self.b();
}
}

struct NonConstImpl;
struct ConstImpl;

impl ConstDefaultFn for NonConstImpl {
fn b(self) {}
}

impl const ConstDefaultFn for ConstImpl {
fn b(self) {}
}

const fn test() {
NonConstImpl.a();
//~^ ERROR calls in constant functions are limited to constant functions, tuple structs and tuple variants
ConstImpl.a();
}

fn main() {}
Loading