From f3d93b6c625a0837cd6f2bedc713436e56856b5d Mon Sep 17 00:00:00 2001 From: cole Date: Sat, 4 Jun 2022 17:05:33 -0500 Subject: [PATCH] Allow unstable items to be re-exported unstably without requiring the feature be enabled --- compiler/rustc_middle/src/middle/stability.rs | 77 +++++++++++++++++-- compiler/rustc_passes/src/stability.rs | 40 +++++++++- compiler/rustc_typeck/src/astconv/mod.rs | 2 + .../allow-unstable-reexport.rs | 30 ++++++++ .../allow-unstable-reexport.stderr | 27 +++++++ .../auxiliary/lint-stability-reexport.rs | 9 +++ 6 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 src/test/ui/stability-attribute/allow-unstable-reexport.rs create mode 100644 src/test/ui/stability-attribute/allow-unstable-reexport.stderr create mode 100644 src/test/ui/stability-attribute/auxiliary/lint-stability-reexport.rs diff --git a/compiler/rustc_middle/src/middle/stability.rs b/compiler/rustc_middle/src/middle/stability.rs index 8b11e35a7c3f7..62e0fec06615e 100644 --- a/compiler/rustc_middle/src/middle/stability.rs +++ b/compiler/rustc_middle/src/middle/stability.rs @@ -9,10 +9,9 @@ use rustc_attr::{self as attr, ConstStability, Deprecation, Stability}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{Applicability, Diagnostic}; use rustc_feature::GateIssue; -use rustc_hir as hir; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::{self, HirId}; +use rustc_hir::{self as hir, HirId}; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE}; use rustc_session::lint::{BuiltinLintDiagnostics, Level, Lint, LintBuffer}; @@ -306,6 +305,14 @@ fn suggestion_for_allocator_api( None } +/// An override option for eval_stability. +pub enum AllowUnstable { + /// Don't emit an unstable error for the item + Yes, + /// Handle the item normally + No, +} + impl<'tcx> TyCtxt<'tcx> { /// Evaluates the stability of an item. /// @@ -322,6 +329,28 @@ impl<'tcx> TyCtxt<'tcx> { id: Option, span: Span, method_span: Option, + ) -> EvalResult { + self.eval_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No) + } + + /// Evaluates the stability of an item. + /// + /// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding + /// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending + /// unstable feature otherwise. + /// + /// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been + /// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to + /// `id`. + /// + /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally. + pub fn eval_stability_allow_unstable( + self, + def_id: DefId, + id: Option, + span: Span, + method_span: Option, + allow_unstable: AllowUnstable, ) -> EvalResult { // Deprecated attributes apply in-crate and cross-crate. if let Some(id) = id { @@ -419,6 +448,10 @@ impl<'tcx> TyCtxt<'tcx> { } } + if matches!(allow_unstable, AllowUnstable::Yes) { + return EvalResult::Allow; + } + let suggestion = suggestion_for_allocator_api(self, def_id, span, feature); EvalResult::Deny { feature, reason, issue, suggestion, is_soft } } @@ -445,11 +478,38 @@ impl<'tcx> TyCtxt<'tcx> { span: Span, method_span: Option, ) { - self.check_optional_stability(def_id, id, span, method_span, |span, def_id| { - // The API could be uncallable for other reasons, for example when a private module - // was referenced. - self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id)); - }) + self.check_stability_allow_unstable(def_id, id, span, method_span, AllowUnstable::No) + } + + /// Checks if an item is stable or error out. + /// + /// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not + /// exist, emits an error. + /// + /// This function will also check if the item is deprecated. + /// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted. + /// + /// Pass `AllowUnstable::Yes` to `allow_unstable` to force an unstable item to be allowed. Deprecation warnings will be emitted normally. + pub fn check_stability_allow_unstable( + self, + def_id: DefId, + id: Option, + span: Span, + method_span: Option, + allow_unstable: AllowUnstable, + ) { + self.check_optional_stability( + def_id, + id, + span, + method_span, + allow_unstable, + |span, def_id| { + // The API could be uncallable for other reasons, for example when a private module + // was referenced. + self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id)); + }, + ) } /// Like `check_stability`, except that we permit items to have custom behaviour for @@ -462,6 +522,7 @@ impl<'tcx> TyCtxt<'tcx> { id: Option, span: Span, method_span: Option, + allow_unstable: AllowUnstable, unmarked: impl FnOnce(Span, DefId), ) { let soft_handler = |lint, span, msg: &_| { @@ -469,7 +530,7 @@ impl<'tcx> TyCtxt<'tcx> { lint.build(msg).emit(); }) }; - match self.eval_stability(def_id, id, span, method_span) { + match self.eval_stability_allow_unstable(def_id, id, span, method_span, allow_unstable) { EvalResult::Allow => {} EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable( self.sess, diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs index 70cb1f2a281a8..144a60faad265 100644 --- a/compiler/rustc_passes/src/stability.rs +++ b/compiler/rustc_passes/src/stability.rs @@ -9,10 +9,10 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID}; use rustc_hir::hir_id::CRATE_HIR_ID; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{FieldDef, Generics, HirId, Item, TraitRef, Ty, TyKind, Variant}; +use rustc_hir::{FieldDef, Generics, HirId, Item, ItemKind, TraitRef, Ty, TyKind, Variant}; use rustc_middle::hir::nested_filter; use rustc_middle::middle::privacy::AccessLevels; -use rustc_middle::middle::stability::{DeprecationEntry, Index}; +use rustc_middle::middle::stability::{AllowUnstable, DeprecationEntry, Index}; use rustc_middle::ty::{self, query::Providers, TyCtxt}; use rustc_session::lint; use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED}; @@ -807,12 +807,46 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> { fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, id: hir::HirId) { if let Some(def_id) = path.res.opt_def_id() { let method_span = path.segments.last().map(|s| s.ident.span); - self.tcx.check_stability(def_id, Some(id), path.span, method_span) + self.tcx.check_stability_allow_unstable( + def_id, + Some(id), + path.span, + method_span, + if is_unstable_reexport(self.tcx, id) { + AllowUnstable::Yes + } else { + AllowUnstable::No + }, + ) } intravisit::walk_path(self, path) } } +/// Check whether a path is a `use` item that has been marked as unstable. +/// +/// See issue #94972 for details on why this is a special case +fn is_unstable_reexport<'tcx>(tcx: TyCtxt<'tcx>, id: hir::HirId) -> bool { + // Get the LocalDefId so we can lookup the item to check the kind. + let Some(def_id) = tcx.hir().opt_local_def_id(id) else { return false; }; + + let Some(stab) = tcx.stability().local_stability(def_id) else { + return false; + }; + + if stab.level.is_stable() { + // The re-export is not marked as unstable, don't override + return false; + } + + // If this is a path that isn't a use, we don't need to do anything special + if !matches!(tcx.hir().item(hir::ItemId { def_id }).kind, ItemKind::Use(..)) { + return false; + } + + true +} + struct CheckTraitImplStable<'tcx> { tcx: TyCtxt<'tcx>, fully_stable: bool, diff --git a/compiler/rustc_typeck/src/astconv/mod.rs b/compiler/rustc_typeck/src/astconv/mod.rs index 96d083bb94f3c..18a1cab76d98e 100644 --- a/compiler/rustc_typeck/src/astconv/mod.rs +++ b/compiler/rustc_typeck/src/astconv/mod.rs @@ -24,6 +24,7 @@ use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::intravisit::{walk_generics, Visitor as _}; use rustc_hir::lang_items::LangItem; use rustc_hir::{GenericArg, GenericArgs}; +use rustc_middle::middle::stability::AllowUnstable; use rustc_middle::ty::subst::{self, GenericArgKind, InternalSubsts, Subst, SubstsRef}; use rustc_middle::ty::GenericParamDefKind; use rustc_middle::ty::{self, Const, DefIdTree, EarlyBinder, Ty, TyCtxt, TypeFoldable}; @@ -426,6 +427,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o { Some(arg.id()), arg.span(), None, + AllowUnstable::No, |_, _| { // Default generic parameters may not be marked // with stability attributes, i.e. when the diff --git a/src/test/ui/stability-attribute/allow-unstable-reexport.rs b/src/test/ui/stability-attribute/allow-unstable-reexport.rs new file mode 100644 index 0000000000000..937913954a791 --- /dev/null +++ b/src/test/ui/stability-attribute/allow-unstable-reexport.rs @@ -0,0 +1,30 @@ +// Allow an unstable re-export without requiring a feature gate. +// #94972 + +// aux-build:lint-stability.rs +// aux-build:lint-stability-reexport.rs +#![feature(staged_api)] +#![stable(feature = "lint_stability", since = "1.0.0")] + +extern crate lint_stability; +extern crate lint_stability_reexport; + +#[unstable(feature = "unstable_test_feature", issue = "none")] +pub use lint_stability::unstable; + +// We want to confirm that using a re-export through another crate behaves +// the same way as using an item directly +#[unstable(feature = "unstable_test_feature", issue = "none")] +pub use lint_stability_reexport::unstable_text; + +// Ensure items which aren't marked as unstable can't re-export unstable items +#[stable(feature = "lint_stability", since = "1.0.0")] +pub use lint_stability::unstable as unstable2; +//~^ ERROR use of unstable library feature 'unstable_test_feature' + +fn main() { + // Since we didn't enable the feature in this crate, we still can't + // use these items, even though they're in scope from the `use`s which are now allowed. + unstable(); //~ ERROR use of unstable library feature 'unstable_test_feature' + unstable_text(); //~ ERROR use of unstable library feature 'unstable_test_feature' +} diff --git a/src/test/ui/stability-attribute/allow-unstable-reexport.stderr b/src/test/ui/stability-attribute/allow-unstable-reexport.stderr new file mode 100644 index 0000000000000..a11da9dc8a7be --- /dev/null +++ b/src/test/ui/stability-attribute/allow-unstable-reexport.stderr @@ -0,0 +1,27 @@ +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/allow-unstable-reexport.rs:22:9 + | +LL | pub use lint_stability::unstable as unstable2; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature' + --> $DIR/allow-unstable-reexport.rs:28:5 + | +LL | unstable(); + | ^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error[E0658]: use of unstable library feature 'unstable_test_feature': text + --> $DIR/allow-unstable-reexport.rs:29:5 + | +LL | unstable_text(); + | ^^^^^^^^^^^^^ + | + = help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/stability-attribute/auxiliary/lint-stability-reexport.rs b/src/test/ui/stability-attribute/auxiliary/lint-stability-reexport.rs new file mode 100644 index 0000000000000..9884731d562e9 --- /dev/null +++ b/src/test/ui/stability-attribute/auxiliary/lint-stability-reexport.rs @@ -0,0 +1,9 @@ +#![crate_type = "lib"] +#![feature(staged_api)] +#![stable(feature = "lint_stability", since = "1.0.0")] + +extern crate lint_stability; + +// Re-exporting without enabling the feature "unstable_test_feature" in this crate +#[unstable(feature = "unstable_test_feature", issue = "none")] +pub use lint_stability::unstable_text;