1use crate::interface_matcher::InterfaceMatcher;
4use serde::{Deserialize, Serialize};
5use std::collections::{hash_map::Entry, HashMap, HashSet};
6
7#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
8pub struct Interface {
9 pub name: Option<String>,
11 pub imports: HashMap<(String, String), Import>,
13 pub exports: HashMap<String, Export>,
15}
16
17impl Interface {
18 pub fn merge(&self, other: Interface) -> Result<Interface, String> {
19 let mut base = self.clone();
20
21 for (key, val) in other.imports {
22 match base.imports.entry(key) {
23 Entry::Occupied(e) if *e.get() != val => {
24 let (namespace, name) = e.key();
25 let original_value = e.get();
26 return Err(format!("Conflict detected: the import \"{namespace}\" \"{name}\" was found but the definitions were different: {original_value:?} {val:?}"));
27 }
28 Entry::Occupied(_) => {
29 }
31 Entry::Vacant(e) => {
32 e.insert(val);
33 }
34 };
35 }
36
37 for (key, val) in other.exports {
38 match base.exports.entry(key) {
39 Entry::Occupied(e) if *e.get() != val => {
40 let name = e.key();
41 let original_value = e.get();
42 return Err(format!("Conflict detected: the key \"{name}\" was found in exports but the definitions were different: {original_value:?} {val:?}"));
43 }
44 Entry::Occupied(_) => {
45 }
47 Entry::Vacant(e) => {
48 e.insert(val);
49 }
50 };
51 }
52
53 Ok(base)
54 }
55
56 pub fn create_interface_matcher(&self) -> InterfaceMatcher {
57 let mut namespaces = HashSet::new();
58 let mut namespace_imports: HashMap<String, HashSet<Import>> =
59 HashMap::with_capacity(self.imports.len());
60 let mut exports = HashSet::with_capacity(self.exports.len());
61
62 for (_, import) in self.imports.iter() {
63 match import {
64 Import::Func { namespace, .. } | Import::Global { namespace, .. } => {
65 if !namespaces.contains(namespace) {
66 namespaces.insert(namespace.clone());
67 }
68 let ni = namespace_imports.entry(namespace.clone()).or_default();
69 ni.insert(import.clone());
70 }
71 }
72 }
73 for (_, export) in self.exports.iter() {
74 exports.insert(export.clone());
75 }
76 InterfaceMatcher {
77 namespaces,
78 namespace_imports,
79 exports,
80 }
81 }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
85pub enum Import {
86 Func {
87 namespace: String,
88 name: String,
89 params: Vec<WasmType>,
90 result: Vec<WasmType>,
91 },
92 Global {
93 namespace: String,
94 name: String,
95 var_type: WasmType,
96 },
97}
98
99impl Import {
100 pub fn format_key(ns: &str, name: &str) -> (String, String) {
101 (ns.to_string(), name.to_string())
102 }
103
104 pub fn get_key(&self) -> (String, String) {
106 match self {
107 Import::Func {
108 namespace, name, ..
109 }
110 | Import::Global {
111 namespace, name, ..
112 } => Self::format_key(namespace, name),
113 }
114 }
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
118pub enum Export {
119 Func {
120 name: String,
121 params: Vec<WasmType>,
122 result: Vec<WasmType>,
123 },
124 Global {
125 name: String,
126 var_type: WasmType,
127 },
128}
129
130impl Export {
131 pub fn format_key(name: &str) -> String {
132 name.to_string()
133 }
134
135 pub fn get_key(&self) -> String {
137 match self {
138 Export::Func { name, .. } | Export::Global { name, .. } => Self::format_key(name),
139 }
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
145pub enum WasmType {
146 I32,
147 I64,
148 F32,
149 F64,
150}
151
152impl std::fmt::Display for WasmType {
153 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
154 write!(
155 f,
156 "{}",
157 match self {
158 WasmType::I32 => "i32",
159 WasmType::I64 => "i64",
160 WasmType::F32 => "f32",
161 WasmType::F64 => "f64",
162 }
163 )
164 }
165}
166
167#[cfg(test)]
168mod test {
169 use crate::parser;
170
171 #[test]
172 fn merging_works() {
173 let interface1_src =
174 r#"(interface (func (import "env" "plus_one") (param i32) (result i32)))"#;
175 let interface2_src =
176 r#"(interface (func (import "env" "plus_one") (param i64) (result i64)))"#;
177 let interface3_src =
178 r#"(interface (func (import "env" "times_two") (param i64) (result i64)))"#;
179 let interface4_src =
180 r#"(interface (func (import "env" "times_two") (param i64 i64) (result i64)))"#;
181 let interface5_src = r#"(interface (func (export "empty_bank_account") (param) (result)))"#;
182 let interface6_src =
183 r#"(interface (func (export "empty_bank_account") (param) (result i64)))"#;
184
185 let interface1 = parser::parse_interface(interface1_src).unwrap();
186 let interface2 = parser::parse_interface(interface2_src).unwrap();
187 let interface3 = parser::parse_interface(interface3_src).unwrap();
188 let interface4 = parser::parse_interface(interface4_src).unwrap();
189 let interface5 = parser::parse_interface(interface5_src).unwrap();
190 let interface6 = parser::parse_interface(interface6_src).unwrap();
191
192 assert!(interface1.merge(interface2.clone()).is_err());
193 assert!(interface2.merge(interface1.clone()).is_err());
194 assert!(interface1.merge(interface3.clone()).is_ok());
195 assert!(interface2.merge(interface3.clone()).is_ok());
196 assert!(interface3.merge(interface2).is_ok());
197 assert!(
198 interface1.merge(interface1.clone()).is_ok(),
199 "exact matches are accepted"
200 );
201 assert!(interface3.merge(interface4).is_err());
202 assert!(interface5.merge(interface5.clone()).is_ok());
203 assert!(interface5.merge(interface6).is_err());
204 }
205}