@@ -438,7 +438,10 @@ def is_combound(self):
438
438
@two_way_subtyping
439
439
def is_subtype (self , other ):
440
440
if isinstance (other , UnionType ):
441
- return set (self .types ).issubset (other .types )
441
+ for t in self .types :
442
+ if not any (t .is_subtype (other_t ) for other_t in other .types ):
443
+ return False
444
+ return True
442
445
return other .name == 'Object'
443
446
444
447
def dynamic_subtyping (self , other ):
@@ -491,7 +494,7 @@ def to_type_variable_free(self, factory):
491
494
else t )
492
495
return UnionType (new_types )
493
496
494
- def unify_types (self , other , factory , same_type = True ):
497
+ def unify_types (self , t1 , factory , same_type = True ):
495
498
"""
496
499
This is used in src.ir.type_utils in the function
497
500
unify_types.
@@ -501,31 +504,196 @@ def unify_types(self, other, factory, same_type=True):
501
504
502
505
For more information on the function see the detailed
503
506
explanation at the unify_types function definition.
507
+ """
508
+ t2 = self
509
+ type_var_map = {}
510
+
511
+ if not t2 .has_type_variables ():
512
+ return {}
513
+
514
+ # If T1 is a union type, then get all its types.
515
+ t1_types = (t1 .types if t1 .is_combound () and
516
+ not t1 .is_parameterized ()
517
+ else [t1 ])
518
+
519
+ if not t1 .is_subtype (t2 ):
520
+ # Get the Type Variables of T2
521
+ t_vars = dict (t2 .get_type_variables (factory ))
522
+
523
+ # Find which types of t1 are not already in t2
524
+ add_to_t2 = set (t1_types ) - set (t2 .types )
525
+
526
+ # If T1 is a union type like 100 | number | string,
527
+ # we do not need to substitute both 100 and number
528
+ # in the type variables of T2.
529
+ # Since number is a supertype of 100, if we only
530
+ # substitute number, then we have also covered
531
+ # the subtypes of 100 too!
532
+ # Hence, we only substitute necessary types in T2
533
+ # by ensuring that the T1 type we will subtitute
534
+ # is NOT a subtype of any other types in the T1 union type.
535
+ for t1_t in t1_types :
536
+ if any (t1_t .is_subtype (other_t1_t )
537
+ and t1_t is not other_t1_t
538
+ for other_t1_t in t1_types ):
539
+ add_to_t2 .remove (t1_t )
540
+
541
+ # If T1 is a union type, and its types that we need
542
+ # to substitute in T2 are more than the type variables of T2,
543
+ # then there is no substitution that ensures T1 <: T2.
544
+ if len (add_to_t2 ) > len (t_vars ):
545
+ return {}
546
+
547
+ # Get bounds of type variables of T2
548
+ bounds = [b for b in t_vars .values () if b != {None }]
549
+
550
+ # If the type variables have no bounds, then we can just assign
551
+ # the types from T1 to any type variable.
552
+ if not bounds :
553
+ temp = list (t_vars .keys ())
554
+ for t in add_to_t2 :
555
+ tv = temp .pop (0 )
556
+ type_var_map [tv ] = t
557
+ return type_var_map
558
+
559
+ # Get all the possible substitutions between T1 types (add_to_t2)
560
+ # and type variables of T2. A type variable can be substituted
561
+ # with a type in T1 if the type variable has no bound or
562
+ # if the type is a subtype of a bound.
563
+ possible_substitutions = {}
564
+ for t in add_to_t2 :
565
+ subs = set ()
566
+ # Below remember that:
567
+ # - k is the type variable
568
+ # - v is a set containing its bounds
569
+ for k ,v in t_vars .items ():
570
+ if v == {None } or any (t .is_subtype (b ) for b in v ):
571
+ subs .add (k )
572
+
573
+ # If there are no possible substitutions with type variables
574
+ # for any given type in T1 (add_to_t2) then there is no
575
+ # substitution that ensures T1 <: T2.
576
+ if not subs :
577
+ return {}
578
+ possible_substitutions [t ] = subs
579
+
580
+ # Decide the order of assignments (if possible)
581
+ assignments , flag = self .assign_types_to_type_vars (possible_substitutions )
582
+ if not flag :
583
+ return {}
584
+ type_var_map .update (assignments )
585
+
586
+
587
+ # Instantiate any not-utilized T2 type variables with their bound (if they have one).
588
+ # If they don't have a bound instantiate them with a type from T1.
589
+ leftover_type_vars = [t for t in t2 .types if t .is_type_var () and t not in type_var_map ]
590
+ for type_var in leftover_type_vars :
591
+ type_var_map [type_var ] = type_var .bound if type_var .bound else t1_types [0 ]
592
+
593
+ return type_var_map
594
+
595
+ def assign_types_to_type_vars (self , possible_subs ):
596
+ """
597
+ This method is a helper for the method unify_types of union types (see above)
598
+
599
+ Args:
600
+ - possible_subs: A dict containing (types.Type: set) pairs, which represents possible
601
+ T2 type variable (value) susbstitutions with a T1 type (key).
602
+ The set values contain compatible type vars of T2.
603
+
604
+ - t2_t_vars: The type variables of T2
605
+
606
+ - t1_types: The types of T1 that we want to substitute in T2
607
+
608
+ Returns:
609
+ - A dict of (TypeVariable: types.Type) pairs representing the substitutions in T2
610
+
611
+ This method is needed because we need to find the correct substitutions
612
+ of the type variables in the T2 union type, in order for all T1 types (T1 can be a union type itself)
613
+ to be substituted in T2 (if possible).
504
614
505
- The only way a union type type-unification can be
506
- achieved is when the first of the two types
507
- (here: self) is a union type and the second
508
- is either a type variable or another union type
509
- with similar union structure and has at least one
510
- type variable in its union.
615
+ Consider the following case:
511
616
617
+ T1: number | string
618
+ T2: boolean | X | Y extends number
619
+
620
+ In this case, if we first substituted X with the type number from T1,
621
+ we would have been left with the type variable Y, which is not compatible
622
+ with the type string.
623
+
624
+ As a result we would falsely conclude that we can not unify the types T1 and T2,
625
+ when in reality, had we just substituted Y with number and X with string,
626
+ we would have been ably to correctly unify the two types.
627
+
628
+ A naive solution would be to find all the possible substitution permutations
629
+ between T1 types and T2 type variables.
630
+
631
+ Using our approach, after first creating the possible_subs dict,
632
+ which contains all compatible T2 type variables for each T1 type,
633
+ we first substitute the T1 type that is compatible with the FEWEST
634
+ T2 type variables at any given moment.
635
+
636
+ Going back two the above case, here is how we tackle it with our new approach:
637
+
638
+ T1: number | string
639
+ T2: boolean | X | Y extends number
640
+
641
+ (1) We find the possible substitutions for each T1 type (done outside this method)
642
+ possible_subs = {number: {X, Y},
643
+ string: {X}
644
+ }
645
+
646
+ (2) We sort the dict based on the length of the type variable sets corresponding to each type
647
+ sorted_type_subs = [(string, {X}), (number, {X, Y})]
648
+
649
+ (3) Now we work on the first element, the pair of string and {X}. We assign the substitution
650
+ X: string. We remove X from all possible substitutions for other T1 types and then
651
+ delete the pair with key string from our possible_subs dict.
652
+
653
+ (4) We sort the dict again, it now looks like this:
654
+ sorted_type_subs = [(number, {Y})]
655
+
656
+ (5) Assign the substitution Y: number and repeat the rest of step (3)
657
+
658
+ (6) The possible_subs dict is now empty, so we return our substitution dictionary
659
+ return {X: string, Y: number}
512
660
"""
513
661
type_var_map = {}
514
- if (isinstance (other , UnionType )
515
- and len (self .types ) == len (other .types )
516
- and other .has_type_variables ()):
517
- for i , t in enumerate (self .types ):
518
- t1 = t
519
- t2 = other .types [i ]
520
- is_type_var = t2 .is_type_var ()
521
- if not is_type_var :
522
- if not t2 .is_subtype (t1 ):
523
- return {}
524
- else :
525
- type_var_map [t2 ] = t1
526
- elif other .is_type_var ():
527
- type_var_map [other ] = self
528
- return type_var_map
662
+
663
+ # Continue trying to find type variable susbstitutions until
664
+ # all T1 types are substituted in T2.
665
+ while possible_subs :
666
+ # Sort the possible_subs dict, in order to first find a substitution for the T1
667
+ # type with the fewest compatible T2 type variables.
668
+ sorted_type_subs = sorted (possible_subs .items (), key = lambda x : len (x ), reverse = True )
669
+
670
+ # Get the first (T1 type, T2 type variable) pair (sorted_type_subs is a tuples list)
671
+ type_to_substitute , compatible_tvars = sorted_type_subs [0 ]
672
+
673
+ # If there aren't any compatible_tvars, then that means that there is no possible
674
+ # order of substitutions that ensures all types in T1 are substituted in T2.
675
+ # Hence, we return a False flag to indicate that the type unification is not possible.
676
+ # Note: at this point this happens if at previous iterations of the while loop
677
+ # we substituted all the type variables that are compatible with this specific type_to_substitute.
678
+ if not compatible_tvars :
679
+ return ({}, False )
680
+
681
+ # Get any of the compatible type variables and substitute it with the T1 type
682
+ chosen_tvar = compatible_tvars .pop ()
683
+ type_var_map [chosen_tvar ] = type_to_substitute
684
+
685
+ # Remove the substituted type variable from the possible substitutions
686
+ # of all other T1 types.
687
+ for k in list (possible_subs .keys ()):
688
+ if chosen_tvar in possible_subs [k ]:
689
+ possible_subs .remove (chosen_tvar )
690
+
691
+ # Delete the possible substitutions of the T1 type we just substituted in T2
692
+ del possible_subs [type_to_substitute ]
693
+
694
+ # Return the substitutions we found and a flag confirming that there is a possible
695
+ # order of substitutions that gurantees type unification.
696
+ return (type_var_map , True )
529
697
530
698
def get_name (self ):
531
699
return self .name
0 commit comments