Skip to content

Commit 6d7fa63

Browse files
committed
Simplest type inference works: id for tuples
```ts tupleId: <...T>(x:...T) => ...T; let t = tupleId(["foo", 1]); let u: [number, string] = t; ``` Some small cleanups scattered around too.
1 parent 5fab1b3 commit 6d7fa63

File tree

4 files changed

+133
-58
lines changed

4 files changed

+133
-58
lines changed

src/compiler/checker.ts

Lines changed: 73 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3952,6 +3952,9 @@ namespace ts {
39523952
if (type.flags & TypeFlags.Reference && (<TypeReference>type).target === globalArrayType) {
39533953
return (<TypeReference>type).typeArguments[0];
39543954
}
3955+
else if (type.flags & TypeFlags.TupleKind) {
3956+
return type;
3957+
}
39553958
}
39563959
return anyType;
39573960
}
@@ -5823,8 +5826,8 @@ namespace ts {
58235826
return !!getPropertyOfType(type, "0");
58245827
}
58255828

5826-
function isStringLiteralType(type: Type) {
5827-
return type.flags & TypeFlags.StringLiteral;
5829+
function isStringLiteralType(type: Type): boolean {
5830+
return !!(type.flags & TypeFlags.StringLiteral);
58285831
}
58295832

58305833
/**
@@ -6059,30 +6062,55 @@ namespace ts {
60596062
return;
60606063
}
60616064

6062-
const typeParameters = context.typeParameters;
6063-
for (let i = 0; i < typeParameters.length; i++) {
6064-
if (target === typeParameters[i]) {
6065-
const inferences = context.inferences[i];
6066-
if (!inferences.isFixed) {
6067-
// Any inferences that are made to a type parameter in a union type are inferior
6068-
// to inferences made to a flat (non-union) type. This is because if we infer to
6069-
// T | string[], we really don't know if we should be inferring to T or not (because
6070-
// the correct constituent on the target side could be string[]). Therefore, we put
6071-
// such inferior inferences into a secondary bucket, and only use them if the primary
6072-
// bucket is empty.
6073-
const candidates = inferiority ?
6074-
inferences.secondary || (inferences.secondary = []) :
6075-
inferences.primary || (inferences.primary = []);
6076-
if (!contains(candidates, source)) {
6077-
candidates.push(source);
6078-
}
6065+
const i = indexOf(context.typeParameters, target);
6066+
if (i > -1) {
6067+
const inferences = context.inferences[i];
6068+
if (!inferences.isFixed) {
6069+
// Any inferences that are made to a type parameter in a union type are inferior
6070+
// to inferences made to a flat (non-union) type. This is because if we infer to
6071+
// T | string[], we really don't know if we should be inferring to T or not (because
6072+
// the correct constituent on the target side could be string[]). Therefore, we put
6073+
// such inferior inferences into a secondary bucket, and only use them if the primary
6074+
// bucket is empty.
6075+
const candidates = inferiority ?
6076+
inferences.secondary || (inferences.secondary = []) :
6077+
inferences.primary || (inferences.primary = []);
6078+
if (!contains(candidates, source)) {
6079+
candidates.push(source);
60796080
}
6080-
return;
60816081
}
6082+
return;
60826083
}
60836084
}
60846085
else if (target.flags & TypeFlags.TupleKind) {
6085-
// TODO: Some stuff here. It should look a LOT like the previous branch, so maybe just merge the two.
6086+
// TODO: Source should be a tuple for us to infer anything.
6087+
// If it's a tuple, I think the code will be nearly the same as the previous branch.
6088+
6089+
// If target is a type parameter, make an inference if the source type is a tuple-like type.
6090+
// If it's not, hopefully it will get fixed to a tuple and the second (nth?) round of inference will succeed.
6091+
if (!isTupleLikeType(source)) {
6092+
return;
6093+
}
6094+
6095+
const i = indexOf(context.typeParameters, target);
6096+
if (i > -1) {
6097+
const inferences = context.inferences[i];
6098+
if (!inferences.isFixed) {
6099+
// Any inferences that are made to a type parameter in a union type are inferior
6100+
// to inferences made to a flat (non-union) type. This is because if we infer to
6101+
// T | string[], we really don't know if we should be inferring to T or not (because
6102+
// the correct constituent on the target side could be string[]). Therefore, we put
6103+
// such inferior inferences into a secondary bucket, and only use them if the primary
6104+
// bucket is empty.
6105+
const candidates = inferiority ?
6106+
inferences.secondary || (inferences.secondary = []) :
6107+
inferences.primary || (inferences.primary = []);
6108+
if (!contains(candidates, source)) {
6109+
candidates.push(source);
6110+
}
6111+
}
6112+
return;
6113+
}
60866114
}
60876115
else if (source.flags & TypeFlags.Reference && target.flags & TypeFlags.Reference && (<TypeReference>source).target === (<TypeReference>target).target) {
60886116
// If source and target are references to the same generic type, infer from type arguments
@@ -6111,10 +6139,10 @@ namespace ts {
61116139
typeParameter = <TypeParameter>t;
61126140
typeParameterCount++;
61136141
}
6114-
else if(t.flags & TypeFlags.TupleKind) {
6142+
else if (t.flags & TypeFlags.TupleKind) {
61156143
// TODO: freak out and throw up our hands!! (I don't think spreading kinds across unions should be in v1 -- this should be checked elsewhere)
61166144
// For now, throw a useless string.
6117-
throw 'oh no!';
6145+
throw "oh no!";
61186146
}
61196147
else {
61206148
inferFromTypes(source, t);
@@ -7120,13 +7148,20 @@ namespace ts {
71207148
return applyToContextualType(type, t => getIndexTypeOfStructuredType(t, kind));
71217149
}
71227150

7151+
function contextualTypeIs (typePredicate: (t: Type) => boolean, t: Type): boolean {
7152+
return !!(t.flags & TypeFlags.Union ? forEach((<UnionType>t).types, typePredicate) : typePredicate(t));
7153+
}
7154+
71237155
function contextualTypeIsStringLiteralType(type: Type): boolean {
7124-
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, isStringLiteralType) : isStringLiteralType(type));
7156+
return contextualTypeIs(isStringLiteralType, type);
71257157
}
71267158

7127-
// Return true if the given contextual type is a tuple-like type
71287159
function contextualTypeIsTupleLikeType(type: Type): boolean {
7129-
return !!(type.flags & TypeFlags.Union ? forEach((<UnionType>type).types, isTupleLikeType) : isTupleLikeType(type));
7160+
return contextualTypeIs(isTupleLikeType, type);
7161+
}
7162+
7163+
function contextualTypeIsTupleKind(type: Type): boolean {
7164+
return contextualTypeIs(isTupleKind, type);
71307165
}
71317166

71327167
// Return true if the given contextual type provides an index signature of the given kind
@@ -7461,6 +7496,12 @@ namespace ts {
74617496
return createTupleType(elementTypes);
74627497
}
74637498
}
7499+
else if (contextualType && contextualTypeIsTupleKind(contextualType)) {
7500+
// TODO: Handle contextual typing by a binding pattern
7501+
if (elementTypes.length) {
7502+
return createTupleType(elementTypes);
7503+
}
7504+
}
74647505
}
74657506
return createArrayType(elementTypes.length ? getUnionType(elementTypes) : undefinedType);
74667507
}
@@ -8615,11 +8656,11 @@ namespace ts {
86158656

86168657
// On this call to inferTypeArguments, we may get more inferences for certain type parameters that were not
86178658
// fixed last time. This means that a type parameter that failed inference last time may succeed this time,
8618-
// or vice versa. Therefore, the failedTypeParameterIndex is useless if it points to an unfixed type parameter,
8619-
// because it may change. So here we reset it. However, getInferredType will not revisit any type parameters
8620-
// that were previously fixed. So if a fixed type parameter failed previously, it will fail again because
8621-
// it will contain the exact same set of inferences. So if we reset the index from a fixed type parameter,
8622-
// we will lose information that we won't recover this time around.
8659+
// or vice versa. Therefore, `failedTypeParameterIndex` is useless if it points to an unfixed type parameter,
8660+
// because it may change. So here we reset it if it's already fixed: getInferredType will not revisit any type parameters
8661+
// that were previously fixed. If a fixed type parameter failed previously, it will fail again because
8662+
// it will contain the exact same set of inferences. What's worse, resetting a fixed type parameter means that
8663+
// we would lose information that we won't recover this time around.
86238664
if (context.failedTypeParameterIndex !== undefined && !context.inferences[context.failedTypeParameterIndex].isFixed) {
86248665
context.failedTypeParameterIndex = undefined;
86258666
}
@@ -9172,7 +9213,7 @@ namespace ts {
91729213
}
91739214

91749215
// No signature was applicable. We have already reported the errors for the invalid signature.
9175-
// If this is a type resolution session, e.g. Language Service, try to get better information that anySignature.
9216+
// If this is a type resolution session, e.g. Language Service, try to get better information than anySignature.
91769217
// Pick the first candidate that matches the arity. This way we can get a contextual type for cases like:
91779218
// declare function f(a: { xa: number; xb: number; });
91789219
// f({ |
Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,32 @@
1-
tests/cases/compiler/tupleKinds.ts(7,5): error TS2322: Type '{}' is not assignable to type '[number, string, boolean, C]'.
1+
tests/cases/compiler/tupleKinds.ts(15,5): error TS2322: Type '{}' is not assignable to type '[number, string, boolean, C]'.
22
Property '0' is missing in type '{}'.
33

44

55
==== tests/cases/compiler/tupleKinds.ts (1 errors) ====
6+
function tupleId<...V>(y:...V): ...V {
7+
return y;
8+
}
69
function tuple<...T>(...args:...T): ...T {
710
return args;
811
}
912

13+
let inferredTupleId: [number, string, boolean] = tupleId([1, "foo", false]);
14+
1015
class C { }
11-
let inferred = tuple(1, "foo", false, new C());
12-
let match: [number, string, boolean, C] = tuple(1, "foo", false, new C());
13-
~~~~~
16+
let acceptType = tuple(4, "qux", false, new C());
17+
// TODO: Negative cases don't fail yet when you supply complete type arguments
18+
// TODO: Other negative cases
19+
let typeArguments: [number, string, boolean, C] = tuple<[number, string, boolean, C]>(5, "quack", false, new C());
20+
let inferred: [number, string, boolean, C] = tuple(6, "sequim", false, new C());
21+
~~~~~~~~
1422
!!! error TS2322: Type '{}' is not assignable to type '[number, string, boolean, C]'.
1523
!!! error TS2322: Property '0' is missing in type '{}'.
16-
function notSupportedYet<...T>(tuple: number | ...T): number {
17-
if(typeof tuple === 'number') {
18-
return tuple;
19-
}
20-
else {
21-
return -1;
22-
}
24+
25+
function spreadIntoUnionNotSupportedYet<...T>(tuple: number | ...T): number {
26+
if(typeof tuple === 'number') {
27+
return tuple;
28+
}
29+
else {
30+
return -1;
31+
}
2332
}

tests/baselines/reference/tupleKinds.js

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,52 @@
11
//// [tupleKinds.ts]
2+
function tupleId<...V>(y:...V): ...V {
3+
return y;
4+
}
25
function tuple<...T>(...args:...T): ...T {
36
return args;
47
}
58

9+
let inferredTupleId: [number, string, boolean] = tupleId([1, "foo", false]);
10+
611
class C { }
7-
let inferred = tuple(1, "foo", false, new C());
8-
let match: [number, string, boolean, C] = tuple(1, "foo", false, new C());
9-
function notSupportedYet<...T>(tuple: number | ...T): number {
10-
if(typeof tuple === 'number') {
11-
return tuple;
12-
}
13-
else {
14-
return -1;
15-
}
12+
let acceptType = tuple(4, "qux", false, new C());
13+
// TODO: Negative cases don't fail yet when you supply complete type arguments
14+
// TODO: Other negative cases
15+
let typeArguments: [number, string, boolean, C] = tuple<[number, string, boolean, C]>(5, "quack", false, new C());
16+
let inferred: [number, string, boolean, C] = tuple(6, "sequim", false, new C());
17+
18+
function spreadIntoUnionNotSupportedYet<...T>(tuple: number | ...T): number {
19+
if(typeof tuple === 'number') {
20+
return tuple;
21+
}
22+
else {
23+
return -1;
24+
}
1625
}
1726

1827
//// [tupleKinds.js]
28+
function tupleId(y) {
29+
return y;
30+
}
1931
function tuple() {
2032
var args = [];
2133
for (var _i = 0; _i < arguments.length; _i++) {
2234
args[_i - 0] = arguments[_i];
2335
}
2436
return args;
2537
}
38+
var inferredTupleId = tupleId([1, "foo", false]);
2639
var C = (function () {
2740
function C() {
2841
}
2942
return C;
3043
})();
31-
var inferred = tuple(1, "foo", false, new C());
32-
var match = tuple(1, "foo", false, new C());
33-
function notSupportedYet(tuple) {
44+
var acceptType = tuple(4, "qux", false, new C());
45+
// TODO: Negative cases don't fail yet when you supply complete type arguments
46+
// TODO: Other negative cases
47+
var typeArguments = tuple(5, "quack", false, new C());
48+
var inferred = tuple(6, "sequim", false, new C());
49+
function spreadIntoUnionNotSupportedYet(tuple) {
3450
if (typeof tuple === 'number') {
3551
return tuple;
3652
}

tests/cases/compiler/tupleKinds.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1+
function tupleId<...V>(y:...V): ...V {
2+
return y;
3+
}
14
function tuple<...T>(...args:...T): ...T {
25
return args;
36
}
47

8+
let inferredTupleId: [number, string, boolean] = tupleId([1, "foo", false]);
9+
510
class C { }
6-
let inferred = tuple(1, "foo", false, new C());
7-
let match: [number, string, boolean, C] = tuple(1, "foo", false, new C());
8-
function notSupportedYet<...T>(tuple: number | ...T): number {
11+
let acceptType = tuple(4, "qux", false, new C());
12+
// TODO: Negative cases don't fail yet when you supply complete type arguments
13+
// TODO: Other negative cases
14+
let typeArguments: [number, string, boolean, C] = tuple<[number, string, boolean, C]>(5, "quack", false, new C());
15+
let inferred: [number, string, boolean, C] = tuple(6, "sequim", false, new C());
16+
17+
function spreadIntoUnionNotSupportedYet<...T>(tuple: number | ...T): number {
918
if(typeof tuple === 'number') {
1019
return tuple;
1120
}

0 commit comments

Comments
 (0)