Coverage Report

Created: 2026-07-05 07:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/wasm-tools/crates/wit-parser/src/metadata.rs
Line
Count
Source
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
17
use crate::{
18
    Docs, Function, IndexMap, InterfaceId, PackageId, Resolve, Stability, TypeDefKind, TypeId,
19
    WorldId, WorldItem, WorldKey,
20
};
21
use alloc::string::{String, ToString};
22
#[cfg(feature = "serde")]
23
use alloc::vec;
24
#[cfg(feature = "serde")]
25
use alloc::vec::Vec;
26
use anyhow::{Result, bail};
27
#[cfg(feature = "serde")]
28
use serde_derive::{Deserialize, Serialize};
29
30
type 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")]
44
const 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.
50
const 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))]
55
pub 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
73
impl PackageMetadata {
74
    pub const SECTION_NAME: &'static str = "package-docs";
75
76
    /// Extract package docs for the given package.
77
10.3k
    pub fn extract(resolve: &Resolve, package: PackageId) -> Self {
78
10.3k
        let package = &resolve.packages[package];
79
80
10.3k
        let worlds = package
81
10.3k
            .worlds
82
10.3k
            .iter()
83
14.5k
            .map(|(name, id)| (name.to_string(), WorldMetadata::extract(resolve, *id)))
84
14.5k
            .filter(|(_, item)| !item.is_empty())
85
10.3k
            .collect();
86
10.3k
        let interfaces = package
87
10.3k
            .interfaces
88
10.3k
            .iter()
89
48.8k
            .map(|(name, id)| (name.to_string(), InterfaceMetadata::extract(resolve, *id)))
90
48.8k
            .filter(|(_, item)| !item.is_empty())
91
10.3k
            .collect();
92
93
10.3k
        Self {
94
10.3k
            docs: package.docs.contents.as_deref().map(Into::into),
95
10.3k
            worlds,
96
10.3k
            interfaces,
97
10.3k
        }
98
10.3k
    }
99
100
    /// Inject package docs for the given package.
101
    ///
102
    /// This will override any existing docs in the [`Resolve`].
103
5.43k
    pub fn inject(&self, resolve: &mut Resolve, package: PackageId) -> Result<()> {
104
5.43k
        for (name, docs) in &self.worlds {
105
1.25k
            let Some(&id) = resolve.packages[package].worlds.get(name) else {
106
0
                bail!("missing world {name:?}");
107
            };
108
1.25k
            docs.inject(resolve, id)?;
109
        }
110
5.43k
        for (name, docs) in &self.interfaces {
111
0
            let Some(&id) = resolve.packages[package].interfaces.get(name) else {
112
0
                bail!("missing interface {name:?}");
113
            };
114
0
            docs.inject(resolve, id)?;
115
        }
116
5.43k
        if let Some(docs) = &self.docs {
117
0
            resolve.packages[package].docs.contents = Some(docs.to_string());
118
5.43k
        }
119
5.43k
        Ok(())
120
5.43k
    }
121
122
    /// Encode package docs as a package-docs custom section.
123
    #[cfg(feature = "serde")]
124
10.3k
    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
10.3k
        let mut data = vec![
132
10.3k
            if TRY_TO_EMIT_V0_BY_DEFAULT && self.is_compatible_with_v0() {
133
0
                0
134
            } else {
135
10.3k
                PACKAGE_DOCS_SECTION_VERSION
136
            },
137
        ];
138
10.3k
        serde_json::to_writer(&mut data, self)?;
139
10.3k
        Ok(data)
140
10.3k
    }
141
142
    /// Decode package docs from package-docs custom section content.
143
    #[cfg(feature = "serde")]
144
5.43k
    pub fn decode(data: &[u8]) -> Result<Self> {
145
5.43k
        match data.first().copied() {
146
            // Our serde structures transparently support v0 and the current
147
            // version, so allow either here.
148
5.43k
            Some(0) | Some(PACKAGE_DOCS_SECTION_VERSION) => {}
149
0
            version => {
150
0
                bail!(
151
                    "expected package-docs version {PACKAGE_DOCS_SECTION_VERSION}, got {version:?}"
152
                );
153
            }
154
        }
155
5.43k
        Ok(serde_json::from_slice(&data[1..])?)
156
5.43k
    }
157
158
    #[cfg(feature = "serde")]
159
0
    fn is_compatible_with_v0(&self) -> bool {
160
0
        self.worlds.iter().all(|(_, w)| w.is_compatible_with_v0())
161
0
            && self
162
0
                .interfaces
163
0
                .iter()
164
0
                .all(|(_, w)| w.is_compatible_with_v0())
165
0
    }
166
}
167
168
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
169
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
170
struct 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
    /// Docs attached to interface imports that aren't inline, mirroring
266
    /// `interface_import_stability`, for example:
267
    ///
268
    /// ```wit
269
    /// world foo {
270
    ///     /// These docs.
271
    ///     import an-interface;
272
    /// }
273
    /// ```
274
    #[cfg_attr(
275
        feature = "serde",
276
        serde(default, skip_serializing_if = "StringMap::is_empty")
277
    )]
278
    interface_import_docs: StringMap<String>,
279
280
    /// Same as `interface_import_docs`, but for exports.
281
    #[cfg_attr(
282
        feature = "serde",
283
        serde(default, skip_serializing_if = "StringMap::is_empty")
284
    )]
285
    interface_export_docs: StringMap<String>,
286
}
287
288
impl WorldMetadata {
289
14.5k
    fn extract(resolve: &Resolve, id: WorldId) -> Self {
290
14.5k
        let world = &resolve.worlds[id];
291
292
14.5k
        let mut interface_imports_or_exports = StringMap::default();
293
14.5k
        let mut types = StringMap::default();
294
14.5k
        let mut func_imports_or_exports = StringMap::default();
295
14.5k
        let mut interface_exports = StringMap::default();
296
14.5k
        let mut func_exports = StringMap::default();
297
14.5k
        let mut interface_import_stability = StringMap::default();
298
14.5k
        let mut interface_export_stability = StringMap::default();
299
14.5k
        let mut interface_import_docs = StringMap::default();
300
14.5k
        let mut interface_export_docs = StringMap::default();
301
302
        // Records the stability and docs of a non-inline (by-reference)
303
        // interface import/export, keyed by its world-key name, so that they
304
        // can be restored by `inject`. Docs and stability are recorded
305
        // independently since either may be present without the other.
306
14.5k
        let mut record_interface_metadata = |key: &WorldKey, item: &WorldItem, import: bool| {
307
8.86k
            let (stability, docs) = match item {
308
                WorldItem::Interface {
309
8.86k
                    stability, docs, ..
310
8.86k
                } => (stability, docs),
311
0
                _ => return,
312
            };
313
8.86k
            let name = resolve.name_world_key(key);
314
8.86k
            if !stability.is_unknown() {
315
1.26k
                let map = if import {
316
603
                    &mut interface_import_stability
317
                } else {
318
657
                    &mut interface_export_stability
319
                };
320
1.26k
                map.insert(name.clone(), stability.clone());
321
7.60k
            }
322
8.86k
            if let Some(contents) = &docs.contents {
323
0
                let map = if import {
324
0
                    &mut interface_import_docs
325
                } else {
326
0
                    &mut interface_export_docs
327
                };
328
0
                map.insert(name, contents.clone());
329
8.86k
            }
330
8.86k
        };
331
332
23.1k
        for ((key, item), import) in world
333
14.5k
            .imports
334
14.5k
            .iter()
335
14.5k
            .map(|p| (p, true))
336
14.5k
            .chain(world.exports.iter().map(|p| (p, false)))
337
        {
338
23.1k
            match key {
339
                // For all named imports with kebab-names extract their
340
                // docs/stability and insert it into one of our maps.
341
21.3k
                WorldKey::Name(name) => match item {
342
8.43k
                    WorldItem::Interface { id, .. } => {
343
8.43k
                        if resolve.interfaces[*id].name.is_some() {
344
7.01k
                            record_interface_metadata(key, item, import);
345
7.01k
                            continue;
346
1.42k
                        }
347
1.42k
                        let data = InterfaceMetadata::extract(resolve, *id);
348
1.42k
                        if data.is_empty() {
349
1.05k
                            continue;
350
370
                        }
351
370
                        let map = if import {
352
289
                            &mut interface_imports_or_exports
353
81
                        } else if !TRY_TO_EMIT_V0_BY_DEFAULT
354
0
                            || interface_imports_or_exports.contains_key(name)
355
                        {
356
81
                            &mut interface_exports
357
                        } else {
358
0
                            &mut interface_imports_or_exports
359
                        };
360
370
                        let prev = map.insert(name.to_string(), data);
361
370
                        assert!(prev.is_none());
362
                    }
363
7.90k
                    WorldItem::Type { id, .. } => {
364
7.90k
                        let data = TypeMetadata::extract(resolve, *id);
365
7.90k
                        if !data.is_empty() {
366
1.17k
                            types.insert(name.to_string(), data);
367
6.73k
                        }
368
                    }
369
4.98k
                    WorldItem::Function(f) => {
370
4.98k
                        let data = FunctionMetadata::extract(f);
371
4.98k
                        if data.is_empty() {
372
3.47k
                            continue;
373
1.51k
                        }
374
1.51k
                        let map = if import {
375
1.28k
                            &mut func_imports_or_exports
376
233
                        } else if !TRY_TO_EMIT_V0_BY_DEFAULT
377
0
                            || func_imports_or_exports.contains_key(name)
378
                        {
379
233
                            &mut func_exports
380
                        } else {
381
0
                            &mut func_imports_or_exports
382
                        };
383
1.51k
                        let prev = map.insert(name.to_string(), data);
384
1.51k
                        assert!(prev.is_none());
385
                    }
386
                },
387
388
                // For interface imports/exports extract the stability/docs and
389
                // record them if necessary.
390
1.84k
                WorldKey::Interface(_) => {
391
1.84k
                    record_interface_metadata(key, item, import);
392
1.84k
                }
393
            }
394
        }
395
396
14.5k
        Self {
397
14.5k
            docs: world.docs.contents.clone(),
398
14.5k
            stability: world.stability.clone(),
399
14.5k
            interface_imports_or_exports,
400
14.5k
            types,
401
14.5k
            func_imports_or_exports,
402
14.5k
            interface_exports,
403
14.5k
            func_exports,
404
14.5k
            interface_import_stability,
405
14.5k
            interface_export_stability,
406
14.5k
            interface_import_docs,
407
14.5k
            interface_export_docs,
408
14.5k
        }
409
14.5k
    }
410
411
1.25k
    fn inject(&self, resolve: &mut Resolve, id: WorldId) -> Result<()> {
412
        // Inject docs/stability for all kebab-named interfaces, both imports
413
        // and exports.
414
1.25k
        for ((name, data), only_export) in self
415
1.25k
            .interface_imports_or_exports
416
1.25k
            .iter()
417
1.25k
            .map(|p| (p, false))
418
1.25k
            .chain(self.interface_exports.iter().map(|p| (p, true)))
419
        {
420
176
            let key = WorldKey::Name(name.to_string());
421
176
            let world = &mut resolve.worlds[id];
422
423
176
            let item = if only_export {
424
38
                world.exports.get_mut(&key)
425
            } else {
426
138
                match world.imports.get_mut(&key) {
427
138
                    Some(item) => Some(item),
428
0
                    None => world.exports.get_mut(&key),
429
                }
430
            };
431
176
            let Some(WorldItem::Interface { id, stability, .. }) = item else {
432
0
                bail!("missing interface {name:?}");
433
            };
434
176
            *stability = data.stability.clone();
435
176
            let id = *id;
436
176
            data.inject(resolve, id)?;
437
        }
438
439
        // Process all types, which are always imported, for this world.
440
1.25k
        for (name, data) in &self.types {
441
594
            let key = WorldKey::Name(name.to_string());
442
594
            let Some(WorldItem::Type { id, .. }) = resolve.worlds[id].imports.get(&key) else {
443
0
                bail!("missing type {name:?}");
444
            };
445
594
            data.inject(resolve, *id)?;
446
        }
447
448
        // Build a map of `name_world_key` for interface imports/exports to the
449
        // actual key. This map is then consluted in the next loop.
450
1.25k
        let world = &resolve.worlds[id];
451
1.25k
        let stabilities = world
452
1.25k
            .imports
453
1.25k
            .iter()
454
3.21k
            .map(|i| (i, true))
455
1.31k
            .chain(world.exports.iter().map(|i| (i, false)))
456
4.52k
            .filter_map(|((key, item), import)| match item {
457
                WorldItem::Interface { .. } => {
458
1.74k
                    Some(((resolve.name_world_key(key), import), key.clone()))
459
                }
460
2.77k
                _ => None,
461
4.52k
            })
462
1.25k
            .collect::<IndexMap<_, _>>();
463
464
1.25k
        let world = &mut resolve.worlds[id];
465
466
        // Update the stability of an interface imports/exports that aren't
467
        // kebab-named.
468
1.25k
        for ((name, stability), import) in self
469
1.25k
            .interface_import_stability
470
1.25k
            .iter()
471
1.25k
            .map(|p| (p, true))
472
1.25k
            .chain(self.interface_export_stability.iter().map(|p| (p, false)))
473
        {
474
585
            let key = match stabilities.get(&(name.clone(), import)) {
475
585
                Some(key) => key.clone(),
476
0
                None => bail!("missing interface `{name}`"),
477
            };
478
585
            let item = if import {
479
278
                world.imports.get_mut(&key)
480
            } else {
481
307
                world.exports.get_mut(&key)
482
            };
483
585
            match item {
484
585
                Some(WorldItem::Interface { stability: s, .. }) => *s = stability.clone(),
485
0
                _ => bail!("item `{name}` wasn't an interface"),
486
            }
487
        }
488
489
        // Update the docs of interface imports/exports that aren't kebab-named,
490
        // reusing the same `stabilities` key map built above.
491
1.25k
        for ((name, docs), import) in self
492
1.25k
            .interface_import_docs
493
1.25k
            .iter()
494
1.25k
            .map(|p| (p, true))
495
1.25k
            .chain(self.interface_export_docs.iter().map(|p| (p, false)))
496
        {
497
0
            let key = match stabilities.get(&(name.clone(), import)) {
498
0
                Some(key) => key.clone(),
499
0
                None => bail!("missing interface `{name}`"),
500
            };
501
0
            let item = if import {
502
0
                world.imports.get_mut(&key)
503
            } else {
504
0
                world.exports.get_mut(&key)
505
            };
506
0
            match item {
507
0
                Some(WorldItem::Interface { docs: d, .. }) => {
508
0
                    d.contents = Some(docs.clone());
509
0
                }
510
0
                _ => bail!("item `{name}` wasn't an interface"),
511
            }
512
        }
513
514
        // Update the docs/stability of all functions imported/exported from
515
        // this world.
516
1.25k
        for ((name, data), only_export) in self
517
1.25k
            .func_imports_or_exports
518
1.25k
            .iter()
519
1.25k
            .map(|p| (p, false))
520
1.25k
            .chain(self.func_exports.iter().map(|p| (p, true)))
521
        {
522
750
            let key = WorldKey::Name(name.to_string());
523
750
            let item = if only_export {
524
109
                world.exports.get_mut(&key)
525
            } else {
526
641
                match world.imports.get_mut(&key) {
527
641
                    Some(item) => Some(item),
528
0
                    None => world.exports.get_mut(&key),
529
                }
530
            };
531
750
            match item {
532
750
                Some(WorldItem::Function(f)) => data.inject(f)?,
533
0
                _ => bail!("missing func {name:?}"),
534
            }
535
        }
536
1.25k
        if let Some(docs) = &self.docs {
537
0
            world.docs.contents = Some(docs.to_string());
538
1.25k
        }
539
1.25k
        world.stability = self.stability.clone();
540
1.25k
        Ok(())
541
1.25k
    }
542
543
14.5k
    fn is_empty(&self) -> bool {
544
14.5k
        self.docs.is_none()
545
14.5k
            && self.interface_imports_or_exports.is_empty()
546
14.3k
            && self.types.is_empty()
547
13.5k
            && self.func_imports_or_exports.is_empty()
548
12.6k
            && self.stability.is_unknown()
549
12.6k
            && self.interface_exports.is_empty()
550
12.5k
            && self.func_exports.is_empty()
551
12.4k
            && self.interface_import_stability.is_empty()
552
12.2k
            && self.interface_export_stability.is_empty()
553
11.9k
            && self.interface_import_docs.is_empty()
554
11.9k
            && self.interface_export_docs.is_empty()
555
14.5k
    }
556
557
    #[cfg(feature = "serde")]
558
0
    fn is_compatible_with_v0(&self) -> bool {
559
0
        self.stability.is_unknown()
560
0
            && self
561
0
                .interface_imports_or_exports
562
0
                .iter()
563
0
                .all(|(_, w)| w.is_compatible_with_v0())
564
0
            && self
565
0
                .func_imports_or_exports
566
0
                .iter()
567
0
                .all(|(_, w)| w.is_compatible_with_v0())
568
0
            && self.types.iter().all(|(_, w)| w.is_compatible_with_v0())
569
            // These maps weren't present in v0, so we're only compatible if
570
            // they're empty.
571
0
            && self.interface_exports.is_empty()
572
0
            && self.func_exports.is_empty()
573
0
            && self.interface_import_stability.is_empty()
574
0
            && self.interface_export_stability.is_empty()
575
0
            && self.interface_import_docs.is_empty()
576
0
            && self.interface_export_docs.is_empty()
577
0
    }
578
}
579
580
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
581
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
582
struct InterfaceMetadata {
583
    #[cfg_attr(
584
        feature = "serde",
585
        serde(default, skip_serializing_if = "Option::is_none")
586
    )]
587
    docs: Option<String>,
588
    #[cfg_attr(
589
        feature = "serde",
590
        serde(default, skip_serializing_if = "Stability::is_unknown")
591
    )]
592
    stability: Stability,
593
    #[cfg_attr(
594
        feature = "serde",
595
        serde(default, skip_serializing_if = "StringMap::is_empty")
596
    )]
597
    funcs: StringMap<FunctionMetadata>,
598
    #[cfg_attr(
599
        feature = "serde",
600
        serde(default, skip_serializing_if = "StringMap::is_empty")
601
    )]
602
    types: StringMap<TypeMetadata>,
603
}
604
605
impl InterfaceMetadata {
606
50.3k
    fn extract(resolve: &Resolve, id: InterfaceId) -> Self {
607
50.3k
        let interface = &resolve.interfaces[id];
608
609
50.3k
        let funcs = interface
610
50.3k
            .functions
611
50.3k
            .iter()
612
50.3k
            .map(|(name, func)| (name.to_string(), FunctionMetadata::extract(func)))
613
50.3k
            .filter(|(_, item)| !item.is_empty())
614
50.3k
            .collect();
615
50.3k
        let types = interface
616
50.3k
            .types
617
50.3k
            .iter()
618
50.3k
            .map(|(name, id)| (name.to_string(), TypeMetadata::extract(resolve, *id)))
619
50.3k
            .filter(|(_, item)| !item.is_empty())
620
50.3k
            .collect();
621
622
50.3k
        Self {
623
50.3k
            docs: interface.docs.contents.clone(),
624
50.3k
            stability: interface.stability.clone(),
625
50.3k
            funcs,
626
50.3k
            types,
627
50.3k
        }
628
50.3k
    }
629
630
176
    fn inject(&self, resolve: &mut Resolve, id: InterfaceId) -> Result<()> {
631
176
        for (name, data) in &self.types {
632
0
            let Some(&id) = resolve.interfaces[id].types.get(name) else {
633
0
                bail!("missing type {name:?}");
634
            };
635
0
            data.inject(resolve, id)?;
636
        }
637
176
        let interface = &mut resolve.interfaces[id];
638
176
        for (name, data) in &self.funcs {
639
0
            let Some(f) = interface.functions.get_mut(name) else {
640
0
                bail!("missing func {name:?}");
641
            };
642
0
            data.inject(f)?;
643
        }
644
176
        if let Some(docs) = &self.docs {
645
0
            interface.docs.contents = Some(docs.to_string());
646
176
        }
647
176
        interface.stability = self.stability.clone();
648
176
        Ok(())
649
176
    }
650
651
50.3k
    fn is_empty(&self) -> bool {
652
50.3k
        self.docs.is_none()
653
50.3k
            && self.funcs.is_empty()
654
50.3k
            && self.types.is_empty()
655
50.3k
            && self.stability.is_unknown()
656
50.3k
    }
657
658
    #[cfg(feature = "serde")]
659
0
    fn is_compatible_with_v0(&self) -> bool {
660
0
        self.stability.is_unknown()
661
0
            && self.funcs.iter().all(|(_, w)| w.is_compatible_with_v0())
662
0
            && self.types.iter().all(|(_, w)| w.is_compatible_with_v0())
663
0
    }
664
}
665
666
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
667
#[cfg_attr(feature = "serde", serde(untagged, deny_unknown_fields))]
668
enum FunctionMetadata {
669
    /// In the v0 format function metadata was only a string so this variant
670
    /// is preserved for the v0 format. In the future this can be removed
671
    /// entirely in favor of just the below struct variant.
672
    ///
673
    /// Note that this is an untagged enum so the name `JustDocs` is just for
674
    /// rust.
675
    JustDocs(Option<String>),
676
677
    /// In the v1+ format we're tracking at least docs but also the stability
678
    /// of functions.
679
    DocsAndStability {
680
        #[cfg_attr(
681
            feature = "serde",
682
            serde(default, skip_serializing_if = "Option::is_none")
683
        )]
684
        docs: Option<String>,
685
        #[cfg_attr(
686
            feature = "serde",
687
            serde(default, skip_serializing_if = "Stability::is_unknown")
688
        )]
689
        stability: Stability,
690
    },
691
}
692
693
impl FunctionMetadata {
694
23.5k
    fn extract(func: &Function) -> Self {
695
23.5k
        if TRY_TO_EMIT_V0_BY_DEFAULT && func.stability.is_unknown() {
696
0
            FunctionMetadata::JustDocs(func.docs.contents.clone())
697
        } else {
698
23.5k
            FunctionMetadata::DocsAndStability {
699
23.5k
                docs: func.docs.contents.clone(),
700
23.5k
                stability: func.stability.clone(),
701
23.5k
            }
702
        }
703
23.5k
    }
704
705
750
    fn inject(&self, func: &mut Function) -> Result<()> {
706
750
        match self {
707
0
            FunctionMetadata::JustDocs(docs) => {
708
0
                func.docs.contents = docs.clone();
709
0
            }
710
750
            FunctionMetadata::DocsAndStability { docs, stability } => {
711
750
                func.docs.contents = docs.clone();
712
750
                func.stability = stability.clone();
713
750
            }
714
        }
715
750
        Ok(())
716
750
    }
717
718
23.5k
    fn is_empty(&self) -> bool {
719
23.5k
        match self {
720
0
            FunctionMetadata::JustDocs(docs) => docs.is_none(),
721
23.5k
            FunctionMetadata::DocsAndStability { docs, stability } => {
722
23.5k
                docs.is_none() && stability.is_unknown()
723
            }
724
        }
725
23.5k
    }
726
727
    #[cfg(feature = "serde")]
728
0
    fn is_compatible_with_v0(&self) -> bool {
729
0
        match self {
730
0
            FunctionMetadata::JustDocs(_) => true,
731
0
            FunctionMetadata::DocsAndStability { .. } => false,
732
        }
733
0
    }
734
}
735
736
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
737
#[cfg_attr(feature = "serde", serde(deny_unknown_fields))]
738
struct TypeMetadata {
739
    #[cfg_attr(
740
        feature = "serde",
741
        serde(default, skip_serializing_if = "Option::is_none")
742
    )]
743
    docs: Option<String>,
744
    #[cfg_attr(
745
        feature = "serde",
746
        serde(default, skip_serializing_if = "Stability::is_unknown")
747
    )]
748
    stability: Stability,
749
    // record fields, variant cases, etc.
750
    #[cfg_attr(
751
        feature = "serde",
752
        serde(default, skip_serializing_if = "StringMap::is_empty")
753
    )]
754
    items: StringMap<String>,
755
}
756
757
impl TypeMetadata {
758
32.3k
    fn extract(resolve: &Resolve, id: TypeId) -> Self {
759
15.6k
        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
760
15.6k
            items
761
15.6k
                .iter()
762
69.9k
                .flat_map(|item| {
763
69.9k
                    let (name, docs) = f(item);
764
69.9k
                    Some((name.to_string(), docs.contents.clone()?))
765
69.9k
                })
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::Case, <wit_parser::metadata::TypeMetadata>::extract::{closure#2}>::{closure#0}
Line
Count
Source
762
10.4k
                .flat_map(|item| {
763
10.4k
                    let (name, docs) = f(item);
764
10.4k
                    Some((name.to_string(), docs.contents.clone()?))
765
10.4k
                })
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::Flag, <wit_parser::metadata::TypeMetadata>::extract::{closure#1}>::{closure#0}
Line
Count
Source
762
2.61k
                .flat_map(|item| {
763
2.61k
                    let (name, docs) = f(item);
764
2.61k
                    Some((name.to_string(), docs.contents.clone()?))
765
2.61k
                })
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::Field, <wit_parser::metadata::TypeMetadata>::extract::{closure#0}>::{closure#0}
Line
Count
Source
762
6.40k
                .flat_map(|item| {
763
6.40k
                    let (name, docs) = f(item);
764
6.40k
                    Some((name.to_string(), docs.contents.clone()?))
765
6.40k
                })
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::EnumCase, <wit_parser::metadata::TypeMetadata>::extract::{closure#3}>::{closure#0}
Line
Count
Source
762
50.4k
                .flat_map(|item| {
763
50.4k
                    let (name, docs) = f(item);
764
50.4k
                    Some((name.to_string(), docs.contents.clone()?))
765
50.4k
                })
766
15.6k
                .collect()
767
15.6k
        }
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::Case, <wit_parser::metadata::TypeMetadata>::extract::{closure#2}>
Line
Count
Source
759
2.96k
        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
760
2.96k
            items
761
2.96k
                .iter()
762
2.96k
                .flat_map(|item| {
763
                    let (name, docs) = f(item);
764
                    Some((name.to_string(), docs.contents.clone()?))
765
                })
766
2.96k
                .collect()
767
2.96k
        }
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::Flag, <wit_parser::metadata::TypeMetadata>::extract::{closure#1}>
Line
Count
Source
759
1.00k
        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
760
1.00k
            items
761
1.00k
                .iter()
762
1.00k
                .flat_map(|item| {
763
                    let (name, docs) = f(item);
764
                    Some((name.to_string(), docs.contents.clone()?))
765
                })
766
1.00k
                .collect()
767
1.00k
        }
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::Field, <wit_parser::metadata::TypeMetadata>::extract::{closure#0}>
Line
Count
Source
759
2.45k
        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
760
2.45k
            items
761
2.45k
                .iter()
762
2.45k
                .flat_map(|item| {
763
                    let (name, docs) = f(item);
764
                    Some((name.to_string(), docs.contents.clone()?))
765
                })
766
2.45k
                .collect()
767
2.45k
        }
<wit_parser::metadata::TypeMetadata>::extract::extract_items::<wit_parser::EnumCase, <wit_parser::metadata::TypeMetadata>::extract::{closure#3}>
Line
Count
Source
759
9.19k
        fn extract_items<T>(items: &[T], f: impl Fn(&T) -> (&String, &Docs)) -> StringMap<String> {
760
9.19k
            items
761
9.19k
                .iter()
762
9.19k
                .flat_map(|item| {
763
                    let (name, docs) = f(item);
764
                    Some((name.to_string(), docs.contents.clone()?))
765
                })
766
9.19k
                .collect()
767
9.19k
        }
768
32.3k
        let ty = &resolve.types[id];
769
32.3k
        let items = match &ty.kind {
770
2.45k
            TypeDefKind::Record(record) => {
771
6.40k
                extract_items(&record.fields, |item| (&item.name, &item.docs))
772
            }
773
1.00k
            TypeDefKind::Flags(flags) => {
774
2.61k
                extract_items(&flags.flags, |item| (&item.name, &item.docs))
775
            }
776
2.96k
            TypeDefKind::Variant(variant) => {
777
10.4k
                extract_items(&variant.cases, |item| (&item.name, &item.docs))
778
            }
779
9.19k
            TypeDefKind::Enum(enum_) => {
780
50.4k
                extract_items(&enum_.cases, |item| (&item.name, &item.docs))
781
            }
782
            // other types don't have inner items
783
16.7k
            _ => IndexMap::default(),
784
        };
785
786
32.3k
        Self {
787
32.3k
            docs: ty.docs.contents.clone(),
788
32.3k
            stability: ty.stability.clone(),
789
32.3k
            items,
790
32.3k
        }
791
32.3k
    }
792
793
594
    fn inject(&self, resolve: &mut Resolve, id: TypeId) -> Result<()> {
794
594
        let ty = &mut resolve.types[id];
795
594
        if !self.items.is_empty() {
796
0
            match &mut ty.kind {
797
0
                TypeDefKind::Record(record) => {
798
0
                    self.inject_items(&mut record.fields, |item| (&item.name, &mut item.docs))?
799
                }
800
0
                TypeDefKind::Flags(flags) => {
801
0
                    self.inject_items(&mut flags.flags, |item| (&item.name, &mut item.docs))?
802
                }
803
0
                TypeDefKind::Variant(variant) => {
804
0
                    self.inject_items(&mut variant.cases, |item| (&item.name, &mut item.docs))?
805
                }
806
0
                TypeDefKind::Enum(enum_) => {
807
0
                    self.inject_items(&mut enum_.cases, |item| (&item.name, &mut item.docs))?
808
                }
809
                _ => {
810
0
                    bail!("got 'items' for unexpected type {ty:?}");
811
                }
812
            }
813
594
        }
814
594
        if let Some(docs) = &self.docs {
815
0
            ty.docs.contents = Some(docs.to_string());
816
594
        }
817
594
        ty.stability = self.stability.clone();
818
594
        Ok(())
819
594
    }
820
821
0
    fn inject_items<T: core::fmt::Debug>(
822
0
        &self,
823
0
        items: &mut [T],
824
0
        f: impl Fn(&mut T) -> (&String, &mut Docs),
825
0
    ) -> Result<()> {
826
0
        let mut unused_docs = self.items.len();
827
0
        for item in items.iter_mut() {
828
0
            let (name, item_docs) = f(item);
829
0
            if let Some(docs) = self.items.get(name.as_str()) {
830
0
                item_docs.contents = Some(docs.to_string());
831
0
                unused_docs -= 1;
832
0
            }
833
        }
834
0
        if unused_docs > 0 {
835
0
            bail!(
836
                "not all 'items' match type items; {item_docs:?} vs {items:?}",
837
                item_docs = self.items
838
            );
839
0
        }
840
0
        Ok(())
841
0
    }
Unexecuted instantiation: <wit_parser::metadata::TypeMetadata>::inject_items::<wit_parser::Case, <wit_parser::metadata::TypeMetadata>::inject::{closure#2}>
Unexecuted instantiation: <wit_parser::metadata::TypeMetadata>::inject_items::<wit_parser::Flag, <wit_parser::metadata::TypeMetadata>::inject::{closure#1}>
Unexecuted instantiation: <wit_parser::metadata::TypeMetadata>::inject_items::<wit_parser::Field, <wit_parser::metadata::TypeMetadata>::inject::{closure#0}>
Unexecuted instantiation: <wit_parser::metadata::TypeMetadata>::inject_items::<wit_parser::EnumCase, <wit_parser::metadata::TypeMetadata>::inject::{closure#3}>
842
843
32.3k
    fn is_empty(&self) -> bool {
844
32.3k
        self.docs.is_none() && self.items.is_empty() && self.stability.is_unknown()
845
32.3k
    }
846
847
    #[cfg(feature = "serde")]
848
0
    fn is_compatible_with_v0(&self) -> bool {
849
0
        self.stability.is_unknown()
850
0
    }
851
}