1pub(crate) use check_instance_returns::*;
16pub(crate) use exhaustive_pattern_match::*;
17pub(crate) use invalid_expr::*;
18pub(crate) use invalid_math_expr::*;
19pub(crate) use missing_fields::*;
20pub use path::*;
21pub(crate) use type_mismatch::*;
22pub(crate) use unresolved_types::*;
23
24mod check_instance_returns;
25mod exhaustive_pattern_match;
26mod invalid_expr;
27mod invalid_math_expr;
28mod invalid_worker_name;
29mod missing_fields;
30mod path;
31mod type_check_in_function_calls;
32mod type_mismatch;
33mod unresolved_types;
34
35use crate::rib_compilation_error::RibCompilationError;
36use crate::type_checker::exhaustive_pattern_match::check_exhaustive_pattern_match;
37use crate::type_checker::invalid_expr::check_invalid_expr;
38use crate::type_checker::invalid_math_expr::check_invalid_math_expr;
39use crate::type_checker::invalid_worker_name::check_invalid_worker_name;
40use crate::type_checker::type_check_in_function_calls::check_type_error_in_function_calls;
41use crate::{Expr, FunctionTypeRegistry};
42
43pub fn type_check(
44 expr: &mut Expr,
45 function_type_registry: &FunctionTypeRegistry,
46) -> Result<(), RibCompilationError> {
47 check_type_error_in_function_calls(expr, function_type_registry)?;
48 check_unresolved_types(expr)?;
49 check_invalid_worker_name(expr)?;
50 check_invalid_expr(expr)?;
51 check_invalid_program_return(expr)?;
52 check_invalid_math_expr(expr)?;
53 check_exhaustive_pattern_match(expr, function_type_registry)?;
54 Ok(())
55}
56
57#[cfg(test)]
58mod type_check_tests {
59
60 mod type_mismatch_errors {
61 use test_r::test;
62
63 use crate::type_checker::type_check_tests::internal;
64 use crate::type_checker::type_check_tests::internal::strip_spaces;
65 use crate::{compile, Expr};
66
67 #[test]
68 fn test_type_mismatch_in_record_in_function_call1() {
69 let expr = r#"
70 let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: "foo", c: [1, 2, 3], d: {da: 4}});
71 result
72 "#;
73
74 let expr = Expr::from_text(expr).unwrap();
75
76 let metadata = internal::get_metadata_with_record_input_params();
77
78 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
79
80 let expected = r#"
81 error in the following rib found at line 2, column 28
82 `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: "foo", c: [1, 2, 3], d: {da: 4}}`
83 found within:
84 `foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: "foo", c: [1, 2, 3], d: {da: 4}})`
85 cause: type mismatch at path: `b`. expected u64
86 invalid argument to the function `foo`
87 "#;
88
89 assert_eq!(error_msg, strip_spaces(expected));
90 }
91
92 #[test]
93 fn test_type_mismatch_in_record_in_function_call2() {
94 let expr = r#"
95 let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: ["foo", "bar"], d: {da: 4}});
96 result
97 "#;
98
99 let expr = Expr::from_text(expr).unwrap();
100
101 let metadata = internal::get_metadata_with_record_input_params();
102
103 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
104
105 let expected = r#"
106 error in the following rib found at line 2, column 28
107 `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: ["foo", "bar"], d: {da: 4}}`
108 found within:
109 `foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: ["foo", "bar"], d: {da: 4}})`
110 cause: type mismatch at path: `c`. expected list<s32>
111 invalid argument to the function `foo`
112 "#;
113
114 assert_eq!(error_msg, strip_spaces(expected));
115 }
116
117 #[test]
118 fn test_type_mismatch_in_record_in_function_call3() {
119 let expr = r#"
120 let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: "foo"}});
121 result
122 "#;
123
124 let expr = Expr::from_text(expr).unwrap();
125
126 let metadata = internal::get_metadata_with_record_input_params();
127
128 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
129
130 let expected = r#"
131 error in the following rib found at line 2, column 28
132 `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: "foo"}}`
133 found within:
134 `foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: "foo"}})`
135 cause: type mismatch at path: `d.da`. expected s32
136 invalid argument to the function `foo`
137 "#;
138
139 assert_eq!(error_msg, strip_spaces(expected));
140 }
141
142 #[test]
147 fn test_type_mismatch_in_record_in_function_call4() {
148 let expr = r#"
149 let result = foo({a: {aa: 1, ab: 2, ac: (1, 2), ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: 1}});
150 result
151 "#;
152
153 let expr = Expr::from_text(expr).unwrap();
154
155 let metadata = internal::get_metadata_with_record_input_params();
156
157 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
158
159 let expected = r#"
160 error in the following rib found at line 2, column 51
161 `(1, 2)`
162 cause: The expression is wrongly used (directly or indirectly) elsewhere resulting in conflicting types: `list`, `tuple`
163 help: ensure this expression is only used in contexts that align with its actual type
164 "#;
165
166 assert_eq!(error_msg, strip_spaces(expected));
167 }
168
169 #[test]
170 fn test_type_mismatch_in_record_in_function_call5() {
171 let expr = r#"
172 let x = {a: "foo"};
173 let result = foo({a: {aa: 1, ab: 2, ac: x, ad: {ada: 1}, ae: (1, "foo")}, b: 2, c: [1, 2], d: {da: 1}});
174 result
175 "#;
176
177 let expr = Expr::from_text(expr).unwrap();
178
179 let metadata = internal::get_metadata_with_record_input_params();
180
181 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
182
183 let expected = r#"
184 error in the following rib found at line 2, column 21
185 `{a: "foo"}`
186 cause: The expression is wrongly used (directly or indirectly) elsewhere resulting in conflicting types: `list`, `record`
187 help: ensure this expression is only used in contexts that align with its actual type
188 "#;
189
190 assert_eq!(error_msg, strip_spaces(expected));
191 }
192
193 #[test]
194 fn test_type_mismatch_in_nested_record_in_function_call1() {
195 let expr = r#"
196 let result = foo({a: {aa: "foo", ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}});
197 result
198 "#;
199
200 let expr = Expr::from_text(expr).unwrap();
201
202 let metadata = internal::get_metadata_with_record_input_params();
203
204 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
205
206 let expected = r#"
207 error in the following rib found at line 2, column 28
208 `{a: {aa: "foo", ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}}`
209 found within:
210 `foo({a: {aa: "foo", ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}})`
211 cause: type mismatch at path: `a.aa`. expected s32
212 invalid argument to the function `foo`
213 "#;
214
215 assert_eq!(error_msg, strip_spaces(expected));
216 }
217
218 #[test]
219 fn test_type_mismatch_in_nested_record_in_function_call2() {
220 let expr = r#"
221 let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}});
222 result
223 "#;
224
225 let expr = Expr::from_text(expr).unwrap();
226
227 let metadata = internal::get_metadata_with_record_input_params();
228
229 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
230
231 let expected = r#"
232 error in the following rib found at line 2, column 28
233 `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}}`
234 found within:
235 `foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: "1"}, ae: (1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}})`
236 cause: type mismatch at path: `a.ad.ada`. expected s32
237 invalid argument to the function `foo`
238 "#;
239
240 assert_eq!(error_msg, strip_spaces(expected));
241 }
242
243 #[test]
244 fn test_type_mismatch_in_nested_record_in_function_call3() {
245 let expr = r#"
246 let bar = {a: {aa: 1, ab: 2, ac: 1, ad: {ada: 1}, ae:(1, "foo")}, b: 3, c: [1, 2, 3], d: {da: 4}};
247 let result = foo(bar);
248 result
249 "#;
250
251 let expr = Expr::from_text(expr).unwrap();
252
253 let metadata = internal::get_metadata_with_record_input_params();
254
255 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
256
257 let expected = r#"
258 error in the following rib found at line 3, column 30
259 `bar`
260 found within:
261 `foo(bar)`
262 cause: type mismatch at path: `a.ac`. expected list<s32>
263 invalid argument to the function `foo`
264 "#;
265
266 assert_eq!(error_msg, strip_spaces(expected));
267 }
268
269 #[test]
270 fn test_type_mismatch_in_nested_record_in_function_call4() {
271 let expr = r#"
272 let result = foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}});
273 result
274 "#;
275
276 let expr = Expr::from_text(expr).unwrap();
277
278 let metadata = internal::get_metadata_with_record_input_params();
279
280 let error_msg = compile(expr, &metadata).unwrap_err().to_string();
281
282 let expected = r#"
283 error in the following rib found at line 2, column 28
284 `{a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}}`
285 found within:
286 `foo({a: {aa: 1, ab: 2, ac: [1, 2], ad: {ada: 1}, ae: (1, 2)}, b: 3, c: [1, 2, 3], d: {da: 4}})`
287 cause: type mismatch at path: `a.ae`. expected string
288 invalid argument to the function `foo`
289 "#;
290
291 assert_eq!(error_msg, strip_spaces(expected));
292 }
293 }
294
295 mod internal {
296 use golem_wasm_ast::analysis::analysed_type::{list, record, s32, str, tuple, u64};
297 use golem_wasm_ast::analysis::{
298 AnalysedExport, AnalysedFunction, AnalysedFunctionParameter, AnalysedFunctionResult,
299 NameTypePair,
300 };
301
302 pub(crate) fn strip_spaces(input: &str) -> String {
303 let lines = input.lines();
304
305 let first_line = lines
306 .clone()
307 .find(|line| !line.trim().is_empty())
308 .unwrap_or("");
309 let margin_width = first_line.chars().take_while(|c| c.is_whitespace()).count();
310
311 let result = lines
312 .map(|line| {
313 if line.trim().is_empty() {
314 String::new()
315 } else {
316 line[margin_width..].to_string()
317 }
318 })
319 .collect::<Vec<String>>()
320 .join("\n");
321
322 result.strip_prefix("\n").unwrap_or(&result).to_string()
323 }
324
325 pub(crate) fn get_metadata_with_record_input_params() -> Vec<AnalysedExport> {
326 let analysed_export = AnalysedExport::Function(AnalysedFunction {
327 name: "foo".to_string(),
328 parameters: vec![AnalysedFunctionParameter {
329 name: "arg1".to_string(),
330 typ: record(vec![
331 NameTypePair {
332 name: "a".to_string(),
333 typ: record(vec![
334 NameTypePair {
335 name: "aa".to_string(),
336 typ: s32(),
337 },
338 NameTypePair {
339 name: "ab".to_string(),
340 typ: s32(),
341 },
342 NameTypePair {
343 name: "ac".to_string(),
344 typ: list(s32()),
345 },
346 NameTypePair {
347 name: "ad".to_string(),
348 typ: record(vec![NameTypePair {
349 name: "ada".to_string(),
350 typ: s32(),
351 }]),
352 },
353 NameTypePair {
354 name: "ae".to_string(),
355 typ: tuple(vec![s32(), str()]),
356 },
357 ]),
358 },
359 NameTypePair {
360 name: "b".to_string(),
361 typ: u64(),
362 },
363 NameTypePair {
364 name: "c".to_string(),
365 typ: list(s32()),
366 },
367 NameTypePair {
368 name: "d".to_string(),
369 typ: record(vec![NameTypePair {
370 name: "da".to_string(),
371 typ: s32(),
372 }]),
373 },
374 ]),
375 }],
376 results: vec![AnalysedFunctionResult {
377 name: None,
378 typ: str(),
379 }],
380 });
381
382 vec![analysed_export]
383 }
384 }
385}