/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 | | } |