golem_wasm_ast/analysis/
mod.rs

1// Copyright 2024-2025 Golem Cloud
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod model;
16pub use model::*;
17
18/// Protobuf representation of analysis results
19#[cfg(feature = "protobuf")]
20pub mod protobuf;
21
22/// Wave format support for types.
23///
24/// This module is optional and can be enabled with the `metadata` feature flag. It is enabled by default.
25#[cfg(feature = "wave")]
26pub mod wave;
27#[cfg(feature = "wit-parser")]
28pub mod wit_parser;
29
30use crate::component::*;
31use crate::core::Mem;
32use crate::AstCustomization;
33use mappable_rc::Mrc;
34use std::cell::RefCell;
35use std::collections::HashMap;
36use std::fmt::Debug;
37use std::rc::Rc;
38
39pub type AnalysisResult<A> = Result<A, AnalysisFailure>;
40
41#[derive(Debug, Clone)]
42struct ComponentStackItem<Ast: AstCustomization + 'static> {
43    component: Mrc<Component<Ast>>,
44    component_idx: Option<ComponentIdx>,
45}
46
47type ResourceIdMap = HashMap<(Vec<ComponentIdx>, ComponentTypeIdx), AnalysedResourceId>;
48
49#[derive(Debug, Clone)]
50pub struct AnalysisContext<Ast: AstCustomization + 'static> {
51    component_stack: Vec<ComponentStackItem<Ast>>,
52    warnings: Rc<RefCell<Vec<AnalysisWarning>>>,
53    resource_ids: Rc<RefCell<ResourceIdMap>>,
54}
55
56impl<Ast: AstCustomization + 'static> AnalysisContext<Ast> {
57    pub fn new(component: Component<Ast>) -> AnalysisContext<Ast> {
58        AnalysisContext::from_rc(Mrc::new(component))
59    }
60
61    /// Initializes an analyzer for a given component
62    pub fn from_rc(component: Mrc<Component<Ast>>) -> AnalysisContext<Ast> {
63        AnalysisContext {
64            component_stack: vec![ComponentStackItem {
65                component,
66                component_idx: None,
67            }],
68            warnings: Rc::new(RefCell::new(Vec::new())),
69            resource_ids: Rc::new(RefCell::new(HashMap::new())),
70        }
71    }
72
73    /// Get all top-level exports from the component with all the type information gathered from
74    /// the component AST.
75    pub fn get_top_level_exports(&self) -> AnalysisResult<Vec<AnalysedExport>> {
76        let component = self.get_component();
77        let mut result = Vec::new();
78        for export in component.exports() {
79            match export.kind {
80                ComponentExternalKind::Func => {
81                    let export = self.analyse_func_export(export.name.as_string(), export.idx)?;
82                    result.push(AnalysedExport::Function(export));
83                }
84                ComponentExternalKind::Instance => {
85                    let instance =
86                        self.analyse_instance_export(export.name.as_string(), export.idx)?;
87                    result.push(AnalysedExport::Instance(instance));
88                }
89                _ => self.warning(AnalysisWarning::UnsupportedExport(
90                    UnsupportedExportWarning {
91                        kind: export.kind.clone(),
92                        name: export.name.as_string(),
93                    },
94                )),
95            }
96        }
97
98        Ok(result)
99    }
100
101    /// Gets all the memories (not just the exported ones) from all modules within the WASM component
102    pub fn get_all_memories(&self) -> AnalysisResult<Vec<Mem>> {
103        let mut result = Vec::new();
104
105        let mut component_stack = vec![self.get_component()];
106        while let Some(component) = component_stack.pop() {
107            for module in component.modules() {
108                for mem in module.mems() {
109                    result.push((*mem).clone());
110                }
111            }
112            for inner_component in component.components() {
113                component_stack.push(inner_component.clone());
114            }
115        }
116        Ok(result)
117    }
118
119    pub fn warnings(&self) -> Vec<AnalysisWarning> {
120        self.warnings.borrow().clone()
121    }
122
123    fn warning(&self, warning: AnalysisWarning) {
124        self.warnings.borrow_mut().push(warning);
125    }
126
127    fn get_resource_id(&self, type_idx: &ComponentTypeIdx) -> AnalysedResourceId {
128        let new_unique_id = self.resource_ids.borrow().len() as u64;
129        let mut resource_ids = self.resource_ids.borrow_mut();
130        let path = self
131            .component_stack
132            .iter()
133            .filter_map(|item| item.component_idx)
134            .collect();
135        let key = (path, *type_idx);
136        resource_ids
137            .entry(key)
138            .or_insert_with(|| {
139                AnalysedResourceId(new_unique_id) // We don't to associate all IDs in each component, so this simple method can always generate a unique one
140            })
141            .clone()
142    }
143
144    fn analyse_func_export(
145        &self,
146        name: String,
147        idx: ComponentFuncIdx,
148    ) -> AnalysisResult<AnalysedFunction> {
149        let (function_section, next_ctx) = self
150            .get_final_referenced(format!("component function {idx}"), |component| {
151                component.get_component_func(idx)
152            })?;
153        let (func_type_section, next_ctx) = match &*function_section {
154            ComponentSection::Canon(Canon::Lift { function_type, .. }) => next_ctx
155                .get_final_referenced(
156                    format!("component function type {function_type}"),
157                    |component| component.get_component_type(*function_type),
158                ),
159            ComponentSection::Import(ComponentImport {
160                desc: ComponentTypeRef::Func(func_type_idx),
161                ..
162            }) => next_ctx.get_final_referenced(
163                format!("component function type {func_type_idx}"),
164                |component| component.get_component_type(*func_type_idx),
165            ),
166            ComponentSection::Import(ComponentImport { desc, .. }) => Err(AnalysisFailure::failed(
167                format!("Expected function import, but got {:?} instead", desc),
168            )),
169            _ => Err(AnalysisFailure::failed(format!(
170                "Expected canonical lift function or function import, but got {} instead",
171                function_section.type_name()
172            ))),
173        }?;
174        match &*func_type_section {
175            ComponentSection::Type(ComponentType::Func(func_type)) => {
176                next_ctx.analyse_component_func_type(name, func_type)
177            }
178            _ => Err(AnalysisFailure::failed(format!(
179                "Expected function type, but got {} instead",
180                func_type_section.type_name()
181            ))),
182        }
183    }
184
185    fn analyse_component_func_type(
186        &self,
187        name: String,
188        func_type: &ComponentFuncType,
189    ) -> AnalysisResult<AnalysedFunction> {
190        let mut params: Vec<AnalysedFunctionParameter> = Vec::new();
191        for (param_name, param_type) in &func_type.params {
192            params.push(AnalysedFunctionParameter {
193                name: param_name.clone(),
194                typ: self.analyse_component_val_type(param_type)?,
195            })
196        }
197
198        let mut results: Vec<AnalysedFunctionResult> = Vec::new();
199        if let Some(tpe) = &func_type.result {
200            results.push(AnalysedFunctionResult {
201                name: None,
202                typ: self.analyse_component_val_type(tpe)?,
203            });
204        }
205
206        Ok(AnalysedFunction {
207            name,
208            parameters: params,
209            results,
210        })
211    }
212
213    fn analyse_component_type_idx(
214        &self,
215        component_type_idx: &ComponentTypeIdx,
216        analysed_resource_mode: Option<AnalysedResourceMode>,
217    ) -> AnalysisResult<AnalysedType> {
218        let (component_type_section, next_ctx) = self.get_final_referenced(
219            format!("component type {component_type_idx}"),
220            |component| component.get_component_type(*component_type_idx),
221        )?;
222        match &*component_type_section {
223            ComponentSection::Type(ComponentType::Defined(component_defined_type)) => {
224                next_ctx.analyse_component_defined_type(component_defined_type)
225            }
226            ComponentSection::Type(ComponentType::Func(_)) => Err(AnalysisFailure::failed(
227                "Passing functions in exported functions is not supported",
228            )),
229            ComponentSection::Type(ComponentType::Component(_)) => Err(AnalysisFailure::failed(
230                "Passing components in exported functions is not supported",
231            )),
232            ComponentSection::Type(ComponentType::Instance(_)) => Err(AnalysisFailure::failed(
233                "Passing instances in exported functions is not supported",
234            )),
235            ComponentSection::Type(ComponentType::Resource { .. }) => Err(AnalysisFailure::failed(
236                "Passing resources in exported functions is not supported",
237            )),
238            ComponentSection::Import(ComponentImport { desc, .. }) => match desc {
239                ComponentTypeRef::Type(TypeBounds::Eq(component_type_idx)) => {
240                    self.analyse_component_type_idx(component_type_idx, analysed_resource_mode)
241                }
242                ComponentTypeRef::Type(TypeBounds::SubResource) => {
243                    match analysed_resource_mode {
244                        Some(resource_mode) => {
245                            let id = next_ctx.get_resource_id(component_type_idx);
246                            Ok(AnalysedType::Handle(TypeHandle {
247                                resource_id: id,
248                                mode: resource_mode,
249                            }))
250                        }
251                        None => Err(AnalysisFailure::failed("Reached a sub-resource type bound without a surrounding borrowed/owned resource type")),
252                    }
253                }
254                _ => Err(AnalysisFailure::failed(format!(
255                    "Imports {desc:?} is not supported as a defined type"
256                ))),
257            },
258            _ => Err(AnalysisFailure::failed(format!(
259                "Expected component type, but got {} instead",
260                component_type_section.type_name()
261            ))),
262        }
263    }
264
265    fn analyse_component_val_type(&self, tpe: &ComponentValType) -> AnalysisResult<AnalysedType> {
266        match tpe {
267            ComponentValType::Primitive(primitive_value_type) => Ok(primitive_value_type.into()),
268            ComponentValType::Defined(component_type_idx) => {
269                self.analyse_component_type_idx(component_type_idx, None)
270            }
271        }
272    }
273
274    fn analyse_component_defined_type(
275        &self,
276        defined_type: &ComponentDefinedType,
277    ) -> AnalysisResult<AnalysedType> {
278        match defined_type {
279            ComponentDefinedType::Primitive { typ } => Ok(typ.into()),
280            ComponentDefinedType::Record { fields } => {
281                let mut result = Vec::new();
282                for (name, typ) in fields {
283                    result.push(NameTypePair {
284                        name: name.clone(),
285                        typ: self.analyse_component_val_type(typ)?,
286                    });
287                }
288                Ok(AnalysedType::Record(TypeRecord { fields: result }))
289            }
290            ComponentDefinedType::Variant { cases } => {
291                let mut result = Vec::new();
292                for case in cases {
293                    result.push(NameOptionTypePair {
294                        name: case.name.clone(),
295                        typ: case
296                            .typ
297                            .as_ref()
298                            .map(|t| self.analyse_component_val_type(t))
299                            .transpose()?,
300                    });
301                }
302                Ok(AnalysedType::Variant(TypeVariant { cases: result }))
303            }
304            ComponentDefinedType::List { elem } => Ok(AnalysedType::List(TypeList {
305                inner: Box::new(self.analyse_component_val_type(elem)?),
306            })),
307            ComponentDefinedType::Tuple { elems } => {
308                let mut result = Vec::new();
309                for elem in elems {
310                    result.push(self.analyse_component_val_type(elem)?);
311                }
312                Ok(AnalysedType::Tuple(TypeTuple { items: result }))
313            }
314            ComponentDefinedType::Flags { names } => Ok(AnalysedType::Flags(TypeFlags {
315                names: names.clone(),
316            })),
317            ComponentDefinedType::Enum { names } => Ok(AnalysedType::Enum(TypeEnum {
318                cases: names.clone(),
319            })),
320            ComponentDefinedType::Option { typ } => Ok(AnalysedType::Option(TypeOption {
321                inner: Box::new(self.analyse_component_val_type(typ)?),
322            })),
323            ComponentDefinedType::Result { ok, err } => Ok(AnalysedType::Result(TypeResult {
324                ok: ok
325                    .as_ref()
326                    .map(|t| self.analyse_component_val_type(t).map(Box::new))
327                    .transpose()?,
328                err: err
329                    .as_ref()
330                    .map(|t| self.analyse_component_val_type(t).map(Box::new))
331                    .transpose()?,
332            })),
333            ComponentDefinedType::Owned { type_idx } => {
334                self.analyse_component_type_idx(type_idx, Some(AnalysedResourceMode::Owned))
335            }
336            ComponentDefinedType::Borrowed { type_idx } => {
337                self.analyse_component_type_idx(type_idx, Some(AnalysedResourceMode::Borrowed))
338            }
339            ComponentDefinedType::Future { .. } => Err(AnalysisFailure::failed(
340                "Future types are not supported yet",
341            )),
342            ComponentDefinedType::Stream { .. } => Err(AnalysisFailure::failed(
343                "Stream types are not supported yet",
344            )),
345        }
346    }
347
348    fn analyse_instance_export(
349        &self,
350        name: String,
351        idx: InstanceIdx,
352    ) -> AnalysisResult<AnalysedInstance> {
353        let (instance_section, next_ctx) = self
354            .get_final_referenced(format!("instance {idx}"), |component| {
355                component.get_instance_wrapped(idx)
356            })?;
357        match &*instance_section {
358            ComponentSection::Instance(instance) => match instance {
359                ComponentInstance::Instantiate { component_idx, .. } => {
360                    let (component_section, next_ctx) = next_ctx.get_final_referenced(
361                        format!("component {component_idx}"),
362                        |component| component.get_component(*component_idx),
363                    )?;
364
365                    match &*component_section {
366                        ComponentSection::Component(referenced_component) => {
367                            let next_ctx = next_ctx.push_component(
368                                Mrc::map(component_section.clone(), |c| c.as_component()),
369                                *component_idx,
370                            );
371                            let mut funcs = Vec::new();
372                            for export in referenced_component.exports() {
373                                match export.kind {
374                                    ComponentExternalKind::Func => {
375                                        let func = next_ctx.analyse_func_export(
376                                            export.name.as_string(),
377                                            export.idx,
378                                        )?;
379                                        funcs.push(func);
380                                    }
381                                    _ => next_ctx.warning(AnalysisWarning::UnsupportedExport(
382                                        UnsupportedExportWarning {
383                                            kind: export.kind.clone(),
384                                            name: export.name.as_string(),
385                                        },
386                                    )),
387                                }
388                            }
389
390                            Ok(AnalysedInstance {
391                                name,
392                                functions: funcs,
393                            })
394                        }
395                        _ => Err(AnalysisFailure::failed(format!(
396                            "Expected component, but got {} instead",
397                            component_section.type_name()
398                        ))),
399                    }
400                }
401                ComponentInstance::FromExports { .. } => Err(AnalysisFailure::failed(
402                    "Instance defined directly from exports are not supported",
403                )),
404            },
405            _ => Err(AnalysisFailure::failed(format!(
406                "Expected instance, but got {} instead",
407                instance_section.type_name()
408            ))),
409        }
410    }
411
412    fn get_component(&self) -> Mrc<Component<Ast>> {
413        self.component_stack.last().unwrap().component.clone()
414    }
415
416    fn get_components_from_stack(&self, count: u32) -> Vec<ComponentStackItem<Ast>> {
417        self.component_stack
418            .iter()
419            .skip(self.component_stack.len() - count as usize - 1)
420            .cloned()
421            .collect()
422    }
423
424    fn push_component(
425        &self,
426        component: Mrc<Component<Ast>>,
427        component_idx: ComponentIdx,
428    ) -> AnalysisContext<Ast> {
429        let mut component_stack = self.component_stack.clone();
430        component_stack.push(ComponentStackItem {
431            component,
432            component_idx: Some(component_idx),
433        });
434        self.with_component_stack(component_stack)
435    }
436
437    fn with_component_stack(
438        &self,
439        component_stack: Vec<ComponentStackItem<Ast>>,
440    ) -> AnalysisContext<Ast> {
441        AnalysisContext {
442            component_stack,
443            warnings: self.warnings.clone(),
444            resource_ids: self.resource_ids.clone(),
445        }
446    }
447
448    fn get_final_referenced<F>(
449        &self,
450        description: impl AsRef<str>,
451        f: F,
452    ) -> AnalysisResult<(Mrc<ComponentSection<Ast>>, AnalysisContext<Ast>)>
453    where
454        F: Fn(&Component<Ast>) -> Option<Mrc<ComponentSection<Ast>>>,
455    {
456        let component = self.get_component();
457        let direct_section = AnalysisFailure::fail_on_missing(f(&component), description)?;
458        self.follow_redirects(direct_section)
459    }
460
461    fn follow_redirects(
462        &self,
463        section: Mrc<ComponentSection<Ast>>,
464    ) -> AnalysisResult<(Mrc<ComponentSection<Ast>>, AnalysisContext<Ast>)> {
465        let component = self.get_component();
466        match &*section {
467            ComponentSection::Export(ComponentExport { kind, idx, .. }) => {
468                let next = match kind {
469                    ComponentExternalKind::Module => AnalysisFailure::fail_on_missing(
470                        component.get_module(*idx),
471                        format!("module {idx}"),
472                    )?,
473                    ComponentExternalKind::Func => AnalysisFailure::fail_on_missing(
474                        component.get_component_func(*idx),
475                        format!("function {idx}"),
476                    )?,
477                    ComponentExternalKind::Value => AnalysisFailure::fail_on_missing(
478                        component.get_value(*idx),
479                        format!("value {idx}"),
480                    )?,
481                    ComponentExternalKind::Type => AnalysisFailure::fail_on_missing(
482                        component.get_component_type(*idx),
483                        format!("type {idx}"),
484                    )?,
485                    ComponentExternalKind::Instance => AnalysisFailure::fail_on_missing(
486                        component.get_instance_wrapped(*idx),
487                        format!("instance {idx}"),
488                    )?,
489                    ComponentExternalKind::Component => AnalysisFailure::fail_on_missing(
490                        component.get_component(*idx),
491                        format!("component {idx}"),
492                    )?,
493                };
494                self.follow_redirects(next)
495            }
496            ComponentSection::Alias(Alias::InstanceExport {
497                instance_idx, name, ..
498            }) => {
499                let (instance_section, next_ctx) = self
500                    .get_final_referenced(format!("instance {instance_idx}"), |component| {
501                        component.get_instance_wrapped(*instance_idx)
502                    })?;
503                next_ctx.find_instance_export(instance_section, name)
504            }
505            ComponentSection::Import(ComponentImport {
506                desc: ComponentTypeRef::Type(TypeBounds::Eq(idx)),
507                ..
508            }) => {
509                let maybe_tpe = component.get_component_type(*idx);
510                let tpe = AnalysisFailure::fail_on_missing(maybe_tpe, format!("type {idx}"))?;
511                self.follow_redirects(tpe)
512            }
513            ComponentSection::Alias(Alias::Outer {
514                kind,
515                target: AliasTarget { count, index },
516            }) => {
517                let referenced_components = self.get_components_from_stack(*count);
518                let referenced_component = referenced_components
519                    .first()
520                    .ok_or(AnalysisFailure::failed(format!(
521                        "Component stack underflow (count={count}, size={}",
522                        self.component_stack.len()
523                    )))?
524                    .component
525                    .clone();
526                match kind {
527                    OuterAliasKind::CoreModule => Err(AnalysisFailure::failed(
528                        "Core module aliases are not supported",
529                    )),
530                    OuterAliasKind::CoreType => Err(AnalysisFailure::failed(
531                        "Core type aliases are not supported",
532                    )),
533                    OuterAliasKind::Type => {
534                        let maybe_tpe = referenced_component.get_component_type(*index);
535                        let tpe =
536                            AnalysisFailure::fail_on_missing(maybe_tpe, format!("type {index}"))?;
537                        let next_ctx = self.with_component_stack(referenced_components);
538                        next_ctx.follow_redirects(tpe)
539                    }
540                    OuterAliasKind::Component => {
541                        let maybe_component = referenced_component.get_component(*index);
542                        let component = AnalysisFailure::fail_on_missing(
543                            maybe_component,
544                            format!("component {index}"),
545                        )?;
546                        let next_ctx = self.with_component_stack(referenced_components);
547                        next_ctx.follow_redirects(component)
548                    }
549                }
550            }
551            // TODO: support other redirections if needed
552            _ => Ok((section, self.clone())),
553        }
554    }
555
556    fn find_instance_export(
557        &self,
558        instance_section: Mrc<ComponentSection<Ast>>,
559        name: &String,
560    ) -> AnalysisResult<(Mrc<ComponentSection<Ast>>, AnalysisContext<Ast>)> {
561        match &*instance_section {
562            ComponentSection::Instance(_) => {
563                let instance = Mrc::map(instance_section, |s| s.as_instance());
564                let (maybe_export, next_ctx) = self.find_export_by_name(&instance, name)?;
565                let export = AnalysisFailure::fail_on_missing(
566                    maybe_export,
567                    format!("missing aliased instance export {name} from instance"),
568                )?;
569                let wrapped = Mrc::new(ComponentSection::Export((*export).clone()));
570                next_ctx.follow_redirects(wrapped)
571            }
572            ComponentSection::Import(ComponentImport {
573                desc: ComponentTypeRef::Instance(type_idx),
574                ..
575            }) => {
576                let maybe_tpe = self.get_component().get_component_type(*type_idx);
577                let tpe = AnalysisFailure::fail_on_missing(maybe_tpe, format!("type {type_idx}"))?;
578                let (tpe, next_ctx) = self.follow_redirects(tpe)?;
579                next_ctx.find_instance_export(tpe, name)
580            }
581            ComponentSection::Type(ComponentType::Instance(decls)) => {
582                match decls.find_export(name) {
583                    Some(decl) => {
584                        match decl {
585                            ComponentTypeRef::Module(type_idx) => {
586                                let maybe_tpe = self.get_component().get_component_type(*type_idx);
587                                let tpe = AnalysisFailure::fail_on_missing(
588                                    maybe_tpe,
589                                    format!("type {type_idx}"),
590                                )?;
591                                self.follow_redirects(tpe)
592                            }
593                            ComponentTypeRef::Func(type_idx) => {
594                                let maybe_tpe = self.get_component().get_component_type(*type_idx);
595                                let tpe = AnalysisFailure::fail_on_missing(
596                                    maybe_tpe,
597                                    format!("type {type_idx}"),
598                                )?;
599                                self.follow_redirects(tpe)
600                            }
601                            ComponentTypeRef::Val(_val_type) => {
602                                todo!()
603                            }
604                            ComponentTypeRef::Type(type_bounds) => {
605                                match type_bounds {
606                                    TypeBounds::Eq(component_type_idx) => {
607                                        let decl = decls.get_component_type(*component_type_idx);
608                                        let decl = AnalysisFailure::fail_on_missing(decl, format!("type {component_type_idx}"))?;
609
610                                        match decl {
611                                            InstanceTypeDeclaration::Core(_) => {
612                                                Err(AnalysisFailure::failed("Core type aliases are not supported"))
613                                            }
614                                            InstanceTypeDeclaration::Type(component_type) => {
615                                                Ok((Mrc::new(ComponentSection::Type(component_type.clone())), self.clone()))
616                                            }
617                                            InstanceTypeDeclaration::Alias(alias) => {
618                                                let component_idx = self.component_stack.last().expect("Component stack is empty").component_idx.unwrap_or_default();
619                                                let new_ctx = self.push_component(self.get_component(), component_idx);
620                                                // Emulating an inner scope by duplicating the current component on the stack (TODO: refactor this)
621                                                // Note: because we not in an inner component, but an inner instance declaration and the current analysis stack
622                                                //       does not have this concept.
623                                                new_ctx.follow_redirects(Mrc::new(ComponentSection::Alias(alias.clone())))
624                                            }
625                                            InstanceTypeDeclaration::Export { .. } => {
626                                                todo!()
627                                            }
628                                        }
629                                    }
630                                    TypeBounds::SubResource => {
631                                        Err(AnalysisFailure::failed("Reached a sub-resource type bound without a surrounding borrowed/owned resource type in find_instance_export"))
632                                    }
633                                }
634                            }
635                            ComponentTypeRef::Instance(type_idx) => {
636                                let maybe_tpe = self.get_component().get_component_type(*type_idx);
637                                let tpe = AnalysisFailure::fail_on_missing(
638                                    maybe_tpe,
639                                    format!("type {type_idx}"),
640                                )?;
641                                self.follow_redirects(tpe)
642                            }
643                            ComponentTypeRef::Component(type_idx) => {
644                                let maybe_tpe = self.get_component().get_component_type(*type_idx);
645                                let tpe = AnalysisFailure::fail_on_missing(
646                                    maybe_tpe,
647                                    format!("type {type_idx}"),
648                                )?;
649                                self.follow_redirects(tpe)
650                            }
651                        }
652                    }
653                    None => Err(AnalysisFailure::failed(format!(
654                        "Could not find exported element {name} in instance type declaration"
655                    ))),
656                }
657            }
658            _ => Err(AnalysisFailure::failed(format!(
659                "Expected instance or imported instance, but got {} instead",
660                instance_section.type_name()
661            ))),
662        }
663    }
664
665    fn find_export_by_name(
666        &self,
667        instance: &ComponentInstance,
668        name: &String,
669    ) -> AnalysisResult<(Option<Mrc<ComponentExport>>, AnalysisContext<Ast>)> {
670        match instance {
671            ComponentInstance::Instantiate { component_idx, .. } => {
672                let (component, next_ctx) = self
673                    .get_final_referenced(format!("component {component_idx}"), |component| {
674                        component.get_component(*component_idx)
675                    })?;
676                let component = Mrc::map(component, |c| c.as_component());
677                let export = component
678                    .exports()
679                    .iter()
680                    .find(|export| export.name == *name)
681                    .cloned();
682                Ok((export, next_ctx.push_component(component, *component_idx)))
683            }
684            ComponentInstance::FromExports { exports } => {
685                let export = exports.iter().find(|export| export.name == *name).cloned();
686                Ok((export.map(Mrc::new), self.clone()))
687            }
688        }
689    }
690}
691
692#[cfg(test)]
693mod tests {
694    use crate::analysis::analysed_type::{f32, field, handle, record, result, str, u32, u64};
695    use crate::analysis::{
696        AnalysedFunction, AnalysedFunctionParameter, AnalysedFunctionResult, AnalysedResourceId,
697        AnalysedResourceMode,
698    };
699    use test_r::test;
700
701    #[test]
702    fn analysed_function_kind() {
703        let cons = AnalysedFunction {
704            name: "[constructor]cart".to_string(),
705            parameters: vec![AnalysedFunctionParameter {
706                name: "user-id".to_string(),
707                typ: str(),
708            }],
709            results: vec![AnalysedFunctionResult {
710                name: None,
711                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Owned),
712            }],
713        };
714        let method = AnalysedFunction {
715            name: "[method]cart.add-item".to_string(),
716            parameters: vec![
717                AnalysedFunctionParameter {
718                    name: "self".to_string(),
719                    typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
720                },
721                AnalysedFunctionParameter {
722                    name: "item".to_string(),
723                    typ: record(vec![
724                        field("product-id", str()),
725                        field("name", str()),
726                        field("price", f32()),
727                        field("quantity", u32()),
728                    ]),
729                },
730            ],
731            results: vec![],
732        };
733        let static_method = AnalysedFunction {
734            name: "[static]cart.merge".to_string(),
735            parameters: vec![
736                AnalysedFunctionParameter {
737                    name: "self".to_string(),
738                    typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
739                },
740                AnalysedFunctionParameter {
741                    name: "that".to_string(),
742                    typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Borrowed),
743                },
744            ],
745            results: vec![AnalysedFunctionResult {
746                name: None,
747                typ: handle(AnalysedResourceId(0), AnalysedResourceMode::Owned),
748            }],
749        };
750        let fun = AnalysedFunction {
751            name: "hash".to_string(),
752            parameters: vec![AnalysedFunctionParameter {
753                name: "path".to_string(),
754                typ: str(),
755            }],
756            results: vec![AnalysedFunctionResult {
757                name: None,
758                typ: result(
759                    record(vec![field("lower", u64()), field("upper", u64())]),
760                    str(),
761                ),
762            }],
763        };
764
765        assert!(cons.is_constructor());
766        assert!(method.is_method());
767        assert!(static_method.is_static_method());
768        assert!(!fun.is_constructor());
769        assert!(!fun.is_method());
770        assert!(!fun.is_static_method());
771    }
772}