1use core::mem::replace;
2
3use anyhow::{Context, Result};
4use relative_path::RelativePathBuf;
5use serde::Serialize;
6
7use crate::alloc::borrow::Cow;
8use crate::alloc::fmt::TryWrite;
9use crate::alloc::prelude::*;
10use crate::doc::artifacts::TestKind;
11use crate::doc::context::{Assoc, AssocFnKind, Kind, Meta};
12use crate::item::ComponentRef;
13use crate::{Hash, Item};
14
15use super::{Builder, Ctxt, IndexEntry, IndexKind, ItemKind};
16
17#[derive(Serialize)]
18pub(super) struct Protocol<'a> {
19 name: &'a str,
20 field: Option<&'a str>,
21 repr: Option<String>,
22 return_type: Option<String>,
23 doc: Option<String>,
24 deprecated: Option<&'a str>,
25}
26
27#[derive(Serialize)]
28pub(super) struct Method<'a> {
29 is_async: bool,
30 deprecated: Option<&'a str>,
31 name: &'a str,
32 args: String,
33 parameters: Option<String>,
34 return_type: Option<String>,
35 line_doc: Option<String>,
36 doc: Option<String>,
37}
38
39#[derive(Serialize)]
40pub(super) struct Variant<'a> {
41 name: &'a str,
42 line_doc: Option<String>,
43 doc: Option<String>,
44}
45
46#[derive(Default, Serialize)]
47pub(super) struct Trait<'a> {
48 #[serde(serialize_with = "super::serialize_item")]
49 pub(super) item: &'a Item,
50 pub(super) hash: Hash,
51 pub(super) name: &'a str,
52 pub(super) url: RelativePathBuf,
53 pub(super) methods: Vec<Method<'a>>,
54 pub(super) protocols: Vec<Protocol<'a>>,
55}
56
57pub(super) fn build_assoc_fns<'m>(
58 cx: &mut Ctxt<'_, 'm>,
59 meta: Meta<'m>,
60) -> Result<(
61 Vec<Protocol<'m>>,
62 Vec<Method<'m>>,
63 Vec<Variant<'m>>,
64 Vec<IndexEntry<'m>>,
65 Vec<Trait<'m>>,
66)> {
67 let (variants, protocols, methods) = associated_for_hash(cx, meta.hash, meta, true)?;
68
69 let mut index = Vec::new();
70
71 if let Some(name) = cx.state.path.file_name() {
72 index.try_reserve(methods.len())?;
73
74 for m in &methods {
75 index.try_push(IndexEntry {
76 path: cx
77 .state
78 .path
79 .with_file_name(format!("{name}#method.{}", m.name)),
80 item: Cow::Owned(meta.item.join([m.name])?),
81 kind: IndexKind::Method,
82 doc: m.line_doc.try_clone()?,
83 })?;
84 }
85
86 for m in &variants {
87 index.try_push(IndexEntry {
88 path: cx
89 .state
90 .path
91 .with_file_name(format!("{name}#variant.{}", m.name)),
92 item: Cow::Owned(meta.item.join([m.name])?),
93 kind: IndexKind::Variant,
94 doc: m.line_doc.try_clone()?,
95 })?;
96 }
97 }
98
99 let mut traits = Vec::new();
100
101 'outer: for hash in cx.context.traits(meta.hash) {
102 let item = 'item: {
103 for meta in cx.context.meta_by_hash(hash)? {
104 match meta.kind {
105 Kind::Trait => break 'item meta.item,
106 _ => continue,
107 }
108 }
109
110 continue 'outer;
111 };
112
113 let (_, protocols, methods) = associated_for_hash(cx, hash, meta, false)?;
114
115 let name = item
116 .last()
117 .and_then(|c| c.as_str())
118 .context("Missing trait name")?;
119
120 let url = cx.item_path(item, ItemKind::Trait)?;
121
122 traits.try_push(Trait {
123 item,
124 hash,
125 name,
126 url,
127 methods,
128 protocols,
129 })?;
130 }
131
132 Ok((protocols, methods, variants, index, traits))
133}
134
135fn associated_for_hash<'m>(
136 cx: &mut Ctxt<'_, 'm>,
137 hash: Hash,
138 meta: Meta<'m>,
139 capture_tests: bool,
140) -> Result<(Vec<Variant<'m>>, Vec<Protocol<'m>>, Vec<Method<'m>>)> {
141 let mut variants = Vec::new();
142 let mut protocols = Vec::new();
143 let mut methods = Vec::new();
144
145 for hash in cx.context.associated(hash) {
146 for assoc in cx.context.associated_meta(hash) {
147 match assoc {
148 Assoc::Variant(variant) => {
149 let line_doc =
150 cx.render_line_docs(meta, variant.docs.get(..1).unwrap_or_default())?;
151
152 let doc = cx.render_docs(meta, variant.docs, capture_tests)?;
153
154 variants.try_push(Variant {
155 name: variant.name,
156 line_doc,
157 doc,
158 })?;
159 }
160 Assoc::Fn(assoc) => {
161 let value;
162
163 let (protocol, value, field) = match assoc.kind {
164 AssocFnKind::Protocol(protocol) => (protocol, "value", None),
165 AssocFnKind::FieldFn(protocol, field) => {
166 value = format!("value.{field}");
167 (protocol, value.as_str(), Some(field))
168 }
169 AssocFnKind::IndexFn(protocol, index) => {
170 value = format!("value.{index}");
171 (protocol, value.as_str(), None)
172 }
173 AssocFnKind::Method(item, name, sig) => {
174 if assoc.trait_hash.is_some() {
176 continue;
177 }
178
179 let line_doc =
180 cx.render_line_docs(meta, assoc.docs.get(..1).unwrap_or_default())?;
181
182 let old = replace(&mut cx.state.item, item);
183 let doc = cx.render_docs(meta, assoc.docs, capture_tests)?;
184 cx.state.item = old;
185
186 let parameters = if !assoc.parameter_types.is_empty() {
187 let mut s = String::new();
188 let mut it = assoc.parameter_types.iter().peekable();
189
190 while let Some(hash) = it.next() {
191 cx.write_link(&mut s, *hash, None, &[])?;
192
193 if it.peek().is_some() {
194 write!(s, ", ")?;
195 }
196 }
197
198 Some(s)
199 } else {
200 None
201 };
202
203 let method = Method {
204 is_async: assoc.is_async,
205 deprecated: assoc.deprecated,
206 name,
207 args: cx.args_to_string(sig, assoc.arguments)?,
208 parameters,
209 return_type: cx.return_type(assoc.return_type)?,
210 line_doc,
211 doc,
212 };
213
214 methods.try_push(method)?;
215 continue;
216 }
217 };
218
219 let kind = replace(&mut cx.state.kind, TestKind::Protocol(protocol));
220
221 let doc = if assoc.docs.is_empty() {
222 cx.render_docs(meta, protocol.doc, false)?
223 } else {
224 cx.render_docs(meta, assoc.docs, capture_tests)?
225 };
226
227 cx.state.kind = kind;
228
229 let repr = if let Some(repr) = protocol.repr {
230 Some(cx.render_code([repr.replace("$value", value.as_ref())])?)
231 } else {
232 None
233 };
234
235 let protocol = Protocol {
236 name: protocol.name,
237 field,
238 repr,
239 return_type: cx.return_type(assoc.return_type)?,
240 doc,
241 deprecated: assoc.deprecated,
242 };
243
244 protocols.try_push(protocol)?;
245 }
246 }
247 }
248 }
249 Ok((variants, protocols, methods))
250}
251
252#[derive(Serialize)]
253struct Params<'a> {
254 #[serde(flatten)]
255 shared: super::Shared<'a>,
256 what: &'a str,
257 what_class: &'a str,
258 module: String,
259 #[serde(serialize_with = "super::serialize_component_ref")]
260 name: ComponentRef<'a>,
261 #[serde(serialize_with = "super::serialize_item")]
262 item: &'a Item,
263 methods: Vec<Method<'a>>,
264 protocols: Vec<Protocol<'a>>,
265 traits: Vec<Trait<'a>>,
266 doc: Option<String>,
267}
268
269#[tracing::instrument(skip_all)]
271pub(crate) fn build<'m>(
272 cx: &mut Ctxt<'_, 'm>,
273 what: &'static str,
274 what_class: &'static str,
275 meta: Meta<'m>,
276) -> Result<(Builder<'m>, Vec<IndexEntry<'m>>)> {
277 let module = cx.module_path_html(meta, false)?;
278
279 let (protocols, methods, _, index, traits) = build_assoc_fns(cx, meta)?;
280 let name = meta.item.last().context("Missing module name")?;
281
282 let doc = cx.render_docs(meta, meta.docs, true)?;
283
284 let builder = Builder::new(cx, move |cx| {
285 cx.type_template.render(&Params {
286 shared: cx.shared()?,
287 what,
288 what_class,
289 module,
290 name,
291 item: meta.item,
292 methods,
293 protocols,
294 traits,
295 doc,
296 })
297 })?;
298
299 Ok((builder, index))
300}