1use 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#[cfg(feature = "serde")]
44const PACKAGE_DOCS_SECTION_VERSION: u8 = 1;
45
46const TRY_TO_EMIT_V0_BY_DEFAULT: bool = false;
51
52#[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 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 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 #[cfg(feature = "serde")]
124 pub fn encode(&self) -> Result<Vec<u8>> {
125 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 #[cfg(feature = "serde")]
144 pub fn decode(data: &[u8]) -> Result<Self> {
145 match data.first().copied() {
146 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 #[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 #[cfg_attr(
217 feature = "serde",
218 serde(default, skip_serializing_if = "StringMap::is_empty")
219 )]
220 types: StringMap<TypeMetadata>,
221
222 #[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 #[cfg_attr(
231 feature = "serde",
232 serde(default, skip_serializing_if = "StringMap::is_empty")
233 )]
234 interface_exports: StringMap<InterfaceMetadata>,
235
236 #[cfg_attr(
238 feature = "serde",
239 serde(default, skip_serializing_if = "StringMap::is_empty")
240 )]
241 func_exports: StringMap<FunctionMetadata>,
242
243 #[cfg_attr(
253 feature = "serde",
254 serde(default, skip_serializing_if = "StringMap::is_empty")
255 )]
256 interface_import_stability: StringMap<Stability>,
257
258 #[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 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 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 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 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 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 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 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 && 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 JustDocs(Option<String>),
609
610 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 #[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 _ => 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}