src/tools/jsondoclint/src/validator.rs RUST 511 lines View on github.com → Search inside
1use std::collections::HashSet;2use std::hash::Hash;34use rustdoc_json_types::{5    AssocItemConstraint, AssocItemConstraintKind, Constant, Crate, DynTrait, Enum, Function,6    FunctionPointer, FunctionSignature, GenericArg, GenericArgs, GenericBound, GenericParamDef,7    Generics, Id, Impl, ItemEnum, ItemSummary, Module, Path, Primitive, ProcMacro, Static, Struct,8    StructKind, Term, Trait, TraitAlias, Type, TypeAlias, Union, Use, Variant, VariantKind,9    WherePredicate,10};11use serde_json::Value;1213use crate::item_kind::Kind;14use crate::{Error, ErrorKind, json_find};1516// This is a rustc implementation detail that we rely on here17const LOCAL_CRATE_ID: u32 = 0;1819/// The Validator walks over the JSON tree, and ensures it is well formed.20/// It is made of several parts.21///22/// - `check_*`: These take a type from [`rustdoc_json_types`], and check that23///              it is well formed. This involves calling `check_*` functions on24///              fields of that item, and `add_*` functions on [`Id`]s.25/// - `add_*`: These add an [`Id`] to the worklist, after validating it to check if26///            the `Id` is a kind expected in this situation.27#[derive(Debug)]28pub struct Validator<'a> {29    pub(crate) errs: Vec<Error>,30    krate: &'a Crate,31    krate_json: Value,32    /// Worklist of Ids to check.33    todo: HashSet<&'a Id>,34    /// Ids that have already been visited, so don't need to be checked again.35    seen_ids: HashSet<&'a Id>,36    /// Ids that have already been reported missing.37    missing_ids: HashSet<&'a Id>,38}3940enum PathKind {41    Trait,42    /// Structs, Enums, Unions and TypeAliases.43    ///44    /// This doesn't include trait's because traits are not types.45    Type,46}4748impl<'a> Validator<'a> {49    pub fn new(krate: &'a Crate, krate_json: Value) -> Self {50        Self {51            krate,52            krate_json,53            errs: Vec::new(),54            seen_ids: HashSet::new(),55            todo: HashSet::new(),56            missing_ids: HashSet::new(),57        }58    }5960    pub fn check_crate(&mut self) {61        // Graph traverse the index62        let root = &self.krate.root;63        self.add_mod_id(root);64        while let Some(id) = set_remove(&mut self.todo) {65            self.seen_ids.insert(id);66            self.check_item(id);67        }6869        let root_crate_id = self.krate.index[root].crate_id;70        assert_eq!(root_crate_id, LOCAL_CRATE_ID, "LOCAL_CRATE_ID is wrong");71        for (id, item_info) in &self.krate.paths {72            self.check_item_info(id, item_info);73        }74    }7576    fn check_items(&mut self, id: &Id, items: &[Id]) {77        let mut visited_ids = HashSet::with_capacity(items.len());7879        for item in items {80            if !visited_ids.insert(item) {81                self.fail(82                    id,83                    ErrorKind::Custom(format!("Duplicated entry in `items` field: `{item:?}`")),84                );85            }86        }87    }8889    fn check_item(&mut self, id: &'a Id) {90        if let Some(item) = &self.krate.index.get(id) {91            item.links.values().for_each(|id| self.add_any_id(id));9293            match &item.inner {94                ItemEnum::Use(x) => self.check_use(x),95                ItemEnum::Union(x) => self.check_union(x),96                ItemEnum::Struct(x) => self.check_struct(x),97                ItemEnum::StructField(x) => self.check_struct_field(x),98                ItemEnum::Enum(x) => self.check_enum(x),99                ItemEnum::Variant(x) => self.check_variant(x, id),100                ItemEnum::Function(x) => self.check_function(x),101                ItemEnum::Trait(x) => self.check_trait(x, id),102                ItemEnum::TraitAlias(x) => self.check_trait_alias(x),103                ItemEnum::Impl(x) => self.check_impl(x, id),104                ItemEnum::TypeAlias(x) => self.check_type_alias(x),105                ItemEnum::Constant { type_, const_ } => {106                    self.check_type(type_);107                    self.check_constant(const_);108                }109                ItemEnum::Static(x) => self.check_static(x),110                ItemEnum::ExternType => {} // nop111                ItemEnum::Macro(x) => self.check_macro(x),112                ItemEnum::ProcMacro(x) => self.check_proc_macro(x),113                ItemEnum::Primitive(x) => self.check_primitive_type(x),114                ItemEnum::Module(x) => self.check_module(x, id),115                // FIXME: Why don't these have their own structs?116                ItemEnum::ExternCrate { .. } => {}117                ItemEnum::AssocConst { type_, value: _ } => self.check_type(type_),118                ItemEnum::AssocType { generics, bounds, type_ } => {119                    self.check_generics(generics);120                    bounds.iter().for_each(|b| self.check_generic_bound(b));121                    if let Some(ty) = type_ {122                        self.check_type(ty);123                    }124                }125            }126        } else {127            assert!(self.krate.paths.contains_key(id));128        }129    }130131    // Core checkers132    fn check_module(&mut self, module: &'a Module, id: &Id) {133        self.check_items(id, &module.items);134        module.items.iter().for_each(|i| self.add_mod_item_id(i));135    }136137    fn check_use(&mut self, x: &'a Use) {138        if x.is_glob {139            self.add_glob_import_item_id(x.id.as_ref().unwrap());140        } else if let Some(id) = &x.id {141            self.add_import_item_id(id);142        }143    }144145    fn check_union(&mut self, x: &'a Union) {146        self.check_generics(&x.generics);147        x.fields.iter().for_each(|i| self.add_field_id(i));148        x.impls.iter().for_each(|i| self.add_impl_id(i));149    }150151    fn check_struct(&mut self, x: &'a Struct) {152        self.check_generics(&x.generics);153        match &x.kind {154            StructKind::Unit => {}155            StructKind::Tuple(fields) => fields.iter().flatten().for_each(|f| self.add_field_id(f)),156            StructKind::Plain { fields, has_stripped_fields: _ } => {157                fields.iter().for_each(|f| self.add_field_id(f))158            }159        }160        x.impls.iter().for_each(|i| self.add_impl_id(i));161    }162163    fn check_struct_field(&mut self, x: &'a Type) {164        self.check_type(x);165    }166167    fn check_enum(&mut self, x: &'a Enum) {168        self.check_generics(&x.generics);169        x.variants.iter().for_each(|i| self.add_variant_id(i));170        x.impls.iter().for_each(|i| self.add_impl_id(i));171    }172173    fn check_variant(&mut self, x: &'a Variant, id: &'a Id) {174        let Variant { kind, discriminant } = x;175176        if let Some(discr) = discriminant {177            if let (Err(_), Err(_)) = (discr.value.parse::<i128>(), discr.value.parse::<u128>()) {178                self.fail(179                    id,180                    ErrorKind::Custom(format!(181                        "Failed to parse discriminant value `{}`",182                        discr.value183                    )),184                );185            }186        }187188        match kind {189            VariantKind::Plain => {}190            VariantKind::Tuple(tys) => tys.iter().flatten().for_each(|t| self.add_field_id(t)),191            VariantKind::Struct { fields, has_stripped_fields: _ } => {192                fields.iter().for_each(|f| self.add_field_id(f))193            }194        }195    }196197    fn check_function(&mut self, x: &'a Function) {198        self.check_generics(&x.generics);199        self.check_function_signature(&x.sig);200    }201202    fn check_trait(&mut self, x: &'a Trait, id: &Id) {203        self.check_items(id, &x.items);204        self.check_generics(&x.generics);205        x.items.iter().for_each(|i| self.add_trait_item_id(i));206        x.bounds.iter().for_each(|i| self.check_generic_bound(i));207        x.implementations.iter().for_each(|i| self.add_impl_id(i));208    }209210    fn check_trait_alias(&mut self, x: &'a TraitAlias) {211        self.check_generics(&x.generics);212        x.params.iter().for_each(|i| self.check_generic_bound(i));213    }214215    fn check_impl(&mut self, x: &'a Impl, id: &Id) {216        self.check_items(id, &x.items);217        self.check_generics(&x.generics);218        if let Some(path) = &x.trait_ {219            self.check_path(path, PathKind::Trait);220        }221        self.check_type(&x.for_);222        x.items.iter().for_each(|i| self.add_trait_item_id(i));223        if let Some(blanket_impl) = &x.blanket_impl {224            self.check_type(blanket_impl)225        }226    }227228    fn check_type_alias(&mut self, x: &'a TypeAlias) {229        self.check_generics(&x.generics);230        self.check_type(&x.type_);231    }232233    fn check_constant(&mut self, _x: &'a Constant) {234        // nop235    }236237    fn check_static(&mut self, x: &'a Static) {238        self.check_type(&x.type_);239    }240241    fn check_macro(&mut self, _: &'a str) {242        // nop243    }244245    fn check_proc_macro(&mut self, _: &'a ProcMacro) {246        // nop247    }248249    fn check_primitive_type(&mut self, x: &'a Primitive) {250        x.impls.iter().for_each(|i| self.add_impl_id(i));251    }252253    fn check_generics(&mut self, x: &'a Generics) {254        x.params.iter().for_each(|p| self.check_generic_param_def(p));255        x.where_predicates.iter().for_each(|w| self.check_where_predicate(w));256    }257258    fn check_type(&mut self, x: &'a Type) {259        match x {260            Type::ResolvedPath(path) => self.check_path(path, PathKind::Type),261            Type::DynTrait(dyn_trait) => self.check_dyn_trait(dyn_trait),262            Type::Generic(_) => {}263            Type::Primitive(_) => {}264            Type::Pat { type_, __pat_unstable_do_not_use: _ } => self.check_type(type_),265            Type::FunctionPointer(fp) => self.check_function_pointer(&**fp),266            Type::Tuple(tys) => tys.iter().for_each(|ty| self.check_type(ty)),267            Type::Slice(inner) => self.check_type(&**inner),268            Type::Array { type_, len: _ } => self.check_type(&**type_),269            Type::ImplTrait(bounds) => bounds.iter().for_each(|b| self.check_generic_bound(b)),270            Type::Infer => {}271            Type::RawPointer { is_mutable: _, type_ } => self.check_type(&**type_),272            Type::BorrowedRef { lifetime: _, is_mutable: _, type_ } => self.check_type(&**type_),273            Type::QualifiedPath { name: _, args, self_type, trait_ } => {274                self.check_opt_generic_args(&args);275                self.check_type(&**self_type);276                if let Some(trait_) = trait_ {277                    self.check_path(trait_, PathKind::Trait);278                }279            }280        }281    }282283    fn check_function_signature(&mut self, x: &'a FunctionSignature) {284        x.inputs.iter().for_each(|(_name, ty)| self.check_type(ty));285        if let Some(output) = &x.output {286            self.check_type(output);287        }288    }289290    fn check_generic_bound(&mut self, x: &'a GenericBound) {291        match x {292            GenericBound::TraitBound { trait_, generic_params, modifier: _ } => {293                self.check_path(trait_, PathKind::Trait);294                generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));295            }296            GenericBound::Outlives(_) => {}297            GenericBound::Use(_) => {}298        }299    }300301    fn check_path(&mut self, x: &'a Path, kind: PathKind) {302        match kind {303            PathKind::Trait => self.add_trait_or_alias_id(&x.id),304            PathKind::Type => self.add_type_id(&x.id),305        }306307        // FIXME: More robust support for checking things in $.index also exist in $.paths308        if !self.krate.paths.contains_key(&x.id) {309            self.fail(&x.id, ErrorKind::Custom(format!("No entry in '$.paths' for {x:?}")));310        }311312        self.check_opt_generic_args(&x.args);313    }314315    fn check_opt_generic_args(&mut self, x: &'a Option<Box<GenericArgs>>) {316        let Some(x) = x else { return };317        match &**x {318            GenericArgs::AngleBracketed { args, constraints } => {319                args.iter().for_each(|arg| self.check_generic_arg(arg));320                constraints.iter().for_each(|bind| self.check_assoc_item_constraint(bind));321            }322            GenericArgs::Parenthesized { inputs, output } => {323                inputs.iter().for_each(|ty| self.check_type(ty));324                if let Some(o) = output {325                    self.check_type(o);326                }327            }328            GenericArgs::ReturnTypeNotation => {}329        }330    }331332    fn check_generic_param_def(&mut self, gpd: &'a GenericParamDef) {333        match &gpd.kind {334            rustdoc_json_types::GenericParamDefKind::Lifetime { outlives: _ } => {}335            rustdoc_json_types::GenericParamDefKind::Type { bounds, default, is_synthetic: _ } => {336                bounds.iter().for_each(|b| self.check_generic_bound(b));337                if let Some(ty) = default {338                    self.check_type(ty);339                }340            }341            rustdoc_json_types::GenericParamDefKind::Const { type_, default: _ } => {342                self.check_type(type_)343            }344        }345    }346347    fn check_generic_arg(&mut self, arg: &'a GenericArg) {348        match arg {349            GenericArg::Lifetime(_) => {}350            GenericArg::Type(ty) => self.check_type(ty),351            GenericArg::Const(c) => self.check_constant(c),352            GenericArg::Infer => {}353        }354    }355356    fn check_assoc_item_constraint(&mut self, bind: &'a AssocItemConstraint) {357        self.check_opt_generic_args(&bind.args);358        match &bind.binding {359            AssocItemConstraintKind::Equality(term) => self.check_term(term),360            AssocItemConstraintKind::Constraint(bounds) => {361                bounds.iter().for_each(|b| self.check_generic_bound(b))362            }363        }364    }365366    fn check_term(&mut self, term: &'a Term) {367        match term {368            Term::Type(ty) => self.check_type(ty),369            Term::Constant(con) => self.check_constant(con),370        }371    }372373    fn check_where_predicate(&mut self, w: &'a WherePredicate) {374        match w {375            WherePredicate::BoundPredicate { type_, bounds, generic_params } => {376                self.check_type(type_);377                bounds.iter().for_each(|b| self.check_generic_bound(b));378                generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));379            }380            WherePredicate::LifetimePredicate { lifetime: _, outlives: _ } => {381                // nop, all strings.382            }383            WherePredicate::EqPredicate { lhs, rhs } => {384                self.check_type(lhs);385                self.check_term(rhs);386            }387        }388    }389390    fn check_dyn_trait(&mut self, dyn_trait: &'a DynTrait) {391        for pt in &dyn_trait.traits {392            self.check_path(&pt.trait_, PathKind::Trait);393            pt.generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));394        }395    }396397    fn check_function_pointer(&mut self, fp: &'a FunctionPointer) {398        self.check_function_signature(&fp.sig);399        fp.generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));400    }401402    fn check_item_info(&mut self, id: &Id, item_info: &ItemSummary) {403        // FIXME: Their should be a better way to determine if an item is local, rather than relying on `LOCAL_CRATE_ID`,404        // which encodes rustc implementation details.405        if item_info.crate_id == LOCAL_CRATE_ID && !self.krate.index.contains_key(id) {406            self.errs.push(Error {407                id: id.clone(),408                kind: ErrorKind::Custom(409                    "Id for local item in `paths` but not in `index`".to_owned(),410                ),411            })412        }413    }414415    fn add_id_checked(&mut self, id: &'a Id, valid: fn(Kind) -> bool, expected: &str) {416        if let Some(kind) = self.kind_of(id) {417            if valid(kind) {418                if !self.seen_ids.contains(id) {419                    self.todo.insert(id);420                }421            } else {422                self.fail_expecting(id, expected);423            }424        } else if !self.missing_ids.contains(id) {425            self.missing_ids.insert(id);426427            let sels = json_find::find_selector(&self.krate_json, &Value::Number(id.0.into()));428            assert_ne!(sels.len(), 0);429430            self.fail(id, ErrorKind::NotFound(sels))431        }432    }433434    fn add_any_id(&mut self, id: &'a Id) {435        self.add_id_checked(id, |_| true, "any kind of item");436    }437438    fn add_field_id(&mut self, id: &'a Id) {439        self.add_id_checked(id, Kind::is_struct_field, "StructField");440    }441442    fn add_mod_id(&mut self, id: &'a Id) {443        self.add_id_checked(id, Kind::is_module, "Module");444    }445    fn add_impl_id(&mut self, id: &'a Id) {446        self.add_id_checked(id, Kind::is_impl, "Impl");447    }448449    fn add_variant_id(&mut self, id: &'a Id) {450        self.add_id_checked(id, Kind::is_variant, "Variant");451    }452453    fn add_trait_or_alias_id(&mut self, id: &'a Id) {454        self.add_id_checked(id, Kind::is_trait_or_alias, "Trait (or TraitAlias)");455    }456457    fn add_type_id(&mut self, id: &'a Id) {458        self.add_id_checked(id, Kind::is_type, "Type (Struct, Enum, Union or TypeAlias)");459    }460461    /// Add an Id that appeared in a trait462    fn add_trait_item_id(&mut self, id: &'a Id) {463        self.add_id_checked(id, Kind::can_appear_in_trait, "Trait inner item");464    }465466    /// Add an Id that can be `use`d467    fn add_import_item_id(&mut self, id: &'a Id) {468        self.add_id_checked(id, Kind::can_appear_in_import, "Import inner item");469    }470471    fn add_glob_import_item_id(&mut self, id: &'a Id) {472        self.add_id_checked(id, Kind::can_appear_in_glob_import, "Glob import inner item");473    }474475    /// Add an Id that appeared in a mod476    fn add_mod_item_id(&mut self, id: &'a Id) {477        self.add_id_checked(id, Kind::can_appear_in_mod, "Module inner item")478    }479480    fn fail_expecting(&mut self, id: &Id, expected: &str) {481        let kind = self.kind_of(id).unwrap(); // We know it has a kind, as it's wrong.482        self.fail(id, ErrorKind::Custom(format!("Expected {expected} but found {kind:?}")));483    }484485    fn fail(&mut self, id: &Id, kind: ErrorKind) {486        self.errs.push(Error { id: id.clone(), kind });487    }488489    fn kind_of(&mut self, id: &Id) -> Option<Kind> {490        if let Some(item) = self.krate.index.get(id) {491            Some(Kind::from_item(item))492        } else if let Some(summary) = self.krate.paths.get(id) {493            Some(Kind::from_summary(summary))494        } else {495            None496        }497    }498}499500fn set_remove<T: Hash + Eq + Clone>(set: &mut HashSet<T>) -> Option<T> {501    if let Some(id) = set.iter().next() {502        let id = id.clone();503        set.take(&id)504    } else {505        None506    }507}508509#[cfg(test)]510mod tests;

Code quality findings 7

Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// - `check_*`: These take a type from [`rustdoc_json_types`], and check that
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// fields of that item, and `add_*` functions on [`Id`]s.
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
/// - `add_*`: These add an [`Id`] to the worklist, after validating it to check if
Warning: Direct indexing (e.g., `vec[i]`, `slice[i]`) panics on out-of-bounds access. Prefer using `.get(index)` or `.get_mut(index)` which return Option<&T>/Option<&mut T>.
warning correctness unchecked-indexing
let root_crate_id = self.krate.index[root].crate_id;
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
self.add_glob_import_item_id(x.id.as_ref().unwrap());
Warning: '.unwrap()' will panic on None/Err variants. Prefer using pattern matching (match, if let), combinators (map, and_then), or the '?' operator for robust error handling.
warning correctness unwrap-usage
let kind = self.kind_of(id).unwrap(); // We know it has a kind, as it's wrong.
Info: This standard library function returns a Result. Ensure the Result is handled properly (e.g., using '?', match, if let) rather than potentially panicking with .unwrap() or .expect().
info correctness unhandled-result
if let (Err(_), Err(_)) = (discr.value.parse::<i128>(), discr.value.parse::<u128>()) {

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.