Skip to content

Commit 047d465

Browse files
committed
wip: add tests for Concrete unification
1 parent 0971bf5 commit 047d465

1 file changed

Lines changed: 175 additions & 2 deletions

File tree

src/passes/thir_gen/unification_table.rs

Lines changed: 175 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,16 @@ pub enum Solution {
1616
/// Some integer.
1717
AnyInteger,
1818
/// Some unsigned integer.
19-
#[expect(dead_code, reason = "unsigned integer may be used in the future")]
19+
#[cfg_attr(
20+
not(test),
21+
expect(dead_code, reason = "unsigned integer may be used in the future")
22+
)]
2023
UnsignedInteger,
2124
/// Some signed integer.
22-
#[expect(dead_code, reason = "signed integer may be used in the future")]
25+
#[cfg_attr(
26+
not(test),
27+
expect(dead_code, reason = "signed integer may be used in the future")
28+
)]
2329
SignedInteger,
2430
/// An error.
2531
#[expect(dead_code, reason = "error may be used in the future")]
@@ -247,3 +253,170 @@ impl UnificationTable {
247253
}
248254
}
249255
}
256+
257+
#[cfg(test)]
258+
mod test {
259+
use super::*;
260+
261+
#[fixture]
262+
fn table() -> UnificationTable {
263+
UnificationTable::new()
264+
}
265+
266+
#[fixture]
267+
fn types() -> Types {
268+
Types::new()
269+
}
270+
271+
#[rstest]
272+
#[case::unknown(
273+
|_, _| Solution::Unknown
274+
)]
275+
#[case::concrete(
276+
|_, u8| Solution::Concrete(u8)
277+
)]
278+
#[case::inferred(
279+
|unknown, _| Solution::Inferred(
280+
CompositeType::Ref(unknown)
281+
)
282+
)]
283+
#[case::any_integer(
284+
|_, _| Solution::AnyInteger
285+
)]
286+
#[case::unsigned_integer(
287+
|_, _| Solution::UnsignedInteger
288+
)]
289+
#[case::signed_integer(
290+
|_, _| Solution::SignedInteger
291+
)]
292+
fn unknown(
293+
mut table: UnificationTable,
294+
types: Types,
295+
#[case] solution: impl FnOnce(SolutionId, TypeId) -> Solution,
296+
) {
297+
let solution = solution(table.set.insert(Solution::Unknown), types.u8());
298+
299+
let unknown = table.set.insert(Solution::Unknown);
300+
let other = table.set.insert(solution.clone());
301+
302+
let result = table.unify(&types, unknown, other);
303+
assert_eq!(
304+
table.get(result),
305+
&solution,
306+
"unifying with `Unknown` should always result in the other solution"
307+
);
308+
}
309+
310+
mod concrete {
311+
use super::*;
312+
313+
#[rstest]
314+
fn concrete_match(mut table: UnificationTable, types: Types) {
315+
let lhs = table.set.insert(Solution::Concrete(types.u8()));
316+
let rhs = table.set.insert(Solution::Concrete(types.u8()));
317+
318+
let result = table.unify(&types, lhs, rhs);
319+
assert_eq!(table.get(result), &Solution::Concrete(types.u8()));
320+
}
321+
322+
#[rstest]
323+
#[should_panic(expected = "cannot unify different concrete types")]
324+
fn concrete_mismatch(mut table: UnificationTable, types: Types) {
325+
let lhs = table.set.insert(Solution::Concrete(types.u8()));
326+
let rhs = table.set.insert(Solution::Concrete(types.i8()));
327+
328+
table.unify(&types, lhs, rhs);
329+
}
330+
331+
#[rstest]
332+
fn propagate_non_never_type(mut table: UnificationTable, types: Types) {
333+
let lhs = table.set.insert(Solution::Concrete(types.u8()));
334+
let rhs = table.set.insert(Solution::Concrete(types.never()));
335+
336+
let result = table.unify(&types, lhs, rhs);
337+
assert_eq!(table.get(result), &Solution::Concrete(types.u8()));
338+
}
339+
340+
#[rstest]
341+
#[should_panic(expected = "cannot infer with primitive")]
342+
fn primitive_with_inferred(mut table: UnificationTable, types: Types) {
343+
let lhs = table.set.insert(Solution::Concrete(types.u8()));
344+
let unknown = table.set.insert(Solution::Unknown);
345+
let rhs = table
346+
.set
347+
.insert(Solution::Inferred(CompositeType::Ref(unknown)));
348+
349+
table.unify(&types, lhs, rhs);
350+
}
351+
352+
#[rstest]
353+
#[case::reference(
354+
|types: &mut Types, ids: [_; 1]| types.ref_of(ids[0]),
355+
|solutions: [_; 1]| CompositeType::Ref(solutions[0])
356+
)]
357+
#[case::tuple(
358+
|types: &mut Types, ids: [_; 2]| types.tuple(ids),
359+
|solutions: [_; 2]| CompositeType::Tuple(vec![solutions[0], solutions[1]])
360+
)]
361+
#[case::function(
362+
|types: &mut Types, ids: [_; 3]| types.function([ids[0], ids[1]], ids[2]),
363+
|solutions: [_; 3]| CompositeType::Function{ parameters: vec![solutions[0], solutions[1]], return_ty: solutions[2] }
364+
)]
365+
fn composite_with_inferred<const N: usize>(
366+
mut table: UnificationTable,
367+
mut types: Types,
368+
#[case] composite: impl FnOnce(&mut Types, [TypeId; N]) -> TypeId,
369+
#[case] inferred: impl FnOnce([SolutionId; N]) -> CompositeType<SolutionId>,
370+
) {
371+
let type_selection = [types.u8(), types.i8(), types.boolean()];
372+
let unknowns = std::array::from_fn(|_| table.unknown());
373+
374+
let composite_ty = composite(&mut types, *type_selection[0..N].as_array().unwrap());
375+
let inferred = inferred(unknowns);
376+
377+
let composite = table.set.insert(Solution::Concrete(composite_ty));
378+
let inferred = table.set.insert(Solution::Inferred(inferred));
379+
380+
let result = table.unify(&types, composite, inferred);
381+
382+
// Must result in concrete type.
383+
assert_eq!(table.get(result), &Solution::Concrete(composite_ty));
384+
385+
// Unknowns should be merged.
386+
for (ty, unknown) in type_selection.into_iter().zip(unknowns) {
387+
assert_eq!(table.get(unknown), &Solution::Concrete(ty));
388+
}
389+
}
390+
391+
#[rstest]
392+
#[case::any_u8(Solution::AnyInteger, |types: &mut Types| types.u8())]
393+
#[case::any_i8(Solution::AnyInteger, |types: &mut Types| types.i8())]
394+
#[should_panic(expected = "type is not any integer")]
395+
#[case::any_boolean(Solution::AnyInteger, |types: &mut Types| types.boolean())]
396+
#[case::unsigned_u8(Solution::UnsignedInteger, |types: &mut Types| types.u8())]
397+
#[should_panic(expected = "type is not unsigned integer")]
398+
#[case::unsigned_i8(Solution::UnsignedInteger, |types: &mut Types| types.i8())]
399+
#[should_panic(expected = "type is not unsigned integer")]
400+
#[case::unsigned_boolean(Solution::UnsignedInteger, |types: &mut Types| types.boolean())]
401+
#[should_panic(expected = "type is not signed integer")]
402+
#[case::signed_u8(Solution::SignedInteger, |types: &mut Types| types.u8())]
403+
#[case::signed_i8(Solution::SignedInteger, |types: &mut Types| types.i8())]
404+
#[should_panic(expected = "type is not signed integer")]
405+
#[case::signed_boolean(Solution::SignedInteger, |types: &mut Types| types.boolean())]
406+
fn integer_solutions(
407+
mut table: UnificationTable,
408+
mut types: Types,
409+
#[case] solution: Solution,
410+
#[case] concrete: impl FnOnce(&mut Types) -> TypeId,
411+
) {
412+
let ty = concrete(&mut types);
413+
414+
let concrete = table.set.insert(Solution::Concrete(ty));
415+
let integer_solution = table.set.insert(solution);
416+
417+
let result = table.unify(&types, concrete, integer_solution);
418+
419+
assert_eq!(table.get(result), &Solution::Concrete(ty));
420+
}
421+
}
422+
}

0 commit comments

Comments
 (0)