@@ -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