1use std::collections::BTreeMap;
20
21use crate::ir::{Api, Module, TypeRef};
22
23use crate::model::Ty;
24use crate::pkg::Identity;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum TypeKind {
29 Record,
31 Enum,
33 RichEnum,
35 Interface,
37 CallbackInterface,
39}
40
41#[derive(Debug, Clone, PartialEq, Eq)]
43pub struct TypeDecl {
44 pub module_path: String,
46 pub kind: TypeKind,
48}
49
50#[derive(Debug, Clone, PartialEq)]
63pub struct ResolvedApi {
64 api: Api,
65 types: BTreeMap<String, TypeDecl>,
66 identity: Identity,
67}
68
69impl ResolvedApi {
70 #[must_use]
72 pub fn with_identity(mut self, identity: Identity) -> Self {
73 self.identity = identity;
74 self
75 }
76
77 pub fn identity(&self) -> &Identity {
80 &self.identity
81 }
82
83 #[doc(hidden)]
92 pub fn assume_valid(api: Api) -> Self {
93 let mut types = BTreeMap::new();
94 for module in &api.modules {
95 index_module(module, "", &mut types);
96 }
97 Self {
98 api,
99 types,
100 identity: Identity::default(),
101 }
102 }
103
104 pub fn api(&self) -> &Api {
106 &self.api
107 }
108
109 pub fn declaration(&self, name: &str) -> Option<&TypeDecl> {
111 self.types.get(bare(name))
112 }
113
114 pub fn unresolved(&self) -> Vec<String> {
118 let mut out = std::collections::BTreeSet::new();
119 let mut visit = |ty: &TypeRef| {
120 ty.walk(&mut |t| {
121 if let TypeRef::Named(n) = t {
122 if self.declaration(n).is_none() {
123 out.insert(n.clone());
124 }
125 }
126 });
127 };
128 for_each_type_ref(&self.api.modules, &mut visit);
129 out.into_iter().collect()
130 }
131
132 pub fn qualified_name(&self, name: &str) -> String {
135 match self.declaration(name) {
136 Some(decl) => format!("{}.{}", decl.module_path, bare(name)),
137 None => name.to_string(),
138 }
139 }
140
141 pub fn resolve(&self, ty: &TypeRef) -> Ty {
145 match ty {
146 TypeRef::I8 => Ty::I8,
147 TypeRef::I16 => Ty::I16,
148 TypeRef::I32 => Ty::I32,
149 TypeRef::I64 => Ty::I64,
150 TypeRef::U8 => Ty::U8,
151 TypeRef::U16 => Ty::U16,
152 TypeRef::U32 => Ty::U32,
153 TypeRef::U64 => Ty::U64,
154 TypeRef::F32 => Ty::F32,
155 TypeRef::F64 => Ty::F64,
156 TypeRef::Bool => Ty::Bool,
157 TypeRef::StringUtf8 => Ty::StringUtf8,
158 TypeRef::Bytes => Ty::Bytes,
159 TypeRef::Named(name) => {
160 let qualified = self.qualified_name(name);
161 match self.declaration(name).map(|d| d.kind) {
162 Some(TypeKind::Enum) => Ty::Enum(qualified),
163 Some(TypeKind::RichEnum) => Ty::RichEnum(qualified),
164 Some(TypeKind::Interface) => Ty::Interface(qualified),
165 Some(TypeKind::CallbackInterface) => Ty::CallbackInterface(qualified),
166 Some(TypeKind::Record) | None => Ty::Record(qualified),
167 }
168 }
169 TypeRef::Optional(inner) => Ty::Optional(Box::new(self.resolve(inner))),
170 TypeRef::List(inner) => Ty::List(Box::new(self.resolve(inner))),
171 TypeRef::Map(k, v) => Ty::Map(Box::new(self.resolve(k)), Box::new(self.resolve(v))),
172 TypeRef::Iterator(inner) => Ty::Iterator(Box::new(self.resolve(inner))),
173 }
174 }
175}
176
177pub(crate) fn for_each_type_ref(modules: &[Module], f: &mut dyn FnMut(&TypeRef)) {
179 fn fns(fs: &[crate::ir::Function], f: &mut dyn FnMut(&TypeRef)) {
180 for func in fs {
181 for p in &func.params {
182 f(&p.ty);
183 }
184 if let Some(r) = &func.returns {
185 f(r);
186 }
187 }
188 }
189 for m in modules {
190 fns(&m.functions, f);
191 for i in &m.interfaces {
192 fns(&i.constructors, f);
193 fns(&i.methods, f);
194 fns(&i.statics, f);
195 }
196 for c in &m.callback_interfaces {
197 fns(&c.methods, f);
198 }
199 for s in &m.structs {
200 for field in &s.fields {
201 f(&field.ty);
202 }
203 }
204 for e in &m.enums {
205 for v in &e.variants {
206 for field in &v.fields {
207 f(&field.ty);
208 }
209 }
210 }
211 if let Some(d) = &m.errors {
212 for c in &d.codes {
213 for field in &c.fields {
214 f(&field.ty);
215 }
216 }
217 }
218 for_each_type_ref(&m.modules, f);
219 }
220}
221
222fn bare(name: &str) -> &str {
223 name.rsplit('.').next().unwrap_or(name)
224}
225
226fn index_module(module: &Module, parent: &str, out: &mut BTreeMap<String, TypeDecl>) {
227 let path = if parent.is_empty() {
228 module.name.clone()
229 } else {
230 format!("{parent}.{}", module.name)
231 };
232 let mut add = |name: &str, kind: TypeKind| {
233 out.entry(name.to_string()).or_insert(TypeDecl {
234 module_path: path.clone(),
235 kind,
236 });
237 };
238 for s in &module.structs {
239 add(&s.name, TypeKind::Record);
240 }
241 for e in &module.enums {
242 let kind = if e.is_rich() {
243 TypeKind::RichEnum
244 } else {
245 TypeKind::Enum
246 };
247 add(&e.name, kind);
248 }
249 for i in &module.interfaces {
250 add(&i.name, TypeKind::Interface);
251 }
252 for c in &module.callback_interfaces {
253 add(&c.name, TypeKind::CallbackInterface);
254 }
255 for child in &module.modules {
256 index_module(child, &path, out);
257 }
258}
259
260impl std::ops::Deref for ResolvedApi {
261 type Target = Api;
262
263 fn deref(&self) -> &Api {
264 &self.api
265 }
266}
267
268impl AsRef<Api> for ResolvedApi {
269 fn as_ref(&self) -> &Api {
270 &self.api
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277 use crate::ir::{
278 CallbackInterfaceDef, EnumDef, EnumVariant, Function, InterfaceDef, StructDef, StructField,
279 CURRENT_SCHEMA_VERSION,
280 };
281
282 fn module(name: &str) -> Module {
283 Module {
284 name: name.into(),
285 doc: None,
286 functions: vec![],
287 interfaces: vec![],
288 callback_interfaces: vec![],
289 structs: vec![],
290 enums: vec![],
291 errors: None,
292 modules: vec![],
293 }
294 }
295
296 fn api() -> ResolvedApi {
297 let shared = Module {
298 enums: vec![
299 EnumDef {
300 name: "Status".into(),
301 doc: None,
302 deprecated: None,
303 variants: vec![EnumVariant {
304 name: "Ok".into(),
305 value: 0,
306 doc: None,
307 fields: vec![],
308 }],
309 },
310 EnumDef {
311 name: "Shape".into(),
312 doc: None,
313 deprecated: None,
314 variants: vec![EnumVariant {
315 name: "Circle".into(),
316 value: 0,
317 doc: None,
318 fields: vec![StructField {
319 name: "r".into(),
320 ty: TypeRef::F64,
321 doc: None,
322 }],
323 }],
324 },
325 ],
326 structs: vec![StructDef {
327 name: "Point".into(),
328 doc: None,
329 deprecated: None,
330 fields: vec![],
331 }],
332 modules: vec![Module {
333 interfaces: vec![InterfaceDef {
334 name: "Store".into(),
335 doc: None,
336 deprecated: None,
337 constructors: vec![],
338 methods: vec![],
339 statics: vec![],
340 }],
341 callback_interfaces: vec![CallbackInterfaceDef {
342 name: "Watcher".into(),
343 doc: None,
344 deprecated: None,
345 methods: vec![Function {
346 name: "on_change".into(),
347 params: vec![],
348 returns: None,
349 doc: None,
350 throws: false,
351 r#async: false,
352 cancellable: false,
353 deprecated: None,
354 }],
355 }],
356 ..module("inner")
357 }],
358 ..module("shared")
359 };
360 ResolvedApi::assume_valid(Api {
361 version: CURRENT_SCHEMA_VERSION.into(),
362 modules: vec![shared, module("orders")],
363 })
364 }
365
366 #[test]
367 fn kinds_and_qualification() {
368 let api = api();
369 assert_eq!(
370 api.resolve(&TypeRef::Named("Status".into())),
371 Ty::Enum("shared.Status".into())
372 );
373 assert_eq!(
374 api.resolve(&TypeRef::Named("Status".into())),
375 Ty::Enum("shared.Status".into())
376 );
377 assert_eq!(
378 api.resolve(&TypeRef::Named("Shape".into())),
379 Ty::RichEnum("shared.Shape".into())
380 );
381 assert_eq!(
382 api.resolve(&TypeRef::Named("Store".into())),
383 Ty::Interface("shared.inner.Store".into())
384 );
385 assert_eq!(
386 api.resolve(&TypeRef::Named("Store".into())),
387 Ty::Interface("shared.inner.Store".into())
388 );
389 assert_eq!(
391 api.resolve(&TypeRef::Named("shared.Point".into())),
392 Ty::Record("shared.Point".into())
393 );
394 assert_eq!(
395 api.resolve(&TypeRef::Named("Watcher".into())),
396 Ty::CallbackInterface("shared.inner.Watcher".into())
397 );
398 assert_eq!(
399 api.declaration("Watcher").unwrap().kind,
400 TypeKind::CallbackInterface
401 );
402 }
403
404 #[test]
405 fn composites_resolve_recursively() {
406 let api = api();
407 let ty = TypeRef::List(Box::new(TypeRef::Map(
408 Box::new(TypeRef::StringUtf8),
409 Box::new(TypeRef::Optional(Box::new(TypeRef::Named("Point".into())))),
410 )));
411 assert_eq!(
412 api.resolve(&ty),
413 Ty::List(Box::new(Ty::Map(
414 Box::new(Ty::StringUtf8),
415 Box::new(Ty::Optional(Box::new(Ty::Record("shared.Point".into()))))
416 )))
417 );
418 }
419
420 #[test]
421 fn unknown_names_fall_back_to_records() {
422 let api = api();
423 assert_eq!(
424 api.resolve(&TypeRef::Named("Elsewhere".into())),
425 Ty::Record("Elsewhere".into())
426 );
427 assert!(api.unresolved().is_empty());
428 assert!(api.declaration("Elsewhere").is_none());
429 assert_eq!(api.declaration("Point").unwrap().kind, TypeKind::Record);
430 }
431}