rustdoc-json: Structured attributes
Implements https://www.github.com/rust-lang/rust/issues/141358. This has 2 primary benefits: 1. For rustdoc-json consumers, they no longer need to parse strings of attributes, but it's there in a structured and normalized way. 2. For rustc contributors, the output of HIR pretty printing is no longer a versioned thing in the output. People can work on https://github.com/rust-lang/rust/issues/131229 without needing to bump `FORMAT_VERSION`. (Over time, as the attribute refractor continues, I expect we'll add new things to `rustdoc_json_types::Attribute`. But this can be done separately to the rustc changes).
This commit is contained in:
@@ -759,79 +759,48 @@ impl Item {
|
||||
Some(tcx.visibility(def_id))
|
||||
}
|
||||
|
||||
fn attributes_without_repr(&self, tcx: TyCtxt<'_>, is_json: bool) -> Vec<String> {
|
||||
const ALLOWED_ATTRIBUTES: &[Symbol] =
|
||||
&[sym::export_name, sym::link_section, sym::no_mangle, sym::non_exhaustive];
|
||||
/// Get a list of attributes excluding `#[repr]` to display.
|
||||
///
|
||||
/// Only used by the HTML output-format.
|
||||
fn attributes_without_repr(&self) -> Vec<String> {
|
||||
self.attrs
|
||||
.other_attrs
|
||||
.iter()
|
||||
.filter_map(|attr| {
|
||||
if let hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) = attr {
|
||||
.filter_map(|attr| match attr {
|
||||
hir::Attribute::Parsed(AttributeKind::LinkSection { name, .. }) => {
|
||||
Some(format!("#[link_section = \"{name}\"]"))
|
||||
}
|
||||
// NoMangle is special cased, as it appears in HTML output, and we want to show it in source form, not HIR printing.
|
||||
// It is also used by cargo-semver-checks.
|
||||
else if let hir::Attribute::Parsed(AttributeKind::NoMangle(..)) = attr {
|
||||
hir::Attribute::Parsed(AttributeKind::NoMangle(..)) => {
|
||||
Some("#[no_mangle]".to_string())
|
||||
} else if let hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) = attr
|
||||
{
|
||||
Some(format!("#[export_name = \"{name}\"]"))
|
||||
} else if let hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) = attr {
|
||||
Some("#[non_exhaustive]".to_string())
|
||||
} else if is_json {
|
||||
match attr {
|
||||
// rustdoc-json stores this in `Item::deprecation`, so we
|
||||
// don't want it it `Item::attrs`.
|
||||
hir::Attribute::Parsed(AttributeKind::Deprecation { .. }) => None,
|
||||
// We have separate pretty-printing logic for `#[repr(..)]` attributes.
|
||||
hir::Attribute::Parsed(AttributeKind::Repr { .. }) => None,
|
||||
// target_feature is special-cased because cargo-semver-checks uses it
|
||||
hir::Attribute::Parsed(AttributeKind::TargetFeature(features, _)) => {
|
||||
let mut output = String::new();
|
||||
for (i, (feature, _)) in features.iter().enumerate() {
|
||||
if i != 0 {
|
||||
output.push_str(", ");
|
||||
}
|
||||
output.push_str(&format!("enable=\"{}\"", feature.as_str()));
|
||||
}
|
||||
Some(format!("#[target_feature({output})]"))
|
||||
}
|
||||
hir::Attribute::Parsed(AttributeKind::AutomaticallyDerived(..)) => {
|
||||
Some("#[automatically_derived]".to_string())
|
||||
}
|
||||
_ => Some({
|
||||
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
|
||||
assert_eq!(s.pop(), Some('\n'));
|
||||
s
|
||||
}),
|
||||
}
|
||||
} else {
|
||||
if !attr.has_any_name(ALLOWED_ATTRIBUTES) {
|
||||
return None;
|
||||
}
|
||||
Some(
|
||||
rustc_hir_pretty::attribute_to_string(&tcx, attr)
|
||||
.replace("\\\n", "")
|
||||
.replace('\n', "")
|
||||
.replace(" ", " "),
|
||||
)
|
||||
}
|
||||
hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) => {
|
||||
Some(format!("#[export_name = \"{name}\"]"))
|
||||
}
|
||||
hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) => {
|
||||
Some("#[non_exhaustive]".to_string())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Vec<String> {
|
||||
let mut attrs = self.attributes_without_repr(tcx, is_json);
|
||||
/// Get a list of attributes to display on this item.
|
||||
///
|
||||
/// Only used by the HTML output-format.
|
||||
pub(crate) fn attributes(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Vec<String> {
|
||||
let mut attrs = self.attributes_without_repr();
|
||||
|
||||
if let Some(repr_attr) = self.repr(tcx, cache, is_json) {
|
||||
if let Some(repr_attr) = self.repr(tcx, cache) {
|
||||
attrs.push(repr_attr);
|
||||
}
|
||||
attrs
|
||||
}
|
||||
|
||||
/// Returns a stringified `#[repr(...)]` attribute.
|
||||
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache, is_json: bool) -> Option<String> {
|
||||
repr_attributes(tcx, cache, self.def_id()?, self.type_(), is_json)
|
||||
///
|
||||
/// Only used by the HTML output-format.
|
||||
pub(crate) fn repr(&self, tcx: TyCtxt<'_>, cache: &Cache) -> Option<String> {
|
||||
repr_attributes(tcx, cache, self.def_id()?, self.type_())
|
||||
}
|
||||
|
||||
pub fn is_doc_hidden(&self) -> bool {
|
||||
@@ -843,12 +812,14 @@ impl Item {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a string representing the `#[repr]` attribute if present.
|
||||
///
|
||||
/// Only used by the HTML output-format.
|
||||
pub(crate) fn repr_attributes(
|
||||
tcx: TyCtxt<'_>,
|
||||
cache: &Cache,
|
||||
def_id: DefId,
|
||||
item_type: ItemType,
|
||||
is_json: bool,
|
||||
) -> Option<String> {
|
||||
use rustc_abi::IntegerType;
|
||||
|
||||
@@ -865,7 +836,6 @@ pub(crate) fn repr_attributes(
|
||||
// Render `repr(transparent)` iff the non-1-ZST field is public or at least one
|
||||
// field is public in case all fields are 1-ZST fields.
|
||||
let render_transparent = cache.document_private
|
||||
|| is_json
|
||||
|| adt
|
||||
.all_fields()
|
||||
.find(|field| {
|
||||
|
||||
@@ -1191,7 +1191,7 @@ fn render_assoc_item(
|
||||
// a whitespace prefix and newline.
|
||||
fn render_attributes_in_pre(it: &clean::Item, prefix: &str, cx: &Context<'_>) -> impl fmt::Display {
|
||||
fmt::from_fn(move |f| {
|
||||
for a in it.attributes(cx.tcx(), cx.cache(), false) {
|
||||
for a in it.attributes(cx.tcx(), cx.cache()) {
|
||||
writeln!(f, "{prefix}{a}")?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -1207,7 +1207,7 @@ fn render_code_attribute(code_attr: CodeAttribute, w: &mut impl fmt::Write) {
|
||||
// When an attribute is rendered inside a <code> tag, it is formatted using
|
||||
// a div to produce a newline after it.
|
||||
fn render_attributes_in_code(w: &mut impl fmt::Write, it: &clean::Item, cx: &Context<'_>) {
|
||||
for attr in it.attributes(cx.tcx(), cx.cache(), false) {
|
||||
for attr in it.attributes(cx.tcx(), cx.cache()) {
|
||||
render_code_attribute(CodeAttribute(attr), w);
|
||||
}
|
||||
}
|
||||
@@ -1219,7 +1219,7 @@ fn render_repr_attributes_in_code(
|
||||
def_id: DefId,
|
||||
item_type: ItemType,
|
||||
) {
|
||||
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type, false) {
|
||||
if let Some(repr) = clean::repr_attributes(cx.tcx(), cx.cache(), def_id, item_type) {
|
||||
render_code_attribute(CodeAttribute(repr), w);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1487,12 +1487,11 @@ impl<'a, 'cx: 'a> ItemUnion<'a, 'cx> {
|
||||
self.cx.cache(),
|
||||
self.def_id,
|
||||
ItemType::Union,
|
||||
false,
|
||||
) {
|
||||
writeln!(f, "{repr}")?;
|
||||
};
|
||||
} else {
|
||||
for a in self.it.attributes(self.cx.tcx(), self.cx.cache(), false) {
|
||||
for a in self.it.attributes(self.cx.tcx(), self.cx.cache()) {
|
||||
writeln!(f, "{a}")?;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
use rustc_abi::ExternAbi;
|
||||
use rustc_ast::ast;
|
||||
use rustc_attr_data_structures::{self as attrs, DeprecatedSince};
|
||||
use rustc_hir as hir;
|
||||
use rustc_hir::def::CtorKind;
|
||||
use rustc_hir::def_id::DefId;
|
||||
use rustc_hir::{HeaderSafety, Safety};
|
||||
use rustc_metadata::rendered_const;
|
||||
use rustc_middle::ty::TyCtxt;
|
||||
use rustc_middle::{bug, ty};
|
||||
use rustc_span::{Pos, kw, sym};
|
||||
use rustdoc_json_types::*;
|
||||
@@ -39,7 +41,12 @@ impl JsonRenderer<'_> {
|
||||
})
|
||||
.collect();
|
||||
let docs = item.opt_doc_value();
|
||||
let attrs = item.attributes(self.tcx, &self.cache, true);
|
||||
let attrs = item
|
||||
.attrs
|
||||
.other_attrs
|
||||
.iter()
|
||||
.filter_map(|a| maybe_from_hir_attr(a, item.item_id, self.tcx))
|
||||
.collect();
|
||||
let span = item.span(self.tcx);
|
||||
let visibility = item.visibility(self.tcx);
|
||||
let clean::ItemInner { name, item_id, .. } = *item.inner;
|
||||
@@ -886,3 +893,93 @@ impl FromClean<ItemType> for ItemKind {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Maybe convert a attribute from hir to json.
|
||||
///
|
||||
/// Returns `None` if the attribute shouldn't be in the output.
|
||||
fn maybe_from_hir_attr(
|
||||
attr: &hir::Attribute,
|
||||
item_id: ItemId,
|
||||
tcx: TyCtxt<'_>,
|
||||
) -> Option<Attribute> {
|
||||
use attrs::AttributeKind as AK;
|
||||
|
||||
let kind = match attr {
|
||||
hir::Attribute::Parsed(kind) => kind,
|
||||
|
||||
hir::Attribute::Unparsed(_) => {
|
||||
// FIXME: We should handle `#[doc(hidden)]`.
|
||||
return Some(other_attr(tcx, attr));
|
||||
}
|
||||
};
|
||||
|
||||
Some(match kind {
|
||||
AK::Deprecation { .. } => return None, // Handled separately into Item::deprecation.
|
||||
AK::DocComment { .. } => unreachable!("doc comments stripped out earlier"),
|
||||
|
||||
AK::MustUse { reason, span: _ } => {
|
||||
Attribute::MustUse { reason: reason.map(|s| s.to_string()) }
|
||||
}
|
||||
AK::Repr { .. } => repr_attr(
|
||||
tcx,
|
||||
item_id.as_def_id().expect("all items that could have #[repr] have a DefId"),
|
||||
),
|
||||
AK::ExportName { name, span: _ } => Attribute::ExportName(name.to_string()),
|
||||
AK::LinkSection { name, span: _ } => Attribute::LinkSection(name.to_string()),
|
||||
AK::TargetFeature(features, _span) => Attribute::TargetFeature {
|
||||
enable: features.iter().map(|(feat, _span)| feat.to_string()).collect(),
|
||||
},
|
||||
|
||||
AK::NoMangle(_) => Attribute::NoMangle,
|
||||
AK::NonExhaustive(_) => Attribute::NonExhaustive,
|
||||
AK::AutomaticallyDerived(_) => Attribute::AutomaticallyDerived,
|
||||
|
||||
_ => other_attr(tcx, attr),
|
||||
})
|
||||
}
|
||||
|
||||
fn other_attr(tcx: TyCtxt<'_>, attr: &hir::Attribute) -> Attribute {
|
||||
let mut s = rustc_hir_pretty::attribute_to_string(&tcx, attr);
|
||||
assert_eq!(s.pop(), Some('\n'));
|
||||
Attribute::Other(s)
|
||||
}
|
||||
|
||||
fn repr_attr(tcx: TyCtxt<'_>, def_id: DefId) -> Attribute {
|
||||
let repr = tcx.adt_def(def_id).repr();
|
||||
|
||||
let kind = if repr.c() {
|
||||
ReprKind::C
|
||||
} else if repr.transparent() {
|
||||
ReprKind::Transparent
|
||||
} else if repr.simd() {
|
||||
ReprKind::Simd
|
||||
} else {
|
||||
ReprKind::Rust
|
||||
};
|
||||
|
||||
let align = repr.align.map(|a| a.bytes());
|
||||
let packed = repr.pack.map(|p| p.bytes());
|
||||
let int = repr.int.map(format_integer_type);
|
||||
|
||||
Attribute::Repr(AttributeRepr { kind, align, packed, int })
|
||||
}
|
||||
|
||||
fn format_integer_type(it: rustc_abi::IntegerType) -> String {
|
||||
use rustc_abi::Integer::*;
|
||||
use rustc_abi::IntegerType::*;
|
||||
match it {
|
||||
Pointer(true) => "isize",
|
||||
Pointer(false) => "usize",
|
||||
Fixed(I8, true) => "i8",
|
||||
Fixed(I8, false) => "u8",
|
||||
Fixed(I16, true) => "i16",
|
||||
Fixed(I16, false) => "u16",
|
||||
Fixed(I32, true) => "i32",
|
||||
Fixed(I32, false) => "u32",
|
||||
Fixed(I64, true) => "i64",
|
||||
Fixed(I64, false) => "u64",
|
||||
Fixed(I128, true) => "i128",
|
||||
Fixed(I128, false) => "u128",
|
||||
}
|
||||
.to_owned()
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ pub type FxHashMap<K, V> = HashMap<K, V>; // re-export for use in src/librustdoc
|
||||
// will instead cause conflicts. See #94591 for more. (This paragraph and the "Latest feature" line
|
||||
// are deliberately not in a doc comment, because they need not be in public docs.)
|
||||
//
|
||||
// Latest feature: Pretty printing of no_mangle attributes changed
|
||||
pub const FORMAT_VERSION: u32 = 53;
|
||||
// Latest feature: Structured Attributes
|
||||
pub const FORMAT_VERSION: u32 = 54;
|
||||
|
||||
/// The root of the emitted JSON blob.
|
||||
///
|
||||
@@ -195,13 +195,94 @@ pub struct Item {
|
||||
/// - `#[repr(C)]` and other reprs also appear as themselves,
|
||||
/// though potentially with a different order: e.g. `repr(i8, C)` may become `repr(C, i8)`.
|
||||
/// Multiple repr attributes on the same item may be combined into an equivalent single attr.
|
||||
pub attrs: Vec<String>,
|
||||
pub attrs: Vec<Attribute>,
|
||||
/// Information about the item’s deprecation, if present.
|
||||
pub deprecation: Option<Deprecation>,
|
||||
/// The type-specific fields describing this item.
|
||||
pub inner: ItemEnum,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// An attribute, e.g. `#[repr(C)]`
|
||||
///
|
||||
/// This doesn't include:
|
||||
/// - `#[doc = "Doc Comment"]` or `/// Doc comment`. These are in [`Item::docs`] instead.
|
||||
/// - `#[deprecated]`. These are in [`Item::deprecation`] instead.
|
||||
pub enum Attribute {
|
||||
/// `#[non_exhaustive]`
|
||||
NonExhaustive,
|
||||
|
||||
/// `#[must_use]`
|
||||
MustUse { reason: Option<String> },
|
||||
|
||||
/// `#[export_name = "name"]`
|
||||
ExportName(String),
|
||||
|
||||
/// `#[link_section = "name"]`
|
||||
LinkSection(String),
|
||||
|
||||
/// `#[automatically_derived]`
|
||||
AutomaticallyDerived,
|
||||
|
||||
/// `#[repr]`
|
||||
Repr(AttributeRepr),
|
||||
|
||||
/// `#[no_mangle]`
|
||||
NoMangle,
|
||||
|
||||
/// #[target_feature(enable = "feature1", enable = "feature2")]
|
||||
TargetFeature { enable: Vec<String> },
|
||||
|
||||
/// Something else.
|
||||
///
|
||||
/// Things here are explicitly *not* covered by the [`FORMAT_VERSION`]
|
||||
/// constant, and may change without bumping the format version.
|
||||
///
|
||||
/// As an implementation detail, this is currently either:
|
||||
/// 1. A HIR debug printing, like `"#[attr = Optimize(Speed)]"`
|
||||
/// 2. The attribute as it appears in source form, like
|
||||
/// `"#[optimize(speed)]"`.
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
/// The contents of a `#[repr(...)]` attribute.
|
||||
///
|
||||
/// Used in [`Attribute::Repr`].
|
||||
pub struct AttributeRepr {
|
||||
/// The representation, e.g. `#[repr(C)]`, `#[repr(transparent)]`
|
||||
pub kind: ReprKind,
|
||||
|
||||
/// Alignment in bytes, if explicitly specified by `#[repr(align(...)]`.
|
||||
pub align: Option<u64>,
|
||||
/// Alignment in bytes, if explicitly specified by `#[repr(packed(...)]]`.
|
||||
pub packed: Option<u64>,
|
||||
|
||||
/// The integer type for an enum descriminant, if explicitly specified.
|
||||
///
|
||||
/// e.g. `"i32"`, for `#[repr(C, i32)]`
|
||||
pub int: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
/// The kind of `#[repr]`.
|
||||
///
|
||||
/// See [AttributeRepr::kind]`.
|
||||
pub enum ReprKind {
|
||||
/// `#[repr(Rust)]`
|
||||
///
|
||||
/// Also the default.
|
||||
Rust,
|
||||
/// `#[repr(C)]`
|
||||
C,
|
||||
/// `#[repr(transparent)]
|
||||
Transparent,
|
||||
/// `#[repr(simd)]`
|
||||
Simd,
|
||||
}
|
||||
|
||||
/// A range of source code.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Span {
|
||||
@@ -1343,7 +1424,7 @@ pub struct Static {
|
||||
|
||||
/// Is the static `unsafe`?
|
||||
///
|
||||
/// This is only true if it's in an `extern` block, and not explicity marked
|
||||
/// This is only true if it's in an `extern` block, and not explicitly marked
|
||||
/// as `safe`.
|
||||
///
|
||||
/// ```rust
|
||||
|
||||
@@ -9,5 +9,5 @@ impl Default for Manual {
|
||||
}
|
||||
}
|
||||
|
||||
//@ is '$.index[?(@.inner.impl.for.resolved_path.path == "Derive" && @.inner.impl.trait.path == "Default")].attrs' '["#[automatically_derived]"]'
|
||||
//@ is '$.index[?(@.inner.impl.for.resolved_path.path == "Derive" && @.inner.impl.trait.path == "Default")].attrs' '["automatically_derived"]'
|
||||
//@ is '$.index[?(@.inner.impl.for.resolved_path.path == "Manual" && @.inner.impl.trait.path == "Default")].attrs' '[]'
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
//@ is "$.index[?(@.name=='cold_fn')].attrs" '["#[attr = Cold]"]'
|
||||
//@ is "$.index[?(@.name=='cold_fn')].attrs" '[{"other": "#[attr = Cold]"}]'
|
||||
#[cold]
|
||||
pub fn cold_fn() {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@ edition: 2021
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[export_name = \"altered\"]"]'
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '[{"export_name": "altered"}]'
|
||||
#[export_name = "altered"]
|
||||
pub extern "C" fn example() {}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
#![no_std]
|
||||
|
||||
// The representation of `#[unsafe(export_name = ..)]` in rustdoc in edition 2024
|
||||
// is still `#[export_name = ..]` without the `unsafe` attribute wrapper.
|
||||
// doesn't mention the `unsafe`.
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[export_name = \"altered\"]"]'
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '[{"export_name": "altered"}]'
|
||||
#[unsafe(export_name = "altered")]
|
||||
pub extern "C" fn example() {}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//@ is "$.index[?(@.name=='just_inline')].attrs" '["#[attr = Inline(Hint)]"]'
|
||||
//@ is "$.index[?(@.name=='just_inline')].attrs" '[{"other": "#[attr = Inline(Hint)]"}]'
|
||||
#[inline]
|
||||
pub fn just_inline() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='inline_always')].attrs" '["#[attr = Inline(Always)]"]'
|
||||
//@ is "$.index[?(@.name=='inline_always')].attrs" '[{"other": "#[attr = Inline(Always)]"}]'
|
||||
#[inline(always)]
|
||||
pub fn inline_always() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='inline_never')].attrs" '["#[attr = Inline(Never)]"]'
|
||||
//@ is "$.index[?(@.name=='inline_never')].attrs" '[{"other": "#[attr = Inline(Never)]"}]'
|
||||
#[inline(never)]
|
||||
pub fn inline_never() {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
//@ edition: 2021
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[link_section = \".text\"]"]'
|
||||
//@ count "$.index[?(@.name=='example')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='example')].attrs[*].link_section" '".text"'
|
||||
#[link_section = ".text"]
|
||||
pub extern "C" fn example() {}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
// Since the 2024 edition the link_section attribute must use the unsafe qualification.
|
||||
// However, the unsafe qualification is not shown by rustdoc.
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[link_section = \".text\"]"]'
|
||||
//@ count "$.index[?(@.name=='example')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='example')].attrs[*].link_section" '".text"'
|
||||
#[unsafe(link_section = ".text")]
|
||||
pub extern "C" fn example() {}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[attr = MustUse]"]'
|
||||
//@ is "$.index[?(@.name=='example')].attrs[*].must_use.reason" null
|
||||
#[must_use]
|
||||
pub fn example() -> impl Iterator<Item = i64> {}
|
||||
|
||||
//@ is "$.index[?(@.name=='explicit_message')].attrs" '["#[attr = MustUse {reason: \"does nothing if you do not use it\"}]"]'
|
||||
//@ is "$.index[?(@.name=='explicit_message')].attrs[*].must_use.reason" '"does nothing if you do not use it"'
|
||||
#[must_use = "does nothing if you do not use it"]
|
||||
pub fn explicit_message() -> impl Iterator<Item = i64> {}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//@ edition: 2021
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[no_mangle]"]'
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["no_mangle"]'
|
||||
#[no_mangle]
|
||||
pub extern "C" fn example() {}
|
||||
|
||||
@@ -4,6 +4,6 @@
|
||||
// The representation of `#[unsafe(no_mangle)]` in rustdoc in edition 2024
|
||||
// is still `#[no_mangle]` without the `unsafe` attribute wrapper.
|
||||
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["#[no_mangle]"]'
|
||||
//@ is "$.index[?(@.name=='example')].attrs" '["no_mangle"]'
|
||||
#[unsafe(no_mangle)]
|
||||
pub extern "C" fn example() {}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='MyEnum')].attrs" '["#[non_exhaustive]"]'
|
||||
//@ is "$.index[?(@.name=='MyEnum')].attrs" '["non_exhaustive"]'
|
||||
#[non_exhaustive]
|
||||
pub enum MyEnum {
|
||||
First,
|
||||
}
|
||||
|
||||
pub enum NonExhaustiveVariant {
|
||||
//@ is "$.index[?(@.name=='Variant')].attrs" '["#[non_exhaustive]"]'
|
||||
//@ is "$.index[?(@.name=='Variant')].attrs" '["non_exhaustive"]'
|
||||
#[non_exhaustive]
|
||||
Variant(i64),
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='MyStruct')].attrs" '["#[non_exhaustive]"]'
|
||||
//@ is "$.index[?(@.name=='MyStruct')].attrs" '["non_exhaustive"]'
|
||||
#[non_exhaustive]
|
||||
pub struct MyStruct {
|
||||
pub x: i64,
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
#![feature(optimize_attribute)]
|
||||
|
||||
//@ is "$.index[?(@.name=='speed')].attrs" '["#[attr = Optimize(Speed)]"]'
|
||||
//@ is "$.index[?(@.name=='speed')].attrs" '[{"other": "#[attr = Optimize(Speed)]"}]'
|
||||
#[optimize(speed)]
|
||||
pub fn speed() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='size')].attrs" '["#[attr = Optimize(Size)]"]'
|
||||
//@ is "$.index[?(@.name=='size')].attrs" '[{"other": "#[attr = Optimize(Size)]"}]'
|
||||
#[optimize(size)]
|
||||
pub fn size() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='none')].attrs" '["#[attr = Optimize(DoNotOptimize)]"]'
|
||||
//@ is "$.index[?(@.name=='none')].attrs" '[{"other": "#[attr = Optimize(DoNotOptimize)]"}]'
|
||||
#[optimize(none)]
|
||||
pub fn none() {}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='Aligned')].attrs" '["#[repr(align(4))]"]'
|
||||
//@ count "$.index[?(@.name=='Aligned')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='Aligned')].attrs[*].repr.align" 4
|
||||
#[repr(align(4))]
|
||||
pub struct Aligned {
|
||||
a: i8,
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='ReprCStruct')].attrs" '["#[repr(C)]"]'
|
||||
//@ count "$.index[?(@.name=='ReprCStruct')].attrs" 1
|
||||
//@ is "$.index[?(@.name=='ReprCStruct')].attrs[*].repr.kind" '"c"'
|
||||
//@ is "$.index[?(@.name=='ReprCStruct')].attrs[*].repr.int" null
|
||||
//@ is "$.index[?(@.name=='ReprCStruct')].attrs[*].repr.packed" null
|
||||
//@ is "$.index[?(@.name=='ReprCStruct')].attrs[*].repr.align" null
|
||||
#[repr(C)]
|
||||
pub struct ReprCStruct(pub i64);
|
||||
|
||||
//@ is "$.index[?(@.name=='ReprCEnum')].attrs" '["#[repr(C)]"]'
|
||||
//@ count "$.index[?(@.name=='ReprCEnum')].attrs" 1
|
||||
//@ is "$.index[?(@.name=='ReprCEnum')].attrs[*].repr.kind" '"c"'
|
||||
//@ is "$.index[?(@.name=='ReprCEnum')].attrs[*].repr.int" null
|
||||
//@ is "$.index[?(@.name=='ReprCEnum')].attrs[*].repr.packed" null
|
||||
//@ is "$.index[?(@.name=='ReprCEnum')].attrs[*].repr.align" null
|
||||
#[repr(C)]
|
||||
pub enum ReprCEnum {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReprCUnion')].attrs" '["#[repr(C)]"]'
|
||||
//@ count "$.index[?(@.name=='ReprCUnion')].attrs" 1
|
||||
//@ is "$.index[?(@.name=='ReprCUnion')].attrs[*].repr.kind" '"c"'
|
||||
//@ is "$.index[?(@.name=='ReprCUnion')].attrs[*].repr.int" null
|
||||
//@ is "$.index[?(@.name=='ReprCUnion')].attrs[*].repr.packed" null
|
||||
//@ is "$.index[?(@.name=='ReprCUnion')].attrs[*].repr.align" null
|
||||
#[repr(C)]
|
||||
pub union ReprCUnion {
|
||||
pub left: i64,
|
||||
|
||||
11
tests/rustdoc-json/attrs/repr_c_int_enum.rs
Normal file
11
tests/rustdoc-json/attrs/repr_c_int_enum.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
//@ count "$.index[?(@.name=='Foo')].attrs" 1
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs[*].repr.kind" '"c"'
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs[*].repr.int" '"u8"'
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs[*].repr.packed" null
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs[*].repr.align" 16
|
||||
#[repr(C, u8)]
|
||||
#[repr(align(16))]
|
||||
pub enum Foo {
|
||||
A(bool) = b'A',
|
||||
B(char) = b'C',
|
||||
}
|
||||
@@ -1,35 +1,34 @@
|
||||
#![no_std]
|
||||
|
||||
// Combinations of `#[repr(..)]` attributes.
|
||||
// Rustdoc JSON emits normalized output, regardless of the original source.
|
||||
|
||||
//@ is "$.index[?(@.name=='ReprCI8')].attrs" '["#[repr(C, i8)]"]'
|
||||
//@ is "$.index[?(@.name=='ReprCI8')].attrs" '[{"repr":{"align":null,"int":"i8","kind":"c","packed":null}}]'
|
||||
#[repr(C, i8)]
|
||||
pub enum ReprCI8 {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='SeparateReprCI16')].attrs" '["#[repr(C, i16)]"]'
|
||||
//@ is "$.index[?(@.name=='SeparateReprCI16')].attrs" '[{"repr":{"align":null,"int":"i16","kind":"c","packed":null}}]'
|
||||
#[repr(C)]
|
||||
#[repr(i16)]
|
||||
pub enum SeparateReprCI16 {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReversedReprCUsize')].attrs" '["#[repr(C, usize)]"]'
|
||||
//@ is "$.index[?(@.name=='ReversedReprCUsize')].attrs" '[{"repr":{"align":null,"int":"usize","kind":"c","packed":null}}]'
|
||||
#[repr(usize, C)]
|
||||
pub enum ReversedReprCUsize {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReprCPacked')].attrs" '["#[repr(C, packed(1))]"]'
|
||||
//@ is "$.index[?(@.name=='ReprCPacked')].attrs" '[{"repr":{"align":null,"int":null,"kind":"c","packed":1}}]'
|
||||
#[repr(C, packed)]
|
||||
pub struct ReprCPacked {
|
||||
a: i8,
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='SeparateReprCPacked')].attrs" '["#[repr(C, packed(2))]"]'
|
||||
//@ is "$.index[?(@.name=='SeparateReprCPacked')].attrs" '[{"repr":{"align":null,"int":null,"kind":"c","packed":2}}]'
|
||||
#[repr(C)]
|
||||
#[repr(packed(2))]
|
||||
pub struct SeparateReprCPacked {
|
||||
@@ -37,21 +36,21 @@ pub struct SeparateReprCPacked {
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReversedReprCPacked')].attrs" '["#[repr(C, packed(2))]"]'
|
||||
//@ is "$.index[?(@.name=='ReversedReprCPacked')].attrs" '[{"repr":{"align":null,"int":null,"kind":"c","packed":2}}]'
|
||||
#[repr(packed(2), C)]
|
||||
pub struct ReversedReprCPacked {
|
||||
a: i8,
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReprCAlign')].attrs" '["#[repr(C, align(16))]"]'
|
||||
//@ is "$.index[?(@.name=='ReprCAlign')].attrs" '[{"repr":{"align":16,"int":null,"kind":"c","packed":null}}]'
|
||||
#[repr(C, align(16))]
|
||||
pub struct ReprCAlign {
|
||||
a: i8,
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='SeparateReprCAlign')].attrs" '["#[repr(C, align(2))]"]'
|
||||
//@ is "$.index[?(@.name=='SeparateReprCAlign')].attrs" '[{"repr":{"align":2,"int":null,"kind":"c","packed":null}}]'
|
||||
#[repr(C)]
|
||||
#[repr(align(2))]
|
||||
pub struct SeparateReprCAlign {
|
||||
@@ -59,25 +58,25 @@ pub struct SeparateReprCAlign {
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReversedReprCAlign')].attrs" '["#[repr(C, align(2))]"]'
|
||||
//@ is "$.index[?(@.name=='ReversedReprCAlign')].attrs" '[{"repr":{"align":2,"int":null,"kind":"c","packed":null}}]'
|
||||
#[repr(align(2), C)]
|
||||
pub struct ReversedReprCAlign {
|
||||
a: i8,
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='AlignedExplicitRepr')].attrs" '["#[repr(C, align(16), isize)]"]'
|
||||
//@ is "$.index[?(@.name=='AlignedExplicitRepr')].attrs" '[{"repr":{"align":16,"int":"isize","kind":"c","packed":null}}]'
|
||||
#[repr(C, align(16), isize)]
|
||||
pub enum AlignedExplicitRepr {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='ReorderedAlignedExplicitRepr')].attrs" '["#[repr(C, align(16), isize)]"]'
|
||||
//@ is "$.index[?(@.name=='ReorderedAlignedExplicitRepr')].attrs" '[{"repr":{"align":16,"int":"isize","kind":"c","packed":null}}]'
|
||||
#[repr(isize, C, align(16))]
|
||||
pub enum ReorderedAlignedExplicitRepr {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='Transparent')].attrs" '["#[repr(transparent)]"]'
|
||||
//@ is "$.index[?(@.name=='Transparent')].attrs" '[{"repr":{"align":null,"int":null,"kind":"transparent","packed":null}}]'
|
||||
#[repr(transparent)]
|
||||
pub struct Transparent(i64);
|
||||
|
||||
@@ -1,18 +1,27 @@
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='I8')].attrs" '["#[repr(i8)]"]'
|
||||
//@ is "$.index[?(@.name=='I8')].attrs[*].repr.int" '"i8"'
|
||||
//@ is "$.index[?(@.name=='I8')].attrs[*].repr.kind" '"rust"'
|
||||
//@ is "$.index[?(@.name=='I8')].attrs[*].repr.align" null
|
||||
//@ is "$.index[?(@.name=='I8')].attrs[*].repr.packed" null
|
||||
#[repr(i8)]
|
||||
pub enum I8 {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='I32')].attrs" '["#[repr(i32)]"]'
|
||||
//@ is "$.index[?(@.name=='I32')].attrs[*].repr.int" '"i32"'
|
||||
//@ is "$.index[?(@.name=='I32')].attrs[*].repr.kind" '"rust"'
|
||||
//@ is "$.index[?(@.name=='I32')].attrs[*].repr.align" null
|
||||
//@ is "$.index[?(@.name=='I32')].attrs[*].repr.packed" null
|
||||
#[repr(i32)]
|
||||
pub enum I32 {
|
||||
First,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='Usize')].attrs" '["#[repr(usize)]"]'
|
||||
//@ is "$.index[?(@.name=='Usize')].attrs[*].repr.int" '"usize"'
|
||||
//@ is "$.index[?(@.name=='Usize')].attrs[*].repr.kind" '"rust"'
|
||||
//@ is "$.index[?(@.name=='Usize')].attrs[*].repr.align" null
|
||||
//@ is "$.index[?(@.name=='Usize')].attrs[*].repr.packed" null
|
||||
#[repr(usize)]
|
||||
pub enum Usize {
|
||||
First,
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#![no_std]
|
||||
|
||||
// Note the normalization:
|
||||
// `#[repr(packed)]` in source becomes `#[repr(packed(1))]` in rustdoc JSON.
|
||||
// `#[repr(packed)]` in source becomes `{"repr": {"packed": 1, ...}}` in rustdoc JSON.
|
||||
//
|
||||
//@ is "$.index[?(@.name=='Packed')].attrs" '["#[repr(packed(1))]"]'
|
||||
//@ is "$.index[?(@.name=='Packed')].attrs[*].repr.packed" 1
|
||||
//@ is "$.index[?(@.name=='Packed')].attrs[*].repr.kind" '"rust"'
|
||||
#[repr(packed)]
|
||||
pub struct Packed {
|
||||
a: i8,
|
||||
b: i64,
|
||||
}
|
||||
|
||||
//@ is "$.index[?(@.name=='PackedAligned')].attrs" '["#[repr(packed(4))]"]'
|
||||
//@ is "$.index[?(@.name=='PackedAligned')].attrs[*].repr.packed" 4
|
||||
//@ is "$.index[?(@.name=='PackedAligned')].attrs[*].repr.kind" '"rust"'
|
||||
#[repr(packed(4))]
|
||||
pub struct PackedAligned {
|
||||
a: i8,
|
||||
|
||||
@@ -1,38 +1,49 @@
|
||||
//@ is "$.index[?(@.name=='test1')].attrs" '["#[target_feature(enable=\"avx\")]"]'
|
||||
//@ is "$.index[?(@.name=='test1')].inner.function.header.is_unsafe" false
|
||||
//@ count "$.index[?(@.name=='test1')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='test1')].attrs[*].target_feature.enable" '["avx"]'
|
||||
#[target_feature(enable = "avx")]
|
||||
pub fn test1() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='test2')].attrs" '["#[target_feature(enable=\"avx\", enable=\"avx2\")]"]'
|
||||
//@ is "$.index[?(@.name=='test2')].inner.function.header.is_unsafe" false
|
||||
//@ count "$.index[?(@.name=='test2')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='test2')].attrs[*].target_feature.enable" '["avx", "avx2"]'
|
||||
#[target_feature(enable = "avx,avx2")]
|
||||
pub fn test2() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='test3')].attrs" '["#[target_feature(enable=\"avx\", enable=\"avx2\")]"]'
|
||||
//@ is "$.index[?(@.name=='test3')].inner.function.header.is_unsafe" false
|
||||
//@ count "$.index[?(@.name=='test3')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='test3')].attrs[*].target_feature.enable" '["avx", "avx2"]'
|
||||
#[target_feature(enable = "avx", enable = "avx2")]
|
||||
pub fn test3() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='test4')].attrs" '["#[target_feature(enable=\"avx\", enable=\"avx2\", enable=\"avx512f\")]"]'
|
||||
//@ is "$.index[?(@.name=='test4')].inner.function.header.is_unsafe" false
|
||||
//@ count "$.index[?(@.name=='test4')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='test4')].attrs[*].target_feature.enable" '["avx", "avx2", "avx512f"]'
|
||||
#[target_feature(enable = "avx", enable = "avx2,avx512f")]
|
||||
pub fn test4() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='test_unsafe_fn')].attrs" '["#[target_feature(enable=\"avx\")]"]'
|
||||
//@ count "$.index[?(@.name=='test5')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='test5')].attrs[*].target_feature.enable" '["avx", "avx2"]'
|
||||
#[target_feature(enable = "avx")]
|
||||
#[target_feature(enable = "avx2")]
|
||||
pub fn test5() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='test_unsafe_fn')].inner.function.header.is_unsafe" true
|
||||
//@ count "$.index[?(@.name=='test_unsafe_fn')].attrs[*]" 1
|
||||
//@ is "$.index[?(@.name=='test_unsafe_fn')].attrs[*].target_feature.enable" '["avx"]'
|
||||
#[target_feature(enable = "avx")]
|
||||
pub unsafe fn test_unsafe_fn() {}
|
||||
|
||||
pub struct Example;
|
||||
|
||||
impl Example {
|
||||
//@ is "$.index[?(@.name=='safe_assoc_fn')].attrs" '["#[target_feature(enable=\"avx\")]"]'
|
||||
//@ is "$.index[?(@.name=='safe_assoc_fn')].inner.function.header.is_unsafe" false
|
||||
//@ is "$.index[?(@.name=='safe_assoc_fn')].attrs[*].target_feature.enable" '["avx"]'
|
||||
#[target_feature(enable = "avx")]
|
||||
pub fn safe_assoc_fn() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='unsafe_assoc_fn')].attrs" '["#[target_feature(enable=\"avx\")]"]'
|
||||
//@ is "$.index[?(@.name=='unsafe_assoc_fn')].inner.function.header.is_unsafe" true
|
||||
//@ is "$.index[?(@.name=='unsafe_assoc_fn')].attrs[*].target_feature.enable" '["avx"]'
|
||||
#[target_feature(enable = "avx")]
|
||||
pub unsafe fn unsafe_assoc_fn() {}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#[repr(i32)]
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs" '["#[repr(i32)]"]'
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs[*].repr.int" '"i32"'
|
||||
pub enum Foo {
|
||||
//@ is "$.index[?(@.name=='Struct')].inner.variant.discriminant" null
|
||||
//@ count "$.index[?(@.name=='Struct')].inner.variant.kind.struct.fields[*]" 0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#[repr(u32)]
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs" '["#[repr(u32)]"]'
|
||||
//@ is "$.index[?(@.name=='Foo')].attrs[*].repr.int" '"u32"'
|
||||
pub enum Foo {
|
||||
//@ is "$.index[?(@.name=='Tuple')].inner.variant.discriminant" null
|
||||
//@ count "$.index[?(@.name=='Tuple')].inner.variant.kind.tuple[*]" 0
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
//@ !has "$.index[?(@.name=='match')]"
|
||||
//@ has "$.index[?(@.name=='foo')]"
|
||||
//@ is "$.index[?(@.name=='foo')].attrs" '["#[doc(keyword = \"match\")]"]'
|
||||
//@ is "$.index[?(@.name=='foo')].attrs[*].other" '"#[doc(keyword = \"match\")]"'
|
||||
//@ is "$.index[?(@.name=='foo')].docs" '"this is a test!"'
|
||||
#[doc(keyword = "match")]
|
||||
/// this is a test!
|
||||
@@ -13,7 +13,7 @@ pub mod foo {}
|
||||
|
||||
//@ !has "$.index[?(@.name=='break')]"
|
||||
//@ has "$.index[?(@.name=='bar')]"
|
||||
//@ is "$.index[?(@.name=='bar')].attrs" '["#[doc(keyword = \"break\")]"]'
|
||||
//@ is "$.index[?(@.name=='bar')].attrs[*].other" '"#[doc(keyword = \"break\")]"'
|
||||
//@ is "$.index[?(@.name=='bar')].docs" '"hello"'
|
||||
#[doc(keyword = "break")]
|
||||
/// hello
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
//@ compile-flags: --document-hidden-items
|
||||
#![no_std]
|
||||
|
||||
//@ is "$.index[?(@.name=='func')].attrs" '["#[doc(hidden)]"]'
|
||||
//@ is "$.index[?(@.name=='func')].attrs" '[{"other": "#[doc(hidden)]"}]'
|
||||
#[doc(hidden)]
|
||||
pub fn func() {}
|
||||
|
||||
//@ is "$.index[?(@.name=='Unit')].attrs" '["#[doc(hidden)]"]'
|
||||
//@ is "$.index[?(@.name=='Unit')].attrs" '[{"other": "#[doc(hidden)]"}]'
|
||||
#[doc(hidden)]
|
||||
pub struct Unit;
|
||||
|
||||
//@ is "$.index[?(@.name=='hidden')].attrs" '["#[doc(hidden)]"]'
|
||||
//@ is "$.index[?(@.name=='hidden')].attrs" '[{"other": "#[doc(hidden)]"}]'
|
||||
#[doc(hidden)]
|
||||
pub mod hidden {
|
||||
//@ is "$.index[?(@.name=='Inner')].attrs" '[]'
|
||||
|
||||
Reference in New Issue
Block a user