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:
Alona Enraght-Moony
2025-06-20 02:48:15 +00:00
parent cccf075eba
commit 078332fdc8
28 changed files with 318 additions and 124 deletions

View File

@@ -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| {

View File

@@ -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);
}
}

View File

@@ -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}")?;
}
}

View File

@@ -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()
}

View File

@@ -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 items 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

View File

@@ -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' '[]'

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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> {}

View File

@@ -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() {}

View File

@@ -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() {}

View File

@@ -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,

View File

@@ -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() {}

View File

@@ -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,

View File

@@ -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,

View 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',
}

View File

@@ -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);

View File

@@ -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,

View File

@@ -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,

View File

@@ -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() {}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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" '[]'