Auto merge of #79539 - aDotInTheVoid:json-mvp, r=jyn514

Rustdoc: JSON backend experimental impl, with new tests.

Based on #75114 by `@P1n3appl3`

The first commit is all of #75114, but squased to 1 commit, as that was much easier to rebase onto master.

The git history is a mess, but I think I'll edit it after review, so it's obvious whats new.

## Still to do

- [ ] Update docs.
- [ ] Add bless option to tests.
- [ ] Add test option for multiple files in same crate.
- [ ] Decide if the tests should check for json to be equal or subset.
- [ ] Go through the rest of the review for the original pr. (This is open because the test system is done(ish), but stuff like [not using a hashmap](https://github.com/rust-lang/rust/pull/75114#discussion_r519474420) and [using `CRATE_DEF_INDEX` ](https://github.com/rust-lang/rust/pull/75114#discussion_r519470764) hasn't)

I'm also sure how many of these we need to do before landing on nightly, as it would be nice to get this in tree, so it isn't effected by churn like #79125, #79041, #79061

r? `@jyn514`
This commit is contained in:
bors 2020-12-02 23:18:43 +00:00
commit 7dc1e852d4
14 changed files with 2192 additions and 21 deletions

View File

@ -425,6 +425,7 @@ macro_rules! describe {
test::RustdocJSNotStd,
test::RustdocTheme,
test::RustdocUi,
test::RustdocJson,
// Run bootstrap close to the end as it's unlikely to fail
test::Bootstrap,
// Run run-make last, since these won't pass without make on Windows

View File

@ -904,6 +904,12 @@ fn run(self, builder: &Builder<'_>) {
host_test!(Rustdoc { path: "src/test/rustdoc", mode: "rustdoc", suite: "rustdoc" });
host_test!(RustdocUi { path: "src/test/rustdoc-ui", mode: "ui", suite: "rustdoc-ui" });
host_test!(RustdocJson {
path: "src/test/rustdoc-json",
mode: "rustdoc-json",
suite: "rustdoc-json"
});
host_test!(Pretty { path: "src/test/pretty", mode: "pretty", suite: "pretty" });
default_test!(RunMake { path: "src/test/run-make", mode: "run-make", suite: "run-make" });
@ -1001,6 +1007,7 @@ fn run(self, builder: &Builder<'_>) {
|| (mode == "run-make" && suite.ends_with("fulldeps"))
|| (mode == "ui" && is_rustdoc)
|| mode == "js-doc-test"
|| mode == "rustdoc-json"
{
cmd.arg("--rustdoc-path").arg(builder.rustdoc(compiler));
}

View File

@ -337,6 +337,42 @@ pub fn from_def_id_and_parts(
}
impl ItemKind {
/// Some items contain others such as structs (for their fields) and Enums
/// (for their variants). This method returns those contained items.
crate fn inner_items(&self) -> impl Iterator<Item = &Item> {
match self {
StructItem(s) => s.fields.iter(),
UnionItem(u) => u.fields.iter(),
VariantItem(Variant { kind: VariantKind::Struct(v) }) => v.fields.iter(),
EnumItem(e) => e.variants.iter(),
TraitItem(t) => t.items.iter(),
ImplItem(i) => i.items.iter(),
ModuleItem(m) => m.items.iter(),
ExternCrateItem(_, _)
| ImportItem(_)
| FunctionItem(_)
| TypedefItem(_, _)
| OpaqueTyItem(_)
| StaticItem(_)
| ConstantItem(_)
| TraitAliasItem(_)
| TyMethodItem(_)
| MethodItem(_, _)
| StructFieldItem(_)
| VariantItem(_)
| ForeignFunctionItem(_)
| ForeignStaticItem(_)
| ForeignTypeItem
| MacroItem(_)
| ProcMacroItem(_)
| PrimitiveItem(_)
| AssocConstItem(_, _)
| AssocTypeItem(_, _)
| StrippedItem(_)
| KeywordItem(_) => [].iter(),
}
}
crate fn is_type_alias(&self) -> bool {
match *self {
ItemKind::TypedefItem(_, _) | ItemKind::AssocTypeItem(_, _) => true,
@ -1613,6 +1649,11 @@ impl Path {
crate fn last_name(&self) -> &str {
self.segments.last().expect("segments were empty").name.as_str()
}
crate fn whole_name(&self) -> String {
String::from(if self.global { "::" } else { "" })
+ &self.segments.iter().map(|s| s.name.clone()).collect::<Vec<_>>().join("::")
}
}
#[derive(Clone, PartialEq, Eq, Debug, Hash)]

View File

@ -0,0 +1,599 @@
//! These from impls are used to create the JSON types which get serialized. They're very close to
//! the `clean` types but with some fields removed or stringified to simplify the output and not
//! expose unstable compiler internals.
use std::convert::From;
use rustc_ast::ast;
use rustc_span::def_id::{DefId, CRATE_DEF_INDEX};
use crate::clean;
use crate::doctree;
use crate::formats::item_type::ItemType;
use crate::json::types::*;
impl From<clean::Item> for Option<Item> {
fn from(item: clean::Item) -> Self {
let item_type = ItemType::from(&item);
let clean::Item {
source,
name,
attrs,
kind,
visibility,
def_id,
stability: _,
const_stability: _,
deprecation,
} = item;
match kind {
clean::StrippedItem(_) => None,
_ => Some(Item {
id: def_id.into(),
crate_id: def_id.krate.as_u32(),
name,
source: source.into(),
visibility: visibility.into(),
docs: attrs.collapsed_doc_value().unwrap_or_default(),
links: attrs
.links
.into_iter()
.filter_map(|clean::ItemLink { link, did, .. }| {
did.map(|did| (link, did.into()))
})
.collect(),
attrs: attrs
.other_attrs
.iter()
.map(rustc_ast_pretty::pprust::attribute_to_string)
.collect(),
deprecation: deprecation.map(Into::into),
kind: item_type.into(),
inner: kind.into(),
}),
}
}
}
impl From<clean::Span> for Option<Span> {
fn from(span: clean::Span) -> Self {
let clean::Span { loline, locol, hiline, hicol, .. } = span;
match span.filename {
rustc_span::FileName::Real(name) => Some(Span {
filename: match name {
rustc_span::RealFileName::Named(path) => path,
rustc_span::RealFileName::Devirtualized { local_path, virtual_name: _ } => {
local_path
}
},
begin: (loline, locol),
end: (hiline, hicol),
}),
_ => None,
}
}
}
impl From<clean::Deprecation> for Deprecation {
fn from(deprecation: clean::Deprecation) -> Self {
let clean::Deprecation { since, note, is_since_rustc_version: _ } = deprecation;
Deprecation { since, note }
}
}
impl From<clean::Visibility> for Visibility {
fn from(v: clean::Visibility) -> Self {
use clean::Visibility::*;
match v {
Public => Visibility::Public,
Inherited => Visibility::Default,
Restricted(did, _) if did.index == CRATE_DEF_INDEX => Visibility::Crate,
Restricted(did, path) => Visibility::Restricted {
parent: did.into(),
path: path.to_string_no_crate_verbose(),
},
}
}
}
impl From<clean::GenericArgs> for GenericArgs {
fn from(args: clean::GenericArgs) -> Self {
use clean::GenericArgs::*;
match args {
AngleBracketed { args, bindings } => GenericArgs::AngleBracketed {
args: args.into_iter().map(Into::into).collect(),
bindings: bindings.into_iter().map(Into::into).collect(),
},
Parenthesized { inputs, output } => GenericArgs::Parenthesized {
inputs: inputs.into_iter().map(Into::into).collect(),
output: output.map(Into::into),
},
}
}
}
impl From<clean::GenericArg> for GenericArg {
fn from(arg: clean::GenericArg) -> Self {
use clean::GenericArg::*;
match arg {
Lifetime(l) => GenericArg::Lifetime(l.0),
Type(t) => GenericArg::Type(t.into()),
Const(c) => GenericArg::Const(c.into()),
}
}
}
impl From<clean::Constant> for Constant {
fn from(constant: clean::Constant) -> Self {
let clean::Constant { type_, expr, value, is_literal } = constant;
Constant { type_: type_.into(), expr, value, is_literal }
}
}
impl From<clean::TypeBinding> for TypeBinding {
fn from(binding: clean::TypeBinding) -> Self {
TypeBinding { name: binding.name, binding: binding.kind.into() }
}
}
impl From<clean::TypeBindingKind> for TypeBindingKind {
fn from(kind: clean::TypeBindingKind) -> Self {
use clean::TypeBindingKind::*;
match kind {
Equality { ty } => TypeBindingKind::Equality(ty.into()),
Constraint { bounds } => {
TypeBindingKind::Constraint(bounds.into_iter().map(Into::into).collect())
}
}
}
}
impl From<DefId> for Id {
fn from(did: DefId) -> Self {
Id(format!("{}:{}", did.krate.as_u32(), u32::from(did.index)))
}
}
impl From<clean::ItemKind> for ItemEnum {
fn from(item: clean::ItemKind) -> Self {
use clean::ItemKind::*;
match item {
ModuleItem(m) => ItemEnum::ModuleItem(m.into()),
ExternCrateItem(c, a) => ItemEnum::ExternCrateItem { name: c, rename: a },
ImportItem(i) => ItemEnum::ImportItem(i.into()),
StructItem(s) => ItemEnum::StructItem(s.into()),
UnionItem(u) => ItemEnum::StructItem(u.into()),
StructFieldItem(f) => ItemEnum::StructFieldItem(f.into()),
EnumItem(e) => ItemEnum::EnumItem(e.into()),
VariantItem(v) => ItemEnum::VariantItem(v.into()),
FunctionItem(f) => ItemEnum::FunctionItem(f.into()),
ForeignFunctionItem(f) => ItemEnum::FunctionItem(f.into()),
TraitItem(t) => ItemEnum::TraitItem(t.into()),
TraitAliasItem(t) => ItemEnum::TraitAliasItem(t.into()),
MethodItem(m, _) => ItemEnum::MethodItem(m.into()),
TyMethodItem(m) => ItemEnum::MethodItem(m.into()),
ImplItem(i) => ItemEnum::ImplItem(i.into()),
StaticItem(s) => ItemEnum::StaticItem(s.into()),
ForeignStaticItem(s) => ItemEnum::StaticItem(s.into()),
ForeignTypeItem => ItemEnum::ForeignTypeItem,
TypedefItem(t, _) => ItemEnum::TypedefItem(t.into()),
OpaqueTyItem(t) => ItemEnum::OpaqueTyItem(t.into()),
ConstantItem(c) => ItemEnum::ConstantItem(c.into()),
MacroItem(m) => ItemEnum::MacroItem(m.source),
ProcMacroItem(m) => ItemEnum::ProcMacroItem(m.into()),
AssocConstItem(t, s) => ItemEnum::AssocConstItem { type_: t.into(), default: s },
AssocTypeItem(g, t) => ItemEnum::AssocTypeItem {
bounds: g.into_iter().map(Into::into).collect(),
default: t.map(Into::into),
},
StrippedItem(inner) => (*inner).into(),
PrimitiveItem(_) | KeywordItem(_) => {
panic!("{:?} is not supported for JSON output", item)
}
}
}
}
impl From<clean::Module> for Module {
fn from(module: clean::Module) -> Self {
Module { is_crate: module.is_crate, items: ids(module.items) }
}
}
impl From<clean::Struct> for Struct {
fn from(struct_: clean::Struct) -> Self {
let clean::Struct { struct_type, generics, fields, fields_stripped } = struct_;
Struct {
struct_type: struct_type.into(),
generics: generics.into(),
fields_stripped,
fields: ids(fields),
impls: Vec::new(), // Added in JsonRenderer::item
}
}
}
impl From<clean::Union> for Struct {
fn from(struct_: clean::Union) -> Self {
let clean::Union { struct_type, generics, fields, fields_stripped } = struct_;
Struct {
struct_type: struct_type.into(),
generics: generics.into(),
fields_stripped,
fields: ids(fields),
impls: Vec::new(), // Added in JsonRenderer::item
}
}
}
impl From<doctree::StructType> for StructType {
fn from(struct_type: doctree::StructType) -> Self {
use doctree::StructType::*;
match struct_type {
Plain => StructType::Plain,
Tuple => StructType::Tuple,
Unit => StructType::Unit,
}
}
}
fn stringify_header(header: &rustc_hir::FnHeader) -> String {
let mut s = String::from(header.unsafety.prefix_str());
if header.asyncness == rustc_hir::IsAsync::Async {
s.push_str("async ")
}
if header.constness == rustc_hir::Constness::Const {
s.push_str("const ")
}
s
}
impl From<clean::Function> for Function {
fn from(function: clean::Function) -> Self {
let clean::Function { decl, generics, header, all_types: _, ret_types: _ } = function;
Function {
decl: decl.into(),
generics: generics.into(),
header: stringify_header(&header),
abi: header.abi.to_string(),
}
}
}
impl From<clean::Generics> for Generics {
fn from(generics: clean::Generics) -> Self {
Generics {
params: generics.params.into_iter().map(Into::into).collect(),
where_predicates: generics.where_predicates.into_iter().map(Into::into).collect(),
}
}
}
impl From<clean::GenericParamDef> for GenericParamDef {
fn from(generic_param: clean::GenericParamDef) -> Self {
GenericParamDef { name: generic_param.name, kind: generic_param.kind.into() }
}
}
impl From<clean::GenericParamDefKind> for GenericParamDefKind {
fn from(kind: clean::GenericParamDefKind) -> Self {
use clean::GenericParamDefKind::*;
match kind {
Lifetime => GenericParamDefKind::Lifetime,
Type { did: _, bounds, default, synthetic: _ } => GenericParamDefKind::Type {
bounds: bounds.into_iter().map(Into::into).collect(),
default: default.map(Into::into),
},
Const { did: _, ty } => GenericParamDefKind::Const(ty.into()),
}
}
}
impl From<clean::WherePredicate> for WherePredicate {
fn from(predicate: clean::WherePredicate) -> Self {
use clean::WherePredicate::*;
match predicate {
BoundPredicate { ty, bounds } => WherePredicate::BoundPredicate {
ty: ty.into(),
bounds: bounds.into_iter().map(Into::into).collect(),
},
RegionPredicate { lifetime, bounds } => WherePredicate::RegionPredicate {
lifetime: lifetime.0,
bounds: bounds.into_iter().map(Into::into).collect(),
},
EqPredicate { lhs, rhs } => {
WherePredicate::EqPredicate { lhs: lhs.into(), rhs: rhs.into() }
}
}
}
}
impl From<clean::GenericBound> for GenericBound {
fn from(bound: clean::GenericBound) -> Self {
use clean::GenericBound::*;
match bound {
TraitBound(clean::PolyTrait { trait_, generic_params }, modifier) => {
GenericBound::TraitBound {
trait_: trait_.into(),
generic_params: generic_params.into_iter().map(Into::into).collect(),
modifier: modifier.into(),
}
}
Outlives(lifetime) => GenericBound::Outlives(lifetime.0),
}
}
}
impl From<rustc_hir::TraitBoundModifier> for TraitBoundModifier {
fn from(modifier: rustc_hir::TraitBoundModifier) -> Self {
use rustc_hir::TraitBoundModifier::*;
match modifier {
None => TraitBoundModifier::None,
Maybe => TraitBoundModifier::Maybe,
MaybeConst => TraitBoundModifier::MaybeConst,
}
}
}
impl From<clean::Type> for Type {
fn from(ty: clean::Type) -> Self {
use clean::Type::*;
match ty {
ResolvedPath { path, param_names, did, is_generic: _ } => Type::ResolvedPath {
name: path.whole_name(),
id: did.into(),
args: path.segments.last().map(|args| Box::new(args.clone().args.into())),
param_names: param_names
.map(|v| v.into_iter().map(Into::into).collect())
.unwrap_or_default(),
},
Generic(s) => Type::Generic(s),
Primitive(p) => Type::Primitive(p.as_str().to_string()),
BareFunction(f) => Type::FunctionPointer(Box::new((*f).into())),
Tuple(t) => Type::Tuple(t.into_iter().map(Into::into).collect()),
Slice(t) => Type::Slice(Box::new((*t).into())),
Array(t, s) => Type::Array { type_: Box::new((*t).into()), len: s },
ImplTrait(g) => Type::ImplTrait(g.into_iter().map(Into::into).collect()),
Never => Type::Never,
Infer => Type::Infer,
RawPointer(mutability, type_) => Type::RawPointer {
mutable: mutability == ast::Mutability::Mut,
type_: Box::new((*type_).into()),
},
BorrowedRef { lifetime, mutability, type_ } => Type::BorrowedRef {
lifetime: lifetime.map(|l| l.0),
mutable: mutability == ast::Mutability::Mut,
type_: Box::new((*type_).into()),
},
QPath { name, self_type, trait_ } => Type::QualifiedPath {
name,
self_type: Box::new((*self_type).into()),
trait_: Box::new((*trait_).into()),
},
}
}
}
impl From<clean::BareFunctionDecl> for FunctionPointer {
fn from(bare_decl: clean::BareFunctionDecl) -> Self {
let clean::BareFunctionDecl { unsafety, generic_params, decl, abi } = bare_decl;
FunctionPointer {
is_unsafe: unsafety == rustc_hir::Unsafety::Unsafe,
generic_params: generic_params.into_iter().map(Into::into).collect(),
decl: decl.into(),
abi: abi.to_string(),
}
}
}
impl From<clean::FnDecl> for FnDecl {
fn from(decl: clean::FnDecl) -> Self {
let clean::FnDecl { inputs, output, c_variadic, attrs: _ } = decl;
FnDecl {
inputs: inputs.values.into_iter().map(|arg| (arg.name, arg.type_.into())).collect(),
output: match output {
clean::FnRetTy::Return(t) => Some(t.into()),
clean::FnRetTy::DefaultReturn => None,
},
c_variadic,
}
}
}
impl From<clean::Trait> for Trait {
fn from(trait_: clean::Trait) -> Self {
let clean::Trait { unsafety, items, generics, bounds, is_spotlight: _, is_auto } = trait_;
Trait {
is_auto,
is_unsafe: unsafety == rustc_hir::Unsafety::Unsafe,
items: ids(items),
generics: generics.into(),
bounds: bounds.into_iter().map(Into::into).collect(),
implementors: Vec::new(), // Added in JsonRenderer::item
}
}
}
impl From<clean::Impl> for Impl {
fn from(impl_: clean::Impl) -> Self {
let clean::Impl {
unsafety,
generics,
provided_trait_methods,
trait_,
for_,
items,
polarity,
synthetic,
blanket_impl,
} = impl_;
Impl {
is_unsafe: unsafety == rustc_hir::Unsafety::Unsafe,
generics: generics.into(),
provided_trait_methods: provided_trait_methods.into_iter().collect(),
trait_: trait_.map(Into::into),
for_: for_.into(),
items: ids(items),
negative: polarity == Some(clean::ImplPolarity::Negative),
synthetic,
blanket_impl: blanket_impl.map(Into::into),
}
}
}
impl From<clean::Function> for Method {
fn from(function: clean::Function) -> Self {
let clean::Function { header, decl, generics, all_types: _, ret_types: _ } = function;
Method {
decl: decl.into(),
generics: generics.into(),
header: stringify_header(&header),
has_body: true,
}
}
}
impl From<clean::Enum> for Enum {
fn from(enum_: clean::Enum) -> Self {
let clean::Enum { variants, generics, variants_stripped } = enum_;
Enum {
generics: generics.into(),
variants_stripped,
variants: ids(variants),
impls: Vec::new(), // Added in JsonRenderer::item
}
}
}
impl From<clean::VariantStruct> for Struct {
fn from(struct_: clean::VariantStruct) -> Self {
let clean::VariantStruct { struct_type, fields, fields_stripped } = struct_;
Struct {
struct_type: struct_type.into(),
generics: Default::default(),
fields_stripped,
fields: ids(fields),
impls: Vec::new(),
}
}
}
impl From<clean::Variant> for Variant {
fn from(variant: clean::Variant) -> Self {
use clean::VariantKind::*;
match variant.kind {
CLike => Variant::Plain,
Tuple(t) => Variant::Tuple(t.into_iter().map(Into::into).collect()),
Struct(s) => Variant::Struct(ids(s.fields)),
}
}
}
impl From<clean::Import> for Import {
fn from(import: clean::Import) -> Self {
use clean::ImportKind::*;
match import.kind {
Simple(s) => Import {
span: import.source.path.whole_name(),
name: s,
id: import.source.did.map(Into::into),
glob: false,
},
Glob => Import {
span: import.source.path.whole_name(),
name: import.source.path.last_name().to_string(),
id: import.source.did.map(Into::into),
glob: true,
},
}
}
}
impl From<clean::ProcMacro> for ProcMacro {
fn from(mac: clean::ProcMacro) -> Self {
ProcMacro { kind: mac.kind.into(), helpers: mac.helpers }
}
}
impl From<rustc_span::hygiene::MacroKind> for MacroKind {
fn from(kind: rustc_span::hygiene::MacroKind) -> Self {
use rustc_span::hygiene::MacroKind::*;
match kind {
Bang => MacroKind::Bang,
Attr => MacroKind::Attr,
Derive => MacroKind::Derive,
}
}
}
impl From<clean::Typedef> for Typedef {
fn from(typedef: clean::Typedef) -> Self {
let clean::Typedef { type_, generics, item_type: _ } = typedef;
Typedef { type_: type_.into(), generics: generics.into() }
}
}
impl From<clean::OpaqueTy> for OpaqueTy {
fn from(opaque: clean::OpaqueTy) -> Self {
OpaqueTy {
bounds: opaque.bounds.into_iter().map(Into::into).collect(),
generics: opaque.generics.into(),
}
}
}
impl From<clean::Static> for Static {
fn from(stat: clean::Static) -> Self {
Static {
type_: stat.type_.into(),
mutable: stat.mutability == ast::Mutability::Mut,
expr: stat.expr,
}
}
}
impl From<clean::TraitAlias> for TraitAlias {
fn from(alias: clean::TraitAlias) -> Self {
TraitAlias {
generics: alias.generics.into(),
params: alias.bounds.into_iter().map(Into::into).collect(),
}
}
}
impl From<ItemType> for ItemKind {
fn from(kind: ItemType) -> Self {
use ItemType::*;
match kind {
Module => ItemKind::Module,
ExternCrate => ItemKind::ExternCrate,
Import => ItemKind::Import,
Struct => ItemKind::Struct,
Union => ItemKind::Union,
Enum => ItemKind::Enum,
Function => ItemKind::Function,
Typedef => ItemKind::Typedef,
OpaqueTy => ItemKind::OpaqueTy,
Static => ItemKind::Static,
Constant => ItemKind::Constant,
Trait => ItemKind::Trait,
Impl => ItemKind::Impl,
TyMethod | Method => ItemKind::Method,
StructField => ItemKind::StructField,
Variant => ItemKind::Variant,
Macro => ItemKind::Macro,
Primitive => ItemKind::Primitive,
AssocConst => ItemKind::AssocConst,
AssocType => ItemKind::AssocType,
ForeignType => ItemKind::ForeignType,
Keyword => ItemKind::Keyword,
TraitAlias => ItemKind::TraitAlias,
ProcAttribute => ItemKind::ProcAttribute,
ProcDerive => ItemKind::ProcDerive,
}
}
}
fn ids(items: impl IntoIterator<Item = clean::Item>) -> Vec<Id> {
items.into_iter().filter(|x| !x.is_stripped()).map(|i| i.def_id.into()).collect()
}

View File

@ -1,47 +1,238 @@
//! Rustdoc's JSON backend
//!
//! This module contains the logic for rendering a crate as JSON rather than the normal static HTML
//! output. See [the RFC](https://github.com/rust-lang/rfcs/pull/2963) and the [`types`] module
//! docs for usage and details.
mod conversions;
pub mod types;
use std::cell::RefCell;
use std::fs::File;
use std::path::PathBuf;
use std::rc::Rc;
use rustc_data_structures::fx::FxHashMap;
use rustc_span::edition::Edition;
use crate::clean;
use crate::config::{RenderInfo, RenderOptions};
use crate::error::Error;
use crate::formats::cache::Cache;
use crate::formats::FormatRenderer;
use rustc_span::edition::Edition;
use crate::html::render::cache::ExternalLocation;
#[derive(Clone)]
crate struct JsonRenderer {}
crate struct JsonRenderer {
/// A mapping of IDs that contains all local items for this crate which gets output as a top
/// level field of the JSON blob.
index: Rc<RefCell<FxHashMap<types::Id, types::Item>>>,
/// The directory where the blob will be written to.
out_path: PathBuf,
}
impl JsonRenderer {
fn get_trait_implementors(
&mut self,
id: rustc_span::def_id::DefId,
cache: &Cache,
) -> Vec<types::Id> {
cache
.implementors
.get(&id)
.map(|implementors| {
implementors
.iter()
.map(|i| {
let item = &i.impl_item;
self.item(item.clone(), cache).unwrap();
item.def_id.into()
})
.collect()
})
.unwrap_or_default()
}
fn get_impls(&mut self, id: rustc_span::def_id::DefId, cache: &Cache) -> Vec<types::Id> {
cache
.impls
.get(&id)
.map(|impls| {
impls
.iter()
.filter_map(|i| {
let item = &i.impl_item;
if item.def_id.is_local() {
self.item(item.clone(), cache).unwrap();
Some(item.def_id.into())
} else {
None
}
})
.collect()
})
.unwrap_or_default()
}
fn get_trait_items(&mut self, cache: &Cache) -> Vec<(types::Id, types::Item)> {
cache
.traits
.iter()
.filter_map(|(&id, trait_item)| {
// only need to synthesize items for external traits
if !id.is_local() {
trait_item.items.clone().into_iter().for_each(|i| self.item(i, cache).unwrap());
Some((
id.into(),
types::Item {
id: id.into(),
crate_id: id.krate.as_u32(),
name: cache
.paths
.get(&id)
.unwrap_or_else(|| {
cache
.external_paths
.get(&id)
.expect("Trait should either be in local or external paths")
})
.0
.last()
.map(Clone::clone),
visibility: types::Visibility::Public,
kind: types::ItemKind::Trait,
inner: types::ItemEnum::TraitItem(trait_item.clone().into()),
source: None,
docs: Default::default(),
links: Default::default(),
attrs: Default::default(),
deprecation: Default::default(),
},
))
} else {
None
}
})
.collect()
}
}
impl FormatRenderer for JsonRenderer {
fn init(
_krate: clean::Crate,
_options: RenderOptions,
krate: clean::Crate,
options: RenderOptions,
_render_info: RenderInfo,
_edition: Edition,
_cache: &mut Cache,
) -> Result<(Self, clean::Crate), Error> {
unimplemented!()
debug!("Initializing json renderer");
Ok((
JsonRenderer {
index: Rc::new(RefCell::new(FxHashMap::default())),
out_path: options.output,
},
krate,
))
}
fn item(&mut self, _item: clean::Item, _cache: &Cache) -> Result<(), Error> {
unimplemented!()
/// Inserts an item into the index. This should be used rather than directly calling insert on
/// the hashmap because certain items (traits and types) need to have their mappings for trait
/// implementations filled out before they're inserted.
fn item(&mut self, item: clean::Item, cache: &Cache) -> Result<(), Error> {
// Flatten items that recursively store other items
item.kind.inner_items().for_each(|i| self.item(i.clone(), cache).unwrap());
let id = item.def_id;
if let Some(mut new_item) = item.into(): Option<types::Item> {
if let types::ItemEnum::TraitItem(ref mut t) = new_item.inner {
t.implementors = self.get_trait_implementors(id, cache)
} else if let types::ItemEnum::StructItem(ref mut s) = new_item.inner {
s.impls = self.get_impls(id, cache)
} else if let types::ItemEnum::EnumItem(ref mut e) = new_item.inner {
e.impls = self.get_impls(id, cache)
}
self.index.borrow_mut().insert(id.into(), new_item);
}
Ok(())
}
fn mod_item_in(
&mut self,
_item: &clean::Item,
_item_name: &str,
_cache: &Cache,
item: &clean::Item,
_module_name: &str,
cache: &Cache,
) -> Result<(), Error> {
unimplemented!()
use clean::types::ItemKind::*;
if let ModuleItem(m) = &item.kind {
for item in &m.items {
match &item.kind {
// These don't have names so they don't get added to the output by default
ImportItem(_) => self.item(item.clone(), cache).unwrap(),
ExternCrateItem(_, _) => self.item(item.clone(), cache).unwrap(),
ImplItem(i) => {
i.items.iter().for_each(|i| self.item(i.clone(), cache).unwrap())
}
_ => {}
}
}
}
self.item(item.clone(), cache).unwrap();
Ok(())
}
fn mod_item_out(&mut self, _item_name: &str) -> Result<(), Error> {
unimplemented!()
Ok(())
}
fn after_krate(&mut self, _krate: &clean::Crate, _cache: &Cache) -> Result<(), Error> {
unimplemented!()
fn after_krate(&mut self, krate: &clean::Crate, cache: &Cache) -> Result<(), Error> {
debug!("Done with crate");
let mut index = (*self.index).clone().into_inner();
index.extend(self.get_trait_items(cache));
let output = types::Crate {
root: types::Id(String::from("0:0")),
crate_version: krate.version.clone(),
includes_private: cache.document_private,
index,
paths: cache
.paths
.clone()
.into_iter()
.chain(cache.external_paths.clone().into_iter())
.map(|(k, (path, kind))| {
(
k.into(),
types::ItemSummary { crate_id: k.krate.as_u32(), path, kind: kind.into() },
)
})
.collect(),
external_crates: cache
.extern_locations
.iter()
.map(|(k, v)| {
(
k.as_u32(),
types::ExternalCrate {
name: v.0.clone(),
html_root_url: match &v.2 {
ExternalLocation::Remote(s) => Some(s.clone()),
_ => None,
},
},
)
})
.collect(),
format_version: 1,
};
let mut p = self.out_path.clone();
p.push(output.index.get(&output.root).unwrap().name.clone().unwrap());
p.set_extension("json");
let file = File::create(&p).map_err(|error| Error { error: error.to_string(), file: p })?;
serde_json::ser::to_writer(&file, &output).unwrap();
Ok(())
}
fn after_run(&mut self, _diag: &rustc_errors::Handler) -> Result<(), Error> {
unimplemented!()
Ok(())
}
}

View File

@ -0,0 +1,490 @@
//! Rustdoc's JSON output interface
//!
//! These types are the public API exposed through the `--output-format json` flag. The [`Crate`]
//! struct is the root of the JSON blob and all other items are contained within.
use std::path::PathBuf;
use rustc_data_structures::fx::FxHashMap;
use serde::{Deserialize, Serialize};
/// A `Crate` is the root of the emitted JSON blob. It contains all type/documentation information
/// about the language items in the local crate, as well as info about external items to allow
/// tools to find or link to them.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Crate {
/// The id of the root [`Module`] item of the local crate.
pub root: Id,
/// The version string given to `--crate-version`, if any.
pub crate_version: Option<String>,
/// Whether or not the output includes private items.
pub includes_private: bool,
/// A collection of all items in the local crate as well as some external traits and their
/// items that are referenced locally.
pub index: FxHashMap<Id, Item>,
/// Maps IDs to fully qualified paths and other info helpful for generating links.
pub paths: FxHashMap<Id, ItemSummary>,
/// Maps `crate_id` of items to a crate name and html_root_url if it exists.
pub external_crates: FxHashMap<u32, ExternalCrate>,
/// A single version number to be used in the future when making backwards incompatible changes
/// to the JSON output.
pub format_version: u32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ExternalCrate {
pub name: String,
pub html_root_url: Option<String>,
}
/// For external (not defined in the local crate) items, you don't get the same level of
/// information. This struct should contain enough to generate a link/reference to the item in
/// question, or can be used by a tool that takes the json output of multiple crates to find
/// the actual item definition with all the relevant info.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ItemSummary {
/// Can be used to look up the name and html_root_url of the crate this item came from in the
/// `external_crates` map.
pub crate_id: u32,
/// The list of path components for the fully qualified path of this item (e.g.
/// `["std", "io", "lazy", "Lazy"]` for `std::io::lazy::Lazy`).
pub path: Vec<String>,
/// Whether this item is a struct, trait, macro, etc.
pub kind: ItemKind,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Item {
/// The unique identifier of this item. Can be used to find this item in various mappings.
pub id: Id,
/// This can be used as a key to the `external_crates` map of [`Crate`] to see which crate
/// this item came from.
pub crate_id: u32,
/// Some items such as impls don't have names.
pub name: Option<String>,
/// The source location of this item (absent if it came from a macro expansion or inline
/// assembly).
pub source: Option<Span>,
/// By default all documented items are public, but you can tell rustdoc to output private items
/// so this field is needed to differentiate.
pub visibility: Visibility,
/// The full markdown docstring of this item.
pub docs: String,
/// This mapping resolves [intra-doc links](https://github.com/rust-lang/rfcs/blob/master/text/1946-intra-rustdoc-links.md) from the docstring to their IDs
pub links: FxHashMap<String, Id>,
/// Stringified versions of the attributes on this item (e.g. `"#[inline]"`)
pub attrs: Vec<String>,
pub deprecation: Option<Deprecation>,
pub kind: ItemKind,
pub inner: ItemEnum,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Span {
/// The path to the source file for this span relative to the path `rustdoc` was invoked with.
pub filename: PathBuf,
/// Zero indexed Line and Column of the first character of the `Span`
pub begin: (usize, usize),
/// Zero indexed Line and Column of the last character of the `Span`
pub end: (usize, usize),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Deprecation {
pub since: Option<String>,
pub note: Option<String>,
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Visibility {
Public,
/// For the most part items are private by default. The exceptions are associated items of
/// public traits and variants of public enums.
Default,
Crate,
/// For `pub(in path)` visibility. `parent` is the module it's restricted to and `path` is how
/// that module was referenced (like `"super::super"` or `"crate::foo::bar"`).
Restricted {
parent: Id,
path: String,
},
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GenericArgs {
/// <'a, 32, B: Copy, C = u32>
AngleBracketed { args: Vec<GenericArg>, bindings: Vec<TypeBinding> },
/// Fn(A, B) -> C
Parenthesized { inputs: Vec<Type>, output: Option<Type> },
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GenericArg {
Lifetime(String),
Type(Type),
Const(Constant),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Constant {
#[serde(rename = "type")]
pub type_: Type,
pub expr: String,
pub value: Option<String>,
pub is_literal: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TypeBinding {
pub name: String,
pub binding: TypeBindingKind,
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum TypeBindingKind {
Equality(Type),
Constraint(Vec<GenericBound>),
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Id(pub String);
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ItemKind {
Module,
ExternCrate,
Import,
Struct,
StructField,
Union,
Enum,
Variant,
Function,
Typedef,
OpaqueTy,
Constant,
Trait,
TraitAlias,
Method,
Impl,
Static,
ForeignType,
Macro,
ProcAttribute,
ProcDerive,
AssocConst,
AssocType,
Primitive,
Keyword,
}
#[serde(untagged)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ItemEnum {
ModuleItem(Module),
ExternCrateItem {
name: String,
rename: Option<String>,
},
ImportItem(Import),
StructItem(Struct),
StructFieldItem(Type),
EnumItem(Enum),
VariantItem(Variant),
FunctionItem(Function),
TraitItem(Trait),
TraitAliasItem(TraitAlias),
MethodItem(Method),
ImplItem(Impl),
TypedefItem(Typedef),
OpaqueTyItem(OpaqueTy),
ConstantItem(Constant),
StaticItem(Static),
/// `type`s from an extern block
ForeignTypeItem,
/// Declarative macro_rules! macro
MacroItem(String),
ProcMacroItem(ProcMacro),
AssocConstItem {
#[serde(rename = "type")]
type_: Type,
/// e.g. `const X: usize = 5;`
default: Option<String>,
},
AssocTypeItem {
bounds: Vec<GenericBound>,
/// e.g. `type X = usize;`
default: Option<Type>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Module {
pub is_crate: bool,
pub items: Vec<Id>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Struct {
pub struct_type: StructType,
pub generics: Generics,
pub fields_stripped: bool,
pub fields: Vec<Id>,
pub impls: Vec<Id>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Enum {
pub generics: Generics,
pub variants_stripped: bool,
pub variants: Vec<Id>,
pub impls: Vec<Id>,
}
#[serde(rename_all = "snake_case")]
#[serde(tag = "variant_kind", content = "variant_inner")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Variant {
Plain,
Tuple(Vec<Type>),
Struct(Vec<Id>),
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum StructType {
Plain,
Tuple,
Unit,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Function {
pub decl: FnDecl,
pub generics: Generics,
pub header: String,
pub abi: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Method {
pub decl: FnDecl,
pub generics: Generics,
pub header: String,
pub has_body: bool,
}
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct Generics {
pub params: Vec<GenericParamDef>,
pub where_predicates: Vec<WherePredicate>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GenericParamDef {
pub name: String,
pub kind: GenericParamDefKind,
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GenericParamDefKind {
Lifetime,
Type { bounds: Vec<GenericBound>, default: Option<Type> },
Const(Type),
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum WherePredicate {
BoundPredicate { ty: Type, bounds: Vec<GenericBound> },
RegionPredicate { lifetime: String, bounds: Vec<GenericBound> },
EqPredicate { lhs: Type, rhs: Type },
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum GenericBound {
TraitBound {
#[serde(rename = "trait")]
trait_: Type,
/// Used for HRTBs
generic_params: Vec<GenericParamDef>,
modifier: TraitBoundModifier,
},
Outlives(String),
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum TraitBoundModifier {
None,
Maybe,
MaybeConst,
}
#[serde(rename_all = "snake_case")]
#[serde(tag = "kind", content = "inner")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum Type {
/// Structs, enums, and traits
ResolvedPath {
name: String,
id: Id,
args: Option<Box<GenericArgs>>,
param_names: Vec<GenericBound>,
},
/// Parameterized types
Generic(String),
/// Fixed-size numeric types (plus int/usize/float), char, arrays, slices, and tuples
Primitive(String),
/// `extern "ABI" fn`
FunctionPointer(Box<FunctionPointer>),
/// `(String, u32, Box<usize>)`
Tuple(Vec<Type>),
/// `[u32]`
Slice(Box<Type>),
/// [u32; 15]
Array {
#[serde(rename = "type")]
type_: Box<Type>,
len: String,
},
/// `impl TraitA + TraitB + ...`
ImplTrait(Vec<GenericBound>),
/// `!`
Never,
/// `_`
Infer,
/// `*mut u32`, `*u8`, etc.
RawPointer {
mutable: bool,
#[serde(rename = "type")]
type_: Box<Type>,
},
/// `&'a mut String`, `&str`, etc.
BorrowedRef {
lifetime: Option<String>,
mutable: bool,
#[serde(rename = "type")]
type_: Box<Type>,
},
/// `<Type as Trait>::Name` or associated types like `T::Item` where `T: Iterator`
QualifiedPath {
name: String,
self_type: Box<Type>,
#[serde(rename = "trait")]
trait_: Box<Type>,
},
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FunctionPointer {
pub is_unsafe: bool,
pub generic_params: Vec<GenericParamDef>,
pub decl: FnDecl,
pub abi: String,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FnDecl {
pub inputs: Vec<(String, Type)>,
pub output: Option<Type>,
pub c_variadic: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Trait {
pub is_auto: bool,
pub is_unsafe: bool,
pub items: Vec<Id>,
pub generics: Generics,
pub bounds: Vec<GenericBound>,
pub implementors: Vec<Id>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct TraitAlias {
pub generics: Generics,
pub params: Vec<GenericBound>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Impl {
pub is_unsafe: bool,
pub generics: Generics,
pub provided_trait_methods: Vec<String>,
#[serde(rename = "trait")]
pub trait_: Option<Type>,
#[serde(rename = "for")]
pub for_: Type,
pub items: Vec<Id>,
pub negative: bool,
pub synthetic: bool,
pub blanket_impl: Option<Type>,
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Import {
/// The full path being imported.
pub span: String,
/// May be different from the last segment of `source` when renaming imports:
/// `use source as name;`
pub name: String,
/// The ID of the item being imported.
pub id: Option<Id>, // FIXME is this actually ever None?
/// Whether this import uses a glob: `use source::*;`
pub glob: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProcMacro {
pub kind: MacroKind,
pub helpers: Vec<String>,
}
#[serde(rename_all = "snake_case")]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum MacroKind {
/// A bang macro `foo!()`.
Bang,
/// An attribute macro `#[foo]`.
Attr,
/// A derive macro `#[derive(Foo)]`
Derive,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Typedef {
#[serde(rename = "type")]
pub type_: Type,
pub generics: Generics,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OpaqueTy {
pub bounds: Vec<GenericBound>,
pub generics: Generics,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Static {
#[serde(rename = "type")]
pub type_: Type,
pub mutable: bool,
pub expr: String,
}

View File

@ -14,6 +14,7 @@
#![feature(crate_visibility_modifier)]
#![feature(never_type)]
#![feature(once_cell)]
#![feature(type_ascription)]
#![recursion_limit = "256"]
#[macro_use]

View File

@ -0,0 +1,187 @@
#!/usr/bin/env python
# This test ensures that every ID in the produced json actually resolves to an item either in
# `index` or `paths`. It DOES NOT check that the structure of the produced json is actually in
# any way correct, for example an empty map would pass.
import sys
import json
crate = json.load(open(sys.argv[1]))
def get_local_item(item_id):
if item_id in crate["index"]:
return crate["index"][item_id]
print("Missing local ID:", item_id)
sys.exit(1)
# local IDs have to be in `index`, external ones can sometimes be in `index` but otherwise have
# to be in `paths`
def valid_id(item_id):
return item_id in crate["index"] or item_id[0] != "0" and item_id in crate["paths"]
def check_generics(generics):
for param in generics["params"]:
check_generic_param(param)
for where_predicate in generics["where_predicates"]:
if "bound_predicate" in where_predicate:
pred = where_predicate["bound_predicate"]
check_type(pred["ty"])
for bound in pred["bounds"]:
check_generic_bound(bound)
elif "region_predicate" in where_predicate:
pred = where_predicate["region_predicate"]
for bound in pred["bounds"]:
check_generic_bound(bound)
elif "eq_predicate" in where_predicate:
pred = where_predicate["eq_predicate"]
check_type(pred["rhs"])
check_type(pred["lhs"])
def check_generic_param(param):
if "type" in param["kind"]:
ty = param["kind"]["type"]
if ty["default"]:
check_type(ty["default"])
for bound in ty["bounds"]:
check_generic_bound(bound)
elif "const" in param["kind"]:
check_type(param["kind"]["const"])
def check_generic_bound(bound):
if "trait_bound" in bound:
for param in bound["trait_bound"]["generic_params"]:
check_generic_param(param)
check_type(bound["trait_bound"]["trait"])
def check_decl(decl):
for (_name, ty) in decl["inputs"]:
check_type(ty)
if decl["output"]:
check_type(decl["output"])
def check_type(ty):
if ty["kind"] == "resolved_path":
for bound in ty["inner"]["param_names"]:
check_generic_bound(bound)
args = ty["inner"]["args"]
if args:
if "angle_bracketed" in args:
for arg in args["angle_bracketed"]["args"]:
if "type" in arg:
check_type(arg["type"])
elif "const" in arg:
check_type(arg["const"]["type"])
for binding in args["angle_bracketed"]["bindings"]:
if "equality" in binding["binding"]:
check_type(binding["binding"]["equality"])
elif "constraint" in binding["binding"]:
for bound in binding["binding"]["constraint"]:
check_generic_bound(bound)
elif "parenthesized" in args:
for ty in args["parenthesized"]["inputs"]:
check_type(ty)
if args["parenthesized"]["output"]:
check_type(args["parenthesized"]["output"])
if not valid_id(ty["inner"]["id"]):
print("Type contained an invalid ID:", ty["inner"]["id"])
sys.exit(1)
elif ty["kind"] == "tuple":
for ty in ty["inner"]:
check_type(ty)
elif ty["kind"] == "slice":
check_type(ty["inner"])
elif ty["kind"] == "impl_trait":
for bound in ty["inner"]:
check_generic_bound(bound)
elif ty["kind"] in ("raw_pointer", "borrowed_ref", "array"):
check_type(ty["inner"]["type"])
elif ty["kind"] == "function_pointer":
for param in ty["inner"]["generic_params"]:
check_generic_param(param)
check_decl(ty["inner"]["inner"])
elif ty["kind"] == "qualified_path":
check_type(ty["inner"]["self_type"])
check_type(ty["inner"]["trait"])
work_list = set([crate["root"]])
visited = work_list.copy()
while work_list:
current = work_list.pop()
visited.add(current)
item = get_local_item(current)
# check intradoc links
for (_name, link) in item["links"].items():
if not valid_id(link):
print("Intra-doc link contains invalid ID:", link)
# check all fields that reference types such as generics as well as nested items
# (modules, structs, traits, and enums)
if item["kind"] == "module":
work_list |= set(item["inner"]["items"]) - visited
elif item["kind"] == "struct":
check_generics(item["inner"]["generics"])
work_list |= (
set(item["inner"]["fields"]) | set(item["inner"]["impls"])
) - visited
elif item["kind"] == "struct_field":
check_type(item["inner"])
elif item["kind"] == "enum":
check_generics(item["inner"]["generics"])
work_list |= (
set(item["inner"]["variants"]) | set(item["inner"]["impls"])
) - visited
elif item["kind"] == "variant":
if item["inner"]["variant_kind"] == "tuple":
for ty in item["inner"]["variant_inner"]:
check_type(ty)
elif item["inner"]["variant_kind"] == "struct":
work_list |= set(item["inner"]["variant_inner"]) - visited
elif item["kind"] in ("function", "method"):
check_generics(item["inner"]["generics"])
check_decl(item["inner"]["decl"])
elif item["kind"] in ("static", "constant", "assoc_const"):
check_type(item["inner"]["type"])
elif item["kind"] == "typedef":
check_type(item["inner"]["type"])
check_generics(item["inner"]["generics"])
elif item["kind"] == "opaque_ty":
check_generics(item["inner"]["generics"])
for bound in item["inner"]["bounds"]:
check_generic_bound(bound)
elif item["kind"] == "trait_alias":
check_generics(item["inner"]["params"])
for bound in item["inner"]["bounds"]:
check_generic_bound(bound)
elif item["kind"] == "trait":
check_generics(item["inner"]["generics"])
for bound in item["inner"]["bounds"]:
check_generic_bound(bound)
work_list |= (
set(item["inner"]["items"]) | set(item["inner"]["implementors"])
) - visited
elif item["kind"] == "impl":
check_generics(item["inner"]["generics"])
if item["inner"]["trait"]:
check_type(item["inner"]["trait"])
if item["inner"]["blanket_impl"]:
check_type(item["inner"]["blanket_impl"])
check_type(item["inner"]["for"])
for assoc_item in item["inner"]["items"]:
if not valid_id(assoc_item):
print("Impl block referenced a missing ID:", assoc_item)
sys.exit(1)
elif item["kind"] == "assoc_type":
for bound in item["inner"]["bounds"]:
check_generic_bound(bound)
if item["inner"]["default"]:
check_type(item["inner"]["default"])

View File

@ -0,0 +1,129 @@
#!/usr/bin/env python
# This script can check that an expected json blob is a subset of what actually gets produced.
# The comparison is independent of the value of IDs (which are unstable) and instead uses their
# relative ordering to check them against eachother by looking them up in their respective blob's
# `index` or `paths` mappings. To add a new test run `rustdoc --output-format json -o . yourtest.rs`
# and then create `yourtest.expected` by stripping unnecessary details from `yourtest.json`. If
# you're on windows, replace `\` with `/`.
import copy
import sys
import json
import types
# Used instead of the string ids when used as references.
# Not used as keys in `index` or `paths`
class ID(str):
pass
class SubsetException(Exception):
def __init__(self, msg, trace):
self.msg = msg
self.trace = msg
super().__init__("{}: {}".format(trace, msg))
def check_subset(expected_main, actual_main, base_dir):
expected_index = expected_main["index"]
expected_paths = expected_main["paths"]
actual_index = actual_main["index"]
actual_paths = actual_main["paths"]
already_checked = set()
def _check_subset(expected, actual, trace):
expected_type = type(expected)
actual_type = type(actual)
if actual_type is str:
actual = normalize(actual).replace(base_dir, "$TEST_BASE_DIR")
if expected_type is not actual_type:
raise SubsetException(
"expected type `{}`, got `{}`".format(expected_type, actual_type), trace
)
if expected_type in (int, bool, str) and expected != actual:
raise SubsetException("expected `{}`, got: `{}`".format(expected, actual), trace)
if expected_type is dict:
for key in expected:
if key not in actual:
raise SubsetException(
"Key `{}` not found in output".format(key), trace
)
new_trace = copy.deepcopy(trace)
new_trace.append(key)
_check_subset(expected[key], actual[key], new_trace)
elif expected_type is list:
expected_elements = len(expected)
actual_elements = len(actual)
if expected_elements != actual_elements:
raise SubsetException(
"Found {} items, expected {}".format(
expected_elements, actual_elements
),
trace,
)
for expected, actual in zip(expected, actual):
new_trace = copy.deepcopy(trace)
new_trace.append(expected)
_check_subset(expected, actual, new_trace)
elif expected_type is ID and expected not in already_checked:
already_checked.add(expected)
_check_subset(
expected_index.get(expected, {}), actual_index.get(actual, {}), trace
)
_check_subset(
expected_paths.get(expected, {}), actual_paths.get(actual, {}), trace
)
_check_subset(expected_main["root"], actual_main["root"], [])
def rustdoc_object_hook(obj):
# No need to convert paths, index and external_crates keys to ids, since
# they are the target of resolution, and never a source itself.
if "id" in obj and obj["id"]:
obj["id"] = ID(obj["id"])
if "root" in obj:
obj["root"] = ID(obj["root"])
if "items" in obj:
obj["items"] = [ID(id) for id in obj["items"]]
if "variants" in obj:
obj["variants"] = [ID(id) for id in obj["variants"]]
if "fields" in obj:
obj["fields"] = [ID(id) for id in obj["fields"]]
if "impls" in obj:
obj["impls"] = [ID(id) for id in obj["impls"]]
if "implementors" in obj:
obj["implementors"] = [ID(id) for id in obj["implementors"]]
if "links" in obj:
obj["links"] = {s: ID(id) for s, id in obj["links"]}
if "variant_kind" in obj and obj["variant_kind"] == "struct":
obj["variant_inner"] = [ID(id) for id in obj["variant_inner"]]
return obj
def main(expected_fpath, actual_fpath, base_dir):
print(
"checking that {} is a logical subset of {}".format(
expected_fpath, actual_fpath
)
)
with open(expected_fpath) as expected_file:
expected_main = json.load(expected_file, object_hook=rustdoc_object_hook)
with open(actual_fpath) as actual_file:
actual_main = json.load(actual_file, object_hook=rustdoc_object_hook)
check_subset(expected_main, actual_main, base_dir)
print("all checks passed")
def normalize(s):
return s.replace('\\', '/')
if __name__ == "__main__":
if len(sys.argv) < 4:
print("Usage: `compare.py expected.json actual.json test-dir`")
else:
main(sys.argv[1], sys.argv[2], normalize(sys.argv[3]))

View File

@ -0,0 +1,456 @@
{
"root": "0:0",
"version": null,
"includes_private": false,
"index": {
"0:9": {
"crate_id": 0,
"name": "Unit",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
7,
0
],
"end": [
7,
16
]
},
"visibility": "public",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct",
"inner": {
"struct_type": "unit",
"generics": {
"params": [],
"where_predicates": []
},
"fields_stripped": false,
"fields": []
}
},
"0:8": {
"crate_id": 0,
"name": "1",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
5,
22
],
"end": [
5,
28
]
},
"visibility": "default",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct_field",
"inner": {
"kind": "resolved_path",
"inner": {
"name": "String",
"id": "5:5035",
"args": {
"angle_bracketed": {
"args": [],
"bindings": []
}
},
"param_names": []
}
}
},
"0:18": {
"crate_id": 0,
"name": "stuff",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
15,
4
],
"end": [
15,
17
]
},
"visibility": "default",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct_field",
"inner": {
"kind": "resolved_path",
"inner": {
"name": "Vec",
"id": "5:4322",
"args": {
"angle_bracketed": {
"args": [
{
"type": {
"kind": "generic",
"inner": "T"
}
}
],
"bindings": []
}
},
"param_names": []
}
}
},
"0:11": {
"crate_id": 0,
"name": "WithPrimitives",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
9,
0
],
"end": [
12,
1
]
},
"visibility": "public",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct",
"inner": {
"struct_type": "plain",
"generics": {
"params": [
{
"name": "'a",
"kind": "lifetime"
}
],
"where_predicates": []
},
"fields_stripped": true
}
},
"0:14": {
"crate_id": 0,
"name": "s",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
11,
4
],
"end": [
11,
14
]
},
"visibility": "default",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct_field",
"inner": {
"kind": "borrowed_ref",
"inner": {
"lifetime": "'a",
"mutable": false,
"type": {
"kind": "primitive",
"inner": "str"
}
}
}
},
"0:19": {
"crate_id": 0,
"name": "things",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
16,
4
],
"end": [
16,
25
]
},
"visibility": "default",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct_field",
"inner": {
"kind": "resolved_path",
"inner": {
"name": "HashMap",
"id": "1:6600",
"args": {
"angle_bracketed": {
"args": [
{
"type": {
"kind": "generic",
"inner": "U"
}
},
{
"type": {
"kind": "generic",
"inner": "U"
}
}
],
"bindings": []
}
},
"param_names": []
}
}
},
"0:15": {
"crate_id": 0,
"name": "WithGenerics",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
14,
0
],
"end": [
17,
1
]
},
"visibility": "public",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct",
"inner": {
"struct_type": "plain",
"generics": {
"params": [
{
"name": "T",
"kind": {
"type": {
"bounds": [],
"default": null
}
}
},
{
"name": "U",
"kind": {
"type": {
"bounds": [],
"default": null
}
}
}
],
"where_predicates": []
},
"fields_stripped": true
}
},
"0:0": {
"crate_id": 0,
"name": "structs",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
1,
0
],
"end": [
17,
1
]
},
"visibility": "public",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "module",
"inner": {
"is_crate": true,
"items": [
"0:4",
"0:5",
"0:9",
"0:11",
"0:15"
]
}
},
"0:13": {
"crate_id": 0,
"name": "num",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
10,
4
],
"end": [
10,
12
]
},
"visibility": "default",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct_field",
"inner": {
"kind": "primitive",
"inner": "u32"
}
},
"0:5": {
"crate_id": 0,
"name": "Tuple",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
5,
0
],
"end": [
5,
30
]
},
"visibility": "public",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct",
"inner": {
"struct_type": "tuple",
"generics": {
"params": [],
"where_predicates": []
},
"fields_stripped": true
}
},
"0:4": {
"crate_id": 0,
"name": "PlainEmpty",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
3,
0
],
"end": [
3,
24
]
},
"visibility": "public",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct",
"inner": {
"struct_type": "plain",
"generics": {
"params": [],
"where_predicates": []
},
"fields_stripped": false,
"fields": []
}
},
"0:7": {
"crate_id": 0,
"name": "0",
"source": {
"filename": "$TEST_BASE_DIR/structs.rs",
"begin": [
5,
17
],
"end": [
5,
20
]
},
"visibility": "default",
"docs": "",
"links": {},
"attrs": [],
"deprecation": null,
"kind": "struct_field",
"inner": {
"kind": "primitive",
"inner": "u32"
}
}
},
"paths": {
"5:4322": {
"crate_id": 5,
"path": [
"alloc",
"vec",
"Vec"
],
"kind": "struct"
},
"5:5035": {
"crate_id": 5,
"path": [
"alloc",
"string",
"String"
],
"kind": "struct"
},
"1:6600": {
"crate_id": 1,
"path": [
"std",
"collections",
"hash",
"map",
"HashMap"
],
"kind": "struct"
}
},
"external_crates": {
"1": {
"name": "std"
},
"5": {
"name": "alloc"
}
},
"format_version": 1
}

View File

@ -0,0 +1,17 @@
use std::collections::HashMap;
pub struct PlainEmpty {}
pub struct Tuple(u32, String);
pub struct Unit;
pub struct WithPrimitives<'a> {
num: u32,
s: &'a str,
}
pub struct WithGenerics<T, U> {
stuff: Vec<T>,
things: HashMap<U, U>,
}

View File

@ -17,6 +17,7 @@ pub enum Mode {
DebugInfo,
Codegen,
Rustdoc,
RustdocJson,
CodegenUnits,
Incremental,
RunMake,
@ -48,6 +49,7 @@ fn from_str(s: &str) -> Result<Mode, ()> {
"debuginfo" => Ok(DebugInfo),
"codegen" => Ok(Codegen),
"rustdoc" => Ok(Rustdoc),
"rustdoc-json" => Ok(RustdocJson),
"codegen-units" => Ok(CodegenUnits),
"incremental" => Ok(Incremental),
"run-make" => Ok(RunMake),
@ -70,6 +72,7 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
DebugInfo => "debuginfo",
Codegen => "codegen",
Rustdoc => "rustdoc",
RustdocJson => "rustdoc-json",
CodegenUnits => "codegen-units",
Incremental => "incremental",
RunMake => "run-make",

View File

@ -68,7 +68,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
"mode",
"which sort of compile tests to run",
"compile-fail | run-fail | run-pass-valgrind | pretty | debug-info | codegen | rustdoc \
codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
| rustdoc-json | codegen-units | incremental | run-make | ui | js-doc-test | mir-opt | assembly",
)
.reqopt(
"",

View File

@ -2,7 +2,7 @@
use crate::common::{expected_output_path, UI_EXTENSIONS, UI_FIXED, UI_STDERR, UI_STDOUT};
use crate::common::{output_base_dir, output_base_name, output_testname_unique};
use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, Ui};
use crate::common::{Assembly, Incremental, JsDocTest, MirOpt, RunMake, RustdocJson, Ui};
use crate::common::{Codegen, CodegenUnits, DebugInfo, Debugger, Rustdoc};
use crate::common::{CompareMode, FailMode, PassMode};
use crate::common::{CompileFail, Pretty, RunFail, RunPassValgrind};
@ -342,6 +342,7 @@ fn run_revision(&self) {
DebugInfo => self.run_debuginfo_test(),
Codegen => self.run_codegen_test(),
Rustdoc => self.run_rustdoc_test(),
RustdocJson => self.run_rustdoc_json_test(),
CodegenUnits => self.run_codegen_units_test(),
Incremental => self.run_incremental_test(),
RunMake => self.run_rmake_test(),
@ -1600,6 +1601,10 @@ fn document(&self, out_dir: &Path) -> ProcRes {
.arg(&self.testpaths.file)
.args(&self.props.compile_flags);
if self.config.mode == RustdocJson {
rustdoc.arg("--output-format").arg("json");
}
if let Some(ref linker) = self.config.linker {
rustdoc.arg(format!("-Clinker={}", linker));
}
@ -1887,7 +1892,9 @@ fn compose_and_run(
}
fn is_rustdoc(&self) -> bool {
self.config.src_base.ends_with("rustdoc-ui") || self.config.src_base.ends_with("rustdoc-js")
self.config.src_base.ends_with("rustdoc-ui")
|| self.config.src_base.ends_with("rustdoc-js")
|| self.config.src_base.ends_with("rustdoc-json")
}
fn make_compile_args(
@ -1968,8 +1975,8 @@ fn make_compile_args(
rustc.arg(dir_opt);
}
RunFail | RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RunMake
| CodegenUnits | JsDocTest | Assembly => {
RunFail | RunPassValgrind | Pretty | DebugInfo | Codegen | Rustdoc | RustdocJson
| RunMake | CodegenUnits | JsDocTest | Assembly => {
// do not use JSON output
}
}
@ -2466,6 +2473,48 @@ fn compare_to_default_rustdoc(&mut self, out_dir: &Path) {
eprintln!("{}", String::from_utf8_lossy(&output.stderr));
}
fn run_rustdoc_json_test(&self) {
//FIXME: Add bless option.
assert!(self.revision.is_none(), "revisions not relevant here");
let out_dir = self.output_base_dir();
let _ = fs::remove_dir_all(&out_dir);
create_dir_all(&out_dir).unwrap();
let proc_res = self.document(&out_dir);
if !proc_res.status.success() {
self.fatal_proc_rec("rustdoc failed!", &proc_res);
}
let root = self.config.find_rust_src_root().unwrap();
let mut json_out = out_dir.join(self.testpaths.file.file_stem().unwrap());
json_out.set_extension("json");
let res = self.cmd2procres(
Command::new(&self.config.docck_python)
.arg(root.join("src/test/rustdoc-json/check_missing_items.py"))
.arg(&json_out),
);
if !res.status.success() {
self.fatal_proc_rec("check_missing_items failed!", &res);
}
let mut expected = self.testpaths.file.clone();
expected.set_extension("expected");
let res = self.cmd2procres(
Command::new(&self.config.docck_python)
.arg(root.join("src/test/rustdoc-json/compare.py"))
.arg(&expected)
.arg(&json_out)
.arg(&expected.parent().unwrap()),
);
if !res.status.success() {
self.fatal_proc_rec("compare failed!", &res);
}
}
fn get_lines<P: AsRef<Path>>(
&self,
path: &P,