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        for ((key, item), import) in world
279            .imports
280            .iter()
281            .map(|p| (p, true))
282            .chain(world.exports.iter().map(|p| (p, false)))
283        {
284            match key {
285                // For all named imports with kebab-names extract their
286                // docs/stability and insert it into one of our maps.
287                WorldKey::Name(name) => match item {
288                    WorldItem::Interface { id, .. } => {
289                        let data = InterfaceMetadata::extract(resolve, *id);
290                        if data.is_empty() {
291                            continue;
292                        }
293                        let map = if import {
294                            &mut interface_imports_or_exports
295                        } else if !TRY_TO_EMIT_V0_BY_DEFAULT
296                            || interface_imports_or_exports.contains_key(name)
297                        {
298                            &mut interface_exports
299                        } else {
300                            &mut interface_imports_or_exports
301                        };
302                        let prev = map.insert(name.to_string(), data);
303                        assert!(prev.is_none());
304                    }
305                    WorldItem::Type { id, .. } => {
306                        let data = TypeMetadata::extract(resolve, *id);
307                        if !data.is_empty() {
308                            types.insert(name.to_string(), data);
309                        }
310                    }
311                    WorldItem::Function(f) => {
312                        let data = FunctionMetadata::extract(f);
313                        if data.is_empty() {
314                            continue;
315                        }
316                        let map = if import {
317                            &mut func_imports_or_exports
318                        } else if !TRY_TO_EMIT_V0_BY_DEFAULT
319                            || func_imports_or_exports.contains_key(name)
320                        {
321                            &mut func_exports
322                        } else {
323                            &mut func_imports_or_exports
324                        };
325                        let prev = map.insert(name.to_string(), data);
326                        assert!(prev.is_none());
327                    }
328                },
329
330                // For interface imports/exports extract the stability and
331                // record it if necessary.
332                WorldKey::Interface(_) => {
333                    let stability = match item {
334                        WorldItem::Interface { stability, .. } => stability,
335                        _ => continue,
336                    };
337                    if stability.is_unknown() {
338                        continue;
339                    }
340
341                    let map = if import {
342                        &mut interface_import_stability
343                    } else {
344                        &mut interface_export_stability
345                    };
346                    let name = resolve.name_world_key(key);
347                    map.insert(name, stability.clone());
348                }
349            }
350        }
351
352        Self {
353            docs: world.docs.contents.clone(),
354            stability: world.stability.clone(),
355            interface_imports_or_exports,
356            types,
357            func_imports_or_exports,
358            interface_exports,
359            func_exports,
360            interface_import_stability,
361            interface_export_stability,
362        }
363    }
364
365    fn inject(&self, resolve: &mut Resolve, id: WorldId) -> Result<()> {
366        // Inject docs/stability for all kebab-named interfaces, both imports
367        // and exports.
368        for ((name, data), only_export) in self
369            .interface_imports_or_exports
370            .iter()
371            .map(|p| (p, false))
372            .chain(self.interface_exports.iter().map(|p| (p, true)))
373        {
374            let key = WorldKey::Name(name.to_string());
375            let world = &mut resolve.worlds[id];
376
377            let item = if only_export {
378                world.exports.get_mut(&key)
379            } else {
380                match world.imports.get_mut(&key) {
381                    Some(item) => Some(item),
382                    None => world.exports.get_mut(&key),
383                }
384            };
385            let Some(WorldItem::Interface { id, stability, .. }) = item else {
386                bail!("missing interface {name:?}");
387            };
388            *stability = data.stability.clone();
389            let id = *id;
390            data.inject(resolve, id)?;
391        }
392
393        // Process all types, which are always imported, for this world.
394        for (name, data) in &self.types {
395            let key = WorldKey::Name(name.to_string());
396            let Some(WorldItem::Type { id, .. }) = resolve.worlds[id].imports.get(&key) else {
397                bail!("missing type {name:?}");
398            };
399            data.inject(resolve, *id)?;
400        }
401
402        // Build a map of `name_world_key` for interface imports/exports to the
403        // actual key. This map is then consluted in the next loop.
404        let world = &resolve.worlds[id];
405        let stabilities = world
406            .imports
407            .iter()
408            .map(|i| (i, true))
409            .chain(world.exports.iter().map(|i| (i, false)))
410            .filter_map(|((key, item), import)| match item {
411                WorldItem::Interface { .. } => {
412                    Some(((resolve.name_world_key(key), import), key.clone()))
413                }
414                _ => None,
415            })
416            .collect::<IndexMap<_, _>>();
417
418        let world = &mut resolve.worlds[id];
419
420        // Update the stability of an interface imports/exports that aren't
421        // kebab-named.
422        for ((name, stability), import) in self
423            .interface_import_stability
424            .iter()
425            .map(|p| (p, true))
426            .chain(self.interface_export_stability.iter().map(|p| (p, false)))
427        {
428            let key = match stabilities.get(&(name.clone(), import)) {
429                Some(key) => key.clone(),
430                None => bail!("missing interface `{name}`"),
431            };
432            let item = if import {
433                world.imports.get_mut(&key)
434            } else {
435                world.exports.get_mut(&key)
436            };
437            match item {
438                Some(WorldItem::Interface { stability: s, .. }) => *s = stability.clone(),
439                _ => bail!("item `{name}` wasn't an interface"),
440            }
441        }
442
443        // Update the docs/stability of all functions imported/exported from
444        // this world.
445        for ((name, data), only_export) in self
446            .func_imports_or_exports
447            .iter()
448            .map(|p| (p, false))
449            .chain(self.func_exports.iter().map(|p| (p, true)))
450        {
451            let key = WorldKey::Name(name.to_string());
452            let item = if only_export {
453                world.exports.get_mut(&key)
454            } else {
455                match world.imports.get_mut(&key) {
456                    Some(item) => Some(item),
457                    None => world.exports.get_mut(&key),
458                }
459            };
460            match item {
461                Some(WorldItem::Function(f)) => data.inject(f)?,
462                _ => bail!("missing func {name:?}"),
463            }
464        }
465        if let Some(docs) = &self.docs {
466            world.docs.contents = Some(docs.to_string());
467        }
468        world.stability = self.stability.clone();
469        Ok(())
470    }
471
472    fn is_empty(&self) -> bool {
473        self.docs.is_none()
474            && self.interface_imports_or_exports.is_empty()
475            && self.types.is_empty()
476            && self.func_imports_or_exports.is_empty()
477            && self.stability.is_unknown()
478            && self.interface_exports.is_empty()
479            && self.func_exports.is_empty()
480            && self.interface_import_stability.is_empty()
481            && self.interface_export_stability.is_empty()
482    }
483
484    #[cfg(feature = "serde")]
485    fn is_compatible_with_v0(&self) -> bool {
486        self.stability.is_unknown()
487            && self
488                .interface_imports_or_exports
489                .iter()
490                .all(|(_, w)| w.is_compatible_with_v0())
491            && self
492                .func_imports_or_exports
493                .iter()
494                .all(|(_, w)| w.is_compatible_with_v0())
495            && self.types.iter().all(|(_, w)| w.is_compatible_with_v0())
496            // These maps weren't present in v0, so we're only compatible if
497            // they're empty.
498            && self.interface_exports.is_empty()
499            && self.func_exports.is_empty()
500            && self.interface_import_stability.is_empty()
501            && self.interface_export_stability.is_empty()
502    }
503}
504
505#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
506#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
507struct InterfaceMetadata {
508    #[cfg_attr(
509        feature = "serde",
510        serde(default, skip_serializing_if = "Option::is_none")
511    )]
512    docs: Option<String>,
513    #[cfg_attr(
514        feature = "serde",
515        serde(default, skip_serializing_if = "Stability::is_unknown")
516    )]
517    stability: Stability,
518    #[cfg_attr(
519        feature = "serde",
520        serde(default, skip_serializing_if = "StringMap::is_empty")
521    )]
522    funcs: StringMap<FunctionMetadata>,
523    #[cfg_attr(
524        feature = "serde",
525        serde(default, skip_serializing_if = "StringMap::is_empty")
526    )]
527    types: StringMap<TypeMetadata>,
528}
529
530impl InterfaceMetadata {
531    fn extract(resolve: &Resolve, id: InterfaceId) -> Self {
532        let interface = &resolve.interfaces[id];
533
534        let funcs = interface
535            .functions
536            .iter()
537            .map(|(name, func)| (name.to_string(), FunctionMetadata::extract(func)))
538            .filter(|(_, item)| !item.is_empty())
539            .collect();
540        let types = interface
541            .types
542            .iter()
543            .map(|(name, id)| (name.to_string(), TypeMetadata::extract(resolve, *id)))
544            .filter(|(_, item)| !item.is_empty())
545            .collect();
546
547        Self {
548            docs: interface.docs.contents.clone(),
549            stability: interface.stability.clone(),
550            funcs,
551            types,
552        }
553    }
554
555    fn inject(&self, resolve: &mut Resolve, id: InterfaceId) -> Result<()> {
556        for (name, data) in &self.types {
557            let Some(&id) = resolve.interfaces[id].types.get(name) else {
558                bail!("missing type {name:?}");
559            };
560            data.inject(resolve, id)?;
561        }
562        let interface = &mut resolve.interfaces[id];
563        for (name, data) in &self.funcs {
564            let Some(f) = interface.functions.get_mut(name) else {
565                bail!("missing func {name:?}");
566            };
567            data.inject(f)?;
568        }
569        if let Some(docs) = &self.docs {
570            interface.docs.contents = Some(docs.to_string());
571        }
572        interface.stability = self.stability.clone();
573        Ok(())
574    }
575
576    fn is_empty(&self) -> bool {
577        self.docs.is_none()
578            && self.funcs.is_empty()
579            && self.types.is_empty()
580            && self.stability.is_unknown()
581    }
582
583    #[cfg(feature = "serde")]
584    fn is_compatible_with_v0(&self) -> bool {
585        self.stability.is_unknown()
586            && self.funcs.iter().all(|(_, w)| w.is_compatible_with_v0())
587            && self.types.iter().all(|(_, w)| w.is_compatible_with_v0())
588    }
589}
590
591#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
592#[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))]
593enum FunctionMetadata {
594    /// In the v0 format function metadata was only a string so this variant
595    /// is preserved for the v0 format. In the future this can be removed
596    /// entirely in favor of just the below struct variant.
597    ///
598    /// Note that this is an untagged enum so the name `JustDocs` is just for
599    /// rust.
600    JustDocs(Option<String>),
601
602    /// In the v1+ format we're tracking at least docs but also the stability
603    /// of functions.
604    DocsAndStabilty {
605        #[cfg_attr(
606            feature = "serde",
607            serde(default, skip_serializing_if = "Option::is_none")
608        )]
609        docs: Option<String>,
610        #[cfg_attr(
611            feature = "serde",
612            serde(default, skip_serializing_if = "Stability::is_unknown")
613        )]
614        stability: Stability,
615    },
616}
617
618impl FunctionMetadata {
619    fn extract(func: &Function) -> Self {
620        if TRY_TO_EMIT_V0_BY_DEFAULT && func.stability.is_unknown() {
621            FunctionMetadata::JustDocs(func.docs.contents.clone())
622        } else {
623            FunctionMetadata::DocsAndStabilty {
624                docs: func.docs.contents.clone(),
625                stability: func.stability.clone(),
626            }
627        }
628    }
629
630    fn inject(&self, func: &mut Function) -> Result<()> {
631        match self {
632            FunctionMetadata::JustDocs(docs) => {
633                func.docs.contents = docs.clone();
634            }
635            FunctionMetadata::DocsAndStabilty { docs, stability } => {
636                func.docs.contents = docs.clone();
637                func.stability = stability.clone();
638            }
639        }
640        Ok(())
641    }
642
643    fn is_empty(&self) -> bool {
644        match self {
645            FunctionMetadata::JustDocs(docs) => docs.is_none(),
646            FunctionMetadata::DocsAndStabilty { docs, stability } => {
647                docs.is_none() && stability.is_unknown()
648            }
649        }
650    }
651
652    #[cfg(feature = "serde")]
653    fn is_compatible_with_v0(&self) -> bool {
654        match self {
655            FunctionMetadata::JustDocs(_) => true,
656            FunctionMetadata::DocsAndStabilty { .. } => false,
657        }
658    }
659}
660
661#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
662#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
663struct TypeMetadata {
664    #[cfg_attr(
665        feature = "serde",
666        serde(default, skip_serializing_if = "Option::is_none")
667    )]
668    docs: Option<String>,
669    #[cfg_attr(
670        feature = "serde",
671        serde(default, skip_serializing_if = "Stability::is_unknown")
672    )]
673    stability: Stability,
674    // record fields, variant cases, etc.
675    #[cfg_attr(
676        feature = "serde",
677        serde(default, skip_serializing_if = "StringMap::is_empty")
678    )]
679    items: StringMap<String>,
680}
681
682impl TypeMetadata {
683    fn extract(resolve: &Resolve, id: TypeId) -> Self {
684        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
685            items
686                .iter()
687                .flat_map(|item| {
688                    let (name, docs) = f(item);
689                    Some((name.to_string(), docs.contents.clone()?))
690                })
691                .collect()
692        }
693        let ty = &resolve.types[id];
694        let items = match &ty.kind {
695            TypeDefKind::Record(record) => {
696                extract_items(&record.fields, |item| (&item.name, &item.docs))
697            }
698            TypeDefKind::Flags(flags) => {
699                extract_items(&flags.flags, |item| (&item.name, &item.docs))
700            }
701            TypeDefKind::Variant(variant) => {
702                extract_items(&variant.cases, |item| (&item.name, &item.docs))
703            }
704            TypeDefKind::Enum(enum_) => {
705                extract_items(&enum_.cases, |item| (&item.name, &item.docs))
706            }
707            // other types don't have inner items
708            _ => IndexMap::default(),
709        };
710
711        Self {
712            docs: ty.docs.contents.clone(),
713            stability: ty.stability.clone(),
714            items,
715        }
716    }
717
718    fn inject(&self, resolve: &mut Resolve, id: TypeId) -> Result<()> {
719        let ty = &mut resolve.types[id];
720        if !self.items.is_empty() {
721            match &mut ty.kind {
722                TypeDefKind::Record(record) => {
723                    self.inject_items(&mut record.fields, |item| (&item.name, &mut item.docs))?
724                }
725                TypeDefKind::Flags(flags) => {
726                    self.inject_items(&mut flags.flags, |item| (&item.name, &mut item.docs))?
727                }
728                TypeDefKind::Variant(variant) => {
729                    self.inject_items(&mut variant.cases, |item| (&item.name, &mut item.docs))?
730                }
731                TypeDefKind::Enum(enum_) => {
732                    self.inject_items(&mut enum_.cases, |item| (&item.name, &mut item.docs))?
733                }
734                _ => {
735                    bail!("got 'items' for unexpected type {ty:?}");
736                }
737            }
738        }
739        if let Some(docs) = &self.docs {
740            ty.docs.contents = Some(docs.to_string());
741        }
742        ty.stability = self.stability.clone();
743        Ok(())
744    }
745
746    fn inject_items<T: core::fmt::Debug>(
747        &self,
748        items: &mut [T],
749        f: impl Fn(&mut T) -> (&String, &mut Docs),
750    ) -> Result<()> {
751        let mut unused_docs = self.items.len();
752        for item in items.iter_mut() {
753            let (name, item_docs) = f(item);
754            if let Some(docs) = self.items.get(name.as_str()) {
755                item_docs.contents = Some(docs.to_string());
756                unused_docs -= 1;
757            }
758        }
759        if unused_docs > 0 {
760            bail!(
761                "not all 'items' match type items; {item_docs:?} vs {items:?}",
762                item_docs = self.items
763            );
764        }
765        Ok(())
766    }
767
768    fn is_empty(&self) -> bool {
769        self.docs.is_none() && self.items.is_empty() && self.stability.is_unknown()
770    }
771
772    #[cfg(feature = "serde")]
773    fn is_compatible_with_v0(&self) -> bool {
774        self.stability.is_unknown()
775    }
776}