Skip to content

Commit 37150d9

Browse files
committed
Turn on findBuiltinTypes
Type parameter inference is special-cased, just moved from its previous place with no improvement.
1 parent 945d423 commit 37150d9

File tree

4 files changed

+65
-53
lines changed

4 files changed

+65
-53
lines changed

src/services/codefixes/inferFromUsage.ts

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ namespace ts.codefix {
421421
}
422422

423423
function single(): Type {
424-
return unifyFromUsage(inferTypesFromReferencesSingle(references));
424+
return unifyTypes(inferTypesFromReferencesSingle(references));
425425
}
426426

427427
function parameters(declaration: FunctionLike): ParameterInference[] | undefined {
@@ -457,7 +457,7 @@ namespace ts.codefix {
457457
const inferred = inferTypesFromReferencesSingle(getReferences(parameter.name, program, cancellationToken));
458458
types.push(...(isRest ? mapDefined(inferred, checker.getElementTypeOfArrayType) : inferred));
459459
}
460-
const type = unifyFromUsage(types);
460+
const type = unifyTypes(types);
461461
return {
462462
type: isRest ? checker.createArrayType(type) : type,
463463
isOptional: isOptional && !isRest,
@@ -473,7 +473,7 @@ namespace ts.codefix {
473473
calculateUsageOfNode(reference, usage);
474474
}
475475

476-
return unifyFromUsage(usage.candidateThisTypes || emptyArray);
476+
return unifyTypes(usage.candidateThisTypes || emptyArray);
477477
}
478478

479479
function inferTypesFromReferencesSingle(references: readonly Identifier[]): Type[] {
@@ -627,6 +627,9 @@ namespace ts.codefix {
627627
else if (otherOperandType.flags & TypeFlags.StringLike) {
628628
usage.isString = true;
629629
}
630+
else if (otherOperandType.flags & TypeFlags.Any) {
631+
// do nothing, maybe we'll learn something elsewhere
632+
}
630633
else {
631634
usage.isNumberOrString = true;
632635
}
@@ -748,7 +751,7 @@ namespace ts.codefix {
748751
return inferences.filter(i => toRemove.every(f => !f(i)));
749752
}
750753

751-
function unifyFromUsage(inferences: ReadonlyArray<Type>, fallback = checker.getAnyType()): Type {
754+
function unifyTypes(inferences: ReadonlyArray<Type>, fallback = checker.getAnyType()): Type {
752755
if (!inferences.length) return fallback;
753756

754757
// 1. string or number individually override string | number
@@ -774,7 +777,7 @@ namespace ts.codefix {
774777
good = good.filter(i => !(checker.getObjectFlags(i) & ObjectFlags.Anonymous));
775778
good.push(unifyAnonymousTypes(anons));
776779
}
777-
return checker.getWidenedType(checker.getUnionType(good));
780+
return checker.getWidenedType(checker.getUnionType(good, UnionReduction.Subtype));
778781
}
779782

780783
function unifyAnonymousTypes(anons: AnonymousType[]) {
@@ -832,16 +835,7 @@ namespace ts.codefix {
832835
}
833836

834837
types.push(...(usage.candidateTypes || []).map(t => checker.getBaseTypeOfLiteralType(t)));
835-
types.push(...findBuiltinType(usage));
836-
837-
if (usage.properties && hasCalls(usage.properties.get("then" as __String))) {
838-
const paramType = getParameterTypeFromCalls(0, usage.properties.get("then" as __String)!.calls!, /*isRestParameter*/ false)!; // TODO: GH#18217
839-
const types = paramType.getCallSignatures().map(sig => sig.getReturnType());
840-
types.push(checker.createPromiseType(types.length ? checker.getUnionType(types, UnionReduction.Subtype) : checker.getAnyType()));
841-
}
842-
else if (usage.properties && hasCalls(usage.properties.get("push" as __String))) {
843-
types.push(checker.createArrayType(getParameterTypeFromCalls(0, usage.properties.get("push" as __String)!.calls!, /*isRestParameter*/ false)!));
844-
}
838+
types.push(...findBuiltinTypes(usage));
845839

846840
if (usage.numberIndex) {
847841
types.push(checker.createArrayType(recur(usage.numberIndex)));
@@ -864,11 +858,12 @@ namespace ts.codefix {
864858
}
865859

866860
if (usage.calls) {
867-
callSignatures.push(getSignatureFromCalls(usage.calls));
861+
callSignatures.push(getSignatureFromCalls(usage.calls, checker.getVoidType()));
868862
}
869863

870864
if (usage.constructs) {
871-
constructSignatures.push(getSignatureFromCalls(usage.constructs));
865+
// TODO: fallback return should maybe be {}?
866+
constructSignatures.push(getSignatureFromCalls(usage.constructs, checker.getVoidType()));
872867
}
873868

874869
if (usage.stringIndex) {
@@ -880,7 +875,7 @@ namespace ts.codefix {
880875
return types; // TODO: Should cache this since I HOPE it doesn't change
881876

882877
function recur(innerUsage: Usage): Type {
883-
return unifyFromUsage(inferFromUsage(innerUsage));
878+
return unifyTypes(inferFromUsage(innerUsage));
884879
}
885880
}
886881

@@ -931,80 +926,88 @@ namespace ts.codefix {
931926
}
932927
}
933928

934-
function findBuiltinType(usage: Usage): Type[] {
929+
function findBuiltinTypes(usage: Usage): Type[] {
930+
if (!usage.properties || !usage.properties.size) return [];
935931
const builtins = [
936932
checker.getStringType(),
937933
checker.getNumberType(),
938934
checker.createArrayType(checker.getAnyType()),
939935
checker.createPromiseType(checker.getAnyType()),
940936
// checker.getFunctionType() // TODO: not sure what this was supposed to be good for.
941937
];
938+
// TODO: Still need to infer type parameters
942939
const matches = builtins.filter(t => matchesAllPropertiesOf(t, usage));
943-
if (false && 0 < matches.length && matches.length < 3) {
944-
return matches;
940+
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+
});
945953
}
946954
return [];
947955
}
948956

949957
function matchesAllPropertiesOf(type: Type, usage: Usage) {
950958
if (!usage.properties) return false;
951959
let result = true;
952-
usage.properties.forEach((prop, name) => {
953-
const source = checker.getUnionType(inferFromUsage(prop));
954-
const target = checker.getTypeOfPropertyOfType(type, name as string);
955-
if (target && prop.calls) {
956-
const sigs = checker.getSignaturesOfType(target, ts.SignatureKind.Call);
957-
result = result && !!sigs.length && sigs.some(
958-
sig => checker.isTypeAssignableTo(
959-
getFunctionFromCalls(prop.calls!),
960-
checker.createAnonymousType(undefined!, createSymbolTable(), [sig], emptyArray, undefined, undefined)));
960+
usage.properties.forEach((propUsage, name) => {
961+
const source = checker.getTypeOfPropertyOfType(type, name as string);
962+
if (!source) {
963+
result = false;
964+
return;
965+
}
966+
if (propUsage.calls) {
967+
const sigs = checker.getSignaturesOfType(source, ts.SignatureKind.Call);
968+
result = result && !!sigs.length && checker.isTypeAssignableTo(source, getFunctionFromCalls(propUsage.calls));
961969
}
962970
else {
963-
result = result && !!source && !!target && checker.isTypeAssignableTo(source, target);
971+
result = result && checker.isTypeAssignableTo(source, unifyTypes(inferFromUsage(propUsage)));
964972
}
965973
});
966974
return result;
967975
}
968976

969977

970978
function getFunctionFromCalls(calls: CallUsage[]) {
971-
return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls)], emptyArray, undefined, undefined);
979+
return checker.createAnonymousType(undefined!, createSymbolTable(), [getSignatureFromCalls(calls, checker.getAnyType())], emptyArray, undefined, undefined);
972980
}
973981

974982
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)
975984
let types: Type[] = [];
976-
if (calls) {
977-
for (const call of calls) {
978-
if (call.argumentTypes.length > parameterIndex) {
979-
if (isRestParameter) {
980-
types = concatenate(types, map(call.argumentTypes.slice(parameterIndex), a => checker.getBaseTypeOfLiteralType(a)));
981-
}
982-
else {
983-
types.push(checker.getBaseTypeOfLiteralType(call.argumentTypes[parameterIndex]));
984-
}
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]));
985992
}
986993
}
987994
}
988-
989-
if (types.length) {
990-
const type = checker.getWidenedType(checker.getUnionType(types, UnionReduction.Subtype));
991-
return isRestParameter ? checker.createArrayType(type) : type;
992-
}
993-
return undefined;
995+
const type = unifyTypes(types);
996+
return isRestParameter ? checker.createArrayType(type) : type;
994997
}
995998

996-
function getSignatureFromCalls(calls: CallUsage[]): Signature {
999+
function getSignatureFromCalls(calls: CallUsage[], fallbackReturn: Type): Signature {
9971000
const parameters: Symbol[] = [];
9981001
const length = Math.max(...calls.map(c => c.argumentTypes.length));
9991002
for (let i = 0; i < length; i++) {
10001003
const symbol = checker.createSymbol(SymbolFlags.FunctionScopedVariable, escapeLeadingUnderscores(`arg${i}`));
1001-
symbol.type = unifyFromUsage(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType()));
1004+
symbol.type = unifyTypes(calls.map(call => call.argumentTypes[i] || checker.getUndefinedType()));
10021005
if (calls.some(call => call.argumentTypes[i] === undefined)) {
10031006
symbol.flags |= SymbolFlags.Optional;
10041007
}
10051008
parameters.push(symbol);
10061009
}
1007-
const returnType = unifyFromUsage(inferFromUsage(combineUsages(calls.map(call => call.return_))), checker.getVoidType());
1010+
const returnType = unifyTypes(inferFromUsage(combineUsages(calls.map(call => call.return_))), fallbackReturn);
10081011
// TODO: GH#18217
10091012
return checker.createSignature(/*declaration*/ undefined!, /*typeParameters*/ undefined, /*thisParameter*/ undefined, parameters, returnType, /*typePredicate*/ undefined, length, /*hasRestParameter*/ false, /*hasLiteralTypes*/ false);
10101013
}

tests/cases/fourslash/codeFixInferFromFunctionUsage.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
// @noImplicitAny: true
44
////function wrap( [| arr |] ) {
5-
//// arr.sort(function (a: number, b: number) { return a < b ? -1 : 1 })
5+
//// arr.other(function (a: number, b: number) { return a < b ? -1 : 1 })
66
//// }
77

88
// https://github.com/Microsoft/TypeScript/issues/29330
9-
verify.rangeAfterCodeFix("arr: { sort: (arg0: (a: number, b: number) => 1 | -1) => void; }");
9+
verify.rangeAfterCodeFix("arr: { other: (arg0: (a: number, b: number) => 1 | -1) => void; }");
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
// @noImplicitAny: true
4+
////function wrap( [| s |] ) {
5+
//// return s.length + s.toUpperCase()
6+
//// }
7+
8+
// https://github.com/Microsoft/TypeScript/issues/29330
9+
verify.rangeAfterCodeFix("s: string");

tests/cases/fourslash/codeFixInferFromUsageCallBodyBoth.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/// <reference path='fourslash.ts' />
22

33
////class C {
4-
////
4+
//// p = 2
55
////}
66
////var c = new C()
77
////function f([|x, y |]) {

0 commit comments

Comments
 (0)