@@ -379,8 +379,8 @@ namespace ts.codefix {
379
379
interface UsageContext {
380
380
isNumber ?: boolean ;
381
381
isString ?: boolean ;
382
- hasNonVacuousType ?: boolean ;
383
- hasNonVacuousNonAnonymousType ?: boolean ;
382
+ /** Used ambiguously, eg x + ___ or object[___]; results in string | number if no other evidence exists */
383
+ isNumberOrString ?: boolean ;
384
384
385
385
candidateTypes ?: Type [ ] ;
386
386
properties ?: UnderscoreEscapedMap < UsageContext > ;
@@ -510,8 +510,7 @@ namespace ts.codefix {
510
510
break ;
511
511
512
512
case SyntaxKind . PlusToken :
513
- usageContext . isNumber = true ;
514
- usageContext . isString = true ;
513
+ usageContext . isNumberOrString = true ;
515
514
break ;
516
515
517
516
// case SyntaxKind.ExclamationToken:
@@ -582,8 +581,7 @@ namespace ts.codefix {
582
581
usageContext . isString = true ;
583
582
}
584
583
else {
585
- usageContext . isNumber = true ;
586
- usageContext . isString = true ;
584
+ usageContext . isNumberOrString = true ;
587
585
}
588
586
break ;
589
587
@@ -657,8 +655,7 @@ namespace ts.codefix {
657
655
658
656
function inferTypeFromPropertyElementExpressionContext ( parent : ElementAccessExpression , node : Expression , checker : TypeChecker , usageContext : UsageContext ) : void {
659
657
if ( node === parent . argumentExpression ) {
660
- usageContext . isNumber = true ;
661
- usageContext . isString = true ;
658
+ usageContext . isNumberOrString = true ;
662
659
return ;
663
660
}
664
661
else {
@@ -674,17 +671,50 @@ namespace ts.codefix {
674
671
}
675
672
}
676
673
674
+ interface Priority {
675
+ high : ( t : Type ) => boolean ;
676
+ low : ( t : Type ) => boolean ;
677
+ }
678
+
679
+ function removeLowPriorityInferences ( inferences : ReadonlyArray < Type > , priorities : Priority [ ] ) : Type [ ] {
680
+ const toRemove : ( ( t : Type ) => boolean ) [ ] = [ ] ;
681
+ for ( const i of inferences ) {
682
+ for ( const { high, low } of priorities ) {
683
+ if ( high ( i ) ) {
684
+ Debug . assert ( ! low ( i ) ) ;
685
+ toRemove . push ( low ) ;
686
+ }
687
+ }
688
+ }
689
+ return inferences . filter ( i => toRemove . every ( f => ! f ( i ) ) ) ;
690
+ }
691
+
677
692
export function unifyFromContext ( inferences : ReadonlyArray < Type > , checker : TypeChecker , fallback = checker . getAnyType ( ) ) : Type {
678
693
if ( ! inferences . length ) return fallback ;
679
- const hasNonVacuousType = inferences . some ( i => ! ( i . flags & ( TypeFlags . Any | TypeFlags . Void ) ) ) ;
680
- const hasNonVacuousNonAnonymousType = inferences . some (
681
- i => ! ( i . flags & ( TypeFlags . Nullable | TypeFlags . Any | TypeFlags . Void ) ) && ! ( checker . getObjectFlags ( i ) & ObjectFlags . Anonymous ) ) ;
682
- const anons = inferences . filter ( i => checker . getObjectFlags ( i ) & ObjectFlags . Anonymous ) as AnonymousType [ ] ;
683
- const good = [ ] ;
684
- if ( ! hasNonVacuousNonAnonymousType && anons . length ) {
694
+
695
+ // 1. string or number individually override string | number
696
+ // 2. non-any, non-void overrides any or void
697
+ // 3. non-nullable, non-any, non-void, non-anonymous overrides anonymous types
698
+ const stringNumber = checker . getUnionType ( [ checker . getStringType ( ) , checker . getNumberType ( ) ] ) ;
699
+ const priorities : Priority [ ] = [
700
+ {
701
+ high : t => t === checker . getStringType ( ) || t === checker . getNumberType ( ) ,
702
+ low : t => t === stringNumber
703
+ } ,
704
+ {
705
+ high : t => ! ( t . flags & ( TypeFlags . Any | TypeFlags . Void ) ) ,
706
+ low : t => ! ! ( t . flags & ( TypeFlags . Any | TypeFlags . Void ) )
707
+ } ,
708
+ {
709
+ high : t => ! ( t . flags & ( TypeFlags . Nullable | TypeFlags . Any | TypeFlags . Void ) ) && ! ( checker . getObjectFlags ( t ) & ObjectFlags . Anonymous ) ,
710
+ low : t => ! ! ( checker . getObjectFlags ( t ) & ObjectFlags . Anonymous )
711
+ } ] ;
712
+ let good = removeLowPriorityInferences ( inferences , priorities ) ;
713
+ const anons = good . filter ( i => checker . getObjectFlags ( i ) & ObjectFlags . Anonymous ) as AnonymousType [ ] ;
714
+ if ( anons . length ) {
715
+ good = good . filter ( i => ! ( checker . getObjectFlags ( i ) & ObjectFlags . Anonymous ) ) ;
685
716
good . push ( unifyAnonymousTypes ( anons , checker ) ) ;
686
717
}
687
- good . push ( ...inferences . filter ( i => ! ( checker . getObjectFlags ( i ) & ObjectFlags . Anonymous ) && ! ( hasNonVacuousType && i . flags & ( TypeFlags . Any | TypeFlags . Void ) ) ) ) ;
688
718
return checker . getWidenedType ( checker . getUnionType ( good ) ) ;
689
719
}
690
720
@@ -731,12 +761,16 @@ namespace ts.codefix {
731
761
732
762
function inferFromContext ( usageContext : UsageContext , checker : TypeChecker ) {
733
763
const types = [ ] ;
764
+
734
765
if ( usageContext . isNumber ) {
735
766
types . push ( checker . getNumberType ( ) ) ;
736
767
}
737
768
if ( usageContext . isString ) {
738
769
types . push ( checker . getStringType ( ) ) ;
739
770
}
771
+ if ( usageContext . isNumberOrString ) {
772
+ types . push ( checker . getUnionType ( [ checker . getStringType ( ) , checker . getNumberType ( ) ] ) ) ;
773
+ }
740
774
741
775
types . push ( ...( usageContext . candidateTypes || [ ] ) . map ( t => checker . getBaseTypeOfLiteralType ( t ) ) ) ;
742
776
@@ -750,7 +784,7 @@ namespace ts.codefix {
750
784
}
751
785
752
786
if ( usageContext . numberIndexContext ) {
753
- return [ checker . createArrayType ( recur ( usageContext . numberIndexContext ) ) ] ;
787
+ types . push ( checker . createArrayType ( recur ( usageContext . numberIndexContext ) ) ) ;
754
788
}
755
789
else if ( usageContext . properties || usageContext . callContexts || usageContext . constructContexts || usageContext . stringIndexContext ) {
756
790
const members = createUnderscoreEscapedMap < Symbol > ( ) ;
0 commit comments