Merge pull request #32919 from microsoft/fix32752

Stricter criteria for eliminating types in unions during inference
This commit is contained in:
Anders Hejlsberg 2019-08-16 10:55:48 -07:00 committed by GitHub
commit 489abcacd4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 297 additions and 49 deletions

View File

@ -15487,8 +15487,7 @@ namespace ts {
let visited: Map<number>;
let bivariant = false;
let propagationType: Type;
let inferenceMatch = false;
let inferenceIncomplete = false;
let inferencePriority = InferencePriority.MaxValue;
let allowComplexConstraintInference = true;
inferFromTypes(originalSource, originalTarget);
@ -15601,7 +15600,7 @@ namespace ts {
clearCachedInferences(inferences);
}
}
inferenceMatch = true;
inferencePriority = Math.min(inferencePriority, priority);
return;
}
else {
@ -15695,19 +15694,15 @@ namespace ts {
const key = source.id + "," + target.id;
const status = visited && visited.get(key);
if (status !== undefined) {
if (status & 1) inferenceMatch = true;
if (status & 2) inferenceIncomplete = true;
inferencePriority = Math.min(inferencePriority, status);
return;
}
(visited || (visited = createMap<number>())).set(key, 0);
const saveInferenceMatch = inferenceMatch;
const saveInferenceIncomplete = inferenceIncomplete;
inferenceMatch = false;
inferenceIncomplete = false;
(visited || (visited = createMap<number>())).set(key, InferencePriority.Circularity);
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
action(source, target);
visited.set(key, (inferenceMatch ? 1 : 0) | (inferenceIncomplete ? 2 : 0));
inferenceMatch = inferenceMatch || saveInferenceMatch;
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
visited.set(key, inferencePriority);
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
}
function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) {
@ -15779,10 +15774,11 @@ namespace ts {
let nakedTypeVariable: Type | undefined;
const sources = source.flags & TypeFlags.Union ? (<UnionType>source).types : [source];
const matched = new Array<boolean>(sources.length);
const saveInferenceIncomplete = inferenceIncomplete;
inferenceIncomplete = false;
let inferenceCircularity = false;
// First infer to types that are not naked type variables. For each source type we
// track whether inferences were made from that particular type to some target.
// track whether inferences were made from that particular type to some target with
// equal priority (i.e. of equal quality) to what we would infer for a naked type
// parameter.
for (const t of targets) {
if (getInferenceInfoForType(t)) {
nakedTypeVariable = t;
@ -15790,20 +15786,20 @@ namespace ts {
}
else {
for (let i = 0; i < sources.length; i++) {
const saveInferenceMatch = inferenceMatch;
inferenceMatch = false;
const saveInferencePriority = inferencePriority;
inferencePriority = InferencePriority.MaxValue;
inferFromTypes(sources[i], t);
if (inferenceMatch) matched[i] = true;
inferenceMatch = inferenceMatch || saveInferenceMatch;
if (inferencePriority === priority) matched[i] = true;
inferenceCircularity = inferenceCircularity || inferencePriority === InferencePriority.Circularity;
inferencePriority = Math.min(inferencePriority, saveInferencePriority);
}
}
}
const inferenceComplete = !inferenceIncomplete;
inferenceIncomplete = inferenceIncomplete || saveInferenceIncomplete;
// If the target has a single naked type variable and inference completed (meaning we
// explored the types fully), create a union of the source types from which no inferences
// have been made so far and infer from that union to the naked type variable.
if (typeVariableCount === 1 && inferenceComplete) {
// If the target has a single naked type variable and no inference circularities were
// encountered above (meaning we explored the types fully), create a union of the source
// types from which no inferences have been made so far and infer from that union to the
// naked type variable.
if (typeVariableCount === 1 && !inferenceCircularity) {
const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s);
if (unmatched.length) {
inferFromTypes(getUnionType(unmatched), nakedTypeVariable!);
@ -15906,7 +15902,7 @@ namespace ts {
const symbol = isNonConstructorObject ? target.symbol : undefined;
if (symbol) {
if (contains(symbolStack, symbol)) {
inferenceIncomplete = true;
inferencePriority = InferencePriority.Circularity;
return;
}
(symbolStack || (symbolStack = [])).push(symbol);

View File

@ -4502,8 +4502,10 @@ namespace ts {
LiteralKeyof = 1 << 5, // Inference made from a string literal to a keyof T
NoConstraints = 1 << 6, // Don't infer from constraints of instantiable types
AlwaysStrict = 1 << 7, // Always use strict rules for contravariant inferences
MaxValue = 1 << 8, // Seed for inference priority tracking
PriorityImpliesCombination = ReturnType | MappedTypeConstraint | LiteralKeyof, // These priorities imply that the resulting type should be a combination of all candidates
Circularity = -1, // Inference circularity (value less than all other priorities)
}
/* @internal */

View File

@ -2442,7 +2442,9 @@ declare namespace ts {
LiteralKeyof = 32,
NoConstraints = 64,
AlwaysStrict = 128,
PriorityImpliesCombination = 56
MaxValue = 256,
PriorityImpliesCombination = 56,
Circularity = -1
}
/** @deprecated Use FileExtensionInfo instead. */
export type JsFileExtensionInfo = FileExtensionInfo;

View File

@ -2442,7 +2442,9 @@ declare namespace ts {
LiteralKeyof = 32,
NoConstraints = 64,
AlwaysStrict = 128,
PriorityImpliesCombination = 56
MaxValue = 256,
PriorityImpliesCombination = 56,
Circularity = -1
}
/** @deprecated Use FileExtensionInfo instead. */
export type JsFileExtensionInfo = FileExtensionInfo;

View File

@ -58,4 +58,30 @@ tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference
declare function bar<T>(x: T, y: string | T): T;
const y = bar(1, 2);
// Repro from #32752
const containsPromises: unique symbol = Symbol();
type DeepPromised<T> =
{ [containsPromises]?: true } &
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
async function fun<T>(deepPromised: DeepPromised<T>) {
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
for (const value of Object.values(deepPromisedWithIndexer)) {
const awaitedValue = await value;
if (awaitedValue)
await fun(awaitedValue);
}
}
// Repro from #32752
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
declare function baz<T>(dp: Deep<T>): T;
declare let xx: { a: string | undefined };
baz(xx);

View File

@ -50,29 +50,64 @@ foo(x);
declare function bar<T>(x: T, y: string | T): T;
const y = bar(1, 2);
// Repro from #32752
const containsPromises: unique symbol = Symbol();
type DeepPromised<T> =
{ [containsPromises]?: true } &
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
async function fun<T>(deepPromised: DeepPromised<T>) {
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
for (const value of Object.values(deepPromisedWithIndexer)) {
const awaitedValue = await value;
if (awaitedValue)
await fun(awaitedValue);
}
}
// Repro from #32752
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
declare function baz<T>(dp: Deep<T>): T;
declare let xx: { a: string | undefined };
baz(xx);
//// [unionTypeInference.js]
"use strict";
exports.__esModule = true;
var a1 = f1(1, 2); // 1 | 2
var a2 = f1(1, "hello"); // 1
var a3 = f1(1, sn); // number
var a4 = f1(undefined, "abc"); // undefined
var a5 = f1("foo", "bar"); // "foo"
var a6 = f1(true, false); // boolean
var a7 = f1("hello", 1); // Error
const a1 = f1(1, 2); // 1 | 2
const a2 = f1(1, "hello"); // 1
const a3 = f1(1, sn); // number
const a4 = f1(undefined, "abc"); // undefined
const a5 = f1("foo", "bar"); // "foo"
const a6 = f1(true, false); // boolean
const a7 = f1("hello", 1); // Error
var b1 = f2(["string", true]); // boolean
var c1 = f3(5); // 5
var c2 = f3(sn); // number
var c3 = f3(true); // true
var c4 = f3(b); // true
var c5 = f3("abc"); // never
var d1 = f4("abc");
var d2 = f4(s);
var d3 = f4(42); // Error
const c1 = f3(5); // 5
const c2 = f3(sn); // number
const c3 = f3(true); // true
const c4 = f3(b); // true
const c5 = f3("abc"); // never
const d1 = f4("abc");
const d2 = f4(s);
const d3 = f4(42); // Error
function qux(p1, p2) {
p1 = p2;
}
foo(x);
var y = bar(1, 2);
const y = bar(1, 2);
// Repro from #32752
const containsPromises = Symbol();
async function fun(deepPromised) {
const deepPromisedWithIndexer = deepPromised;
for (const value of Object.values(deepPromisedWithIndexer)) {
const awaitedValue = await value;
if (awaitedValue)
await fun(awaitedValue);
}
}
baz(xx);

View File

@ -163,12 +163,12 @@ declare function foo<T>(x: T | Promise<T>): void;
>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21))
>x : Symbol(x, Decl(unionTypeInference.ts, 45, 24))
>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21))
declare let x: false | Promise<true>;
>x : Symbol(x, Decl(unionTypeInference.ts, 46, 11))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
foo(x);
>foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1))
@ -187,3 +187,92 @@ const y = bar(1, 2);
>y : Symbol(y, Decl(unionTypeInference.ts, 50, 5))
>bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7))
// Repro from #32752
const containsPromises: unique symbol = Symbol();
>containsPromises : Symbol(containsPromises, Decl(unionTypeInference.ts, 54, 5))
>Symbol : Symbol(Symbol, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.symbol.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2019.symbol.d.ts, --, --))
type DeepPromised<T> =
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
{ [containsPromises]?: true } &
>[containsPromises] : Symbol([containsPromises], Decl(unionTypeInference.ts, 57, 5))
>containsPromises : Symbol(containsPromises, Decl(unionTypeInference.ts, 54, 5))
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --), Decl(lib.es2015.iterable.d.ts, --, --), Decl(lib.es2015.promise.d.ts, --, --), Decl(lib.es2015.symbol.wellknown.d.ts, --, --), Decl(lib.es2018.promise.d.ts, --, --))
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
>T : Symbol(T, Decl(unionTypeInference.ts, 56, 18))
>TKey : Symbol(TKey, Decl(unionTypeInference.ts, 58, 7))
async function fun<T>(deepPromised: DeepPromised<T>) {
>fun : Symbol(fun, Decl(unionTypeInference.ts, 58, 92))
>T : Symbol(T, Decl(unionTypeInference.ts, 60, 19))
>deepPromised : Symbol(deepPromised, Decl(unionTypeInference.ts, 60, 22))
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
>T : Symbol(T, Decl(unionTypeInference.ts, 60, 19))
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
>deepPromisedWithIndexer : Symbol(deepPromisedWithIndexer, Decl(unionTypeInference.ts, 61, 9))
>DeepPromised : Symbol(DeepPromised, Decl(unionTypeInference.ts, 54, 49))
>name : Symbol(name, Decl(unionTypeInference.ts, 61, 51))
>deepPromised : Symbol(deepPromised, Decl(unionTypeInference.ts, 60, 22))
for (const value of Object.values(deepPromisedWithIndexer)) {
>value : Symbol(value, Decl(unionTypeInference.ts, 62, 14))
>Object.values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>values : Symbol(ObjectConstructor.values, Decl(lib.es2017.object.d.ts, --, --), Decl(lib.es2017.object.d.ts, --, --))
>deepPromisedWithIndexer : Symbol(deepPromisedWithIndexer, Decl(unionTypeInference.ts, 61, 9))
const awaitedValue = await value;
>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13))
>value : Symbol(value, Decl(unionTypeInference.ts, 62, 14))
if (awaitedValue)
>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13))
await fun(awaitedValue);
>fun : Symbol(fun, Decl(unionTypeInference.ts, 58, 92))
>awaitedValue : Symbol(awaitedValue, Decl(unionTypeInference.ts, 63, 13))
}
}
// Repro from #32752
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1))
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18))
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18))
>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1))
>T : Symbol(T, Decl(unionTypeInference.ts, 71, 10))
>K : Symbol(K, Decl(unionTypeInference.ts, 71, 18))
declare function baz<T>(dp: Deep<T>): T;
>baz : Symbol(baz, Decl(unionTypeInference.ts, 71, 53))
>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21))
>dp : Symbol(dp, Decl(unionTypeInference.ts, 73, 24))
>Deep : Symbol(Deep, Decl(unionTypeInference.ts, 67, 1))
>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21))
>T : Symbol(T, Decl(unionTypeInference.ts, 73, 21))
declare let xx: { a: string | undefined };
>xx : Symbol(xx, Decl(unionTypeInference.ts, 74, 11))
>a : Symbol(a, Decl(unionTypeInference.ts, 74, 17))
baz(xx);
>baz : Symbol(baz, Decl(unionTypeInference.ts, 71, 53))
>xx : Symbol(xx, Decl(unionTypeInference.ts, 74, 11))

View File

@ -185,3 +185,72 @@ const y = bar(1, 2);
>1 : 1
>2 : 2
// Repro from #32752
const containsPromises: unique symbol = Symbol();
>containsPromises : unique symbol
>Symbol() : unique symbol
>Symbol : SymbolConstructor
type DeepPromised<T> =
>DeepPromised : DeepPromised<T>
{ [containsPromises]?: true } &
>[containsPromises] : true | undefined
>containsPromises : unique symbol
>true : true
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
async function fun<T>(deepPromised: DeepPromised<T>) {
>fun : <T>(deepPromised: DeepPromised<T>) => Promise<void>
>deepPromised : DeepPromised<T>
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
>deepPromisedWithIndexer : DeepPromised<{ [name: string]: {} | null | undefined; }>
>name : string
>null : null
>deepPromised : DeepPromised<T>
for (const value of Object.values(deepPromisedWithIndexer)) {
>value : {} | ({ [containsPromises]?: true | undefined; } & {}) | Promise<{ [containsPromises]?: true | undefined; } & {}> | null | undefined
>Object.values(deepPromisedWithIndexer) : ({} | ({ [containsPromises]?: true | undefined; } & {}) | Promise<{ [containsPromises]?: true | undefined; } & {}> | null | undefined)[]
>Object.values : { <T>(o: { [s: string]: T; } | ArrayLike<T>): T[]; (o: {}): any[]; }
>Object : ObjectConstructor
>values : { <T>(o: { [s: string]: T; } | ArrayLike<T>): T[]; (o: {}): any[]; }
>deepPromisedWithIndexer : DeepPromised<{ [name: string]: {} | null | undefined; }>
const awaitedValue = await value;
>awaitedValue : {} | ({ [containsPromises]?: true | undefined; } & {}) | null | undefined
>await value : {} | ({ [containsPromises]?: true | undefined; } & {}) | null | undefined
>value : {} | ({ [containsPromises]?: true | undefined; } & {}) | Promise<{ [containsPromises]?: true | undefined; } & {}> | null | undefined
if (awaitedValue)
>awaitedValue : {} | ({ [containsPromises]?: true | undefined; } & {}) | null | undefined
await fun(awaitedValue);
>await fun(awaitedValue) : void
>fun(awaitedValue) : Promise<void>
>fun : <T>(deepPromised: DeepPromised<T>) => Promise<void>
>awaitedValue : {} | ({ [containsPromises]?: true | undefined; } & {})
}
}
// Repro from #32752
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
>Deep : Deep<T>
declare function baz<T>(dp: Deep<T>): T;
>baz : <T>(dp: Deep<T>) => T
>dp : Deep<T>
declare let xx: { a: string | undefined };
>xx : { a: string | undefined; }
>a : string | undefined
baz(xx);
>baz(xx) : { a: string | undefined; }
>baz : <T>(dp: Deep<T>) => T
>xx : { a: string | undefined; }

View File

@ -1,4 +1,5 @@
// @strict: true
// @target: esnext
declare const b: boolean;
declare const s: string;
@ -51,3 +52,29 @@ foo(x);
declare function bar<T>(x: T, y: string | T): T;
const y = bar(1, 2);
// Repro from #32752
const containsPromises: unique symbol = Symbol();
type DeepPromised<T> =
{ [containsPromises]?: true } &
{ [TKey in keyof T]: T[TKey] | DeepPromised<T[TKey]> | Promise<DeepPromised<T[TKey]>> };
async function fun<T>(deepPromised: DeepPromised<T>) {
const deepPromisedWithIndexer: DeepPromised<{ [name: string]: {} | null | undefined }> = deepPromised;
for (const value of Object.values(deepPromisedWithIndexer)) {
const awaitedValue = await value;
if (awaitedValue)
await fun(awaitedValue);
}
}
// Repro from #32752
type Deep<T> = { [K in keyof T]: T[K] | Deep<T[K]> };
declare function baz<T>(dp: Deep<T>): T;
declare let xx: { a: string | undefined };
baz(xx);