Skip to content

Commit 383286f

Browse files
committed
Add type parameter inference
It's a smeary copy of the checker's type parameter, so I feel bad about duplicating that code. Not sure what the solution is, architecturally.
1 parent 37150d9 commit 383286f

File tree

2 files changed

+91
-42
lines changed

2 files changed

+91
-42
lines changed

src/services/codefixes/inferFromUsage.ts

Lines changed: 87 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,19 @@ namespace ts.codefix {
393393

394394
function inferTypeFromReferences(program: Program, references: ReadonlyArray<Identifier>, cancellationToken: CancellationToken) {
395395
const checker = program.getTypeChecker();
396+
const builtinConstructors: { [s: string]: (t: Type) => Type } = {
397+
string: () => checker.getStringType(),
398+
number: () => checker.getNumberType(),
399+
Array: t => checker.createArrayType(t),
400+
Promise: t => checker.createPromiseType(t),
401+
};
402+
const builtins = [
403+
checker.getStringType(),
404+
checker.getNumberType(),
405+
checker.createArrayType(checker.getAnyType()),
406+
checker.createPromiseType(checker.getAnyType()),
407+
];
408+
396409
return {
397410
single,
398411
parameters,
@@ -777,7 +790,7 @@ namespace ts.codefix {
777790
good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous));
778791
good.push(unifyAnonymousTypes(anons));
779792
}
780-
return checker.getWidenedType(checker.getUnionType(good, UnionReduction.Subtype));
793+
return checker.getWidenedType(checker.getUnionType(good.map(checker.getBaseTypeOfLiteralType), UnionReduction.Subtype));
781794
}
782795

783796
function unifyAnonymousTypes(anons: AnonymousType[]) {
@@ -928,28 +941,9 @@ namespace ts.codefix {
928941

929942
function findBuiltinTypes(usage: Usage): Type[] {
930943
if (!usage.properties || !usage.properties.size) return [];
931-
const builtins = [
932-
checker.getStringType(),
933-
checker.getNumberType(),
934-
checker.createArrayType(checker.getAnyType()),
935-
checker.createPromiseType(checker.getAnyType()),
936-
// checker.getFunctionType() // TODO: not sure what this was supposed to be good for.
937-
];
938-
// TODO: Still need to infer type parameters
939944
const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage));
940945
if (0 < matches.length && matches.length < 3) {
941-
return matches.map(m => {
942-
// special-case array and promise for now
943-
if (m === builtins[3] && hasCalls(usage.properties!.get("then" as __String))) {
944-
const paramType = getParameterTypeFromCalls(0, usage.properties!.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217
945-
const returns = paramType.getCallSignatures().map(sig => sig.getReturnType());
946-
return checker.createPromiseType(returns.length ? checker.getUnionType(returns, UnionReduction.Subtype) : checker.getAnyType());
947-
}
948-
else if (m === builtins[2] && hasCalls(usage.properties!.get("push" as __String))) {
949-
return checker.createArrayType(getParameterTypeFromCalls(0, usage.properties!.get("push" as __String)!.calls!, /*isRestParameter*/ false)!);
950-
}
951-
return m;
952-
});
946+
return matches.map(m => inferTypeParameterFromUsage(m, usage));
953947
}
954948
return [];
955949
}
@@ -974,26 +968,84 @@ namespace ts.codefix {
974968
return result;
975969
}
976970

971+
// inference is limited to
972+
// 1. generic types with a single parameter
973+
// 2. inference to/from calls with a single signature
974+
function inferTypeParameterFromUsage(type: Type, usage: Usage) {
975+
if (!usage.properties || !(getObjectFlags(type) & ObjectFlags.Reference)) return type;
976+
const generic = (type as TypeReference).target;
977+
const singleTypeParameter = singleOrUndefined(generic.typeParameters);
978+
if (!singleTypeParameter) return type;
977979

978-
function getFunctionFromCalls(calls: CallUsage[]) {
979-
return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls, checker.getAnyType())], emptyArray, undefined, undefined);
980+
const types: Type[] = [];
981+
usage.properties.forEach((propUsage, name) => {
982+
const source = checker.getTypeOfPropertyOfType(generic, name as string);
983+
if (!source) {
984+
return Debug.fail("generic should have all the properties of its reference.");
985+
}
986+
if (!propUsage.calls) return;
987+
988+
types.push(...infer(source, getFunctionFromCalls(propUsage.calls), singleTypeParameter));
989+
});
990+
return builtinConstructors[type.symbol.escapedName as string](unifyTypes(types));
980991
}
981992

982-
function getParameterTypeFromCalls(parameterIndex: number, calls: CallUsage[], isRestParameter: boolean) {
983-
// TODO: This is largely redundant with getSignatureFromCalls, I think. (though it handles rest parameters correctly, so that needs to be integrated there)
984-
let types: Type[] = [];
985-
for (const call of calls) {
986-
if (call.argumentTypes.length > parameterIndex) {
987-
if (isRestParameter) {
988-
types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a)));
989-
}
990-
else {
991-
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
993+
// TODO: Source and target are bad names. Should be builtinType and usageType...or something
994+
// and search is a bad name
995+
function infer(source: Type, target: Type, search: Type): readonly Type[] {
996+
if (source === search) {
997+
return [target];
998+
}
999+
else if (source.flags & TypeFlags.UnionOrIntersection) {
1000+
return flatMap((source as UnionOrIntersectionType).types, t => infer(t, target, search));
1001+
}
1002+
else if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference) {
1003+
// this is wrong because we need a reference to the targetType to, so we can check that it's also a reference
1004+
const sourceArgs = (source as TypeReference).typeArguments;
1005+
const targetArgs = (target as TypeReference).typeArguments;
1006+
const types = [];
1007+
if (sourceArgs && targetArgs) {
1008+
for (let i = 0; i < sourceArgs.length; i++) {
1009+
if (targetArgs[i]) {
1010+
types.push(...infer(sourceArgs[i], targetArgs[i], search));
1011+
}
9921012
}
9931013
}
1014+
return types;
1015+
}
1016+
const sourceSigs = checker.getSignaturesOfType(source, SignatureKind.Call);
1017+
const targetSigs = checker.getSignaturesOfType(target, SignatureKind.Call);
1018+
if (sourceSigs.length === 1 && targetSigs.length === 1) {
1019+
return inferFromSignatures(sourceSigs[0], targetSigs[0], search);
1020+
}
1021+
return [];
1022+
}
1023+
1024+
function inferFromSignatures(sourceSig: Signature, targetSig: Signature, search: Type) {
1025+
const types = [];
1026+
for (let i = 0; i < sourceSig.parameters.length; i++) {
1027+
const sourceParam = sourceSig.parameters[i];
1028+
const targetParam = targetSig.parameters[i];
1029+
const isRest = sourceSig.declaration && isRestParameter(sourceSig.declaration.parameters[i]);
1030+
if (!targetParam) {
1031+
break;
1032+
}
1033+
let sourceType = checker.getTypeOfSymbolAtLocation(sourceParam, sourceParam.valueDeclaration);
1034+
let elementType = isRest && checker.getElementTypeOfArrayType(sourceType);
1035+
if (elementType) {
1036+
sourceType = elementType;
1037+
}
1038+
const targetType = (targetParam as SymbolLinks).type || checker.getTypeOfSymbolAtLocation(targetParam, targetParam.valueDeclaration);
1039+
types.push(...infer(sourceType, targetType, search));
9941040
}
995-
const type = unifyTypes(types);
996-
return isRestParameter ? checker.createArrayType(type) : type;
1041+
const sourceReturn = checker.getReturnTypeOfSignature(sourceSig);
1042+
const targetReturn = checker.getReturnTypeOfSignature(targetSig);
1043+
types.push(...infer(sourceReturn, targetReturn, search));
1044+
return types;
1045+
}
1046+
1047+
function getFunctionFromCalls(calls: CallUsage[]) {
1048+
return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls, checker.getAnyType())], emptyArray, undefined, undefined);
9971049
}
9981050

9991051
function getSignatureFromCalls(calls: CallUsage[], fallbackReturn: Type): Signature {
@@ -1023,9 +1075,5 @@ namespace ts.codefix {
10231075
(usage.candidateThisTypes || (usage.candidateThisTypes = [])).push(type);
10241076
}
10251077
}
1026-
1027-
function hasCalls(usage: Usage | undefined): boolean {
1028-
return !!usage && !!usage.calls;
1029-
}
10301078
}
10311079
}
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
/// <reference path='fourslash.ts' />
22

33
// @noImplicitAny: true
4-
////function wrap( [| s |] ) {
5-
//// return s.length + s.toUpperCase()
4+
//// function wrap( [| s |] ) {
5+
//// return s.length + s.indexOf('hi')
66
//// }
77

88
// https://github.com/Microsoft/TypeScript/issues/29330
9-
verify.rangeAfterCodeFix("s: string");
9+
verify.rangeAfterCodeFix("s: string | string[]");
10+

0 commit comments

Comments
 (0)