@@ -455,16 +455,19 @@ impl Type {
455455
456456 /// Subtract `other` from `self`, preserving specialization if possible.
457457 pub fn subtract ( & self , other : Type ) -> Type {
458+ // If self is a subtype of other, the result is empty (no negative types).
458459 if self . is_subtype ( other) { return types:: Empty ; }
459- // If self's specialization is not a subtype of other's specialization (eg Int,Double or
460- // Top,Int or Int,Empty or Int[5],Int[4]), subtracting has no effect.
460+ // Self is not a subtype of other. That means either:
461+ // * Their type bits do not overlap at all (eg Int vs String)
462+ // * Their type bits overlap but self's specialization is not a subtype of other's (eg
463+ // Fixnum[5] vs Fixnum[4])
464+ // Check for the latter case, returning self unchanged if so.
461465 if !self . spec_is_subtype_of ( other) {
462466 return * self ;
463467 }
464- // Now the specializations overlap: self's specialization is a subtype of other's
465- // specialization (eg Int,Top or Int[4],Int[4], but NOT Empty,Int because that would mean
466- // self is a subtype of other). Keep self's specialization.
467- // Subtract the bits
468+ // Now self is either a supertype of other (eg Object vs String or Fixnum vs Fixnum[5]) or
469+ // their type bits do not overlap at all (eg Int vs String).
470+ // Just subtract the bits and keep self's specialization.
468471 let bits = self . bits & !other. bits ;
469472 Type { bits, spec : self . spec }
470473 }
@@ -1076,4 +1079,45 @@ mod tests {
10761079 assert ! ( !types:: CBool . has_value( Const :: CBool ( true ) ) ) ;
10771080 assert ! ( !types:: CShape . has_value( Const :: CShape ( crate :: cruby:: ShapeId ( 0x1234 ) ) ) ) ;
10781081 }
1082+
1083+ #[ test]
1084+ fn test_subtract_with_superset_returns_empty ( ) {
1085+ let left = types:: NilClass ;
1086+ let right = types:: BasicObject ;
1087+ let result = left. subtract ( right) ;
1088+ assert_bit_equal ( result, types:: Empty ) ;
1089+ }
1090+
1091+ #[ test]
1092+ fn test_subtract_with_subset_removes_bits ( ) {
1093+ let left = types:: BasicObject ;
1094+ let right = types:: NilClass ;
1095+ let result = left. subtract ( right) ;
1096+ assert_subtype ( result, types:: BasicObject ) ;
1097+ assert_not_subtype ( types:: NilClass , result) ;
1098+ }
1099+
1100+ #[ test]
1101+ fn test_subtract_with_no_overlap_returns_self ( ) {
1102+ let left = types:: Fixnum ;
1103+ let right = types:: StringExact ;
1104+ let result = left. subtract ( right) ;
1105+ assert_bit_equal ( result, left) ;
1106+ }
1107+
1108+ #[ test]
1109+ fn test_subtract_with_no_specialization_overlap_returns_self ( ) {
1110+ let left = Type :: fixnum ( 4 ) ;
1111+ let right = Type :: fixnum ( 5 ) ;
1112+ let result = left. subtract ( right) ;
1113+ assert_bit_equal ( result, left) ;
1114+ }
1115+
1116+ #[ test]
1117+ fn test_subtract_with_specialization_subset_removes_specialization ( ) {
1118+ let left = types:: Fixnum ;
1119+ let right = Type :: fixnum ( 42 ) ;
1120+ let result = left. subtract ( right) ;
1121+ assert_bit_equal ( result, types:: Fixnum ) ;
1122+ }
10791123}
0 commit comments