Skip to main content

wit_parser/
metadata.rs

1//! Implementation of encoding/decoding package metadata (docs/stability) in a
2//! custom section.
3//!
4//! This module contains the particulars for how this custom section is encoded
5//! and decoded at this time. As of the time of this writing the component model
6//! binary format does not have any means of storing documentation and/or item
7//! stability inline with items themselves. These are important to preserve when
8//! round-tripping WIT through the WebAssembly binary format, however, so this
9//! module implements this with a custom section.
10//!
11//! The custom section, named `SECTION_NAME`, is stored within the component
12//! that encodes a WIT package. This section is itself JSON-encoded with a small
13//! version header to help forwards/backwards compatibility. The hope is that
14//! one day this custom section will be obsoleted by extensions to the binary
15//! format to store this information inline.
16
17use crate::{
18    Docs, Function, IndexMap, InterfaceId, PackageId, Resolve, Stability, TypeDefKind, TypeId,
19    WorldId, WorldItem, WorldKey,
20};
21use alloc::string::{String, ToString};
22#[cfg(feature = "serde")]
23use alloc::vec;
24#[cfg(feature = "serde")]
25use alloc::vec::Vec;
26use anyhow::{Result, bail};
27#[cfg(feature = "serde")]
28use serde_derive::{Deserialize, Serialize};
29
30type StringMap<V> = IndexMap<String, V>;
31
32/// Current supported format of the custom section.
33///
34/// This byte is a prefix byte intended to be a general version marker for the
35/// entire custom section. This is bumped when backwards-incompatible changes
36/// are made to prevent older implementations from loading newer versions.
37///
38/// The history of this is:
39///
40/// * [????/??/??] 0 - the original format added
41/// * [2024/04/19] 1 - extensions were added for item stability and
42///   additionally having world imports/exports have the same name.
43#[cfg(feature = "serde")]
44const PACKAGE_DOCS_SECTION_VERSION: u8 = 1;
45
46/// At this time the v1 format was just written. For compatibility with older
47/// tools we'll still try to emit the v0 format by default, if the input is
48/// compatible. This will be turned off in the future once enough published
49/// versions support the v1 format.
50const TRY_TO_EMIT_V0_BY_DEFAULT: bool = false;
51
52/// Represents serializable doc comments parsed from a WIT package.
53#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
54#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
55pub struct PackageMetadata {
56    #[cfg_attr(
57        feature = "serde",
58        serde(default, skip_serializing_if = "Option::is_none")
59    )]
60    docs: Option<String>,
61    #[cfg_attr(
62        feature = "serde",
63        serde(default, skip_serializing_if = "StringMap::is_empty")
64    )]
65    worlds: StringMap<WorldMetadata>,
66    #[cfg_attr(
67        feature = "serde",
68        serde(default, skip_serializing_if = "StringMap::is_empty")
69    )]
70    interfaces: StringMap<InterfaceMetadata>,
71}
72
73impl PackageMetadata {
74    pub const SECTION_NAME: &'static str = "package-docs";
75
76    /// Extract package docs for the given package.
77    pub fn extract(resolve: &Resolve, package: PackageId) -> Self {
78        let package = &resolve.packages[package];
79
80        let worlds = package
81            .worlds
82            .iter()
83            .map(|(name, id)| (name.to_string(), WorldMetadata::extract(resolve, *id)))
84            .filter(|(_, item)| !item.is_empty())
85            .collect();
86        let interfaces = package
87            .interfaces
88            .iter()
89            .map(|(name, id)| (name.to_string(), InterfaceMetadata::extract(resolve, *id)))
90            .filter(|(_, item)| !item.is_empty())
91            .collect();
92
93        Self {
94            docs: package.docs.contents.as_deref().map(Into::into),
95            worlds,
96            interfaces,
97        }
98    }
99
100    /// Inject package docs for the given package.
101    ///
102    /// This will override any existing docs in the [`Resolve`].
103    pub fn inject(&self, resolve: &mut Resolve, package: PackageId) -> Result<()> {
104        for (name, docs) in &self.worlds {
105            let Some(&id) = resolve.packages[package].worlds.get(name) else {
106                bail!("missing world {name:?}");
107            };
108            docs.inject(resolve, id)?;
109        }
110        for (name, docs) in &self.interfaces {
111            let Some(&id) = resolve.packages[package].interfaces.get(name) else {
112                bail!("missing interface {name:?}");
113            };
114            docs.inject(resolve, id)?;
115        }
116        if let Some(docs) = &self.docs {
117            resolve.packages[package].docs.contents = Some(docs.to_string());
118        }
119        Ok(())
120    }
121
122    /// Encode package docs as a package-docs custom section.
123    #[cfg(feature = "serde")]
124    pub fn encode(&self) -> Result<Vec<u8>> {
125        // Version byte, followed by JSON encoding of docs.
126        //
127        // Note that if this document is compatible with the v0 format then
128        // that's preferred to keep older tools working at this time.
129        // Eventually this branch will be removed and v1 will unconditionally
130        // be used.
131        let mut data = vec![
132            if TRY_TO_EMIT_V0_BY_DEFAULT && self.is_compatible_with_v0() {
133                0
134            } else {
135                PACKAGE_DOCS_SECTION_VERSION
136            },
137        ];
138        serde_json::to_writer(&mut data, self)?;
139        Ok(data)
140    }
141
142    /// Decode package docs from package-docs custom section content.
143    #[cfg(feature = "serde")]
144    pub fn decode(data: &[u8]) -> Result<Self> {
145        match data.first().copied() {
146            // Our serde structures transparently support v0 and the current
147            // version, so allow either here.
148            Some(0) | Some(PACKAGE_DOCS_SECTION_VERSION) => {}
149            version => {
150                bail!(
151                    "expected package-docs version {PACKAGE_DOCS_SECTION_VERSION}, got {version:?}"
152                );
153            }
154        }
155        Ok(serde_json::from_slice(&data[1..])?)
156    }
157
158    #[cfg(feature = "serde")]
159    fn is_compatible_with_v0(&self) -> bool {
160        self.worlds.iter().all(|(_, w)| w.is_compatible_with_v0())
161            && self
162                .interfaces
163                .iter()
164                .all(|(_, w)| w.is_compatible_with_v0())
165    }
166}
167
168#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
169#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
170struct WorldMetadata {
171    #[cfg_attr(
172        feature = "serde",
173        serde(default, skip_serializing_if = "Option::is_none")
174    )]
175    docs: Option<String>,
176    #[cfg_attr(
177        feature = "serde",
178        serde(default, skip_serializing_if = "Stability::is_unknown")
179    )]
180    stability: Stability,
181
182    /// Metadata for named interface, e.g.:
183    ///
184    /// ```wit
185    /// world foo {
186    ///     import x: interface {}
187    /// }
188    /// ```
189    ///
190    /// In the v0 format this was called "interfaces", hence the
191    /// `serde(rename)`. When support was originally added here imports/exports
192    /// could not overlap in their name, but now they can. This map has thus
193    /// been repurposed as:
194    ///
195    /// * If an interface is imported, it goes here.
196    /// * If an interface is exported, and no interface was imported with the
197    ///   same name, it goes here.
198    ///
199    /// Otherwise exports go inside the `interface_exports` map.
200    ///
201    /// In the future when v0 support is dropped this should become only
202    /// imports, not either imports-or-exports.
203    #[cfg_attr(
204        feature = "serde",
205        serde(
206            default,
207            rename = "interfaces",
208            skip_serializing_if = "StringMap::is_empty"
209        )
210    )]
211    interface_imports_or_exports: StringMap<InterfaceMetadata>,
212
213    /// All types in this interface.
214    ///
215    /// Note that at this time types are only imported, never exported.
216    #[cfg_attr(
217        feature = "serde",
218        serde(default, skip_serializing_if = "StringMap::is_empty")
219    )]
220    types: StringMap<TypeMetadata>,
221
222    /// Same as `interface_imports_or_exports`, but for functions.
223    #[cfg_attr(
224        feature = "serde",
225        serde(default, rename = "funcs", skip_serializing_if = "StringMap::is_empty")
226    )]
227    func_imports_or_exports: StringMap<FunctionMetadata>,
228
229    /// The "export half" of `interface_imports_or_exports`.
230    #[cfg_attr(
231        feature = "serde",
232        serde(default, skip_serializing_if = "StringMap::is_empty")
233    )]
234    interface_exports: StringMap<InterfaceMetadata>,
235
236    /// The "export half" of `func_imports_or_exports`.
237    #[cfg_attr(
238        feature = "serde",
239        serde(default, skip_serializing_if = "StringMap::is_empty")
240    )]
241    func_exports: StringMap<FunctionMetadata>,
242
243    /// Stability annotations for interface imports that aren't inline, for
244    /// example:
245    ///
246    /// ```wit
247    /// world foo {
248    ///     @since(version = 1.0.0)
249    ///     import an-interface;
250    /// }
251    /// ```
252    #[cfg_attr(
253        feature = "serde",
254        serde(default, skip_serializing_if = "StringMap::is_empty")
255    )]
256    interface_import_stability: StringMap<Stability>,
257
258    /// Same as `interface_import_stability`, but for exports.
259    #[cfg_attr(
260        feature = "serde",
261        serde(default, skip_serializing_if = "StringMap::is_empty")
262    )]
263    interface_export_stability: StringMap<Stability>,
264}
265
266impl WorldMetadata {
267    fn extract(resolve: &Resolve, id: WorldId) -> Self {
268        let world = &resolve.worlds[id];
269
270        let mut interface_imports_or_exports = StringMap::default();
271        let mut types = StringMap::default();
272        let mut func_imports_or_exports = StringMap::default();
273        let mut interface_exports = StringMap::default();
274        let mut func_exports = StringMap::default();
275        let mut interface_import_stability = StringMap::default();
276        let mut interface_export_stability = StringMap::default();
277
278        let mut record_interface_stability = |key: &WorldKey, item: &WorldItem, import: bool| {
279            let stability = match item {
280                WorldItem::Interface { stability, .. } => stability,
281                _ => return,
282            };
283            if stability.is_unknown() {
284                return;
285            }
286
287            let map = if import {
288                &mut interface_import_stability
289            } else {
290                &mut interface_export_stability
291            };
292            let name = resolve.name_world_key(key);
293            map.insert(name, stability.clone());
294        };
295
296        for ((key, item), import) in world
297            .imports
298            .iter()
299            .map(|p| (p, true))
300            .chain(world.exports.iter().map(|p| (p, false)))
301        {
302            match key {
303                // For all named imports with kebab-names extract their
304                // docs/stability and insert it into one of our maps.
305                WorldKey::Name(name) => match item {
306                    WorldItem::Interface { id, .. } => {
307                        if resolve.interfaces[*id].name.is_some() {
308                            record_interface_stability(key, item, import);
309                            continue;
310                        }
311                        let data = InterfaceMetadata::extract(resolve, *id);
312                        if data.is_empty() {
313                            continue;
314                        }
315                        let map = if import {
316                            &mut interface_imports_or_exports
317                        } else if !TRY_TO_EMIT_V0_BY_DEFAULT
318                            || interface_imports_or_exports.contains_key(name)
319                        {
320                            &mut interface_exports
321                        } else {
322                            &mut interface_imports_or_exports
323                        };
324                        let prev = map.insert(name.to_string(), data);
325                        assert!(prev.is_none());
326                    }
327                    WorldItem::Type { id, .. } => {
328                        let data = TypeMetadata::extract(resolve, *id);
329                        if !data.is_empty() {
330                            types.insert(name.to_string(), data);
331                        }
332                    }
333                    WorldItem::Function(f) => {
334                        let data = FunctionMetadata::extract(f);
335                        if data.is_empty() {
336                            continue;
337                        }
338                        let map = if import {
339                            &mut func_imports_or_exports
340                        } else if !TRY_TO_EMIT_V0_BY_DEFAULT
341                            || func_imports_or_exports.contains_key(name)
342                        {
343                            &mut func_exports
344                        } else {
345                            &mut func_imports_or_exports
346                        };
347                        let prev = map.insert(name.to_string(), data);
348                        assert!(prev.is_none());
349                    }
350                },
351
352                // For interface imports/exports extract the stability and
353                // record it if necessary.
354                WorldKey::Interface(_) => {
355                    record_interface_stability(key, item, import);
356                }
357            }
358        }
359
360        Self {
361            docs: world.docs.contents.clone(),
362            stability: world.stability.clone(),
363            interface_imports_or_exports,
364            types,
365            func_imports_or_exports,
366            interface_exports,
367            func_exports,
368            interface_import_stability,
369            interface_export_stability,
370        }
371    }
372
373    fn inject(&self, resolve: &mut Resolve, id: WorldId) -> Result<()> {
374        // Inject docs/stability for all kebab-named interfaces, both imports
375        // and exports.
376        for ((name, data), only_export) in self
377            .interface_imports_or_exports
378            .iter()
379            .map(|p| (p, false))
380            .chain(self.interface_exports.iter().map(|p| (p, true)))
381        {
382            let key = WorldKey::Name(name.to_string());
383            let world = &mut resolve.worlds[id];
384
385            let item = if only_export {
386                world.exports.get_mut(&key)
387            } else {
388                match world.imports.get_mut(&key) {
389                    Some(item) => Some(item),
390                    None => world.exports.get_mut(&key),
391                }
392            };
393            let Some(WorldItem::Interface { id, stability, .. }) = item else {
394                bail!("missing interface {name:?}");
395            };
396            *stability = data.stability.clone();
397            let id = *id;
398            data.inject(resolve, id)?;
399        }
400
401        // Process all types, which are always imported, for this world.
402        for (name, data) in &self.types {
403            let key = WorldKey::Name(name.to_string());
404            let Some(WorldItem::Type { id, .. }) = resolve.worlds[id].imports.get(&key) else {
405                bail!("missing type {name:?}");
406            };
407            data.inject(resolve, *id)?;
408        }
409
410        // Build a map of `name_world_key` for interface imports/exports to the
411        // actual key. This map is then consluted in the next loop.
412        let world = &resolve.worlds[id];
413        let stabilities = world
414            .imports
415            .iter()
416            .map(|i| (i, true))
417            .chain(world.exports.iter().map(|i| (i, false)))
418            .filter_map(|((key, item), import)| match item {
419                WorldItem::Interface { .. } => {
420                    Some(((resolve.name_world_key(key), import), key.clone()))
421                }
422                _ => None,
423            })
424            .collect::<IndexMap<_, _>>();
425
426        let world = &mut resolve.worlds[id];
427
428        // Update the stability of an interface imports/exports that aren't
429        // kebab-named.
430        for ((name, stability), import) in self
431            .interface_import_stability
432            .iter()
433            .map(|p| (p, true))
434            .chain(self.interface_export_stability.iter().map(|p| (p, false)))
435        {
436            let key = match stabilities.get(&(name.clone(), import)) {
437                Some(key) => key.clone(),
438                None => bail!("missing interface `{name}`"),
439            };
440            let item = if import {
441                world.imports.get_mut(&key)
442            } else {
443                world.exports.get_mut(&key)
444            };
445            match item {
446                Some(WorldItem::Interface { stability: s, .. }) => *s = stability.clone(),
447                _ => bail!("item `{name}` wasn't an interface"),
448            }
449        }
450
451        // Update the docs/stability of all functions imported/exported from
452        // this world.
453        for ((name, data), only_export) in self
454            .func_imports_or_exports
455            .iter()
456            .map(|p| (p, false))
457            .chain(self.func_exports.iter().map(|p| (p, true)))
458        {
459            let key = WorldKey::Name(name.to_string());
460            let item = if only_export {
461                world.exports.get_mut(&key)
462            } else {
463                match world.imports.get_mut(&key) {
464                    Some(item) => Some(item),
465                    None => world.exports.get_mut(&key),
466                }
467            };
468            match item {
469                Some(WorldItem::Function(f)) => data.inject(f)?,
470                _ => bail!("missing func {name:?}"),
471            }
472        }
473        if let Some(docs) = &self.docs {
474            world.docs.contents = Some(docs.to_string());
475        }
476        world.stability = self.stability.clone();
477        Ok(())
478    }
479
480    fn is_empty(&self) -> bool {
481        self.docs.is_none()
482            && self.interface_imports_or_exports.is_empty()
483            && self.types.is_empty()
484            && self.func_imports_or_exports.is_empty()
485            && self.stability.is_unknown()
486            && self.interface_exports.is_empty()
487            && self.func_exports.is_empty()
488            && self.interface_import_stability.is_empty()
489            && self.interface_export_stability.is_empty()
490    }
491
492    #[cfg(feature = "serde")]
493    fn is_compatible_with_v0(&self) -> bool {
494        self.stability.is_unknown()
495            && self
496                .interface_imports_or_exports
497                .iter()
498                .all(|(_, w)| w.is_compatible_with_v0())
499            && self
500                .func_imports_or_exports
501                .iter()
502                .all(|(_, w)| w.is_compatible_with_v0())
503            && self.types.iter().all(|(_, w)| w.is_compatible_with_v0())
504            // These maps weren't present in v0, so we're only compatible if
505            // they're empty.
506            && self.interface_exports.is_empty()
507            && self.func_exports.is_empty()
508            && self.interface_import_stability.is_empty()
509            && self.interface_export_stability.is_empty()
510    }
511}
512
513#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
514#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
515struct InterfaceMetadata {
516    #[cfg_attr(
517        feature = "serde",
518        serde(default, skip_serializing_if = "Option::is_none")
519    )]
520    docs: Option<String>,
521    #[cfg_attr(
522        feature = "serde",
523        serde(default, skip_serializing_if = "Stability::is_unknown")
524    )]
525    stability: Stability,
526    #[cfg_attr(
527        feature = "serde",
528        serde(default, skip_serializing_if = "StringMap::is_empty")
529    )]
530    funcs: StringMap<FunctionMetadata>,
531    #[cfg_attr(
532        feature = "serde",
533        serde(default, skip_serializing_if = "StringMap::is_empty")
534    )]
535    types: StringMap<TypeMetadata>,
536}
537
538impl InterfaceMetadata {
539    fn extract(resolve: &Resolve, id: InterfaceId) -> Self {
540        let interface = &resolve.interfaces[id];
541
542        let funcs = interface
543            .functions
544            .iter()
545            .map(|(name, func)| (name.to_string(), FunctionMetadata::extract(func)))
546            .filter(|(_, item)| !item.is_empty())
547            .collect();
548        let types = interface
549            .types
550            .iter()
551            .map(|(name, id)| (name.to_string(), TypeMetadata::extract(resolve, *id)))
552            .filter(|(_, item)| !item.is_empty())
553            .collect();
554
555        Self {
556            docs: interface.docs.contents.clone(),
557            stability: interface.stability.clone(),
558            funcs,
559            types,
560        }
561    }
562
563    fn inject(&self, resolve: &mut Resolve, id: InterfaceId) -> Result<()> {
564        for (name, data) in &self.types {
565            let Some(&id) = resolve.interfaces[id].types.get(name) else {
566                bail!("missing type {name:?}");
567            };
568            data.inject(resolve, id)?;
569        }
570        let interface = &mut resolve.interfaces[id];
571        for (name, data) in &self.funcs {
572            let Some(f) = interface.functions.get_mut(name) else {
573                bail!("missing func {name:?}");
574            };
575            data.inject(f)?;
576        }
577        if let Some(docs) = &self.docs {
578            interface.docs.contents = Some(docs.to_string());
579        }
580        interface.stability = self.stability.clone();
581        Ok(())
582    }
583
584    fn is_empty(&self) -> bool {
585        self.docs.is_none()
586            && self.funcs.is_empty()
587            && self.types.is_empty()
588            && self.stability.is_unknown()
589    }
590
591    #[cfg(feature = "serde")]
592    fn is_compatible_with_v0(&self) -> bool {
593        self.stability.is_unknown()
594            && self.funcs.iter().all(|(_, w)| w.is_compatible_with_v0())
595            && self.types.iter().all(|(_, w)| w.is_compatible_with_v0())
596    }
597}
598
599#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
600#[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))]
601enum FunctionMetadata {
602    /// In the v0 format function metadata was only a string so this variant
603    /// is preserved for the v0 format. In the future this can be removed
604    /// entirely in favor of just the below struct variant.
605    ///
606    /// Note that this is an untagged enum so the name `JustDocs` is just for
607    /// rust.
608    JustDocs(Option<String>),
609
610    /// In the v1+ format we're tracking at least docs but also the stability
611    /// of functions.
612    DocsAndStability {
613        #[cfg_attr(
614            feature = "serde",
615            serde(default, skip_serializing_if = "Option::is_none")
616        )]
617        docs: Option<String>,
618        #[cfg_attr(
619            feature = "serde",
620            serde(default, skip_serializing_if = "Stability::is_unknown")
621        )]
622        stability: Stability,
623    },
624}
625
626impl FunctionMetadata {
627    fn extract(func: &Function) -> Self {
628        if TRY_TO_EMIT_V0_BY_DEFAULT && func.stability.is_unknown() {
629            FunctionMetadata::JustDocs(func.docs.contents.clone())
630        } else {
631            FunctionMetadata::DocsAndStability {
632                docs: func.docs.contents.clone(),
633                stability: func.stability.clone(),
634            }
635        }
636    }
637
638    fn inject(&self, func: &mut Function) -> Result<()> {
639        match self {
640            FunctionMetadata::JustDocs(docs) => {
641                func.docs.contents = docs.clone();
642            }
643            FunctionMetadata::DocsAndStability { docs, stability } => {
644                func.docs.contents = docs.clone();
645                func.stability = stability.clone();
646            }
647        }
648        Ok(())
649    }
650
651    fn is_empty(&self) -> bool {
652        match self {
653            FunctionMetadata::JustDocs(docs) => docs.is_none(),
654            FunctionMetadata::DocsAndStability { docs, stability } => {
655                docs.is_none() && stability.is_unknown()
656            }
657        }
658    }
659
660    #[cfg(feature = "serde")]
661    fn is_compatible_with_v0(&self) -> bool {
662        match self {
663            FunctionMetadata::JustDocs(_) => true,
664            FunctionMetadata::DocsAndStability { .. } => false,
665        }
666    }
667}
668
669#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
670#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
671struct TypeMetadata {
672    #[cfg_attr(
673        feature = "serde",
674        serde(default, skip_serializing_if = "Option::is_none")
675    )]
676    docs: Option<String>,
677    #[cfg_attr(
678        feature = "serde",
679        serde(default, skip_serializing_if = "Stability::is_unknown")
680    )]
681    stability: Stability,
682    // record fields, variant cases, etc.
683    #[cfg_attr(
684        feature = "serde",
685        serde(default, skip_serializing_if = "StringMap::is_empty")
686    )]
687    items: StringMap<String>,
688}
689
690impl TypeMetadata {
691    fn extract(resolve: &Resolve, id: TypeId) -> Self {
692        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
693            items
694                .iter()
695                .flat_map(|item| {
696                    let (name, docs) = f(item);
697                    Some((name.to_string(), docs.contents.clone()?))
698                })
699                .collect()
700        }
701        let ty = &resolve.types[id];
702        let items = match &ty.kind {
703            TypeDefKind::Record(record) => {
704                extract_items(&record.fields, |item| (&item.name, &item.docs))
705            }
706            TypeDefKind::Flags(flags) => {
707                extract_items(&flags.flags, |item| (&item.name, &item.docs))
708            }
709            TypeDefKind::Variant(variant) => {
710                extract_items(&variant.cases, |item| (&item.name, &item.docs))
711            }
712            TypeDefKind::Enum(enum_) => {
713                extract_items(&enum_.cases, |item| (&item.name, &item.docs))
714            }
715            // other types don't have inner items
716            _ => IndexMap::default(),
717        };
718
719        Self {
720            docs: ty.docs.contents.clone(),
721            stability: ty.stability.clone(),
722            items,
723        }
724    }
725
726    fn inject(&self, resolve: &mut Resolve, id: TypeId) -> Result<()> {
727        let ty = &mut resolve.types[id];
728        if !self.items.is_empty() {
729            match &mut ty.kind {
730                TypeDefKind::Record(record) => {
731                    self.inject_items(&mut record.fields, |item| (&item.name, &mut item.docs))?
732                }
733                TypeDefKind::Flags(flags) => {
734                    self.inject_items(&mut flags.flags, |item| (&item.name, &mut item.docs))?
735                }
736                TypeDefKind::Variant(variant) => {
737                    self.inject_items(&mut variant.cases, |item| (&item.name, &mut item.docs))?
738                }
739                TypeDefKind::Enum(enum_) => {
740                    self.inject_items(&mut enum_.cases, |item| (&item.name, &mut item.docs))?
741                }
742                _ => {
743                    bail!("got 'items' for unexpected type {ty:?}");
744                }
745            }
746        }
747        if let Some(docs) = &self.docs {
748            ty.docs.contents = Some(docs.to_string());
749        }
750        ty.stability = self.stability.clone();
751        Ok(())
752    }
753
754    fn inject_items<T: core::fmt::Debug>(
755        &self,
756        items: &mut [T],
757        f: impl Fn(&mut T) -> (&String, &mut Docs),
758    ) -> Result<()> {
759        let mut unused_docs = self.items.len();
760        for item in items.iter_mut() {
761            let (name, item_docs) = f(item);
762            if let Some(docs) = self.items.get(name.as_str()) {
763                item_docs.contents = Some(docs.to_string());
764                unused_docs -= 1;
765            }
766        }
767        if unused_docs > 0 {
768            bail!(
769                "not all 'items' match type items; {item_docs:?} vs {items:?}",
770                item_docs = self.items
771            );
772        }
773        Ok(())
774    }
775
776    fn is_empty(&self) -> bool {
777        self.docs.is_none() && self.items.is_empty() && self.stability.is_unknown()
778    }
779
780    #[cfg(feature = "serde")]
781    fn is_compatible_with_v0(&self) -> bool {
782        self.stability.is_unknown()
783    }
784}