blob: 0ffb96bef29ba7eb3c524bdcef7b6078c37eae47 [file] [log] [blame]
use std::collections::HashSet;
use std::hash::Hash;
use rustdoc_json_types::{
Constant, Crate, DynTrait, Enum, FnDecl, Function, FunctionPointer, GenericArg, GenericArgs,
GenericBound, GenericParamDef, Generics, Id, Impl, Import, ItemEnum, ItemSummary, Module, Path,
Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias, Type, TypeAlias,
TypeBinding, TypeBindingKind, Union, Variant, VariantKind, WherePredicate,
};
use serde_json::Value;
use crate::item_kind::Kind;
use crate::{json_find, Error, ErrorKind};
// This is a rustc implementation detail that we rely on here
const LOCAL_CRATE_ID: u32 = 0;
/// The Validator walks over the JSON tree, and ensures it is well formed.
/// It is made of several parts.
///
/// - `check_*`: These take a type from [`rustdoc_json_types`], and check that
/// it is well formed. This involves calling `check_*` functions on
/// fields of that item, and `add_*` functions on [`Id`]s.
/// - `add_*`: These add an [`Id`] to the worklist, after validating it to check if
/// the `Id` is a kind expected in this situation.
#[derive(Debug)]
pub struct Validator<'a> {
pub(crate) errs: Vec<Error>,
krate: &'a Crate,
krate_json: Value,
/// Worklist of Ids to check.
todo: HashSet<&'a Id>,
/// Ids that have already been visited, so don't need to be checked again.
seen_ids: HashSet<&'a Id>,
/// Ids that have already been reported missing.
missing_ids: HashSet<&'a Id>,
}
enum PathKind {
Trait,
/// Structs, Enums, Unions and TypeAliases.
///
/// This doesn't include trait's because traits are not types.
Type,
}
impl<'a> Validator<'a> {
pub fn new(krate: &'a Crate, krate_json: Value) -> Self {
Self {
krate,
krate_json,
errs: Vec::new(),
seen_ids: HashSet::new(),
todo: HashSet::new(),
missing_ids: HashSet::new(),
}
}
pub fn check_crate(&mut self) {
// Graph traverse the index
let root = &self.krate.root;
self.add_mod_id(root);
while let Some(id) = set_remove(&mut self.todo) {
self.seen_ids.insert(id);
self.check_item(id);
}
let root_crate_id = self.krate.index[root].crate_id;
assert_eq!(root_crate_id, LOCAL_CRATE_ID, "LOCAL_CRATE_ID is wrong");
for (id, item_info) in &self.krate.paths {
self.check_item_info(id, item_info);
}
}
fn check_items(&mut self, id: &Id, items: &[Id]) {
let mut visited_ids = HashSet::with_capacity(items.len());
for item in items {
if !visited_ids.insert(item) {
self.fail(
id,
ErrorKind::Custom(format!("Duplicated entry in `items` field: `{item:?}`")),
);
}
}
}
fn check_item(&mut self, id: &'a Id) {
if let Some(item) = &self.krate.index.get(id) {
item.links.values().for_each(|id| self.add_any_id(id));
match &item.inner {
ItemEnum::Import(x) => self.check_import(x),
ItemEnum::Union(x) => self.check_union(x),
ItemEnum::Struct(x) => self.check_struct(x),
ItemEnum::StructField(x) => self.check_struct_field(x),
ItemEnum::Enum(x) => self.check_enum(x),
ItemEnum::Variant(x) => self.check_variant(x, id),
ItemEnum::Function(x) => self.check_function(x),
ItemEnum::Trait(x) => self.check_trait(x, id),
ItemEnum::TraitAlias(x) => self.check_trait_alias(x),
ItemEnum::Impl(x) => self.check_impl(x, id),
ItemEnum::TypeAlias(x) => self.check_type_alias(x),
ItemEnum::Constant { type_, const_ } => {
self.check_type(type_);
self.check_constant(const_);
}
ItemEnum::Static(x) => self.check_static(x),
ItemEnum::ForeignType => {} // nop
ItemEnum::Macro(x) => self.check_macro(x),
ItemEnum::ProcMacro(x) => self.check_proc_macro(x),
ItemEnum::Primitive(x) => self.check_primitive_type(x),
ItemEnum::Module(x) => self.check_module(x, id),
// FIXME: Why don't these have their own structs?
ItemEnum::ExternCrate { .. } => {}
ItemEnum::AssocConst { type_, default: _ } => self.check_type(type_),
ItemEnum::AssocType { generics, bounds, default } => {
self.check_generics(generics);
bounds.iter().for_each(|b| self.check_generic_bound(b));
if let Some(ty) = default {
self.check_type(ty);
}
}
}
} else {
assert!(self.krate.paths.contains_key(id));
}
}
// Core checkers
fn check_module(&mut self, module: &'a Module, id: &Id) {
self.check_items(id, &module.items);
module.items.iter().for_each(|i| self.add_mod_item_id(i));
}
fn check_import(&mut self, x: &'a Import) {
if x.glob {
self.add_glob_import_item_id(x.id.as_ref().unwrap());
} else if let Some(id) = &x.id {
self.add_import_item_id(id);
}
}
fn check_union(&mut self, x: &'a Union) {
self.check_generics(&x.generics);
x.fields.iter().for_each(|i| self.add_field_id(i));
x.impls.iter().for_each(|i| self.add_impl_id(i));
}
fn check_struct(&mut self, x: &'a Struct) {
self.check_generics(&x.generics);
match &x.kind {
StructKind::Unit => {}
StructKind::Tuple(fields) => fields.iter().flatten().for_each(|f| self.add_field_id(f)),
StructKind::Plain { fields, fields_stripped: _ } => {
fields.iter().for_each(|f| self.add_field_id(f))
}
}
x.impls.iter().for_each(|i| self.add_impl_id(i));
}
fn check_struct_field(&mut self, x: &'a Type) {
self.check_type(x);
}
fn check_enum(&mut self, x: &'a Enum) {
self.check_generics(&x.generics);
x.variants.iter().for_each(|i| self.add_variant_id(i));
x.impls.iter().for_each(|i| self.add_impl_id(i));
}
fn check_variant(&mut self, x: &'a Variant, id: &'a Id) {
let Variant { kind, discriminant } = x;
if let Some(discr) = discriminant {
if let (Err(_), Err(_)) = (discr.value.parse::<i128>(), discr.value.parse::<u128>()) {
self.fail(
id,
ErrorKind::Custom(format!(
"Failed to parse discriminant value `{}`",
discr.value
)),
);
}
}
match kind {
VariantKind::Plain => {}
VariantKind::Tuple(tys) => tys.iter().flatten().for_each(|t| self.add_field_id(t)),
VariantKind::Struct { fields, fields_stripped: _ } => {
fields.iter().for_each(|f| self.add_field_id(f))
}
}
}
fn check_function(&mut self, x: &'a Function) {
self.check_generics(&x.generics);
self.check_fn_decl(&x.decl);
}
fn check_trait(&mut self, x: &'a Trait, id: &Id) {
self.check_items(id, &x.items);
self.check_generics(&x.generics);
x.items.iter().for_each(|i| self.add_trait_item_id(i));
x.bounds.iter().for_each(|i| self.check_generic_bound(i));
x.implementations.iter().for_each(|i| self.add_impl_id(i));
}
fn check_trait_alias(&mut self, x: &'a TraitAlias) {
self.check_generics(&x.generics);
x.params.iter().for_each(|i| self.check_generic_bound(i));
}
fn check_impl(&mut self, x: &'a Impl, id: &Id) {
self.check_items(id, &x.items);
self.check_generics(&x.generics);
if let Some(path) = &x.trait_ {
self.check_path(path, PathKind::Trait);
}
self.check_type(&x.for_);
x.items.iter().for_each(|i| self.add_trait_item_id(i));
if let Some(blanket_impl) = &x.blanket_impl {
self.check_type(blanket_impl)
}
}
fn check_type_alias(&mut self, x: &'a TypeAlias) {
self.check_generics(&x.generics);
self.check_type(&x.type_);
}
fn check_constant(&mut self, _x: &'a Constant) {
// nop
}
fn check_static(&mut self, x: &'a Static) {
self.check_type(&x.type_);
}
fn check_macro(&mut self, _: &'a str) {
// nop
}
fn check_proc_macro(&mut self, _: &'a ProcMacro) {
// nop
}
fn check_primitive_type(&mut self, x: &'a Primitive) {
x.impls.iter().for_each(|i| self.add_impl_id(i));
}
fn check_generics(&mut self, x: &'a Generics) {
x.params.iter().for_each(|p| self.check_generic_param_def(p));
x.where_predicates.iter().for_each(|w| self.check_where_predicate(w));
}
fn check_type(&mut self, x: &'a Type) {
match x {
Type::ResolvedPath(path) => self.check_path(path, PathKind::Type),
Type::DynTrait(dyn_trait) => self.check_dyn_trait(dyn_trait),
Type::Generic(_) => {}
Type::Primitive(_) => {}
Type::Pat { type_, __pat_unstable_do_not_use: _ } => self.check_type(type_),
Type::FunctionPointer(fp) => self.check_function_pointer(&**fp),
Type::Tuple(tys) => tys.iter().for_each(|ty| self.check_type(ty)),
Type::Slice(inner) => self.check_type(&**inner),
Type::Array { type_, len: _ } => self.check_type(&**type_),
Type::ImplTrait(bounds) => bounds.iter().for_each(|b| self.check_generic_bound(b)),
Type::Infer => {}
Type::RawPointer { mutable: _, type_ } => self.check_type(&**type_),
Type::BorrowedRef { lifetime: _, mutable: _, type_ } => self.check_type(&**type_),
Type::QualifiedPath { name: _, args, self_type, trait_ } => {
self.check_generic_args(&**args);
self.check_type(&**self_type);
if let Some(trait_) = trait_ {
self.check_path(trait_, PathKind::Trait);
}
}
}
}
fn check_fn_decl(&mut self, x: &'a FnDecl) {
x.inputs.iter().for_each(|(_name, ty)| self.check_type(ty));
if let Some(output) = &x.output {
self.check_type(output);
}
}
fn check_generic_bound(&mut self, x: &'a GenericBound) {
match x {
GenericBound::TraitBound { trait_, generic_params, modifier: _ } => {
self.check_path(trait_, PathKind::Trait);
generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));
}
GenericBound::Outlives(_) => {}
GenericBound::Use(_) => {}
}
}
fn check_path(&mut self, x: &'a Path, kind: PathKind) {
match kind {
PathKind::Trait => self.add_trait_or_alias_id(&x.id),
PathKind::Type => self.add_type_id(&x.id),
}
if let Some(args) = &x.args {
self.check_generic_args(&**args);
}
}
fn check_generic_args(&mut self, x: &'a GenericArgs) {
match x {
GenericArgs::AngleBracketed { args, bindings } => {
args.iter().for_each(|arg| self.check_generic_arg(arg));
bindings.iter().for_each(|bind| self.check_type_binding(bind));
}
GenericArgs::Parenthesized { inputs, output } => {
inputs.iter().for_each(|ty| self.check_type(ty));
if let Some(o) = output {
self.check_type(o);
}
}
}
}
fn check_generic_param_def(&mut self, gpd: &'a GenericParamDef) {
match &gpd.kind {
rustdoc_json_types::GenericParamDefKind::Lifetime { outlives: _ } => {}
rustdoc_json_types::GenericParamDefKind::Type { bounds, default, synthetic: _ } => {
bounds.iter().for_each(|b| self.check_generic_bound(b));
if let Some(ty) = default {
self.check_type(ty);
}
}
rustdoc_json_types::GenericParamDefKind::Const { type_, default: _ } => {
self.check_type(type_)
}
}
}
fn check_generic_arg(&mut self, arg: &'a GenericArg) {
match arg {
GenericArg::Lifetime(_) => {}
GenericArg::Type(ty) => self.check_type(ty),
GenericArg::Const(c) => self.check_constant(c),
GenericArg::Infer => {}
}
}
fn check_type_binding(&mut self, bind: &'a TypeBinding) {
self.check_generic_args(&bind.args);
match &bind.binding {
TypeBindingKind::Equality(term) => self.check_term(term),
TypeBindingKind::Constraint(bounds) => {
bounds.iter().for_each(|b| self.check_generic_bound(b))
}
}
}
fn check_term(&mut self, term: &'a Term) {
match term {
Term::Type(ty) => self.check_type(ty),
Term::Constant(con) => self.check_constant(con),
}
}
fn check_where_predicate(&mut self, w: &'a WherePredicate) {
match w {
WherePredicate::BoundPredicate { type_, bounds, generic_params } => {
self.check_type(type_);
bounds.iter().for_each(|b| self.check_generic_bound(b));
generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));
}
WherePredicate::LifetimePredicate { lifetime: _, outlives: _ } => {
// nop, all strings.
}
WherePredicate::EqPredicate { lhs, rhs } => {
self.check_type(lhs);
self.check_term(rhs);
}
}
}
fn check_dyn_trait(&mut self, dyn_trait: &'a DynTrait) {
for pt in &dyn_trait.traits {
self.check_path(&pt.trait_, PathKind::Trait);
pt.generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));
}
}
fn check_function_pointer(&mut self, fp: &'a FunctionPointer) {
self.check_fn_decl(&fp.decl);
fp.generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));
}
fn check_item_info(&mut self, id: &Id, item_info: &ItemSummary) {
// FIXME: Their should be a better way to determine if an item is local, rather than relying on `LOCAL_CRATE_ID`,
// which encodes rustc implementation details.
if item_info.crate_id == LOCAL_CRATE_ID && !self.krate.index.contains_key(id) {
self.errs.push(Error {
id: id.clone(),
kind: ErrorKind::Custom(
"Id for local item in `paths` but not in `index`".to_owned(),
),
})
}
}
fn add_id_checked(&mut self, id: &'a Id, valid: fn(Kind) -> bool, expected: &str) {
if let Some(kind) = self.kind_of(id) {
if valid(kind) {
if !self.seen_ids.contains(id) {
self.todo.insert(id);
}
} else {
self.fail_expecting(id, expected);
}
} else if !self.missing_ids.contains(id) {
self.missing_ids.insert(id);
let sels = json_find::find_selector(&self.krate_json, &Value::String(id.0.clone()));
assert_ne!(sels.len(), 0);
self.fail(id, ErrorKind::NotFound(sels))
}
}
fn add_any_id(&mut self, id: &'a Id) {
self.add_id_checked(id, |_| true, "any kind of item");
}
fn add_field_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::is_struct_field, "StructField");
}
fn add_mod_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::is_module, "Module");
}
fn add_impl_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::is_impl, "Impl");
}
fn add_variant_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::is_variant, "Variant");
}
fn add_trait_or_alias_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::is_trait_or_alias, "Trait (or TraitAlias)");
}
fn add_type_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::is_type, "Type (Struct, Enum, Union or TypeAlias)");
}
/// Add an Id that appeared in a trait
fn add_trait_item_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::can_appear_in_trait, "Trait inner item");
}
/// Add an Id that can be `use`d
fn add_import_item_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::can_appear_in_import, "Import inner item");
}
fn add_glob_import_item_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::can_appear_in_glob_import, "Glob import inner item");
}
/// Add an Id that appeared in a mod
fn add_mod_item_id(&mut self, id: &'a Id) {
self.add_id_checked(id, Kind::can_appear_in_mod, "Module inner item")
}
fn fail_expecting(&mut self, id: &Id, expected: &str) {
let kind = self.kind_of(id).unwrap(); // We know it has a kind, as it's wrong.
self.fail(id, ErrorKind::Custom(format!("Expected {expected} but found {kind:?}")));
}
fn fail(&mut self, id: &Id, kind: ErrorKind) {
self.errs.push(Error { id: id.clone(), kind });
}
fn kind_of(&mut self, id: &Id) -> Option<Kind> {
if let Some(item) = self.krate.index.get(id) {
Some(Kind::from_item(item))
} else if let Some(summary) = self.krate.paths.get(id) {
Some(Kind::from_summary(summary))
} else {
None
}
}
}
fn set_remove<T: Hash + Eq + Clone>(set: &mut HashSet<T>) -> Option<T> {
if let Some(id) = set.iter().next() {
let id = id.clone();
set.take(&id)
} else {
None
}
}
#[cfg(test)]
mod tests;