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

Allow unstable items to be re-exported unstably without requiring the feature be enabled #97301

Merged
merged 1 commit into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 69 additions & 8 deletions compiler/rustc_middle/src/middle/stability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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.
///
Expand All @@ -322,6 +329,28 @@ impl<'tcx> TyCtxt<'tcx> {
id: Option<HirId>,
span: Span,
method_span: Option<Span>,
) -> 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<HirId>,
span: Span,
method_span: Option<Span>,
allow_unstable: AllowUnstable,
) -> EvalResult {
// Deprecated attributes apply in-crate and cross-crate.
if let Some(id) = id {
Expand Down Expand Up @@ -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 }
}
Expand All @@ -445,11 +478,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_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<HirId>,
span: Span,
method_span: Option<Span>,
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
Expand All @@ -462,14 +522,15 @@ impl<'tcx> TyCtxt<'tcx> {
id: Option<HirId>,
span: Span,
method_span: Option<Span>,
allow_unstable: 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_allow_unstable(def_id, id, span, method_span, allow_unstable) {
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_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.
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
///
/// 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: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`.
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;