Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix inference of type arguments when source is 'awaited' #37540

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 37 additions & 14 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -929,7 +929,7 @@ namespace ts {

let _jsxNamespace: __String;
let _jsxFactoryEntity: EntityName | undefined;
let outofbandVarianceMarkerHandler: ((onlyUnreliable: boolean) => void) | undefined;
let outofbandVarianceMarkerHandler: ((variance: VarianceFlags) => void) | undefined;

const subtypeRelation = createMap<RelationComparisonResult>();
const strictSubtypeRelation = createMap<RelationComparisonResult>();
Expand Down Expand Up @@ -15797,6 +15797,9 @@ namespace ts {
if (saved & RelationComparisonResult.ReportsUnreliable) {
instantiateType(source, makeFunctionTypeMapper(reportUnreliableMarkers));
}
if (saved & RelationComparisonResult.ReportsAwaited) {
instantiateType(source, makeFunctionTypeMapper(reportAwaitedMarkers));
}
}
return entry & RelationComparisonResult.Succeeded ? Ternary.True : Ternary.False;
}
Expand Down Expand Up @@ -15831,10 +15834,12 @@ namespace ts {
let propagatingVarianceFlags: RelationComparisonResult = 0;
if (outofbandVarianceMarkerHandler) {
originalHandler = outofbandVarianceMarkerHandler;
outofbandVarianceMarkerHandler = onlyUnreliable => {
outofbandVarianceMarkerHandler = variance => {
propagatingVarianceFlags |=
onlyUnreliable ? RelationComparisonResult.ReportsUnreliable : RelationComparisonResult.ReportsUnmeasurable;
return originalHandler!(onlyUnreliable);
variance === VarianceFlags.Awaited ? RelationComparisonResult.ReportsAwaited :
variance === VarianceFlags.Unreliable ? RelationComparisonResult.ReportsUnreliable :
RelationComparisonResult.ReportsUnmeasurable;
return originalHandler!(variance);
};
}
const result = expandingFlags !== ExpandingFlags.Both ? structuredTypeRelatedTo(source, target, reportErrors, intersectionState) : Ternary.Maybe;
Expand Down Expand Up @@ -15966,7 +15971,7 @@ namespace ts {
}
else if (target.flags & TypeFlags.Awaited && source.flags & TypeFlags.Awaited) {
const targetType = (<AwaitedType>target).awaitedType;
const sourceType = instantiateType((<AwaitedType>source).awaitedType, makeFunctionTypeMapper(reportUnreliableMarkers));
const sourceType = instantiateType((<AwaitedType>source).awaitedType, makeFunctionTypeMapper(reportAwaitedMarkers));
// An `awaited S` is related to an `awaited T` if `S` is related to `T`:
//
// S <: T ⇒ awaited S <: awaited T
Expand Down Expand Up @@ -16250,14 +16255,21 @@ namespace ts {

function reportUnmeasurableMarkers(p: TypeParameter) {
if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
outofbandVarianceMarkerHandler(/*onlyUnreliable*/ false);
outofbandVarianceMarkerHandler(VarianceFlags.Unmeasurable);
}
return p;
}

function reportUnreliableMarkers(p: TypeParameter) {
if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
outofbandVarianceMarkerHandler(/*onlyUnreliable*/ true);
outofbandVarianceMarkerHandler(VarianceFlags.Unreliable);
}
return p;
}

function reportAwaitedMarkers(p: TypeParameter) {
if (outofbandVarianceMarkerHandler && (p === markerSuperType || p === markerSubType || p === markerOtherType)) {
outofbandVarianceMarkerHandler(VarianceFlags.Awaited);
}
return p;
}
Expand Down Expand Up @@ -16971,8 +16983,12 @@ namespace ts {
for (const tp of typeParameters) {
let unmeasurable = false;
let unreliable = false;
let awaited = false;
const oldHandler = outofbandVarianceMarkerHandler;
outofbandVarianceMarkerHandler = (onlyUnreliable) => onlyUnreliable ? unreliable = true : unmeasurable = true;
outofbandVarianceMarkerHandler = (variance) =>
variance === VarianceFlags.Awaited ? awaited = true :
variance === VarianceFlags.Unreliable ? unreliable = true :
unmeasurable = true;
// We first compare instantiations where the type parameter is replaced with
// marker types that have a known subtype relationship. From this we can infer
// invariance, covariance, contravariance or bivariance.
Expand All @@ -16994,6 +17010,9 @@ namespace ts {
if (unreliable) {
variance |= VarianceFlags.Unreliable;
}
if (awaited) {
variance |= VarianceFlags.Awaited;
}
variances.push(variance);
}
cache.variances = variances;
Expand Down Expand Up @@ -18199,10 +18218,10 @@ namespace ts {
propagationType = savePropagationType;
return;
}
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) {
if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol &&
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol))) {
// Source and target are types originating in the same generic type alias declaration.
// Simply infer from source type arguments to target type arguments.
inferFromTypeArguments(source.aliasTypeArguments, target.aliasTypeArguments!, getAliasVariances(source.aliasSymbol));
return;
}
if (source === target && source.flags & TypeFlags.UnionOrIntersection) {
Expand Down Expand Up @@ -18322,9 +18341,9 @@ namespace ts {
}
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
!((<TypeReference>source).node && (<TypeReference>target).node)) {
!((<TypeReference>source).node && (<TypeReference>target).node) &&
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target))) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
}
else if (source.flags & TypeFlags.Index && target.flags & TypeFlags.Index) {
contravariant = !contravariant;
Expand Down Expand Up @@ -18444,6 +18463,9 @@ namespace ts {
}

function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) {
if (some(variances, variance => !!(variance & VarianceFlags.Awaited))) {
return false;
}
const count = sourceTypes.length < targetTypes.length ? sourceTypes.length : targetTypes.length;
for (let i = 0; i < count; i++) {
if (i < variances.length && (variances[i] & VarianceFlags.VarianceMask) === VarianceFlags.Contravariant) {
Expand All @@ -18453,6 +18475,7 @@ namespace ts {
inferFromTypes(sourceTypes[i], targetTypes[i]);
}
}
return true;
}

function inferFromContravariantTypes(source: Type, target: Type) {
Expand Down Expand Up @@ -18658,9 +18681,9 @@ namespace ts {

function inferFromObjectTypesWorker(source: Type, target: Type) {
if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target))) {
(<TypeReference>source).target === (<TypeReference>target).target || isArrayType(source) && isArrayType(target)) &&
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target))) {
// If source and target are references to the same generic type, infer from type arguments
inferFromTypeArguments(getTypeArguments(<TypeReference>source), getTypeArguments(<TypeReference>target), getVariances((<TypeReference>source).target));
return;
}
if (isGenericMappedType(source) && isGenericMappedType(target)) {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1231,7 +1231,7 @@ namespace ts {
enableCPUProfiler,
disableCPUProfiler,
realpath,
debugMode: some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
debugMode: !!process.env.NODE_INSPECTOR_IPC || some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
tryEnableSourceMapsForHost() {
try {
require("source-map-support").install();
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,8 @@ namespace ts {

ReportsUnmeasurable = 1 << 3,
ReportsUnreliable = 1 << 4,
ReportsMask = ReportsUnmeasurable | ReportsUnreliable
ReportsAwaited = 1 << 5,
ReportsMask = ReportsUnmeasurable | ReportsUnreliable | ReportsAwaited
}

export interface Node extends TextRange {
Expand Down Expand Up @@ -4608,7 +4609,8 @@ namespace ts {
VarianceMask = Invariant | Covariant | Contravariant | Independent, // Mask containing all measured variances without the unmeasurable flag
Unmeasurable = 1 << 3, // Variance result is unusable - relationship relies on structural comparisons which are not reflected in generic relationships
Unreliable = 1 << 4, // Variance result is unreliable - checking may produce false negatives, but not false positives
AllowsStructuralFallback = Unmeasurable | Unreliable,
Awaited = 1 << 5, // type argument is awaited
AllowsStructuralFallback = Unmeasurable | Unreliable | Awaited,
}

// Generic class and interface types
Expand Down
6 changes: 3 additions & 3 deletions tests/baselines/reference/awaited.types
Original file line number Diff line number Diff line change
Expand Up @@ -391,15 +391,15 @@ f5(makePromise(1)).then(x => x);

f5(makePromise(makePromise(1))).then(x => x);
>f5(makePromise(makePromise(1))).then(x => x) : Promise<number>
>f5(makePromise(makePromise(1))).then : <TResult1 = Promise<number>, TResult2 = never>(onfulfilled?: (value: number) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<awaited TResult1 | awaited TResult2>
>f5(makePromise(makePromise(1))) : Promise<Promise<number>>
>f5(makePromise(makePromise(1))).then : <TResult1 = number, TResult2 = never>(onfulfilled?: (value: number) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<awaited TResult1 | awaited TResult2>
>f5(makePromise(makePromise(1))) : Promise<number>
>f5 : <U>(u: Promise<U>) => Promise<U>
>makePromise(makePromise(1)) : Promise<Promise<number>>
>makePromise : <T>(x: T) => Promise<T>
>makePromise(1) : Promise<number>
>makePromise : <T>(x: T) => Promise<T>
>1 : 1
>then : <TResult1 = Promise<number>, TResult2 = never>(onfulfilled?: (value: number) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<awaited TResult1 | awaited TResult2>
>then : <TResult1 = number, TResult2 = never>(onfulfilled?: (value: number) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<awaited TResult1 | awaited TResult2>
>x => x : (x: number) => number
>x : number
>x : number
Expand Down
11 changes: 10 additions & 1 deletion tests/baselines/reference/awaitedInference.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@ function f<T>() {
}
const x = f<number>(); // number
const y = f<Promise<number>>(); // number ?
const z = f<Promise<number> | number>(); // number ?
const z = f<Promise<number> | number>(); // number ?

// https://github.com/microsoft/TypeScript/issues/37526
function f1<T>(a: T): Promise<awaited T> {
return new Promise(r => r(a));
}

//// [awaitedInference.js]
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
Expand Down Expand Up @@ -62,3 +67,7 @@ function f() {
var x = f(); // number
var y = f(); // number ?
var z = f(); // number ?
// https://github.com/microsoft/TypeScript/issues/37526
function f1(a) {
return new Promise(function (r) { return r(a); });
}
23 changes: 19 additions & 4 deletions tests/baselines/reference/awaitedInference.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@ type UnwrapAwaited<T> = T extends awaited infer Inner ? Inner : T;
type Result1 = UnwrapAwaited<awaited Promise<number>>; // number
>Result1 : Symbol(Result1, Decl(awaitedInference.ts, 4, 66))
>UnwrapAwaited : Symbol(UnwrapAwaited, Decl(awaitedInference.ts, 2, 36))
>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, --, --))

type Result2 = UnwrapAwaited<awaited Promise<number> | number>; // number
>Result2 : Symbol(Result2, Decl(awaitedInference.ts, 5, 54))
>UnwrapAwaited : Symbol(UnwrapAwaited, Decl(awaitedInference.ts, 2, 36))
>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, --, --))

function f<T>() {
>f : Symbol(f, Decl(awaitedInference.ts, 6, 63))
Expand All @@ -58,10 +58,25 @@ const x = f<number>(); // number
const y = f<Promise<number>>(); // number ?
>y : Symbol(y, Decl(awaitedInference.ts, 13, 5))
>f : Symbol(f, Decl(awaitedInference.ts, 6, 63))
>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, --, --))

const z = f<Promise<number> | number>(); // number ?
>z : Symbol(z, Decl(awaitedInference.ts, 14, 5))
>f : Symbol(f, Decl(awaitedInference.ts, 6, 63))
>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, --, --))

// https://github.com/microsoft/TypeScript/issues/37526
function f1<T>(a: T): Promise<awaited T> {
>f1 : Symbol(f1, Decl(awaitedInference.ts, 14, 40))
>T : Symbol(T, Decl(awaitedInference.ts, 17, 12))
>a : Symbol(a, Decl(awaitedInference.ts, 17, 15))
>T : Symbol(T, Decl(awaitedInference.ts, 17, 12))
>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, --, --))
>T : Symbol(T, Decl(awaitedInference.ts, 17, 12))

return new Promise(r => r(a));
>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, --, --))
>r : Symbol(r, Decl(awaitedInference.ts, 18, 23))
>r : Symbol(r, Decl(awaitedInference.ts, 18, 23))
>a : Symbol(a, Decl(awaitedInference.ts, 17, 15))
}
14 changes: 14 additions & 0 deletions tests/baselines/reference/awaitedInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,17 @@ const z = f<Promise<number> | number>(); // number ?
>f<Promise<number> | number>() : [number | Promise<number>, number]
>f : <T>() => [UnwrapAwaited<T>, UnwrapAwaited<awaited T>]

// https://github.com/microsoft/TypeScript/issues/37526
function f1<T>(a: T): Promise<awaited T> {
>f1 : <T>(a: T) => Promise<awaited T>
>a : T

return new Promise(r => r(a));
>new Promise(r => r(a)) : Promise<T>
>Promise : PromiseConstructor
>r => r(a) : (r: (value?: T | awaited T | PromiseLike<T>) => void) => void
>r : (value?: T | awaited T | PromiseLike<T>) => void
>r(a) : void
>r : (value?: T | awaited T | PromiseLike<T>) => void
>a : T
}
9 changes: 8 additions & 1 deletion tests/cases/conformance/types/awaited/awaitedInference.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// @lib: es2015

declare function foo<T>(f: () => PromiseLike<T>, x: T): void;
declare const nullOrNumber: number | null;
foo(async () => nullOrNumber, null);
Expand All @@ -12,4 +14,9 @@ function f<T>() {
}
const x = f<number>(); // number
const y = f<Promise<number>>(); // number ?
const z = f<Promise<number> | number>(); // number ?
const z = f<Promise<number> | number>(); // number ?

// https://github.com/microsoft/TypeScript/issues/37526
function f1<T>(a: T): Promise<awaited T> {
return new Promise(r => r(a));
}