Skip to content

Commit d03c411

Browse files
committed
Allow unstable items to be re-exported unstably without requiring the feature be enabled
1 parent 0a437b2 commit d03c411

File tree

6 files changed

+174
-10
lines changed

6 files changed

+174
-10
lines changed

compiler/rustc_middle/src/middle/stability.rs

+69-7
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use rustc_attr::{self as attr, ConstStability, Deprecation, Stability};
99
use rustc_data_structures::fx::FxHashMap;
1010
use rustc_errors::{Applicability, Diagnostic};
1111
use rustc_feature::GateIssue;
12-
use rustc_hir as hir;
1312
use rustc_hir::def::DefKind;
1413
use rustc_hir::def_id::{DefId, LocalDefId};
14+
use rustc_hir::{self as hir};
1515
use rustc_hir::{self, HirId};
1616
use rustc_middle::ty::print::with_no_trimmed_paths;
1717
use rustc_session::lint::builtin::{DEPRECATED, DEPRECATED_IN_FUTURE, SOFT_UNSTABLE};
@@ -306,6 +306,14 @@ fn suggestion_for_allocator_api(
306306
None
307307
}
308308

309+
/// An override option for eval_stability.
310+
pub enum AllowUnstable {
311+
/// Don't emit an unstable error for the item
312+
Yes,
313+
/// Handle the item normally
314+
No,
315+
}
316+
309317
impl<'tcx> TyCtxt<'tcx> {
310318
/// Evaluates the stability of an item.
311319
///
@@ -322,6 +330,28 @@ impl<'tcx> TyCtxt<'tcx> {
322330
id: Option<HirId>,
323331
span: Span,
324332
method_span: Option<Span>,
333+
) -> EvalResult {
334+
self.eval_stability_override(def_id, id, span, method_span, AllowUnstable::No)
335+
}
336+
337+
/// Evaluates the stability of an item.
338+
///
339+
/// Returns `EvalResult::Allow` if the item is stable, or unstable but the corresponding
340+
/// `#![feature]` has been provided. Returns `EvalResult::Deny` which describes the offending
341+
/// unstable feature otherwise.
342+
///
343+
/// If `id` is `Some(_)`, this function will also check if the item at `def_id` has been
344+
/// deprecated. If the item is indeed deprecated, we will emit a deprecation lint attached to
345+
/// `id`.
346+
///
347+
/// Pass `EvalOverride::AllowUnstable` to `eval_override` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
348+
pub fn eval_stability_override(
349+
self,
350+
def_id: DefId,
351+
id: Option<HirId>,
352+
span: Span,
353+
method_span: Option<Span>,
354+
eval_override: AllowUnstable,
325355
) -> EvalResult {
326356
// Deprecated attributes apply in-crate and cross-crate.
327357
if let Some(id) = id {
@@ -419,6 +449,10 @@ impl<'tcx> TyCtxt<'tcx> {
419449
}
420450
}
421451

452+
if matches!(eval_override, AllowUnstable::Yes) {
453+
return EvalResult::Allow;
454+
}
455+
422456
let suggestion = suggestion_for_allocator_api(self, def_id, span, feature);
423457
EvalResult::Deny { feature, reason, issue, suggestion, is_soft }
424458
}
@@ -445,11 +479,38 @@ impl<'tcx> TyCtxt<'tcx> {
445479
span: Span,
446480
method_span: Option<Span>,
447481
) {
448-
self.check_optional_stability(def_id, id, span, method_span, |span, def_id| {
449-
// The API could be uncallable for other reasons, for example when a private module
450-
// was referenced.
451-
self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
452-
})
482+
self.check_stability_override(def_id, id, span, method_span, AllowUnstable::No)
483+
}
484+
485+
/// Checks if an item is stable or error out.
486+
///
487+
/// If the item defined by `def_id` is unstable and the corresponding `#![feature]` does not
488+
/// exist, emits an error.
489+
///
490+
/// This function will also check if the item is deprecated.
491+
/// If so, and `id` is not `None`, a deprecated lint attached to `id` will be emitted.
492+
///
493+
/// Pass `EvalOverride::AllowUnstable` to `eval_override` to force an unstable item to be allowed. Deprecation warnings will be emitted normally.
494+
pub fn check_stability_override(
495+
self,
496+
def_id: DefId,
497+
id: Option<HirId>,
498+
span: Span,
499+
method_span: Option<Span>,
500+
eval_override: AllowUnstable,
501+
) {
502+
self.check_optional_stability(
503+
def_id,
504+
id,
505+
span,
506+
method_span,
507+
eval_override,
508+
|span, def_id| {
509+
// The API could be uncallable for other reasons, for example when a private module
510+
// was referenced.
511+
self.sess.delay_span_bug(span, &format!("encountered unmarked API: {:?}", def_id));
512+
},
513+
)
453514
}
454515

455516
/// Like `check_stability`, except that we permit items to have custom behaviour for
@@ -462,14 +523,15 @@ impl<'tcx> TyCtxt<'tcx> {
462523
id: Option<HirId>,
463524
span: Span,
464525
method_span: Option<Span>,
526+
eval_override: AllowUnstable,
465527
unmarked: impl FnOnce(Span, DefId),
466528
) {
467529
let soft_handler = |lint, span, msg: &_| {
468530
self.struct_span_lint_hir(lint, id.unwrap_or(hir::CRATE_HIR_ID), span, |lint| {
469531
lint.build(msg).emit();
470532
})
471533
};
472-
match self.eval_stability(def_id, id, span, method_span) {
534+
match self.eval_stability_override(def_id, id, span, method_span, eval_override) {
473535
EvalResult::Allow => {}
474536
EvalResult::Deny { feature, reason, issue, suggestion, is_soft } => report_unstable(
475537
self.sess,

compiler/rustc_passes/src/stability.rs

+37-3
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ use rustc_hir::def::{DefKind, Res};
99
use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
1010
use rustc_hir::hir_id::CRATE_HIR_ID;
1111
use rustc_hir::intravisit::{self, Visitor};
12-
use rustc_hir::{FieldDef, Generics, HirId, Item, TraitRef, Ty, TyKind, Variant};
12+
use rustc_hir::{FieldDef, Generics, HirId, Item, ItemKind, TraitRef, Ty, TyKind, Variant};
1313
use rustc_middle::hir::nested_filter;
1414
use rustc_middle::middle::privacy::AccessLevels;
15-
use rustc_middle::middle::stability::{DeprecationEntry, Index};
15+
use rustc_middle::middle::stability::{AllowUnstable, DeprecationEntry, Index};
1616
use rustc_middle::ty::{self, query::Providers, TyCtxt};
1717
use rustc_session::lint;
1818
use rustc_session::lint::builtin::{INEFFECTIVE_UNSTABLE_TRAIT_IMPL, USELESS_DEPRECATED};
@@ -807,12 +807,46 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
807807
fn visit_path(&mut self, path: &'tcx hir::Path<'tcx>, id: hir::HirId) {
808808
if let Some(def_id) = path.res.opt_def_id() {
809809
let method_span = path.segments.last().map(|s| s.ident.span);
810-
self.tcx.check_stability(def_id, Some(id), path.span, method_span)
810+
self.tcx.check_stability_override(
811+
def_id,
812+
Some(id),
813+
path.span,
814+
method_span,
815+
if is_unstable_reexport(self.tcx, id) {
816+
AllowUnstable::Yes
817+
} else {
818+
AllowUnstable::No
819+
},
820+
)
811821
}
812822
intravisit::walk_path(self, path)
813823
}
814824
}
815825

826+
/// Check whether a path is a `use` item that has been marked as unstable.
827+
///
828+
/// See issue #94972 for details on why this is a special case
829+
fn is_unstable_reexport<'tcx>(tcx: TyCtxt<'tcx>, id: hir::HirId) -> bool {
830+
// Get the LocalDefId so we can lookup the item to check the kind.
831+
let Some(def_id) = tcx.hir().opt_local_def_id(id) else { return false; };
832+
833+
let Some(stab) = tcx.stability().local_stability(def_id) else {
834+
return false;
835+
};
836+
837+
if stab.level.is_stable() {
838+
// The re-export is not marked as unstable, don't override
839+
return false;
840+
}
841+
842+
// If this is a path that isn't a use, we don't need to do anything special
843+
if !matches!(tcx.hir().item(hir::ItemId { def_id }).kind, ItemKind::Use(..)) {
844+
return false;
845+
}
846+
847+
true
848+
}
849+
816850
struct CheckTraitImplStable<'tcx> {
817851
tcx: TyCtxt<'tcx>,
818852
fully_stable: bool,

compiler/rustc_typeck/src/astconv/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use rustc_hir::def_id::{DefId, LocalDefId};
2424
use rustc_hir::intravisit::{walk_generics, Visitor as _};
2525
use rustc_hir::lang_items::LangItem;
2626
use rustc_hir::{GenericArg, GenericArgs};
27+
use rustc_middle::middle::stability::AllowUnstable;
2728
use rustc_middle::ty::subst::{self, GenericArgKind, InternalSubsts, Subst, SubstsRef};
2829
use rustc_middle::ty::GenericParamDefKind;
2930
use rustc_middle::ty::{self, Const, DefIdTree, EarlyBinder, Ty, TyCtxt, TypeFoldable};
@@ -426,6 +427,7 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
426427
Some(arg.id()),
427428
arg.span(),
428429
None,
430+
AllowUnstable::No,
429431
|_, _| {
430432
// Default generic parameters may not be marked
431433
// with stability attributes, i.e. when the
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Allow an unstable re-export without requiring a feature gate.
2+
// #94972
3+
4+
// aux-build:lint-stability.rs
5+
// aux-build:lint-stability-reexport.rs
6+
#![feature(staged_api)]
7+
#![stable(feature = "lint_stability", since = "1.0.0")]
8+
9+
extern crate lint_stability;
10+
extern crate lint_stability_reexport;
11+
12+
#[unstable(feature = "unstable_test_feature", issue = "none")]
13+
pub use lint_stability::unstable;
14+
15+
// We want to confirm that using a re-export through another crate behaves
16+
// the same way as using an item directly
17+
#[unstable(feature = "unstable_test_feature", issue = "none")]
18+
pub use lint_stability_reexport::unstable_text;
19+
20+
// Ensure items which aren't marked as unstable can't re-export unstable items
21+
#[stable(feature = "lint_stability", since = "1.0.0")]
22+
pub use lint_stability::unstable as unstable2;
23+
//~^ ERROR use of unstable library feature 'unstable_test_feature'
24+
25+
fn main() {
26+
// Since we didn't enable the feature in this crate, we still can't
27+
// use these items, even though they're in scope from the `use`s which are now allowed.
28+
unstable(); //~ ERROR use of unstable library feature 'unstable_test_feature'
29+
unstable_text(); //~ ERROR use of unstable library feature 'unstable_test_feature'
30+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
error[E0658]: use of unstable library feature 'unstable_test_feature'
2+
--> $DIR/allow-unstable-reexport.rs:21:9
3+
|
4+
LL | pub use lint_stability::unstable as unstable2;
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable
8+
9+
error[E0658]: use of unstable library feature 'unstable_test_feature'
10+
--> $DIR/allow-unstable-reexport.rs:27:5
11+
|
12+
LL | unstable();
13+
| ^^^^^^^^
14+
|
15+
= help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable
16+
17+
error[E0658]: use of unstable library feature 'unstable_test_feature': text
18+
--> $DIR/allow-unstable-reexport.rs:28:5
19+
|
20+
LL | unstable_text();
21+
| ^^^^^^^^^^^^^
22+
|
23+
= help: add `#![feature(unstable_test_feature)]` to the crate attributes to enable
24+
25+
error: aborting due to 3 previous errors
26+
27+
For more information about this error, try `rustc --explain E0658`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#![crate_type = "lib"]
2+
#![feature(staged_api)]
3+
#![stable(feature = "lint_stability", since = "1.0.0")]
4+
5+
extern crate lint_stability;
6+
7+
// Re-exporting without enabling the feature "unstable_test_feature" in this crate
8+
#[unstable(feature = "unstable_test_feature", issue = "none")]
9+
pub use lint_stability::unstable_text;

0 commit comments

Comments
 (0)