Skip to content

Commit

Permalink
Merge pull request #33252 from microsoft/fix33131
Browse files Browse the repository at this point in the history
Make lower priority inference when source is completely matched in target
  • Loading branch information
ahejlsberg authored Sep 5, 2019
2 parents 72bb4c2 + aaa064b commit c5e6d95
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 35 deletions.
56 changes: 24 additions & 32 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15593,24 +15593,30 @@ 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((<UnionType>source).types, (<UnionType>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 ? (<UnionType>source).types : [source], (<UnionType>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, (<UnionType>target).types, isTypeOrBaseIdenticalTo)) return;
if (inferFromMatchingType(source, (<UnionType>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((<IntersectionType>target).types,
t => !!getInferenceInfoForType(t) || (isGenericMappedType(t) && !!getInferenceInfoForType(getHomomorphicTypeVariable(t) || neverType)))) {
Expand All @@ -15620,18 +15626,15 @@ namespace ts {
// string[] on the source side and infer string for T.
// Likewise, we consider a homomorphic mapped type constrainted to the target type parameter as similar to a "naked type variable"
// in such scenarios.
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((<IntersectionType>source).types, (<IntersectionType>target).types, isTypeIdenticalTo);
const [sources, targets] = inferFromMatchingTypes(source.flags & TypeFlags.Intersection ? (<IntersectionType>source).types : [source], (<IntersectionType>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, (<IntersectionType>target).types, isTypeIdenticalTo)) return;
}
}
else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) {
target = getActualTypeVariable(target);
Expand Down Expand Up @@ -15781,17 +15784,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;
Expand Down
29 changes: 29 additions & 0 deletions tests/baselines/reference/observableInferenceCanBeMade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//// [observableInferenceCanBeMade.ts]
// Repro from #33131

declare function of<T>(a: T): Observable<T>;
declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;

type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;

interface Subscribable<T> {
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
}
type ObservableInput<T> = Subscribable<T> | Subscribable<never>;


declare class Observable<T> implements Subscribable<T> {
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
}

function asObservable(input: string | ObservableInput<string>): Observable<string> {
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);
}
80 changes: 80 additions & 0 deletions tests/baselines/reference/observableInferenceCanBeMade.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
=== tests/cases/compiler/observableInferenceCanBeMade.ts ===
// Repro from #33131

declare function of<T>(a: T): Observable<T>;
>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<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;
>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> = O extends ObservableInput<infer T> ? 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<T> {
>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<T> = Subscribable<T> | Subscribable<never>;
>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<T> implements Subscribable<T> {
>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<string>): Observable<string> {
>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))
}

57 changes: 57 additions & 0 deletions tests/baselines/reference/observableInferenceCanBeMade.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
=== tests/cases/compiler/observableInferenceCanBeMade.ts ===
// Repro from #33131

declare function of<T>(a: T): Observable<T>;
>of : <T>(a: T) => Observable<T>
>a : T

declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;
>from : <O extends ObservableInput<any>>(input: O) => Observable<ObservedValueOf<O>>
>input : O

type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;
>ObservedValueOf : ObservedValueOf<O>

interface Subscribable<T> {
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<T> = Subscribable<T> | Subscribable<never>;
>ObservableInput : ObservableInput<T>


declare class Observable<T> implements Subscribable<T> {
>Observable : Observable<T>

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<string>): Observable<string> {
>asObservable : (input: string | Subscribable<never> | Subscribable<string>) => Observable<string>
>input : string | Subscribable<never> | Subscribable<string>

return typeof input === 'string' ? of(input) : from(input)
>typeof input === 'string' ? of(input) : from(input) : Observable<string>
>typeof input === 'string' : boolean
>typeof input : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
>input : string | Subscribable<never> | Subscribable<string>
>'string' : "string"
>of(input) : Observable<string>
>of : <T>(a: T) => Observable<T>
>input : string
>from(input) : Observable<string>
>from : <O extends ObservableInput<any>>(input: O) => Observable<ObservedValueOf<O>>
>input : ObservableInput<string>
}

Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ var e1: number | string | boolean;
>e1 : string | number | boolean

f1(a1); // string
>f1(a1) : unknown
>f1(a1) : string
>f1 : <T>(x: string | T) => T
>a1 : string

Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/unionTypeInference.types
Original file line number Diff line number Diff line change
Expand Up @@ -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 : <T>(x: string | false | T) => T
>"abc" : "abc"

Expand Down
22 changes: 22 additions & 0 deletions tests/cases/compiler/observableInferenceCanBeMade.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// @strict: true

// Repro from #33131

declare function of<T>(a: T): Observable<T>;
declare function from<O extends ObservableInput<any>>(input: O): Observable<ObservedValueOf<O>>;

type ObservedValueOf<O> = O extends ObservableInput<infer T> ? T : never;

interface Subscribable<T> {
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
}
type ObservableInput<T> = Subscribable<T> | Subscribable<never>;


declare class Observable<T> implements Subscribable<T> {
subscribe(next?: (value: T) => void, error?: (error: any) => void, complete?: () => void): void;
}

function asObservable(input: string | ObservableInput<string>): Observable<string> {
return typeof input === 'string' ? of(input) : from(input)
}

0 comments on commit c5e6d95

Please sign in to comment.