@@ -433,6 +433,53 @@ namespace ts.codefix {
433433 inferredTypes : Type [ ] | undefined ;
434434 }
435435
436+ function createEmptyUsage ( ) : Usage {
437+ return {
438+ isNumber : undefined ,
439+ isString : undefined ,
440+ isNumberOrString : undefined ,
441+ candidateTypes : undefined ,
442+ properties : undefined ,
443+ calls : undefined ,
444+ constructs : undefined ,
445+ numberIndex : undefined ,
446+ stringIndex : undefined ,
447+ candidateThisTypes : undefined ,
448+ inferredTypes : undefined ,
449+ } ;
450+ }
451+
452+ function combineUsages ( usages : Usage [ ] ) : Usage {
453+ const combinedProperties = createUnderscoreEscapedMap < Usage [ ] > ( ) ;
454+ for ( const u of usages ) {
455+ if ( u . properties ) {
456+ u . properties . forEach ( ( p , name ) => {
457+ if ( ! combinedProperties . has ( name ) ) {
458+ combinedProperties . set ( name , [ ] ) ;
459+ }
460+ combinedProperties . get ( name ) ! . push ( p ) ;
461+ } ) ;
462+ }
463+ }
464+ const properties = createUnderscoreEscapedMap < Usage > ( ) ;
465+ combinedProperties . forEach ( ( ps , name ) => {
466+ properties . set ( name , combineUsages ( ps ) ) ;
467+ } ) ;
468+ return {
469+ isNumber : usages . some ( u => u . isNumber ) ,
470+ isString : usages . some ( u => u . isString ) ,
471+ isNumberOrString : usages . some ( u => u . isNumberOrString ) ,
472+ candidateTypes : flatMap ( usages , u => u . candidateTypes ) as Type [ ] ,
473+ properties,
474+ calls : flatMap ( usages , u => u . calls ) as CallUsage [ ] ,
475+ constructs : flatMap ( usages , u => u . constructs ) as CallUsage [ ] ,
476+ numberIndex : forEach ( usages , u => u . numberIndex ) ,
477+ stringIndex : forEach ( usages , u => u . stringIndex ) ,
478+ candidateThisTypes : flatMap ( usages , u => u . candidateThisTypes ) as Type [ ] ,
479+ inferredTypes : undefined , // clear type cache
480+ } ;
481+ }
482+
436483 function single ( ) : Type {
437484 return unifyTypes ( inferTypesFromReferencesSingle ( references ) ) ;
438485 }
@@ -767,6 +814,10 @@ namespace ts.codefix {
767814 return inferences . filter ( i => toRemove . every ( f => ! f ( i ) ) ) ;
768815 }
769816
817+ function unifyFromUsage ( usage : Usage ) {
818+ return unifyTypes ( inferFromUsage ( usage ) ) ;
819+ }
820+
770821 function unifyTypes ( inferences : ReadonlyArray < Type > ) : Type {
771822 if ( ! inferences . length ) return checker . getAnyType ( ) ;
772823
@@ -837,7 +888,7 @@ namespace ts.codefix {
837888 numberIndices . length ? checker . createIndexInfo ( checker . getUnionType ( numberIndices ) , numberIndexReadonly ) : undefined ) ;
838889 }
839890
840- function inferFromUsage ( usage : Usage ) {
891+ function inferFromUsage ( usage : Usage ) : Type [ ] {
841892 const types = [ ] ;
842893
843894 if ( usage . isNumber ) {
@@ -851,12 +902,20 @@ namespace ts.codefix {
851902 }
852903
853904 types . push ( ...( usage . candidateTypes || [ ] ) . map ( t => checker . getBaseTypeOfLiteralType ( t ) ) ) ;
854- types . push ( ...findBuiltinTypes ( usage ) ) ;
905+ types . push ( ...inferNamedTypesFromProperties ( usage ) ) ;
855906
856907 if ( usage . numberIndex ) {
857- types . push ( checker . createArrayType ( recur ( usage . numberIndex ) ) ) ;
908+ types . push ( checker . createArrayType ( unifyFromUsage ( usage . numberIndex ) ) ) ;
909+ }
910+ const structural = inferStructuralType ( usage ) ;
911+ if ( structural ) {
912+ types . push ( structural ) ;
858913 }
859- else if ( usage . properties && usage . properties . size
914+ return types ;
915+ }
916+
917+ function inferStructuralType ( usage : Usage ) {
918+ if ( usage . properties && usage . properties . size
860919 || usage . calls && usage . calls . length
861920 || usage . constructs && usage . constructs . length
862921 || usage . stringIndex ) {
@@ -868,7 +927,7 @@ namespace ts.codefix {
868927 if ( usage . properties ) {
869928 usage . properties . forEach ( ( u , name ) => {
870929 const symbol = checker . createSymbol ( SymbolFlags . Property , name ) ;
871- symbol . type = recur ( u ) ;
930+ symbol . type = unifyFromUsage ( u ) ;
872931 members . set ( name , symbol ) ;
873932 } ) ;
874933 }
@@ -882,75 +941,23 @@ namespace ts.codefix {
882941 }
883942
884943 if ( usage . stringIndex ) {
885- stringIndexInfo = checker . createIndexInfo ( recur ( usage . stringIndex ) , /*isReadonly*/ false ) ;
944+ stringIndexInfo = checker . createIndexInfo ( unifyFromUsage ( usage . stringIndex ) , /*isReadonly*/ false ) ;
886945 }
887946
888- types . push ( checker . createAnonymousType ( /*symbol*/ undefined ! , members , callSignatures , constructSignatures , stringIndexInfo , /*numberIndexInfo*/ undefined ) ) ; // TODO: GH#18217
889- }
890- return types ; // TODO: Should cache this since I HOPE it doesn't change
891-
892- function recur ( innerUsage : Usage ) : Type {
893- return unifyTypes ( inferFromUsage ( innerUsage ) ) ;
947+ return checker . createAnonymousType ( /*symbol*/ undefined ! , members , callSignatures , constructSignatures , stringIndexInfo , /*numberIndexInfo*/ undefined ) ; // TODO: GH#18217
894948 }
895949 }
896950
897- function createEmptyUsage ( ) : Usage {
898- return {
899- isNumber : undefined ,
900- isString : undefined ,
901- isNumberOrString : undefined ,
902- candidateTypes : undefined ,
903- properties : undefined ,
904- calls : undefined ,
905- constructs : undefined ,
906- numberIndex : undefined ,
907- stringIndex : undefined ,
908- candidateThisTypes : undefined ,
909- inferredTypes : undefined ,
910- }
911- }
912-
913- function combineUsages ( usages : Usage [ ] ) : Usage {
914- const combinedProperties = createUnderscoreEscapedMap < Usage [ ] > ( )
915- for ( const u of usages ) {
916- if ( u . properties ) {
917- u . properties . forEach ( ( p , name ) => {
918- if ( ! combinedProperties . has ( name ) ) {
919- combinedProperties . set ( name , [ ] ) ;
920- }
921- combinedProperties . get ( name ) ! . push ( p ) ;
922- } ) ;
923- }
924- }
925- const properties = createUnderscoreEscapedMap < Usage > ( )
926- combinedProperties . forEach ( ( ps , name ) => {
927- properties . set ( name , combineUsages ( ps ) ) ;
928- } ) ;
929- return {
930- isNumber : usages . some ( u => u . isNumber ) ,
931- isString : usages . some ( u => u . isString ) ,
932- isNumberOrString : usages . some ( u => u . isNumberOrString ) ,
933- candidateTypes : flatMap ( usages , u => u . candidateTypes ) as Type [ ] ,
934- properties,
935- calls : flatMap ( usages , u => u . calls ) as CallUsage [ ] ,
936- constructs : flatMap ( usages , u => u . constructs ) as CallUsage [ ] ,
937- numberIndex : forEach ( usages , u => u . numberIndex ) ,
938- stringIndex : forEach ( usages , u => u . stringIndex ) ,
939- candidateThisTypes : flatMap ( usages , u => u . candidateThisTypes ) as Type [ ] ,
940- inferredTypes : undefined , // clear type cache
941- }
942- }
943-
944- function findBuiltinTypes ( usage : Usage ) : Type [ ] {
951+ function inferNamedTypesFromProperties ( usage : Usage ) : Type [ ] {
945952 if ( ! usage . properties || ! usage . properties . size ) return [ ] ;
946- const matches = builtins . filter ( t => matchesAllPropertiesOf ( t , usage ) ) ;
953+ const matches = builtins . filter ( t => allPropertiesAreAssignableToUsage ( t , usage ) ) ;
947954 if ( 0 < matches . length && matches . length < 3 ) {
948- return matches . map ( m => inferTypeParameterFromUsage ( m , usage ) ) ;
955+ return matches . map ( m => inferInstantiationFromUsage ( m , usage ) ) ;
949956 }
950957 return [ ] ;
951958 }
952959
953- function matchesAllPropertiesOf ( type : Type , usage : Usage ) {
960+ function allPropertiesAreAssignableToUsage ( type : Type , usage : Usage ) {
954961 if ( ! usage . properties ) return false ;
955962 let result = true ;
956963 usage . properties . forEach ( ( propUsage , name ) => {
@@ -960,34 +967,34 @@ namespace ts.codefix {
960967 return ;
961968 }
962969 if ( propUsage . calls ) {
963- const sigs = checker . getSignaturesOfType ( source , ts . SignatureKind . Call ) ;
970+ const sigs = checker . getSignaturesOfType ( source , SignatureKind . Call ) ;
964971 result = result && ! ! sigs . length && checker . isTypeAssignableTo ( source , getFunctionFromCalls ( propUsage . calls ) ) ;
965972 }
966973 else {
967- result = result && checker . isTypeAssignableTo ( source , unifyTypes ( inferFromUsage ( propUsage ) ) ) ;
974+ result = result && checker . isTypeAssignableTo ( source , unifyFromUsage ( propUsage ) ) ;
968975 }
969976 } ) ;
970977 return result ;
971978 }
972979
973- // inference is limited to
974- // 1. generic types with a single parameter
975- // 2. inference to/from calls with a single signature
976- function inferTypeParameterFromUsage ( type : Type , usage : Usage ) {
977- if ( ! usage . properties || ! ( getObjectFlags ( type ) & ObjectFlags . Reference ) ) return type ;
980+ /**
981+ * inference is limited to
982+ * 1. generic types with a single parameter
983+ * 2. inference to/from calls with a single signature
984+ */
985+ function inferInstantiationFromUsage ( type : Type , usage : Usage ) {
986+ if ( ! ( getObjectFlags ( type ) & ObjectFlags . Reference ) || ! usage . properties ) {
987+ return type ;
988+ }
978989 const generic = ( type as TypeReference ) . target ;
979990 const singleTypeParameter = singleOrUndefined ( generic . typeParameters ) ;
980991 if ( ! singleTypeParameter ) return type ;
981992
982993 const types : Type [ ] = [ ] ;
983994 usage . properties . forEach ( ( propUsage , name ) => {
984- const source = checker . getTypeOfPropertyOfType ( generic , name as string ) ;
985- if ( ! source ) {
986- return Debug . fail ( "generic should have all the properties of its reference." ) ;
987- }
988- if ( ! propUsage . calls ) return ;
989-
990- types . push ( ...infer ( source , getFunctionFromCalls ( propUsage . calls ) , singleTypeParameter ) ) ;
995+ const genericPropertyType = checker . getTypeOfPropertyOfType ( generic , name as string ) ;
996+ Debug . assert ( ! ! genericPropertyType , "generic should have all the properties of its reference." ) ;
997+ types . push ( ...infer ( genericPropertyType ! , unifyFromUsage ( propUsage ) , singleTypeParameter ) ) ;
991998 } ) ;
992999 return builtinConstructors [ type . symbol . escapedName as string ] ( unifyTypes ( types ) ) ;
9931000 }
@@ -1033,7 +1040,7 @@ namespace ts.codefix {
10331040 break ;
10341041 }
10351042 let sourceType = checker . getTypeOfSymbolAtLocation ( sourceParam , sourceParam . valueDeclaration ) ;
1036- let elementType = isRest && checker . getElementTypeOfArrayType ( sourceType ) ;
1043+ const elementType = isRest && checker . getElementTypeOfArrayType ( sourceType ) ;
10371044 if ( elementType ) {
10381045 sourceType = elementType ;
10391046 }
@@ -1047,7 +1054,7 @@ namespace ts.codefix {
10471054 }
10481055
10491056 function getFunctionFromCalls ( calls : CallUsage [ ] ) {
1050- return checker . createAnonymousType ( undefined ! , createSymbolTable ( ) , [ getSignatureFromCalls ( calls ) ] , emptyArray , undefined , undefined ) ;
1057+ return checker . createAnonymousType ( undefined ! , createSymbolTable ( ) , [ getSignatureFromCalls ( calls ) ] , emptyArray , /*stringIndexInfo*/ undefined , /*numberIndexInfo*/ undefined ) ;
10511058 }
10521059
10531060 function getSignatureFromCalls ( calls : CallUsage [ ] ) : Signature {
@@ -1061,7 +1068,7 @@ namespace ts.codefix {
10611068 }
10621069 parameters . push ( symbol ) ;
10631070 }
1064- const returnType = unifyTypes ( inferFromUsage ( combineUsages ( calls . map ( call => call . return_ ) ) ) ) ;
1071+ const returnType = unifyFromUsage ( combineUsages ( calls . map ( call => call . return_ ) ) ) ;
10651072 // TODO: GH#18217
10661073 return checker . createSignature ( /*declaration*/ undefined ! , /*typeParameters*/ undefined , /*thisParameter*/ undefined , parameters , returnType , /*typePredicate*/ undefined , length , /*hasRestParameter*/ false , /*hasLiteralTypes*/ false ) ;
10671074 }
0 commit comments