Auto merge of #97177 - oli-obk:const-stability, r=davidtwco

Implement proper stability check for const impl Trait, fall back to unstable const when undeclared

Continuation of #93960

`@jhpratt` it looks to me like the test was simply not testing for the failure you were looking for? Your checks actually do the right thing for const traits?
This commit is contained in:
bors 2022-05-22 06:47:36 +00:00
commit acfd327fd4
22 changed files with 259 additions and 168 deletions

View file

@ -101,6 +101,16 @@ pub struct Stability {
pub feature: Symbol,
}
impl Stability {
pub fn is_unstable(&self) -> bool {
self.level.is_unstable()
}
pub fn is_stable(&self) -> bool {
self.level.is_stable()
}
}
/// Represents the `#[rustc_const_unstable]` and `#[rustc_const_stable]` attributes.
#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(HashStable_Generic)]
@ -111,6 +121,16 @@ pub struct ConstStability {
pub promotable: bool,
}
impl ConstStability {
pub fn is_const_unstable(&self) -> bool {
self.level.is_unstable()
}
pub fn is_const_stable(&self) -> bool {
self.level.is_stable()
}
}
/// The available stability levels.
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
#[derive(HashStable_Generic)]

View file

@ -9,7 +9,7 @@ use rustc_span::symbol::Symbol;
pub fn is_unstable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> Option<Symbol> {
if tcx.is_const_fn_raw(def_id) {
let const_stab = tcx.lookup_const_stability(def_id)?;
if const_stab.level.is_unstable() { Some(const_stab.feature) } else { None }
if const_stab.is_const_unstable() { Some(const_stab.feature) } else { None }
} else {
None
}

View file

@ -229,18 +229,6 @@ impl<'mir, 'tcx> Checker<'mir, 'tcx> {
// The local type and predicate checks are not free and only relevant for `const fn`s.
if self.const_kind() == hir::ConstContext::ConstFn {
// Prevent const trait methods from being annotated as `stable`.
// FIXME: Do this as part of stability checking.
if self.is_const_stable_const_fn() {
if crate::const_eval::is_parent_const_impl_raw(tcx, def_id) {
self.ccx
.tcx
.sess
.struct_span_err(self.span, "trait methods cannot be stable const fn")
.emit();
}
}
for (idx, local) in body.local_decls.iter_enumerated() {
// Handle the return place below.
if idx == RETURN_PLACE || local.internal {
@ -944,7 +932,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> {
// have no `rustc_const_stable` attributes to be const-unstable as well. This
// should be fixed later.
let callee_is_unstable_unmarked = tcx.lookup_const_stability(callee).is_none()
&& tcx.lookup_stability(callee).map_or(false, |s| s.level.is_unstable());
&& tcx.lookup_stability(callee).map_or(false, |s| s.is_unstable());
if callee_is_unstable_unmarked {
trace!("callee_is_unstable_unmarked");
// We do not use `const` modifiers for intrinsic "functions", as intrinsics are

View file

@ -84,8 +84,6 @@ pub fn rustc_allow_const_fn_unstable(
// functions are subject to more stringent restrictions than "const-unstable" functions: They
// cannot use unstable features and can only call other "const-stable" functions.
pub fn is_const_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
use attr::{ConstStability, Stability, StabilityLevel};
// A default body marked const is not const-stable because const
// trait fns currently cannot be const-stable. We shouldn't
// restrict default bodies to only call const-stable functions.
@ -96,22 +94,39 @@ pub fn is_const_stable_const_fn(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
// Const-stability is only relevant for `const fn`.
assert!(tcx.is_const_fn_raw(def_id));
// Functions with `#[rustc_const_unstable]` are const-unstable.
// A function is only const-stable if it has `#[rustc_const_stable]` or it the trait it belongs
// to is const-stable.
match tcx.lookup_const_stability(def_id) {
Some(ConstStability { level: StabilityLevel::Unstable { .. }, .. }) => return false,
Some(ConstStability { level: StabilityLevel::Stable { .. }, .. }) => return true,
None => {}
Some(stab) => stab.is_const_stable(),
None if is_parent_const_stable_trait(tcx, def_id) => {
// Remove this when `#![feature(const_trait_impl)]` is stabilized,
// returning `true` unconditionally.
tcx.sess.delay_span_bug(
tcx.def_span(def_id),
"trait implementations cannot be const stable yet",
);
true
}
None => false, // By default, items are not const stable.
}
}
// Functions with `#[unstable]` are const-unstable.
//
// FIXME(ecstaticmorse): We should keep const-stability attributes wholly separate from normal stability
// attributes. `#[unstable]` should be irrelevant.
if let Some(Stability { level: StabilityLevel::Unstable { .. }, .. }) =
tcx.lookup_stability(def_id)
{
fn is_parent_const_stable_trait(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
let local_def_id = def_id.expect_local();
let hir_id = tcx.local_def_id_to_hir_id(local_def_id);
let Some(parent) = tcx.hir().find_parent_node(hir_id) else { return false };
let parent_def = tcx.hir().get(parent);
if !matches!(
parent_def,
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl(hir::Impl { constness: hir::Constness::Const, .. }),
..
})
) {
return false;
}
true
tcx.lookup_const_stability(parent.owner).map_or(false, |stab| stab.is_const_stable())
}

View file

@ -2791,7 +2791,7 @@ impl<'tcx> TyCtxt<'tcx> {
pub fn is_const_fn(self, def_id: DefId) -> bool {
if self.is_const_fn_raw(def_id) {
match self.lookup_const_stability(def_id) {
Some(stability) if stability.level.is_unstable() => {
Some(stability) if stability.is_const_unstable() => {
// has a `rustc_const_unstable` attribute, check whether the user enabled the
// corresponding feature gate.
self.features()
@ -2808,6 +2808,21 @@ impl<'tcx> TyCtxt<'tcx> {
false
}
}
/// Whether the trait impl is marked const. This does not consider stability or feature gates.
pub fn is_const_trait_impl_raw(self, def_id: DefId) -> bool {
let Some(local_def_id) = def_id.as_local() else { return false };
let hir_id = self.local_def_id_to_hir_id(local_def_id);
let node = self.hir().get(hir_id);
matches!(
node,
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl(hir::Impl { constness: hir::Constness::Const, .. }),
..
})
)
}
}
impl<'tcx> TyCtxtAt<'tcx> {

View file

@ -147,7 +147,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
// Propagate unstability. This can happen even for non-staged-api crates in case
// -Zforce-unstable-if-unmarked is set.
if let Some(stab) = self.parent_stab {
if inherit_deprecation.yes() && stab.level.is_unstable() {
if inherit_deprecation.yes() && stab.is_unstable() {
self.index.stab_map.insert(def_id, stab);
}
}
@ -190,7 +190,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
if const_stab.is_none() {
debug!("annotate: const_stab not found, parent = {:?}", self.parent_const_stab);
if let Some(parent) = self.parent_const_stab {
if parent.level.is_unstable() {
if parent.is_const_unstable() {
self.index.const_stab_map.insert(def_id, parent);
}
}
@ -272,9 +272,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
if stab.is_none() {
debug!("annotate: stab not found, parent = {:?}", self.parent_stab);
if let Some(stab) = self.parent_stab {
if inherit_deprecation.yes() && stab.level.is_unstable()
|| inherit_from_parent.yes()
{
if inherit_deprecation.yes() && stab.is_unstable() || inherit_from_parent.yes() {
self.index.stab_map.insert(def_id, stab);
}
}
@ -532,7 +530,8 @@ impl<'tcx> MissingStabilityAnnotations<'tcx> {
return;
}
let is_const = self.tcx.is_const_fn(def_id.to_def_id());
let is_const = self.tcx.is_const_fn(def_id.to_def_id())
|| self.tcx.is_const_trait_impl_raw(def_id.to_def_id());
let is_stable = self
.tcx
.lookup_stability(def_id)
@ -710,16 +709,23 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
// For implementations of traits, check the stability of each item
// individually as it's possible to have a stable trait with unstable
// items.
hir::ItemKind::Impl(hir::Impl { of_trait: Some(ref t), self_ty, items, .. }) => {
if self.tcx.features().staged_api {
hir::ItemKind::Impl(hir::Impl {
of_trait: Some(ref t),
self_ty,
items,
constness,
..
}) => {
let features = self.tcx.features();
if features.staged_api {
let attrs = self.tcx.hir().attrs(item.hir_id());
let (stab, const_stab) = attr::find_stability(&self.tcx.sess, attrs, item.span);
// If this impl block has an #[unstable] attribute, give an
// error if all involved types and traits are stable, because
// it will have no effect.
// See: https://github.com/rust-lang/rust/issues/55436
let attrs = self.tcx.hir().attrs(item.hir_id());
if let (Some((Stability { level: attr::Unstable { .. }, .. }, span)), _) =
attr::find_stability(&self.tcx.sess, attrs, item.span)
{
if let Some((Stability { level: attr::Unstable { .. }, .. }, span)) = stab {
let mut c = CheckTraitImplStable { tcx: self.tcx, fully_stable: true };
c.visit_ty(self_ty);
c.visit_trait_ref(t);
@ -735,6 +741,19 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
);
}
}
// `#![feature(const_trait_impl)]` is unstable, so any impl declared stable
// needs to have an error emitted.
if features.const_trait_impl
&& *constness == hir::Constness::Const
&& const_stab.map_or(false, |(stab, _)| stab.is_const_stable())
{
self.tcx
.sess
.struct_span_err(item.span, "trait implementations cannot be const stable yet")
.note("see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information")
.emit();
}
}
for impl_item_ref in *items {

View file

@ -344,7 +344,7 @@ pub(crate) fn build_impl(
}
if let Some(stab) = tcx.lookup_stability(did) {
if stab.level.is_unstable() && stab.feature == sym::rustc_private {
if stab.is_unstable() && stab.feature == sym::rustc_private {
return;
}
}
@ -373,7 +373,7 @@ pub(crate) fn build_impl(
}
if let Some(stab) = tcx.lookup_stability(did) {
if stab.level.is_unstable() && stab.feature == sym::rustc_private {
if stab.is_unstable() && stab.feature == sym::rustc_private {
return;
}
}

View file

@ -632,7 +632,7 @@ impl Item {
self.stability(tcx).as_ref().and_then(|s| {
let mut classes = Vec::with_capacity(2);
if s.level.is_unstable() {
if s.is_unstable() {
classes.push("unstable");
}

View file

@ -445,10 +445,7 @@ fn extra_info_tags(item: &clean::Item, parent: &clean::Item, tcx: TyCtxt<'_>) ->
// The "rustc_private" crates are permanently unstable so it makes no sense
// to render "unstable" everywhere.
if item
.stability(tcx)
.as_ref()
.map(|s| s.level.is_unstable() && s.feature != sym::rustc_private)
if item.stability(tcx).as_ref().map(|s| s.is_unstable() && s.feature != sym::rustc_private)
== Some(true)
{
tags += &tag_html("unstable", "", "Experimental");

View file

@ -1,19 +1,18 @@
// build-pass
// check-pass
#![crate_type = "lib"]
#![feature(staged_api)]
#![feature(const_trait_impl)]
#![stable(feature = "foo", since = "1.0.0")]
#[stable(feature = "potato", since = "1.27.0")]
pub struct Data {
_data: u128
_data: u128,
}
#[stable(feature = "potato", since = "1.27.0")]
#[rustc_const_unstable(feature = "data_foo", issue = "none")]
impl const Default for Data {
#[rustc_const_unstable(feature = "data_foo", issue = "none")]
fn default() -> Data {
Data { _data: 42 }
}

View file

@ -1,5 +1,4 @@
#![feature(const_trait_impl)]
#![feature(staged_api)]
#![stable(feature = "rust1", since = "1.0.0")]
@ -13,9 +12,7 @@ pub trait MyTrait {
pub struct Unstable;
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "staged", issue = "none")]
#[rustc_const_unstable(feature = "unstable", issue = "none")]
impl const MyTrait for Unstable {
fn func() {
}
fn func() {}
}

View file

@ -1,45 +0,0 @@
#![feature(allow_internal_unstable)]
#![feature(const_add)]
#![feature(const_trait_impl)]
#![feature(staged_api)]
#![stable(feature = "rust1", since = "1.0.0")]
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Int(i32);
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "rust1", since = "1.0.0")]
impl const std::ops::Sub for Int {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
//~^ ERROR trait methods cannot be stable const fn
Int(self.0 - rhs.0)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "const_add", issue = "none")]
impl const std::ops::Add for Int {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Int(self.0 + rhs.0)
}
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "rust1", since = "1.0.0")]
pub const fn foo() -> Int {
Int(1i32) + Int(2i32)
//~^ ERROR not yet stable as a const fn
}
// ok
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_unstable(feature = "bar", issue = "none")]
pub const fn bar() -> Int {
Int(1i32) + Int(2i32)
}
fn main() {}

View file

@ -1,19 +0,0 @@
error: trait methods cannot be stable const fn
--> $DIR/stability.rs:15:5
|
LL | / fn sub(self, rhs: Self) -> Self {
LL | |
LL | | Int(self.0 - rhs.0)
LL | | }
| |_____^
error: `<Int as Add>::add` is not yet stable as a const fn
--> $DIR/stability.rs:34:5
|
LL | Int(1i32) + Int(2i32)
| ^^^^^^^^^^^^^^^^^^^^^
|
= help: const-stable functions can only call other const-stable functions
error: aborting due to 2 previous errors

View file

@ -0,0 +1,16 @@
// aux-build: staged-api.rs
extern crate staged_api;
use staged_api::*;
// Const stability has no impact on usage in non-const contexts.
fn non_const_context() {
Unstable::func();
}
const fn stable_const_context() {
Unstable::func();
//~^ ERROR cannot call non-const fn `<staged_api::Unstable as staged_api::MyTrait>::func` in constant functions
}
fn main() {}

View file

@ -0,0 +1,11 @@
error[E0015]: cannot call non-const fn `<staged_api::Unstable as staged_api::MyTrait>::func` in constant functions
--> $DIR/staged-api-user-crate.rs:12:5
|
LL | Unstable::func();
| ^^^^^^^^^^^^^^^^
|
= note: calls in constant functions are limited to constant functions, tuple structs and tuple variants
error: aborting due to previous error
For more information about this error, try `rustc --explain E0015`.

View file

@ -1,9 +1,7 @@
// revisions: stock staged
#![cfg_attr(staged, feature(staged))]
// revisions: stable unstable
#![cfg_attr(unstable, feature(unstable))] // The feature from the ./auxiliary/staged-api.rs file.
#![feature(const_trait_impl)]
#![allow(incomplete_features)]
#![feature(staged_api)]
#![stable(feature = "rust1", since = "1.0.0")]
@ -13,27 +11,53 @@ extern crate staged_api;
use staged_api::*;
#[stable(feature = "rust1", since = "1.0.0")]
pub struct Stable;
pub struct Foo;
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(staged, rustc_const_stable(feature = "rust1", since = "1.0.0"))]
// ^ should trigger error with or without the attribute
impl const MyTrait for Stable {
fn func() { //~ ERROR trait methods cannot be stable const fn
}
#[cfg_attr(unstable, rustc_const_unstable(feature = "foo", issue = "none"))]
#[cfg_attr(stable, rustc_const_stable(feature = "foo", since = "1.0.0"))]
impl const MyTrait for Foo {
//[stable]~^ ERROR trait implementations cannot be const stable yet
fn func() {}
}
// Const stability has no impact on usage in non-const contexts.
fn non_const_context() {
Unstable::func();
Stable::func();
Foo::func();
}
#[unstable(feature = "none", issue = "none")]
const fn const_context() {
Unstable::func();
//[stock]~^ ERROR `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
Stable::func();
// ^ This is okay regardless of whether the `unstable` feature is enabled, as this function is
// not const-stable.
Foo::func();
//[unstable]~^ ERROR not yet stable as a const fn
// ^ fails, because the `foo` feature is not active
}
#[stable(feature = "rust1", since = "1.0.0")]
#[cfg_attr(unstable, rustc_const_unstable(feature = "foo", issue = "none"))]
pub const fn const_context_not_const_stable() {
//[stable]~^ ERROR function has missing const stability attribute
Unstable::func();
// ^ This is okay regardless of whether the `unstable` feature is enabled, as this function is
// not const-stable.
Foo::func();
//[unstable]~^ ERROR not yet stable as a const fn
// ^ fails, because the `foo` feature is not active
}
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_const_stable(feature = "cheese", since = "1.0.0")]
const fn stable_const_context() {
Unstable::func();
//[unstable]~^ ERROR not yet stable as a const fn
Foo::func();
//[unstable]~^ ERROR not yet stable as a const fn
const_context_not_const_stable()
//[unstable]~^ ERROR not yet stable as a const fn
}
fn main() {}

View file

@ -0,0 +1,25 @@
error: trait implementations cannot be const stable yet
--> $DIR/staged-api.rs:19:1
|
LL | / impl const MyTrait for Foo {
LL | |
LL | | fn func() {}
LL | | }
| |_^
|
= note: see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information
error: function has missing const stability attribute
--> $DIR/staged-api.rs:42:1
|
LL | / pub const fn const_context_not_const_stable() {
LL | |
LL | | Unstable::func();
LL | | // ^ This is okay regardless of whether the `unstable` feature is enabled, as this function is
... |
LL | | // ^ fails, because the `foo` feature is not active
LL | | }
| |_^
error: aborting due to 2 previous errors

View file

@ -1,10 +0,0 @@
error: trait methods cannot be stable const fn
--> $DIR/staged-api.rs:22:5
|
LL | / fn func() {
LL | |
LL | | }
| |_____^
error: aborting due to previous error

View file

@ -1,18 +0,0 @@
error: trait methods cannot be stable const fn
--> $DIR/staged-api.rs:22:5
|
LL | / fn func() {
LL | |
LL | | }
| |_____^
error: `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
--> $DIR/staged-api.rs:34:5
|
LL | Unstable::func();
| ^^^^^^^^^^^^^^^^
|
= help: add `#![feature(staged)]` to the crate attributes to enable
error: aborting due to 2 previous errors

View file

@ -0,0 +1,42 @@
error: `<Foo as staged_api::MyTrait>::func` is not yet stable as a const fn
--> $DIR/staged-api.rs:35:5
|
LL | Foo::func();
| ^^^^^^^^^^^
|
= help: add `#![feature(foo)]` to the crate attributes to enable
error: `<Foo as staged_api::MyTrait>::func` is not yet stable as a const fn
--> $DIR/staged-api.rs:47:5
|
LL | Foo::func();
| ^^^^^^^^^^^
|
= help: add `#![feature(foo)]` to the crate attributes to enable
error: `<staged_api::Unstable as staged_api::MyTrait>::func` is not yet stable as a const fn
--> $DIR/staged-api.rs:55:5
|
LL | Unstable::func();
| ^^^^^^^^^^^^^^^^
|
= help: const-stable functions can only call other const-stable functions
error: `<Foo as staged_api::MyTrait>::func` is not yet stable as a const fn
--> $DIR/staged-api.rs:57:5
|
LL | Foo::func();
| ^^^^^^^^^^^
|
= help: const-stable functions can only call other const-stable functions
error: `const_context_not_const_stable` is not yet stable as a const fn
--> $DIR/staged-api.rs:59:5
|
LL | const_context_not_const_stable()
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= help: const-stable functions can only call other const-stable functions
error: aborting due to 5 previous errors

View file

@ -18,9 +18,15 @@ impl Foo {
pub const fn bar() {} // ok because function is unstable
}
// FIXME Once #![feature(const_trait_impl)] is allowed to be stable, add a test
// for const trait impls. Right now, a "trait methods cannot be stable const fn"
// error is emitted. This occurs prior to the lint being tested here, such that
// the lint cannot currently be tested on this use case.
#[stable(feature = "stable", since = "1.0.0")]
pub trait Bar {
#[stable(feature = "stable", since = "1.0.0")]
fn fun();
}
#[stable(feature = "stable", since = "1.0.0")]
impl const Bar for Foo {
//~^ ERROR implementation has missing const stability attribute
fn fun() {}
}
fn main() {}

View file

@ -10,5 +10,14 @@ error: associated function has missing const stability attribute
LL | pub const fn foo() {}
| ^^^^^^^^^^^^^^^^^^^^^
error: aborting due to 2 previous errors
error: implementation has missing const stability attribute
--> $DIR/missing-const-stability.rs:27:1
|
LL | / impl const Bar for Foo {
LL | |
LL | | fn fun() {}
LL | | }
| |_^
error: aborting due to 3 previous errors