Skip to content

Commit

Permalink
Allow unstable items to be re-exported unstably without requiring the…
Browse files Browse the repository at this point in the history
… feature be enabled
  • Loading branch information
semicoleon committed Jun 4, 2022
1 parent 0a437b2 commit d03c411
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 10 deletions.
76 changes: 69 additions & 7 deletions compiler/rustc_middle/src/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +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 as hir};
use rustc_hir::{self, HirId};
use rustc_middle::ty::print::with_no_trimmed_paths;
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
Expand Down Expand Up @@ -306,6 +306,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.
///
Expand All @@ -322,6 +330,28 @@ impl<'tcx> TyCtxt<'tcx> {
id: Option<HirId>,
span: Span,
method_span: Option<Span>,
) -> EvalResult {
self.eval_stability_override(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 `EvalOverride::AllowUnstable` to `eval_override` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
pub fn eval_stability_override(
self,
def_id: DefId,
id: Option<HirId>,
span: Span,
method_span: Option<Span>,
eval_override: AllowUnstable,
) -> EvalResult {
// Deprecated attributes apply in-crate and cross-crate.
if let Some(id) = id {
Expand Down Expand Up @@ -419,6 +449,10 @@ impl<'tcx> TyCtxt<'tcx> {
}
}

if matches!(eval_override, AllowUnstable::Yes) {
return EvalResult::Allow;
}

let suggestion = suggestion_for_allocator_api(self, def_id, span, feature);
EvalResult::Deny { feature, reason, issue, suggestion, is_soft }
}
Expand All @@ -445,11 +479,38 @@ impl<'tcx> TyCtxt<'tcx> {
span: Span,
method_span: Option<Span>,
) {
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_override(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 `EvalOverride::AllowUnstable` to `eval_override` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
pub fn check_stability_override(
self,
def_id: DefId,
id: Option<HirId>,
span: Span,
method_span: Option<Span>,
eval_override: AllowUnstable,
) {
self.check_optional_stability(
def_id,
id,
span,
method_span,
eval_override,
|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
Expand All @@ -462,14 +523,15 @@ impl<'tcx> TyCtxt<'tcx> {
id: Option<HirId>,
span: Span,
method_span: Option<Span>,
eval_override: AllowUnstable,
unmarked: impl FnOnce(Span, DefId),
) {
let soft_handler = |lint, span, msg: &_| {
self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
lint.build(msg).emit();
})
};
match self.eval_stability(def_id, id, span, method_span) {
match self.eval_stability_override(def_id, id, span, method_span, eval_override) {
EvalResult::Allow => {}
EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable(
self.sess,
Expand Down
40 changes: 37 additions & 3 deletions compiler/rustc_passes/src/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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_override(
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,
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_typeck/src/astconv/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions src/test/ui/stability-attribute/allow-unstable-reexport.rs
Original file line number Diff line number Diff line change
@@ -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'
}
27 changes: 27 additions & 0 deletions src/test/ui/stability-attribute/allow-unstable-reexport.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
error[E0658]: use of unstable library feature 'unstable_test_feature'
--> $DIR/allow-unstable-reexport.rs:21: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:27: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:28: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`.
Original file line number Diff line number Diff line change
@@ -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;

0 comments on commit d03c411

Please sign in to comment.