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