From 43d3dc8e1741fb80ebd82495860362e0f8e0a0d9 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Sep 2019 16:44:07 -0700 Subject: [PATCH 1/5] Make lower priority inference when entire source is matched in target --- src/compiler/checker.ts | 56 ++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 32 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5d1739edb11bd..dd6afd18cf641 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15568,42 +15568,45 @@ namespace ts { return; } if (target.flags & TypeFlags.Union) { - if (source.flags & TypeFlags.Union) { - // First, infer between identically matching source and target constituents and remove the - // matching types. - const [tempSources, tempTargets] = inferFromMatchingTypes((source).types, (target).types, isTypeOrBaseIdenticalTo); - // Next, infer between closely matching source and target constituents and remove - // the matching types. Types closely match when they are instantiations of the same - // object type or instantiations of the same type alias. - const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); - if (sources.length === 0 || targets.length === 0) { - return; - } - source = getUnionType(sources); - target = getUnionType(targets); + // First, infer between identically matching source and target constituents and remove the + // matching types. + const [tempSources, tempTargets] = inferFromMatchingTypes(source.flags & TypeFlags.Union ? (source).types : [source], (target).types, isTypeOrBaseIdenticalTo); + // Next, infer between closely matching source and target constituents and remove + // the matching types. Types closely match when they are instantiations of the same + // object type or instantiations of the same type alias. + const [sources, targets] = inferFromMatchingTypes(tempSources, tempTargets, isTypeCloselyMatchedBy); + if (targets.length === 0) { + return; } - else { - if (inferFromMatchingType(source, (target).types, isTypeOrBaseIdenticalTo)) return; - if (inferFromMatchingType(source, (target).types, isTypeCloselyMatchedBy)) return; + target = getUnionType(targets); + if (sources.length === 0) { + // All source constituents have been matched and there is nothing further to infer from. + // However, simply making no inferences is undesirable because it could ultimately mean + // inferring a type parameter constraint. Instead, make a lower priority inference from + // the full source to whatever remains in the target. For example, when inferring from + // string to 'string | T', make a lower priority inference of string for T. + const savePriority = priority; + priority |= InferencePriority.NakedTypeVariable; + inferFromTypes(source, target); + priority = savePriority; + return; } + source = getUnionType(sources); } else if (target.flags & TypeFlags.Intersection && some((target).types, t => !!getInferenceInfoForType(t))) { // We reduce intersection types only when they contain naked type parameters. For example, when // inferring from 'string[] & { extra: any }' to 'string[] & T' we want to remove string[] and // infer { extra: any } for T. But when inferring to 'string[] & Iterable' we want to keep the // string[] on the source side and infer string for T. - if (source.flags & TypeFlags.Intersection) { + if (!(source.flags & TypeFlags.Union)) { // Infer between identically matching source and target constituents and remove the matching types. - const [sources, targets] = inferFromMatchingTypes((source).types, (target).types, isTypeIdenticalTo); + const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (source).types : [source], (target).types, isTypeIdenticalTo); if (sources.length === 0 || targets.length === 0) { return; } source = getIntersectionType(sources); target = getIntersectionType(targets); } - else if (!(source.flags & TypeFlags.Union)) { - if (inferFromMatchingType(source, (target).types, isTypeIdenticalTo)) return; - } } else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { target = getActualTypeVariable(target); @@ -15753,17 +15756,6 @@ namespace ts { inferencePriority = Math.min(inferencePriority, saveInferencePriority); } - function inferFromMatchingType(source: Type, targets: Type[], matches: (s: Type, t: Type) => boolean) { - let matched = false; - for (const t of targets) { - if (matches(source, t)) { - inferFromTypes(source, t); - matched = true; - } - } - return matched; - } - function inferFromMatchingTypes(sources: Type[], targets: Type[], matches: (s: Type, t: Type) => boolean): [Type[], Type[]] { let matchedSources: Type[] | undefined; let matchedTargets: Type[] | undefined; From 1a28fd0d9f3e53143c7aaaec8d1d477adcb1236d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Sep 2019 16:46:38 -0700 Subject: [PATCH 2/5] Accept new baselines --- .../baselines/reference/unionAndIntersectionInference2.types | 2 +- tests/baselines/reference/unionTypeInference.types | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/unionAndIntersectionInference2.types b/tests/baselines/reference/unionAndIntersectionInference2.types index 24f24220bd972..a163b25ce2891 100644 --- a/tests/baselines/reference/unionAndIntersectionInference2.types +++ b/tests/baselines/reference/unionAndIntersectionInference2.types @@ -20,7 +20,7 @@ var e1: number | string | boolean; >e1 : string | number | boolean f1(a1); // string ->f1(a1) : unknown +>f1(a1) : string >f1 : (x: string | T) => T >a1 : string diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index 4ce2d9c43a08c..b8107f3c04d0a 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -104,8 +104,8 @@ const c4 = f3(b); // true >b : boolean const c5 = f3("abc"); // never ->c5 : unknown ->f3("abc") : unknown +>c5 : "abc" +>f3("abc") : "abc" >f3 : (x: string | false | T) => T >"abc" : "abc" From 228b727ff16d33dc4925e6414655652a3832ef33 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Sep 2019 16:49:59 -0700 Subject: [PATCH 3/5] Add regression test --- .../compiler/observableInferenceCanBeMade.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 tests/cases/compiler/observableInferenceCanBeMade.ts diff --git a/tests/cases/compiler/observableInferenceCanBeMade.ts b/tests/cases/compiler/observableInferenceCanBeMade.ts new file mode 100644 index 0000000000000..72685daabf6c2 --- /dev/null +++ b/tests/cases/compiler/observableInferenceCanBeMade.ts @@ -0,0 +1,22 @@ +// @strict: true + +// Repro from #33131 + +declare function of(a: T): Observable; +declare function from>(input: O): Observable>; + +type ObservedValueOf = O extends ObservableInput ? T : never; + +interface Subscribable { + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +} +type ObservableInput = Subscribable | Subscribable; + + +declare class Observable implements Subscribable { + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +} + +function asObservable(input: string | ObservableInput): Observable { + return typeof input === 'string' ? of(input) : from(input) +} From 7b4ffb1ca1522b1a9b9e9cb4580fe38c0f34e3dd Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Sep 2019 16:50:06 -0700 Subject: [PATCH 4/5] Accept new baselines --- .../reference/observableInferenceCanBeMade.js | 29 +++++++ .../observableInferenceCanBeMade.symbols | 80 +++++++++++++++++++ .../observableInferenceCanBeMade.types | 57 +++++++++++++ 3 files changed, 166 insertions(+) create mode 100644 tests/baselines/reference/observableInferenceCanBeMade.js create mode 100644 tests/baselines/reference/observableInferenceCanBeMade.symbols create mode 100644 tests/baselines/reference/observableInferenceCanBeMade.types diff --git a/tests/baselines/reference/observableInferenceCanBeMade.js b/tests/baselines/reference/observableInferenceCanBeMade.js new file mode 100644 index 0000000000000..9014ebd6ca3ed --- /dev/null +++ b/tests/baselines/reference/observableInferenceCanBeMade.js @@ -0,0 +1,29 @@ +//// [observableInferenceCanBeMade.ts] +// Repro from #33131 + +declare function of(a: T): Observable; +declare function from>(input: O): Observable>; + +type ObservedValueOf = O extends ObservableInput ? T : never; + +interface Subscribable { + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +} +type ObservableInput = Subscribable | Subscribable; + + +declare class Observable implements Subscribable { + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +} + +function asObservable(input: string | ObservableInput): Observable { + return typeof input === 'string' ? of(input) : from(input) +} + + +//// [observableInferenceCanBeMade.js] +"use strict"; +// Repro from #33131 +function asObservable(input) { + return typeof input === 'string' ? of(input) : from(input); +} diff --git a/tests/baselines/reference/observableInferenceCanBeMade.symbols b/tests/baselines/reference/observableInferenceCanBeMade.symbols new file mode 100644 index 0000000000000..0948c5e10ab16 --- /dev/null +++ b/tests/baselines/reference/observableInferenceCanBeMade.symbols @@ -0,0 +1,80 @@ +=== tests/cases/compiler/observableInferenceCanBeMade.ts === +// Repro from #33131 + +declare function of(a: T): Observable; +>of : Symbol(of, Decl(observableInferenceCanBeMade.ts, 0, 0)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 2, 20)) +>a : Symbol(a, Decl(observableInferenceCanBeMade.ts, 2, 23)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 2, 20)) +>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 2, 20)) + +declare function from>(input: O): Observable>; +>from : Symbol(from, Decl(observableInferenceCanBeMade.ts, 2, 44)) +>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 3, 22)) +>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1)) +>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 3, 54)) +>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 3, 22)) +>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64)) +>ObservedValueOf : Symbol(ObservedValueOf, Decl(observableInferenceCanBeMade.ts, 3, 96)) +>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 3, 22)) + +type ObservedValueOf = O extends ObservableInput ? T : never; +>ObservedValueOf : Symbol(ObservedValueOf, Decl(observableInferenceCanBeMade.ts, 3, 96)) +>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 5, 21)) +>O : Symbol(O, Decl(observableInferenceCanBeMade.ts, 5, 21)) +>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 5, 57)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 5, 57)) + +interface Subscribable { +>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 7, 23)) + + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +>subscribe : Symbol(Subscribable.subscribe, Decl(observableInferenceCanBeMade.ts, 7, 27)) +>next : Symbol(next, Decl(observableInferenceCanBeMade.ts, 8, 14)) +>value : Symbol(value, Decl(observableInferenceCanBeMade.ts, 8, 22)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 7, 23)) +>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 8, 40)) +>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 8, 50)) +>complete : Symbol(complete, Decl(observableInferenceCanBeMade.ts, 8, 70)) +} +type ObservableInput = Subscribable | Subscribable; +>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 10, 21)) +>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 10, 21)) +>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73)) + + +declare class Observable implements Subscribable { +>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 13, 25)) +>Subscribable : Symbol(Subscribable, Decl(observableInferenceCanBeMade.ts, 5, 73)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 13, 25)) + + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +>subscribe : Symbol(Observable.subscribe, Decl(observableInferenceCanBeMade.ts, 13, 56)) +>next : Symbol(next, Decl(observableInferenceCanBeMade.ts, 14, 14)) +>value : Symbol(value, Decl(observableInferenceCanBeMade.ts, 14, 22)) +>T : Symbol(T, Decl(observableInferenceCanBeMade.ts, 13, 25)) +>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 14, 40)) +>error : Symbol(error, Decl(observableInferenceCanBeMade.ts, 14, 50)) +>complete : Symbol(complete, Decl(observableInferenceCanBeMade.ts, 14, 70)) +} + +function asObservable(input: string | ObservableInput): Observable { +>asObservable : Symbol(asObservable, Decl(observableInferenceCanBeMade.ts, 15, 1)) +>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22)) +>ObservableInput : Symbol(ObservableInput, Decl(observableInferenceCanBeMade.ts, 9, 1)) +>Observable : Symbol(Observable, Decl(observableInferenceCanBeMade.ts, 10, 64)) + + return typeof input === 'string' ? of(input) : from(input) +>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22)) +>of : Symbol(of, Decl(observableInferenceCanBeMade.ts, 0, 0)) +>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22)) +>from : Symbol(from, Decl(observableInferenceCanBeMade.ts, 2, 44)) +>input : Symbol(input, Decl(observableInferenceCanBeMade.ts, 17, 22)) +} + diff --git a/tests/baselines/reference/observableInferenceCanBeMade.types b/tests/baselines/reference/observableInferenceCanBeMade.types new file mode 100644 index 0000000000000..bb855f805246e --- /dev/null +++ b/tests/baselines/reference/observableInferenceCanBeMade.types @@ -0,0 +1,57 @@ +=== tests/cases/compiler/observableInferenceCanBeMade.ts === +// Repro from #33131 + +declare function of(a: T): Observable; +>of : (a: T) => Observable +>a : T + +declare function from>(input: O): Observable>; +>from : >(input: O) => Observable> +>input : O + +type ObservedValueOf = O extends ObservableInput ? T : never; +>ObservedValueOf : ObservedValueOf + +interface Subscribable { + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +>subscribe : (next?: ((value: T) => void) | undefined, error?: ((error: any) => void) | undefined, complete?: (() => void) | undefined) => void +>next : ((value: T) => void) | undefined +>value : T +>error : ((error: any) => void) | undefined +>error : any +>complete : (() => void) | undefined +} +type ObservableInput = Subscribable | Subscribable; +>ObservableInput : ObservableInput + + +declare class Observable implements Subscribable { +>Observable : Observable + + subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void; +>subscribe : (next?: ((value: T) => void) | undefined, error?: ((error: any) => void) | undefined, complete?: (() => void) | undefined) => void +>next : ((value: T) => void) | undefined +>value : T +>error : ((error: any) => void) | undefined +>error : any +>complete : (() => void) | undefined +} + +function asObservable(input: string | ObservableInput): Observable { +>asObservable : (input: string | Subscribable | Subscribable) => Observable +>input : string | Subscribable | Subscribable + + return typeof input === 'string' ? of(input) : from(input) +>typeof input === 'string' ? of(input) : from(input) : Observable +>typeof input === 'string' : boolean +>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" +>input : string | Subscribable | Subscribable +>'string' : "string" +>of(input) : Observable +>of : (a: T) => Observable +>input : string +>from(input) : Observable +>from : >(input: O) => Observable> +>input : ObservableInput +} + From aaa064b3b89f41cb9e9c086cb69977e6cb0255f8 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 4 Sep 2019 18:26:59 -0700 Subject: [PATCH 5/5] Fix lint error --- src/compiler/checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ac43b4caea61f..76786ee8674de 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15584,7 +15584,7 @@ namespace ts { // However, simply making no inferences is undesirable because it could ultimately mean // inferring a type parameter constraint. Instead, make a lower priority inference from // the full source to whatever remains in the target. For example, when inferring from - // string to 'string | T', make a lower priority inference of string for T. + // string to 'string | T', make a lower priority inference of string for T. const savePriority = priority; priority |= InferencePriority.NakedTypeVariable; inferFromTypes(source, target);