diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index 259a6328a22f6..24a5a007dedf6 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -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: diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 3860d0daadf98..344c5e8ee6187 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -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 { self.get_impl_data(id).coerce_unsized_info } diff --git a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs index 68cf4304b7165..c0e85d82f7e27 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder/cstore_impl.rs @@ -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) diff --git a/compiler/rustc_metadata/src/rmeta/encoder.rs b/compiler/rustc_metadata/src/rmeta/encoder.rs index 2478d15e554c8..5b10d53159ebd 100644 --- a/compiler/rustc_metadata/src/rmeta/encoder.rs +++ b/compiler/rustc_metadata/src/rmeta/encoder.rs @@ -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 { @@ -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)) } diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 3793058062347..0d67ff32a3f3e 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -390,6 +390,7 @@ struct TraitData { #[derive(TyEncodable, TyDecodable)] struct ImplData { polarity: ty::ImplPolarity, + constness: hir::Constness, defaultness: hir::Defaultness, parent_impl: Option, diff --git a/compiler/rustc_middle/src/dep_graph/dep_node.rs b/compiler/rustc_middle/src/dep_graph/dep_node.rs index aa54d1ae7b9d1..8476929eaeced 100644 --- a/compiler/rustc_middle/src/dep_graph/dep_node.rs +++ b/compiler/rustc_middle/src/dep_graph/dep_node.rs @@ -285,7 +285,7 @@ pub type DepNode = rustc_query_system::dep_graph::DepNode; // 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); diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs index 07b39c97c492a..f37ccf1787dd6 100644 --- a/compiler/rustc_middle/src/hir/map/mod.rs +++ b/compiler/rustc_middle/src/hir/map/mod.rs @@ -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; @@ -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 { let hir_id = self.local_def_id_to_hir_id(did); let ccx = match self.body_owner_kind(hir_id) { @@ -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, }; diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 9a2f1149316e2..c3b13278c009b 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -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()) } } diff --git a/compiler/rustc_mir/src/const_eval/machine.rs b/compiler/rustc_mir/src/const_eval/machine.rs index 279f414e7fef1..f8b66badb8a4c 100644 --- a/compiler/rustc_mir/src/const_eval/machine.rs +++ b/compiler/rustc_mir/src/const_eval/machine.rs @@ -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. diff --git a/compiler/rustc_mir/src/transform/check_consts/validation.rs b/compiler/rustc_mir/src/transform/check_consts/validation.rs index 6216ff6656e28..646ae8ced7eb4 100644 --- a/compiler/rustc_mir/src/transform/check_consts/validation.rs +++ b/compiler/rustc_mir/src/transform/check_consts/validation.rs @@ -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; + } + } + }); + 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 diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index d0795841c5359..71231830e99a7 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -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 @@ -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> { diff --git a/compiler/rustc_passes/src/check_const.rs b/compiler/rustc_passes/src/check_const.rs index d783852aacadd..6ee54cfe37f30 100644 --- a/compiler/rustc_passes/src/check_const.rs +++ b/compiler/rustc_passes/src/check_const.rs @@ -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; @@ -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::>().join("`, `"))) .emit(); } } diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 9051c9d69b5c1..f7a11876f7de4 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -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, diff --git a/compiler/rustc_ty_utils/src/ty.rs b/compiler/rustc_ty_utils/src/ty.rs index ffde9b79265a5..b0d644ae028ce 100644 --- a/compiler/rustc_ty_utils/src/ty.rs +++ b/compiler/rustc_ty_utils/src/ty.rs @@ -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: @@ -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 }; diff --git a/src/test/ui/rfc-2632-const-trait-impl/attr-misuse.rs b/src/test/ui/rfc-2632-const-trait-impl/attr-misuse.rs new file mode 100644 index 0000000000000..338ac3d250536 --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/attr-misuse.rs @@ -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() {} diff --git a/src/test/ui/rfc-2632-const-trait-impl/attr-misuse.stderr b/src/test/ui/rfc-2632-const-trait-impl/attr-misuse.stderr new file mode 100644 index 0000000000000..3af71d6ff78fb --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/attr-misuse.stderr @@ -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 + diff --git a/src/test/ui/rfc-2632-const-trait-impl/const-default-method-bodies.rs b/src/test/ui/rfc-2632-const-trait-impl/const-default-method-bodies.rs new file mode 100644 index 0000000000000..d08c01750c379 --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/const-default-method-bodies.rs @@ -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() {} diff --git a/src/test/ui/rfc-2632-const-trait-impl/const-default-method-bodies.stderr b/src/test/ui/rfc-2632-const-trait-impl/const-default-method-bodies.stderr new file mode 100644 index 0000000000000..d52e83609489b --- /dev/null +++ b/src/test/ui/rfc-2632-const-trait-impl/const-default-method-bodies.stderr @@ -0,0 +1,9 @@ +error[E0015]: calls in constant functions are limited to constant functions, tuple structs and tuple variants + --> $DIR/const-default-method-bodies.rs:26:5 + | +LL | NonConstImpl.a(); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0015`. diff --git a/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.rs b/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.rs index 4ff4fa0d83b2c..def7c34b4e50e 100644 --- a/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.rs +++ b/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.rs @@ -8,13 +8,31 @@ trait Tr { println!("lul"); self.req(); } + + #[default_method_body_is_const] + fn default() {} } struct S; impl const Tr for S { fn req(&self) {} +} //~^^ ERROR const trait implementations may not use non-const default functions + +impl const Tr for u8 { + fn req(&self) {} + fn prov(&self) {} } -//~^^^ ERROR const trait implementations may not use default functions + +impl const Tr for u16 { + fn prov(&self) {} + fn default() {} +} //~^^^ ERROR not all trait items implemented + + +impl const Tr for u32 { + fn req(&self) {} + fn default() {} +} //~^^^ ERROR const trait implementations may not use non-const default functions fn main() {} diff --git a/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.stderr b/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.stderr index 51a7b18fa8d55..eb7f899b4dee2 100644 --- a/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.stderr +++ b/src/test/ui/rfc-2632-const-trait-impl/impl-with-default-fn.stderr @@ -1,10 +1,33 @@ -error: const trait implementations may not use default functions - --> $DIR/impl-with-default-fn.rs:15:1 +error: const trait implementations may not use non-const default functions + --> $DIR/impl-with-default-fn.rs:18:1 | LL | / impl const Tr for S { LL | | fn req(&self) {} LL | | } | |_^ + | + = note: `prov` not implemented + +error: const trait implementations may not use non-const default functions + --> $DIR/impl-with-default-fn.rs:33:1 + | +LL | / impl const Tr for u32 { +LL | | fn req(&self) {} +LL | | fn default() {} +LL | | } + | |_^ + | + = note: `prov` not implemented + +error[E0046]: not all trait items implemented, missing: `req` + --> $DIR/impl-with-default-fn.rs:27:1 + | +LL | fn req(&self); + | -------------- `req` from trait +... +LL | impl const Tr for u16 { + | ^^^^^^^^^^^^^^^^^^^^^ missing `req` in implementation -error: aborting due to previous error +error: aborting due to 3 previous errors +For more information about this error, try `rustc --explain E0046`.