Rollup merge of #99212 - davidtwco:partial-stability-implies, r=michaelwoerister
introduce `implied_by` in `#[unstable]` attribute Requested by the library team [on Zulip](https://rust-lang.zulipchat.com/#narrow/stream/131828-t-compiler/topic/better.20support.20for.20partial.20stabilizations/near/285581519). If part of a feature is stabilized and a new feature is added for the remaining parts, then the `implied_by` meta-item can be added to `#[unstable]` to indicate which now-stable feature was used previously. ```diagnostic error: the feature `foo` has been partially stabilized since 1.62.0 and is succeeded by the feature `foobar` --> $DIR/stability-attribute-implies-using-unstable.rs:3:12 | LL | #![feature(foo)] | ^^^ | note: the lint level is defined here --> $DIR/stability-attribute-implies-using-stable.rs:2:9 | LL | #![deny(stable_features)] | ^^^^^^^^^^^^^^^ help: if you are using features which are still unstable, change to using `foobar` | LL | #![feature(foobar)] | ~~~~~~ help: if you are using features which are now stable, remove this line | LL - #![feature(foo)] | ``` When a `#![feature(..)]` attribute still exists for the now-stable attribute, then there this has two effects: - There will not be an stability error for uses of items from the implied feature which are still unstable (until the `#![feature(..)]` is removed or updated to the new feature). - There will be an improved diagnostic for the remaining use of the feature attribute for the now-stable feature. ```rust /// If part of a feature is stabilized and a new feature is added for the remaining parts, /// then the `implied_by` attribute is used to indicate which now-stable feature previously /// contained a item. /// /// ```pseudo-Rust /// #[unstable(feature = "foo", issue = "...")] /// fn foo() {} /// #[unstable(feature = "foo", issue = "...")] /// fn foobar() {} /// ``` /// /// ...becomes... /// /// ```pseudo-Rust /// #[stable(feature = "foo", since = "1.XX.X")] /// fn foo() {} /// #[unstable(feature = "foobar", issue = "...", implied_by = "foo")] /// fn foobar() {} /// ``` ``` In the Zulip discussion, this was envisioned as `implies` on `#[stable]` but I went with `implied_by` on `#[unstable]` because it means that only the unstable attribute needs to be changed in future, not the new stable attribute, which seems less error-prone. It also isn't particularly feasible for me to detect whether items from the implied feature are used and then only suggest updating _or_ removing the `#![feature(..)]` as appropriate, so I always do both. There's some new information in the cross-crate metadata as a result of this change, that's a little unfortunate, but without requiring that the `#[unstable]` and `#[stable]` attributes both contain the implication information, it's necessary: ```rust /// This mapping is necessary unless both the `#[stable]` and `#[unstable]` attributes should /// specify their implications (both `implies` and `implied_by`). If only one of the two /// attributes do (as in the current implementation, `implied_by` in `#[unstable]`), then this /// mapping is necessary for diagnostics. When a "unnecessary feature attribute" error is /// reported, only the `#[stable]` attribute information is available, so the map is necessary /// to know that the feature implies another feature. If it were reversed, and the `#[stable]` /// attribute had an `implies` meta item, then a map would be necessary when avoiding a "use of /// unstable feature" error for a feature that was implied. ``` I also change some comments to documentation comments in the compiler, add a helper for going from a `Span` to a `Span` for the entire line, and fix a incorrect part of the pre-existing stability attribute diagnostics. cc `@yaahc`
This commit is contained in:
commit
857afc75e6
24 changed files with 357 additions and 62 deletions
|
@ -135,9 +135,42 @@ impl ConstStability {
|
||||||
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
|
#[derive(Encodable, Decodable, PartialEq, Copy, Clone, Debug, Eq, Hash)]
|
||||||
#[derive(HashStable_Generic)]
|
#[derive(HashStable_Generic)]
|
||||||
pub enum StabilityLevel {
|
pub enum StabilityLevel {
|
||||||
// Reason for the current stability level and the relevant rust-lang issue
|
/// `#[unstable]`
|
||||||
Unstable { reason: Option<Symbol>, issue: Option<NonZeroU32>, is_soft: bool },
|
Unstable {
|
||||||
Stable { since: Symbol, allowed_through_unstable_modules: bool },
|
/// Reason for the current stability level.
|
||||||
|
reason: Option<Symbol>,
|
||||||
|
/// Relevant `rust-lang/rust` issue.
|
||||||
|
issue: Option<NonZeroU32>,
|
||||||
|
is_soft: bool,
|
||||||
|
/// If part of a feature is stabilized and a new feature is added for the remaining parts,
|
||||||
|
/// then the `implied_by` attribute is used to indicate which now-stable feature previously
|
||||||
|
/// contained a item.
|
||||||
|
///
|
||||||
|
/// ```pseudo-Rust
|
||||||
|
/// #[unstable(feature = "foo", issue = "...")]
|
||||||
|
/// fn foo() {}
|
||||||
|
/// #[unstable(feature = "foo", issue = "...")]
|
||||||
|
/// fn foobar() {}
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ...becomes...
|
||||||
|
///
|
||||||
|
/// ```pseudo-Rust
|
||||||
|
/// #[stable(feature = "foo", since = "1.XX.X")]
|
||||||
|
/// fn foo() {}
|
||||||
|
/// #[unstable(feature = "foobar", issue = "...", implied_by = "foo")]
|
||||||
|
/// fn foobar() {}
|
||||||
|
/// ```
|
||||||
|
implied_by: Option<Symbol>,
|
||||||
|
},
|
||||||
|
/// `#[stable]`
|
||||||
|
Stable {
|
||||||
|
/// Rust release which stabilized this feature.
|
||||||
|
since: Symbol,
|
||||||
|
/// Is this item allowed to be referred to on stable, despite being contained in unstable
|
||||||
|
/// modules?
|
||||||
|
allowed_through_unstable_modules: bool,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StabilityLevel {
|
impl StabilityLevel {
|
||||||
|
@ -243,6 +276,7 @@ where
|
||||||
let mut issue = None;
|
let mut issue = None;
|
||||||
let mut issue_num = None;
|
let mut issue_num = None;
|
||||||
let mut is_soft = false;
|
let mut is_soft = false;
|
||||||
|
let mut implied_by = None;
|
||||||
for meta in metas {
|
for meta in metas {
|
||||||
let Some(mi) = meta.meta_item() else {
|
let Some(mi) = meta.meta_item() else {
|
||||||
handle_errors(
|
handle_errors(
|
||||||
|
@ -308,6 +342,11 @@ where
|
||||||
}
|
}
|
||||||
is_soft = true;
|
is_soft = true;
|
||||||
}
|
}
|
||||||
|
sym::implied_by => {
|
||||||
|
if !get(mi, &mut implied_by) {
|
||||||
|
continue 'outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
handle_errors(
|
handle_errors(
|
||||||
&sess.parse_sess,
|
&sess.parse_sess,
|
||||||
|
@ -332,7 +371,7 @@ where
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let level = Unstable { reason, issue: issue_num, is_soft };
|
let level = Unstable { reason, issue: issue_num, is_soft, implied_by };
|
||||||
if sym::unstable == meta_name {
|
if sym::unstable == meta_name {
|
||||||
stab = Some((Stability { level, feature }, attr.span));
|
stab = Some((Stability { level, feature }, attr.span));
|
||||||
} else {
|
} else {
|
||||||
|
@ -391,7 +430,7 @@ where
|
||||||
meta.span(),
|
meta.span(),
|
||||||
AttrError::UnknownMetaItem(
|
AttrError::UnknownMetaItem(
|
||||||
pprust::path_to_string(&mi.path),
|
pprust::path_to_string(&mi.path),
|
||||||
&["since", "note"],
|
&["feature", "since"],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
continue 'outer;
|
continue 'outer;
|
||||||
|
|
|
@ -951,6 +951,13 @@ impl<'a, 'tcx> CrateMetadataRef<'a> {
|
||||||
tcx.arena.alloc_from_iter(self.root.lib_features.decode(self))
|
tcx.arena.alloc_from_iter(self.root.lib_features.decode(self))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Iterates over the stability implications in the given crate (when a `#[unstable]` attribute
|
||||||
|
/// has an `implied_by` meta item, then the mapping from the implied feature to the actual
|
||||||
|
/// feature is a stability implication).
|
||||||
|
fn get_stability_implications(self, tcx: TyCtxt<'tcx>) -> &'tcx [(Symbol, Symbol)] {
|
||||||
|
tcx.arena.alloc_from_iter(self.root.stability_implications.decode(self))
|
||||||
|
}
|
||||||
|
|
||||||
/// Iterates over the language items in the given crate.
|
/// Iterates over the language items in the given crate.
|
||||||
fn get_lang_items(self, tcx: TyCtxt<'tcx>) -> &'tcx [(DefId, usize)] {
|
fn get_lang_items(self, tcx: TyCtxt<'tcx>) -> &'tcx [(DefId, usize)] {
|
||||||
tcx.arena.alloc_from_iter(
|
tcx.arena.alloc_from_iter(
|
||||||
|
|
|
@ -291,6 +291,9 @@ provide! { <'tcx> tcx, def_id, other, cdata,
|
||||||
tcx.arena.alloc_slice(&result)
|
tcx.arena.alloc_slice(&result)
|
||||||
}
|
}
|
||||||
defined_lib_features => { cdata.get_lib_features(tcx) }
|
defined_lib_features => { cdata.get_lib_features(tcx) }
|
||||||
|
stability_implications => {
|
||||||
|
cdata.get_stability_implications(tcx).iter().copied().collect()
|
||||||
|
}
|
||||||
is_intrinsic => { cdata.get_is_intrinsic(def_id.index) }
|
is_intrinsic => { cdata.get_is_intrinsic(def_id.index) }
|
||||||
defined_lang_items => { cdata.get_lang_items(tcx) }
|
defined_lang_items => { cdata.get_lang_items(tcx) }
|
||||||
diagnostic_items => { cdata.get_diagnostic_items() }
|
diagnostic_items => { cdata.get_diagnostic_items() }
|
||||||
|
|
|
@ -538,6 +538,11 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||||
let lib_features = self.encode_lib_features();
|
let lib_features = self.encode_lib_features();
|
||||||
let lib_feature_bytes = self.position() - i;
|
let lib_feature_bytes = self.position() - i;
|
||||||
|
|
||||||
|
// Encode the stability implications.
|
||||||
|
i = self.position();
|
||||||
|
let stability_implications = self.encode_stability_implications();
|
||||||
|
let stability_implications_bytes = self.position() - i;
|
||||||
|
|
||||||
// Encode the language items.
|
// Encode the language items.
|
||||||
i = self.position();
|
i = self.position();
|
||||||
let lang_items = self.encode_lang_items();
|
let lang_items = self.encode_lang_items();
|
||||||
|
@ -686,6 +691,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||||
crate_deps,
|
crate_deps,
|
||||||
dylib_dependency_formats,
|
dylib_dependency_formats,
|
||||||
lib_features,
|
lib_features,
|
||||||
|
stability_implications,
|
||||||
lang_items,
|
lang_items,
|
||||||
diagnostic_items,
|
diagnostic_items,
|
||||||
lang_items_missing,
|
lang_items_missing,
|
||||||
|
@ -710,6 +716,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||||
let computed_total_bytes = preamble_bytes
|
let computed_total_bytes = preamble_bytes
|
||||||
+ dep_bytes
|
+ dep_bytes
|
||||||
+ lib_feature_bytes
|
+ lib_feature_bytes
|
||||||
|
+ stability_implications_bytes
|
||||||
+ lang_item_bytes
|
+ lang_item_bytes
|
||||||
+ diagnostic_item_bytes
|
+ diagnostic_item_bytes
|
||||||
+ native_lib_bytes
|
+ native_lib_bytes
|
||||||
|
@ -761,6 +768,7 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||||
p("preamble", preamble_bytes);
|
p("preamble", preamble_bytes);
|
||||||
p("dep", dep_bytes);
|
p("dep", dep_bytes);
|
||||||
p("lib feature", lib_feature_bytes);
|
p("lib feature", lib_feature_bytes);
|
||||||
|
p("stability_implications", stability_implications_bytes);
|
||||||
p("lang item", lang_item_bytes);
|
p("lang item", lang_item_bytes);
|
||||||
p("diagnostic item", diagnostic_item_bytes);
|
p("diagnostic item", diagnostic_item_bytes);
|
||||||
p("native lib", native_lib_bytes);
|
p("native lib", native_lib_bytes);
|
||||||
|
@ -1777,6 +1785,13 @@ impl<'a, 'tcx> EncodeContext<'a, 'tcx> {
|
||||||
self.lazy_array(lib_features.to_vec())
|
self.lazy_array(lib_features.to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn encode_stability_implications(&mut self) -> LazyArray<(Symbol, Symbol)> {
|
||||||
|
empty_proc_macro!(self);
|
||||||
|
let tcx = self.tcx;
|
||||||
|
let implications = tcx.stability_implications(LOCAL_CRATE);
|
||||||
|
self.lazy_array(implications.iter().map(|(k, v)| (*k, *v)))
|
||||||
|
}
|
||||||
|
|
||||||
fn encode_diagnostic_items(&mut self) -> LazyArray<(Symbol, DefIndex)> {
|
fn encode_diagnostic_items(&mut self) -> LazyArray<(Symbol, DefIndex)> {
|
||||||
empty_proc_macro!(self);
|
empty_proc_macro!(self);
|
||||||
let tcx = self.tcx;
|
let tcx = self.tcx;
|
||||||
|
|
|
@ -226,6 +226,7 @@ pub(crate) struct CrateRoot {
|
||||||
crate_deps: LazyArray<CrateDep>,
|
crate_deps: LazyArray<CrateDep>,
|
||||||
dylib_dependency_formats: LazyArray<Option<LinkagePreference>>,
|
dylib_dependency_formats: LazyArray<Option<LinkagePreference>>,
|
||||||
lib_features: LazyArray<(Symbol, Option<Symbol>)>,
|
lib_features: LazyArray<(Symbol, Option<Symbol>)>,
|
||||||
|
stability_implications: LazyArray<(Symbol, Symbol)>,
|
||||||
lang_items: LazyArray<(DefIndex, usize)>,
|
lang_items: LazyArray<(DefIndex, usize)>,
|
||||||
lang_items_missing: LazyArray<lang_items::LangItem>,
|
lang_items_missing: LazyArray<lang_items::LangItem>,
|
||||||
diagnostic_items: LazyArray<(Symbol, DefIndex)>,
|
diagnostic_items: LazyArray<(Symbol, DefIndex)>,
|
||||||
|
|
|
@ -3,14 +3,14 @@ pub mod dependency_format;
|
||||||
pub mod exported_symbols;
|
pub mod exported_symbols;
|
||||||
pub mod lang_items;
|
pub mod lang_items;
|
||||||
pub mod lib_features {
|
pub mod lib_features {
|
||||||
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use rustc_span::symbol::Symbol;
|
use rustc_span::{symbol::Symbol, Span};
|
||||||
|
|
||||||
#[derive(HashStable, Debug)]
|
#[derive(HashStable, Debug)]
|
||||||
pub struct LibFeatures {
|
pub struct LibFeatures {
|
||||||
// A map from feature to stabilisation version.
|
/// A map from feature to stabilisation version.
|
||||||
pub stable: FxHashMap<Symbol, Symbol>,
|
pub stable: FxHashMap<Symbol, (Symbol, Span)>,
|
||||||
pub unstable: FxHashSet<Symbol>,
|
pub unstable: FxHashMap<Symbol, Span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LibFeatures {
|
impl LibFeatures {
|
||||||
|
@ -18,8 +18,8 @@ pub mod lib_features {
|
||||||
let mut all_features: Vec<_> = self
|
let mut all_features: Vec<_> = self
|
||||||
.stable
|
.stable
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(f, s)| (*f, Some(*s)))
|
.map(|(f, (s, _))| (*f, Some(*s)))
|
||||||
.chain(self.unstable.iter().map(|f| (*f, None)))
|
.chain(self.unstable.iter().map(|(f, _)| (*f, None)))
|
||||||
.collect();
|
.collect();
|
||||||
all_features.sort_unstable_by(|a, b| a.0.as_str().partial_cmp(b.0.as_str()).unwrap());
|
all_features.sort_unstable_by(|a, b| a.0.as_str().partial_cmp(b.0.as_str()).unwrap());
|
||||||
all_features
|
all_features
|
||||||
|
|
|
@ -62,6 +62,19 @@ pub struct Index {
|
||||||
pub stab_map: FxHashMap<LocalDefId, Stability>,
|
pub stab_map: FxHashMap<LocalDefId, Stability>,
|
||||||
pub const_stab_map: FxHashMap<LocalDefId, ConstStability>,
|
pub const_stab_map: FxHashMap<LocalDefId, ConstStability>,
|
||||||
pub depr_map: FxHashMap<LocalDefId, DeprecationEntry>,
|
pub depr_map: FxHashMap<LocalDefId, DeprecationEntry>,
|
||||||
|
/// Mapping from feature name to feature name based on the `implied_by` field of `#[unstable]`
|
||||||
|
/// attributes. If a `#[unstable(feature = "implier", implied_by = "impliee")]` attribute
|
||||||
|
/// exists, then this map will have a `impliee -> implier` entry.
|
||||||
|
///
|
||||||
|
/// This mapping is necessary unless both the `#[stable]` and `#[unstable]` attributes should
|
||||||
|
/// specify their implications (both `implies` and `implied_by`). If only one of the two
|
||||||
|
/// attributes do (as in the current implementation, `implied_by` in `#[unstable]`), then this
|
||||||
|
/// mapping is necessary for diagnostics. When a "unnecessary feature attribute" error is
|
||||||
|
/// reported, only the `#[stable]` attribute information is available, so the map is necessary
|
||||||
|
/// to know that the feature implies another feature. If it were reversed, and the `#[stable]`
|
||||||
|
/// attribute had an `implies` meta item, then a map would be necessary when avoiding a "use of
|
||||||
|
/// unstable feature" error for a feature that was implied.
|
||||||
|
pub implications: FxHashMap<Symbol, Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index {
|
impl Index {
|
||||||
|
@ -423,7 +436,9 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
|
|
||||||
match stability {
|
match stability {
|
||||||
Some(Stability {
|
Some(Stability {
|
||||||
level: attr::Unstable { reason, issue, is_soft }, feature, ..
|
level: attr::Unstable { reason, issue, is_soft, implied_by },
|
||||||
|
feature,
|
||||||
|
..
|
||||||
}) => {
|
}) => {
|
||||||
if span.allows_unstable(feature) {
|
if span.allows_unstable(feature) {
|
||||||
debug!("stability: skipping span={:?} since it is internal", span);
|
debug!("stability: skipping span={:?} since it is internal", span);
|
||||||
|
@ -433,6 +448,13 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
return EvalResult::Allow;
|
return EvalResult::Allow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If this item was previously part of a now-stabilized feature which is still
|
||||||
|
// active (i.e. the user hasn't removed the attribute for the stabilized feature
|
||||||
|
// yet) then allow use of this item.
|
||||||
|
if let Some(implied_by) = implied_by && self.features().active(implied_by) {
|
||||||
|
return EvalResult::Allow;
|
||||||
|
}
|
||||||
|
|
||||||
// When we're compiling the compiler itself we may pull in
|
// When we're compiling the compiler itself we may pull in
|
||||||
// crates from crates.io, but those crates may depend on other
|
// crates from crates.io, but those crates may depend on other
|
||||||
// crates also pulled in from crates.io. We want to ideally be
|
// crates also pulled in from crates.io. We want to ideally be
|
||||||
|
|
|
@ -1634,11 +1634,15 @@ rustc_queries! {
|
||||||
storage(ArenaCacheSelector<'tcx>)
|
storage(ArenaCacheSelector<'tcx>)
|
||||||
desc { "calculating the lib features map" }
|
desc { "calculating the lib features map" }
|
||||||
}
|
}
|
||||||
query defined_lib_features(_: CrateNum)
|
query defined_lib_features(_: CrateNum) -> &'tcx [(Symbol, Option<Symbol>)] {
|
||||||
-> &'tcx [(Symbol, Option<Symbol>)] {
|
|
||||||
desc { "calculating the lib features defined in a crate" }
|
desc { "calculating the lib features defined in a crate" }
|
||||||
separate_provide_extern
|
separate_provide_extern
|
||||||
}
|
}
|
||||||
|
query stability_implications(_: CrateNum) -> FxHashMap<Symbol, Symbol> {
|
||||||
|
storage(ArenaCacheSelector<'tcx>)
|
||||||
|
desc { "calculating the implications between `#[unstable]` features defined in a crate" }
|
||||||
|
separate_provide_extern
|
||||||
|
}
|
||||||
/// Whether the function is an intrinsic
|
/// Whether the function is an intrinsic
|
||||||
query is_intrinsic(def_id: DefId) -> bool {
|
query is_intrinsic(def_id: DefId) -> bool {
|
||||||
desc { |tcx| "is_intrinsic({})", tcx.def_path_str(def_id) }
|
desc { |tcx| "is_intrinsic({})", tcx.def_path_str(def_id) }
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// Detecting lib features (i.e., features that are not lang features).
|
//! Detecting lib features (i.e., features that are not lang features).
|
||||||
//
|
//!
|
||||||
// These are declared using stability attributes (e.g., `#[stable (..)]`
|
//! These are declared using stability attributes (e.g., `#[stable (..)]` and `#[unstable (..)]`),
|
||||||
// and `#[unstable (..)]`), but are not declared in one single location
|
//! but are not declared in one single location (unlike lang features), which means we need to
|
||||||
// (unlike lang features), which means we need to collect them instead.
|
//! collect them instead.
|
||||||
|
|
||||||
use rustc_ast::{Attribute, MetaItemKind};
|
use rustc_ast::{Attribute, MetaItemKind};
|
||||||
use rustc_errors::struct_span_err;
|
use rustc_errors::struct_span_err;
|
||||||
|
@ -71,11 +71,11 @@ impl<'tcx> LibFeatureCollector<'tcx> {
|
||||||
|
|
||||||
fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) {
|
fn collect_feature(&mut self, feature: Symbol, since: Option<Symbol>, span: Span) {
|
||||||
let already_in_stable = self.lib_features.stable.contains_key(&feature);
|
let already_in_stable = self.lib_features.stable.contains_key(&feature);
|
||||||
let already_in_unstable = self.lib_features.unstable.contains(&feature);
|
let already_in_unstable = self.lib_features.unstable.contains_key(&feature);
|
||||||
|
|
||||||
match (since, already_in_stable, already_in_unstable) {
|
match (since, already_in_stable, already_in_unstable) {
|
||||||
(Some(since), _, false) => {
|
(Some(since), _, false) => {
|
||||||
if let Some(prev_since) = self.lib_features.stable.get(&feature) {
|
if let Some((prev_since, _)) = self.lib_features.stable.get(&feature) {
|
||||||
if *prev_since != since {
|
if *prev_since != since {
|
||||||
self.span_feature_error(
|
self.span_feature_error(
|
||||||
span,
|
span,
|
||||||
|
@ -89,10 +89,10 @@ impl<'tcx> LibFeatureCollector<'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.lib_features.stable.insert(feature, since);
|
self.lib_features.stable.insert(feature, (since, span));
|
||||||
}
|
}
|
||||||
(None, false, _) => {
|
(None, false, _) => {
|
||||||
self.lib_features.unstable.insert(feature);
|
self.lib_features.unstable.insert(feature, span);
|
||||||
}
|
}
|
||||||
(Some(_), _, true) | (None, true, _) => {
|
(Some(_), _, true) | (None, true, _) => {
|
||||||
self.span_feature_error(
|
self.span_feature_error(
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
//! propagating default levels lexically from parent to children ast nodes.
|
//! propagating default levels lexically from parent to children ast nodes.
|
||||||
|
|
||||||
use attr::StabilityLevel;
|
use attr::StabilityLevel;
|
||||||
use rustc_attr::{self as attr, ConstStability, Stability};
|
use rustc_attr::{self as attr, ConstStability, Stability, Unstable};
|
||||||
use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
|
use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexMap};
|
||||||
use rustc_errors::struct_span_err;
|
use rustc_errors::{struct_span_err, Applicability};
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def::{DefKind, Res};
|
use rustc_hir::def::{DefKind, Res};
|
||||||
use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
|
use rustc_hir::def_id::{LocalDefId, CRATE_DEF_ID};
|
||||||
|
@ -29,13 +29,13 @@ use std::num::NonZeroU32;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
enum AnnotationKind {
|
enum AnnotationKind {
|
||||||
// Annotation is required if not inherited from unstable parents
|
/// Annotation is required if not inherited from unstable parents.
|
||||||
Required,
|
Required,
|
||||||
// Annotation is useless, reject it
|
/// Annotation is useless, reject it.
|
||||||
Prohibited,
|
Prohibited,
|
||||||
// Deprecation annotation is useless, reject it. (Stability attribute is still required.)
|
/// Deprecation annotation is useless, reject it. (Stability attribute is still required.)
|
||||||
DeprecationProhibited,
|
DeprecationProhibited,
|
||||||
// Annotation itself is useless, but it can be propagated to children
|
/// Annotation itself is useless, but it can be propagated to children.
|
||||||
Container,
|
Container,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ impl InheritStability {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A private tree-walker for producing an Index.
|
/// A private tree-walker for producing an `Index`.
|
||||||
struct Annotator<'a, 'tcx> {
|
struct Annotator<'a, 'tcx> {
|
||||||
tcx: TyCtxt<'tcx>,
|
tcx: TyCtxt<'tcx>,
|
||||||
index: &'a mut Index,
|
index: &'a mut Index,
|
||||||
|
@ -94,9 +94,9 @@ struct Annotator<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> Annotator<'a, 'tcx> {
|
impl<'a, 'tcx> Annotator<'a, 'tcx> {
|
||||||
// Determine the stability for a node based on its attributes and inherited
|
/// Determine the stability for a node based on its attributes and inherited stability. The
|
||||||
// stability. The stability is recorded in the index and used as the parent.
|
/// stability is recorded in the index and used as the parent. If the node is a function,
|
||||||
// If the node is a function, `fn_sig` is its signature
|
/// `fn_sig` is its signature.
|
||||||
fn annotate<F>(
|
fn annotate<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
def_id: LocalDefId,
|
def_id: LocalDefId,
|
||||||
|
@ -265,6 +265,10 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Stability { level: Unstable { implied_by: Some(implied_by), .. }, feature } = stab {
|
||||||
|
self.index.implications.insert(implied_by, feature);
|
||||||
|
}
|
||||||
|
|
||||||
self.index.stab_map.insert(def_id, stab);
|
self.index.stab_map.insert(def_id, stab);
|
||||||
stab
|
stab
|
||||||
});
|
});
|
||||||
|
@ -610,6 +614,7 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index {
|
||||||
stab_map: Default::default(),
|
stab_map: Default::default(),
|
||||||
const_stab_map: Default::default(),
|
const_stab_map: Default::default(),
|
||||||
depr_map: Default::default(),
|
depr_map: Default::default(),
|
||||||
|
implications: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
|
@ -637,6 +642,7 @@ fn stability_index(tcx: TyCtxt<'_>, (): ()) -> Index {
|
||||||
reason: Some(Symbol::intern(reason)),
|
reason: Some(Symbol::intern(reason)),
|
||||||
issue: NonZeroU32::new(27812),
|
issue: NonZeroU32::new(27812),
|
||||||
is_soft: false,
|
is_soft: false,
|
||||||
|
implied_by: None,
|
||||||
},
|
},
|
||||||
feature: sym::rustc_private,
|
feature: sym::rustc_private,
|
||||||
};
|
};
|
||||||
|
@ -667,6 +673,7 @@ pub(crate) fn provide(providers: &mut Providers) {
|
||||||
*providers = Providers {
|
*providers = Providers {
|
||||||
check_mod_unstable_api_usage,
|
check_mod_unstable_api_usage,
|
||||||
stability_index,
|
stability_index,
|
||||||
|
stability_implications: |tcx, _| tcx.stability().implications.clone(),
|
||||||
lookup_stability: |tcx, id| tcx.stability().local_stability(id.expect_local()),
|
lookup_stability: |tcx, id| tcx.stability().local_stability(id.expect_local()),
|
||||||
lookup_const_stability: |tcx, id| tcx.stability().local_const_stability(id.expect_local()),
|
lookup_const_stability: |tcx, id| tcx.stability().local_const_stability(id.expect_local()),
|
||||||
lookup_deprecation_entry: |tcx, id| {
|
lookup_deprecation_entry: |tcx, id| {
|
||||||
|
@ -945,12 +952,45 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
|
||||||
remaining_lib_features.remove(&sym::libc);
|
remaining_lib_features.remove(&sym::libc);
|
||||||
remaining_lib_features.remove(&sym::test);
|
remaining_lib_features.remove(&sym::test);
|
||||||
|
|
||||||
let check_features = |remaining_lib_features: &mut FxIndexMap<_, _>, defined_features: &[_]| {
|
// We always collect the lib features declared in the current crate, even if there are
|
||||||
for &(feature, since) in defined_features {
|
// no unknown features, because the collection also does feature attribute validation.
|
||||||
if let Some(since) = since {
|
let local_defined_features = tcx.lib_features(());
|
||||||
if let Some(span) = remaining_lib_features.get(&feature) {
|
let mut all_lib_features: FxHashMap<_, _> =
|
||||||
|
local_defined_features.to_vec().iter().map(|el| *el).collect();
|
||||||
|
let mut implications = tcx.stability_implications(rustc_hir::def_id::LOCAL_CRATE).clone();
|
||||||
|
for &cnum in tcx.crates(()) {
|
||||||
|
implications.extend(tcx.stability_implications(cnum));
|
||||||
|
all_lib_features.extend(tcx.defined_lib_features(cnum).iter().map(|el| *el));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that every feature referenced by an `implied_by` exists (for features defined in the
|
||||||
|
// local crate).
|
||||||
|
for (implied_by, feature) in tcx.stability_implications(rustc_hir::def_id::LOCAL_CRATE) {
|
||||||
|
// Only `implied_by` needs to be checked, `feature` is guaranteed to exist.
|
||||||
|
if !all_lib_features.contains_key(implied_by) {
|
||||||
|
let span = local_defined_features
|
||||||
|
.stable
|
||||||
|
.get(feature)
|
||||||
|
.map(|(_, span)| span)
|
||||||
|
.or_else(|| local_defined_features.unstable.get(feature))
|
||||||
|
.expect("feature that implied another does not exist");
|
||||||
|
tcx.sess
|
||||||
|
.struct_span_err(
|
||||||
|
*span,
|
||||||
|
format!("feature `{implied_by}` implying `{feature}` does not exist"),
|
||||||
|
)
|
||||||
|
.emit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !remaining_lib_features.is_empty() {
|
||||||
|
for (feature, since) in all_lib_features.iter() {
|
||||||
|
if let Some(since) = since && let Some(span) = remaining_lib_features.get(&feature) {
|
||||||
// Warn if the user has enabled an already-stable lib feature.
|
// Warn if the user has enabled an already-stable lib feature.
|
||||||
unnecessary_stable_feature_lint(tcx, *span, feature, since);
|
if let Some(implies) = implications.get(&feature) {
|
||||||
|
unnecessary_partially_stable_feature_lint(tcx, *span, *feature, *implies, *since);
|
||||||
|
} else {
|
||||||
|
unnecessary_stable_feature_lint(tcx, *span, *feature, *since);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
remaining_lib_features.remove(&feature);
|
remaining_lib_features.remove(&feature);
|
||||||
|
@ -958,20 +998,6 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
// We always collect the lib features declared in the current crate, even if there are
|
|
||||||
// no unknown features, because the collection also does feature attribute validation.
|
|
||||||
let local_defined_features = tcx.lib_features(()).to_vec();
|
|
||||||
if !remaining_lib_features.is_empty() {
|
|
||||||
check_features(&mut remaining_lib_features, &local_defined_features);
|
|
||||||
|
|
||||||
for &cnum in tcx.crates(()) {
|
|
||||||
if remaining_lib_features.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
check_features(&mut remaining_lib_features, tcx.defined_lib_features(cnum));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (feature, span) in remaining_lib_features {
|
for (feature, span) in remaining_lib_features {
|
||||||
|
@ -982,12 +1008,41 @@ pub fn check_unused_or_stable_features(tcx: TyCtxt<'_>) {
|
||||||
// don't lint about unused features. We should re-enable this one day!
|
// don't lint about unused features. We should re-enable this one day!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unnecessary_partially_stable_feature_lint(
|
||||||
|
tcx: TyCtxt<'_>,
|
||||||
|
span: Span,
|
||||||
|
feature: Symbol,
|
||||||
|
implies: Symbol,
|
||||||
|
since: Symbol,
|
||||||
|
) {
|
||||||
|
tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| {
|
||||||
|
lint.build(&format!(
|
||||||
|
"the feature `{feature}` has been partially stabilized since {since} and is succeeded \
|
||||||
|
by the feature `{implies}`"
|
||||||
|
))
|
||||||
|
.span_suggestion(
|
||||||
|
span,
|
||||||
|
&format!(
|
||||||
|
"if you are using features which are still unstable, change to using `{implies}`"
|
||||||
|
),
|
||||||
|
implies,
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
)
|
||||||
|
.span_suggestion(
|
||||||
|
tcx.sess.source_map().span_extend_to_line(span),
|
||||||
|
"if you are using features which are now stable, remove this line",
|
||||||
|
"",
|
||||||
|
Applicability::MaybeIncorrect,
|
||||||
|
)
|
||||||
|
.emit();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fn unnecessary_stable_feature_lint(tcx: TyCtxt<'_>, span: Span, feature: Symbol, since: Symbol) {
|
fn unnecessary_stable_feature_lint(tcx: TyCtxt<'_>, span: Span, feature: Symbol, since: Symbol) {
|
||||||
tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| {
|
tcx.struct_span_lint_hir(lint::builtin::STABLE_FEATURES, hir::CRATE_HIR_ID, span, |lint| {
|
||||||
lint.build(&format!(
|
lint.build(&format!(
|
||||||
"the feature `{}` has been stable since {} and no longer requires \
|
"the feature `{feature}` has been stable since {since} and no longer requires an \
|
||||||
an attribute to enable",
|
attribute to enable",
|
||||||
feature, since
|
|
||||||
))
|
))
|
||||||
.emit();
|
.emit();
|
||||||
});
|
});
|
||||||
|
|
|
@ -796,9 +796,16 @@ impl<'a> Resolver<'a> {
|
||||||
) {
|
) {
|
||||||
let span = path.span;
|
let span = path.span;
|
||||||
if let Some(stability) = &ext.stability {
|
if let Some(stability) = &ext.stability {
|
||||||
if let StabilityLevel::Unstable { reason, issue, is_soft } = stability.level {
|
if let StabilityLevel::Unstable { reason, issue, is_soft, implied_by } = stability.level
|
||||||
|
{
|
||||||
let feature = stability.feature;
|
let feature = stability.feature;
|
||||||
if !self.active_features.contains(&feature) && !span.allows_unstable(feature) {
|
|
||||||
|
let is_allowed = |feature| {
|
||||||
|
self.active_features.contains(&feature) || span.allows_unstable(feature)
|
||||||
|
};
|
||||||
|
let allowed_by_implication =
|
||||||
|
implied_by.map(|feature| is_allowed(feature)).unwrap_or(false);
|
||||||
|
if !is_allowed(feature) && !allowed_by_implication {
|
||||||
let lint_buffer = &mut self.lint_buffer;
|
let lint_buffer = &mut self.lint_buffer;
|
||||||
let soft_handler =
|
let soft_handler =
|
||||||
|lint, span, msg: &_| lint_buffer.buffer_lint(lint, node_id, span, msg);
|
|lint, span, msg: &_| lint_buffer.buffer_lint(lint, node_id, span, msg);
|
||||||
|
|
|
@ -718,6 +718,11 @@ impl SourceMap {
|
||||||
sp
|
sp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extends the given `Span` to contain the entire line it is on.
|
||||||
|
pub fn span_extend_to_line(&self, sp: Span) -> Span {
|
||||||
|
self.span_extend_to_prev_char(self.span_extend_to_next_char(sp, '\n', true), '\n', true)
|
||||||
|
}
|
||||||
|
|
||||||
/// Given a `Span`, tries to get a shorter span ending before the first occurrence of `char`
|
/// Given a `Span`, tries to get a shorter span ending before the first occurrence of `char`
|
||||||
/// `c`.
|
/// `c`.
|
||||||
pub fn span_until_char(&self, sp: Span, c: char) -> Span {
|
pub fn span_until_char(&self, sp: Span, c: char) -> Span {
|
||||||
|
|
|
@ -800,6 +800,7 @@ symbols! {
|
||||||
impl_lint_pass,
|
impl_lint_pass,
|
||||||
impl_macros,
|
impl_macros,
|
||||||
impl_trait_in_bindings,
|
impl_trait_in_bindings,
|
||||||
|
implied_by,
|
||||||
import,
|
import,
|
||||||
import_shadowing,
|
import_shadowing,
|
||||||
imported_main,
|
imported_main,
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#![feature(staged_api)]
|
||||||
|
#![stable(feature = "stability_attribute_implies", since = "1.0.0")]
|
||||||
|
|
||||||
|
#[stable(feature = "foo", since = "1.62.0")]
|
||||||
|
pub fn foo() {}
|
||||||
|
|
||||||
|
#[unstable(feature = "foobar", issue = "1", implied_by = "foo")]
|
||||||
|
pub fn foobar() {}
|
|
@ -0,0 +1,10 @@
|
||||||
|
#![feature(staged_api)]
|
||||||
|
#![stable(feature = "stability_attribute_implies", since = "1.0.0")]
|
||||||
|
|
||||||
|
// Tests that `implied_by = "bar"` results in an error being emitted if `bar` does not exist.
|
||||||
|
|
||||||
|
#[unstable(feature = "foobar", issue = "1", implied_by = "bar")]
|
||||||
|
//~^ ERROR feature `bar` implying `foobar` does not exist
|
||||||
|
pub fn foobar() {}
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -0,0 +1,8 @@
|
||||||
|
error: feature `bar` implying `foobar` does not exist
|
||||||
|
--> $DIR/stability-attribute-implies-missing.rs:6:1
|
||||||
|
|
|
||||||
|
LL | #[unstable(feature = "foobar", issue = "1", implied_by = "bar")]
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
// aux-build:stability-attribute-implies.rs
|
||||||
|
|
||||||
|
// Tests that despite the `foobar` feature being implied by now-stable feature `foo`, if `foobar`
|
||||||
|
// isn't allowed in this crate then an error will be emitted.
|
||||||
|
|
||||||
|
extern crate stability_attribute_implies;
|
||||||
|
use stability_attribute_implies::{foo, foobar};
|
||||||
|
//~^ ERROR use of unstable library feature 'foobar'
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
foo(); // no error - stable
|
||||||
|
foobar(); //~ ERROR use of unstable library feature 'foobar'
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
error[E0658]: use of unstable library feature 'foobar'
|
||||||
|
--> $DIR/stability-attribute-implies-no-feature.rs:7:40
|
||||||
|
|
|
||||||
|
LL | use stability_attribute_implies::{foo, foobar};
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
= note: see issue #1 <https://github.com/rust-lang/rust/issues/1> for more information
|
||||||
|
= help: add `#![feature(foobar)]` to the crate attributes to enable
|
||||||
|
|
||||||
|
error[E0658]: use of unstable library feature 'foobar'
|
||||||
|
--> $DIR/stability-attribute-implies-no-feature.rs:12:5
|
||||||
|
|
|
||||||
|
LL | foobar();
|
||||||
|
| ^^^^^^
|
||||||
|
|
|
||||||
|
= note: see issue #1 <https://github.com/rust-lang/rust/issues/1> for more information
|
||||||
|
= help: add `#![feature(foobar)]` to the crate attributes to enable
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0658`.
|
|
@ -0,0 +1,15 @@
|
||||||
|
// aux-build:stability-attribute-implies.rs
|
||||||
|
#![deny(stable_features)]
|
||||||
|
#![feature(foo)]
|
||||||
|
//~^ ERROR the feature `foo` has been partially stabilized since 1.62.0 and is succeeded by the feature `foobar`
|
||||||
|
|
||||||
|
// Tests that the use of `implied_by` in the `#[unstable]` attribute results in a diagnostic
|
||||||
|
// mentioning partial stabilization, and that given the implied unstable feature is unused (there
|
||||||
|
// is no `foobar` call), that the compiler suggests removing the flag.
|
||||||
|
|
||||||
|
extern crate stability_attribute_implies;
|
||||||
|
use stability_attribute_implies::foo;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
foo();
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
error: the feature `foo` has been partially stabilized since 1.62.0 and is succeeded by the feature `foobar`
|
||||||
|
--> $DIR/stability-attribute-implies-using-stable.rs:3:12
|
||||||
|
|
|
||||||
|
LL | #![feature(foo)]
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
note: the lint level is defined here
|
||||||
|
--> $DIR/stability-attribute-implies-using-stable.rs:2:9
|
||||||
|
|
|
||||||
|
LL | #![deny(stable_features)]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
help: if you are using features which are still unstable, change to using `foobar`
|
||||||
|
|
|
||||||
|
LL | #![feature(foobar)]
|
||||||
|
| ~~~~~~
|
||||||
|
help: if you are using features which are now stable, remove this line
|
||||||
|
|
|
||||||
|
LL - #![feature(foo)]
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
// aux-build:stability-attribute-implies.rs
|
||||||
|
#![deny(stable_features)]
|
||||||
|
#![feature(foo)]
|
||||||
|
//~^ ERROR the feature `foo` has been partially stabilized since 1.62.0 and is succeeded by the feature `foobar`
|
||||||
|
|
||||||
|
// Tests that the use of `implied_by` in the `#[unstable]` attribute results in a diagnostic
|
||||||
|
// mentioning partial stabilization and that given the implied unstable feature is used (there is a
|
||||||
|
// `foobar` call), that the compiler suggests changing to that feature and doesn't error about its
|
||||||
|
// use.
|
||||||
|
|
||||||
|
extern crate stability_attribute_implies;
|
||||||
|
use stability_attribute_implies::{foo, foobar};
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
foo();
|
||||||
|
foobar(); // no error!
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
error: the feature `foo` has been partially stabilized since 1.62.0 and is succeeded by the feature `foobar`
|
||||||
|
--> $DIR/stability-attribute-implies-using-unstable.rs:3:12
|
||||||
|
|
|
||||||
|
LL | #![feature(foo)]
|
||||||
|
| ^^^
|
||||||
|
|
|
||||||
|
note: the lint level is defined here
|
||||||
|
--> $DIR/stability-attribute-implies-using-unstable.rs:2:9
|
||||||
|
|
|
||||||
|
LL | #![deny(stable_features)]
|
||||||
|
| ^^^^^^^^^^^^^^^
|
||||||
|
help: if you are using features which are still unstable, change to using `foobar`
|
||||||
|
|
|
||||||
|
LL | #![feature(foobar)]
|
||||||
|
| ~~~~~~
|
||||||
|
help: if you are using features which are now stable, remove this line
|
||||||
|
|
|
||||||
|
LL - #![feature(foo)]
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to previous error
|
||||||
|
|
|
@ -8,7 +8,7 @@ error[E0541]: unknown meta item 'sinse'
|
||||||
--> $DIR/stability-attribute-sanity-2.rs:10:25
|
--> $DIR/stability-attribute-sanity-2.rs:10:25
|
||||||
|
|
|
|
||||||
LL | #[stable(feature = "a", sinse = "1.0.0")]
|
LL | #[stable(feature = "a", sinse = "1.0.0")]
|
||||||
| ^^^^^^^^^^^^^^^ expected one of `since`, `note`
|
| ^^^^^^^^^^^^^^^ expected one of `feature`, `since`
|
||||||
|
|
||||||
error[E0545]: `issue` must be a non-zero numeric string or "none"
|
error[E0545]: `issue` must be a non-zero numeric string or "none"
|
||||||
--> $DIR/stability-attribute-sanity-2.rs:13:27
|
--> $DIR/stability-attribute-sanity-2.rs:13:27
|
||||||
|
|
|
@ -14,7 +14,7 @@ error[E0541]: unknown meta item 'reason'
|
||||||
--> $DIR/stability-attribute-sanity.rs:8:42
|
--> $DIR/stability-attribute-sanity.rs:8:42
|
||||||
|
|
|
|
||||||
LL | #[stable(feature = "a", since = "b", reason)]
|
LL | #[stable(feature = "a", since = "b", reason)]
|
||||||
| ^^^^^^ expected one of `since`, `note`
|
| ^^^^^^ expected one of `feature`, `since`
|
||||||
|
|
||||||
error[E0539]: incorrect meta item
|
error[E0539]: incorrect meta item
|
||||||
--> $DIR/stability-attribute-sanity.rs:11:29
|
--> $DIR/stability-attribute-sanity.rs:11:29
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue