diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1d6b9e842cc4f..72d91c184d7aa 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -13540,6 +13540,9 @@ namespace ts { if (relation !== identityRelation) { source = getApparentType(source); } + else if (isGenericMappedType(source)) { + return Ternary.False; + } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target && !(getObjectFlags(source) & ObjectFlags.MarkerType || getObjectFlags(target) & ObjectFlags.MarkerType)) { // We have type references to the same generic type, and the type references are not marker @@ -15456,9 +15459,11 @@ namespace ts { function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, contravariant = false) { let symbolStack: Symbol[]; - let visited: Map; + let visited: Map; let bivariant = false; let propagationType: Type; + let inferenceCount = 0; + let inferenceIncomplete = false; let allowComplexConstraintInference = true; inferFromTypes(originalSource, originalTarget); @@ -15500,23 +15505,28 @@ namespace ts { // of all their possible values. let matchingTypes: Type[] | undefined; for (const t of (source).types) { - if (typeIdenticalToSomeType(t, (target).types)) { - (matchingTypes || (matchingTypes = [])).push(t); - inferFromTypes(t, t); - } - else if (t.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral)) { - const b = getBaseTypeOfLiteralType(t); - if (typeIdenticalToSomeType(b, (target).types)) { - (matchingTypes || (matchingTypes = [])).push(t, b); - } + const matched = findMatchedType(t, target); + if (matched) { + (matchingTypes || (matchingTypes = [])).push(matched); + inferFromTypes(matched, matched); } } // Next, to improve the quality of inferences, reduce the source and target types by // removing the identically matched constituents. For example, when inferring from // 'string | string[]' to 'string | T' we reduce the types to 'string[]' and 'T'. if (matchingTypes) { - source = removeTypesFromUnionOrIntersection(source, matchingTypes); - target = removeTypesFromUnionOrIntersection(target, matchingTypes); + const s = removeTypesFromUnionOrIntersection(source, matchingTypes); + const t = removeTypesFromUnionOrIntersection(target, matchingTypes); + if (!(s && t)) return; + source = s; + target = t; + } + } + else if (target.flags & TypeFlags.Union && !(target.flags & TypeFlags.EnumLiteral) || target.flags & TypeFlags.Intersection) { + const matched = findMatchedType(source, target); + if (matched) { + inferFromTypes(matched, matched); + return; } } else if (target.flags & (TypeFlags.IndexedAccess | TypeFlags.Substitution)) { @@ -15562,13 +15572,14 @@ namespace ts { clearCachedInferences(inferences); } } + inferenceCount++; return; } else { // Infer to the simplified version of an indexed access, if possible, to (hopefully) expose more bare type parameters to the inference engine const simplified = getSimplifiedType(target, /*writing*/ false); if (simplified !== target) { - inferFromTypesOnce(source, simplified); + invokeOnce(source, simplified, inferFromTypes); } else if (target.flags & TypeFlags.IndexedAccess) { const indexType = getSimplifiedType((target as IndexedAccessType).indexType, /*writing*/ false); @@ -15577,13 +15588,14 @@ namespace ts { if (indexType.flags & TypeFlags.Instantiable) { const simplified = distributeIndexOverObjectType(getSimplifiedType((target as IndexedAccessType).objectType, /*writing*/ false), indexType, /*writing*/ false); if (simplified && simplified !== target) { - inferFromTypesOnce(source, simplified); + invokeOnce(source, simplified, inferFromTypes); } } } } } - if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { + if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && ( + (source).target === (target).target || isArrayType(source) && isArrayType(target))) { // If source and target are references to the same generic type, infer from type arguments inferFromTypeArguments((source).typeArguments || emptyArray, (target).typeArguments || emptyArray, getVariances((source).target)); } @@ -15613,10 +15625,10 @@ namespace ts { } else if (target.flags & TypeFlags.Conditional && !contravariant) { const targetTypes = [getTrueTypeFromConditionalType(target), getFalseTypeFromConditionalType(target)]; - inferToMultipleTypes(source, targetTypes, /*isIntersection*/ false); + inferToMultipleTypes(source, targetTypes, target.flags); } else if (target.flags & TypeFlags.UnionOrIntersection) { - inferToMultipleTypes(source, (target).types, !!(target.flags & TypeFlags.Intersection)); + inferToMultipleTypes(source, (target).types, target.flags); } else if (source.flags & TypeFlags.Union) { // Source is a union or intersection type, infer from each constituent type @@ -15645,39 +15657,22 @@ namespace ts { source = apparentSource; } if (source.flags & (TypeFlags.Object | TypeFlags.Intersection)) { - const key = source.id + "," + target.id; - if (visited && visited.get(key)) { - return; - } - (visited || (visited = createMap())).set(key, true); - // If we are already processing another target type with the same associated symbol (such as - // an instantiation of the same generic type), we do not explore this target as it would yield - // no further inferences. We exclude the static side of classes from this check since it shares - // its symbol with the instance side which would lead to false positives. - const isNonConstructorObject = target.flags & TypeFlags.Object && - !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); - const symbol = isNonConstructorObject ? target.symbol : undefined; - if (symbol) { - if (contains(symbolStack, symbol)) { - return; - } - (symbolStack || (symbolStack = [])).push(symbol); - inferFromObjectTypes(source, target); - symbolStack.pop(); - } - else { - inferFromObjectTypes(source, target); - } + invokeOnce(source, target, inferFromObjectTypes); } } + } - function inferFromTypesOnce(source: Type, target: Type) { - const key = source.id + "," + target.id; - if (!visited || !visited.get(key)) { - (visited || (visited = createMap())).set(key, true); - inferFromTypes(source, target); - } + function invokeOnce(source: Type, target: Type, action: (source: Type, target: Type) => void) { + const key = source.id + "," + target.id; + const count = visited && visited.get(key); + if (count !== undefined) { + inferenceCount += count; + return; } + (visited || (visited = createMap())).set(key, 0); + const startCount = inferenceCount; + action(source, target); + visited.set(key, inferenceCount - startCount); } function inferFromTypeArguments(sourceTypes: readonly Type[], targetTypes: readonly Type[], variances: readonly VarianceFlags[]) { @@ -15714,24 +15709,60 @@ namespace ts { return undefined; } - function inferToMultipleTypes(source: Type, targets: Type[], isIntersection: boolean) { - // We infer from types that are not naked type variables first so that inferences we - // make from nested naked type variables and given slightly higher priority by virtue - // of being first in the candidates array. + function inferToMultipleTypes(source: Type, targets: Type[], targetFlags: TypeFlags) { let typeVariableCount = 0; - for (const t of targets) { - if (getInferenceInfoForType(t)) { - typeVariableCount++; + if (targetFlags & TypeFlags.Union) { + let nakedTypeVariable: Type | undefined; + const sources = source.flags & TypeFlags.Union ? (source).types : [source]; + const matched = new Array(sources.length); + const saveInferenceIncomplete = inferenceIncomplete; + inferenceIncomplete = 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. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + nakedTypeVariable = t; + typeVariableCount++; + } + else { + for (let i = 0; i < sources.length; i++) { + const count = inferenceCount; + inferFromTypes(sources[i], t); + if (count !== inferenceCount) matched[i] = true; + } + } } - else { - inferFromTypes(source, t); + 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) { + const unmatched = flatMap(sources, (s, i) => matched[i] ? undefined : s); + if (unmatched.length) { + inferFromTypes(getUnionType(unmatched), nakedTypeVariable!); + return; + } + } + } + else { + // We infer from types that are not naked type variables first so that inferences we + // make from nested naked type variables and given slightly higher priority by virtue + // of being first in the candidates array. + for (const t of targets) { + if (getInferenceInfoForType(t)) { + typeVariableCount++; + } + else { + inferFromTypes(source, t); + } } } // Inferences directly to naked type variables are given lower priority as they are // less specific. For example, when inferring from Promise to T | Promise, // we want to infer string for T, not Promise | string. For intersection types // we only infer to single naked type variables. - if (isIntersection ? typeVariableCount === 1 : typeVariableCount !== 0) { + if (targetFlags & TypeFlags.Intersection ? typeVariableCount === 1 : typeVariableCount > 0) { const savePriority = priority; priority |= InferencePriority.NakedTypeVariable; for (const t of targets) { @@ -15800,6 +15831,28 @@ namespace ts { } function inferFromObjectTypes(source: Type, target: Type) { + // If we are already processing another target type with the same associated symbol (such as + // an instantiation of the same generic type), we do not explore this target as it would yield + // no further inferences. We exclude the static side of classes from this check since it shares + // its symbol with the instance side which would lead to false positives. + const isNonConstructorObject = target.flags & TypeFlags.Object && + !(getObjectFlags(target) & ObjectFlags.Anonymous && target.symbol && target.symbol.flags & SymbolFlags.Class); + const symbol = isNonConstructorObject ? target.symbol : undefined; + if (symbol) { + if (contains(symbolStack, symbol)) { + inferenceIncomplete = true; + return; + } + (symbolStack || (symbolStack = [])).push(symbol); + inferFromObjectTypesWorker(source, target); + symbolStack.pop(); + } + else { + inferFromObjectTypesWorker(source, target); + } + } + + function inferFromObjectTypesWorker(source: Type, target: Type) { if (isGenericMappedType(source) && isGenericMappedType(target)) { // The source and target types are generic types { [P in S]: X } and { [P in T]: Y }, so we infer // from S to T and from X to Y. @@ -15902,15 +15955,35 @@ namespace ts { } } - function typeIdenticalToSomeType(type: Type, types: Type[]): boolean { + function isMatchableType(type: Type) { + // We exclude non-anonymous object types because some frameworks (e.g. Ember) rely on the ability to + // infer between types that don't witness their type variables. Such types would otherwise be eliminated + // because they appear identical. + return !(type.flags & TypeFlags.Object) || !!(getObjectFlags(type) & ObjectFlags.Anonymous); + } + + function typeMatchedBySomeType(type: Type, types: Type[]): boolean { for (const t of types) { - if (isTypeIdenticalTo(t, type)) { + if (t === type || isMatchableType(t) && isMatchableType(type) && isTypeIdenticalTo(t, type)) { return true; } } return false; } + function findMatchedType(type: Type, target: UnionOrIntersectionType) { + if (typeMatchedBySomeType(type, target.types)) { + return type; + } + if (type.flags & (TypeFlags.NumberLiteral | TypeFlags.StringLiteral) && target.flags & TypeFlags.Union) { + const base = getBaseTypeOfLiteralType(type); + if (typeMatchedBySomeType(base, target.types)) { + return base; + } + } + return undefined; + } + /** * Return a new union or intersection type computed by removing a given set of types * from a given union or intersection type. @@ -15918,11 +15991,11 @@ namespace ts { function removeTypesFromUnionOrIntersection(type: UnionOrIntersectionType, typesToRemove: Type[]) { const reducedTypes: Type[] = []; for (const t of type.types) { - if (!typeIdenticalToSomeType(t, typesToRemove)) { + if (!typeMatchedBySomeType(t, typesToRemove)) { reducedTypes.push(t); } } - return type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes); + return reducedTypes.length ? type.flags & TypeFlags.Union ? getUnionType(reducedTypes) : getIntersectionType(reducedTypes) : undefined; } function hasPrimitiveConstraint(type: TypeParameter): boolean { diff --git a/tests/baselines/reference/unionAndIntersectionInference2.types b/tests/baselines/reference/unionAndIntersectionInference2.types index a163b25ce2891..24f24220bd972 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) : string +>f1(a1) : unknown >f1 : (x: string | T) => T >a1 : string diff --git a/tests/baselines/reference/unionTypeInference.errors.txt b/tests/baselines/reference/unionTypeInference.errors.txt index dfda0d57c2b99..f28f32f337398 100644 --- a/tests/baselines/reference/unionTypeInference.errors.txt +++ b/tests/baselines/reference/unionTypeInference.errors.txt @@ -1,38 +1,61 @@ -tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(9,15): error TS2345: Argument of type '2' is not assignable to parameter of type 'string | 1'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(13,24): error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. +tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts(31,15): error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. -==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (1 errors) ==== - // Verify that inferences made *to* a type parameter in a union type are secondary - // to inferences made directly to that type parameter +==== tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts (2 errors) ==== + declare const b: boolean; + declare const s: string; + declare const sn: string | number; - function f(x: T, y: string|T): T { - return x; + declare function f1(x: T, y: string | T): T; + + 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 + ~ +!!! error TS2345: Argument of type '1' is not assignable to parameter of type 'string'. + + declare function f2(value: [string, T]): T; + + var b1 = f2(["string", true]); // boolean + + declare function f3(x: string | false | T): T; + + 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 + + declare function f4(x: string & T): T; + + const d1 = f4("abc"); + const d2 = f4(s); + const d3 = f4(42); // Error + ~~ +!!! error TS2345: Argument of type '42' is not assignable to parameter of type 'never'. + + export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; + } + export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; } - var a1: number; - var a1 = f(1, 2); - ~ -!!! error TS2345: Argument of type '2' is not assignable to parameter of type 'string | 1'. - var a2: number; - var a2 = f(1, "hello"); - var a3: number; - var a3 = f(1, a1 || "hello"); - var a4: any; - var a4 = f(undefined, "abc"); - - function g(value: [string, T]): T { - return value[1]; + function qux(p1: Foo, p2: Bar) { + p1 = p2; } - var b1: boolean; - var b1 = g(["string", true]); + // Repros from #32434 - function h(x: string|boolean|T): T { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; - } + declare function foo(x: T | Promise): void; + declare let x: false | Promise; + foo(x); - var c1: number; - var c1 = h(5); - var c2: string; - var c2 = h("abc"); + declare function bar(x: T, y: string | T): T; + const y = bar(1, 2); \ No newline at end of file diff --git a/tests/baselines/reference/unionTypeInference.js b/tests/baselines/reference/unionTypeInference.js index bdaa340f6ea6b..e41872eb4d4d8 100644 --- a/tests/baselines/reference/unionTypeInference.js +++ b/tests/baselines/reference/unionTypeInference.js @@ -1,60 +1,78 @@ //// [unionTypeInference.ts] -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter +declare const b: boolean; +declare const s: string; +declare const sn: string | number; -function f(x: T, y: string|T): T { - return x; +declare function f1(x: T, y: string | T): T; + +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 + +declare function f2(value: [string, T]): T; + +var b1 = f2(["string", true]); // boolean + +declare function f3(x: string | false | T): T; + +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 + +declare function f4(x: string & T): T; + +const d1 = f4("abc"); +const d2 = f4(s); +const d3 = f4(42); // Error + +export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; +} +export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; } -var a1: number; -var a1 = f(1, 2); -var a2: number; -var a2 = f(1, "hello"); -var a3: number; -var a3 = f(1, a1 || "hello"); -var a4: any; -var a4 = f(undefined, "abc"); - -function g(value: [string, T]): T { - return value[1]; +function qux(p1: Foo, p2: Bar) { + p1 = p2; } -var b1: boolean; -var b1 = g(["string", true]); +// Repros from #32434 -function h(x: string|boolean|T): T { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; -} +declare function foo(x: T | Promise): void; +declare let x: false | Promise; +foo(x); -var c1: number; -var c1 = h(5); -var c2: string; -var c2 = h("abc"); +declare function bar(x: T, y: string | T): T; +const y = bar(1, 2); //// [unionTypeInference.js] -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter -function f(x, y) { - return x; -} -var a1; -var a1 = f(1, 2); -var a2; -var a2 = f(1, "hello"); -var a3; -var a3 = f(1, a1 || "hello"); -var a4; -var a4 = f(undefined, "abc"); -function g(value) { - return value[1]; -} -var b1; -var b1 = g(["string", true]); -function h(x) { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; +"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 +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 +function qux(p1, p2) { + p1 = p2; } -var c1; -var c1 = h(5); -var c2; -var c2 = h("abc"); +foo(x); +var y = bar(1, 2); diff --git a/tests/baselines/reference/unionTypeInference.symbols b/tests/baselines/reference/unionTypeInference.symbols index 3388f279f4bdb..0d0bf6ae9be81 100644 --- a/tests/baselines/reference/unionTypeInference.symbols +++ b/tests/baselines/reference/unionTypeInference.symbols @@ -1,94 +1,189 @@ === tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts === -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter - -function f(x: T, y: string|T): T { ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) ->x : Symbol(x, Decl(unionTypeInference.ts, 3, 14)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) ->y : Symbol(y, Decl(unionTypeInference.ts, 3, 19)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) ->T : Symbol(T, Decl(unionTypeInference.ts, 3, 11)) - - return x; ->x : Symbol(x, Decl(unionTypeInference.ts, 3, 14)) -} - -var a1: number; ->a1 : Symbol(a1, Decl(unionTypeInference.ts, 7, 3), Decl(unionTypeInference.ts, 8, 3)) - -var a1 = f(1, 2); ->a1 : Symbol(a1, Decl(unionTypeInference.ts, 7, 3), Decl(unionTypeInference.ts, 8, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) - -var a2: number; ->a2 : Symbol(a2, Decl(unionTypeInference.ts, 9, 3), Decl(unionTypeInference.ts, 10, 3)) - -var a2 = f(1, "hello"); ->a2 : Symbol(a2, Decl(unionTypeInference.ts, 9, 3), Decl(unionTypeInference.ts, 10, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) - -var a3: number; ->a3 : Symbol(a3, Decl(unionTypeInference.ts, 11, 3), Decl(unionTypeInference.ts, 12, 3)) - -var a3 = f(1, a1 || "hello"); ->a3 : Symbol(a3, Decl(unionTypeInference.ts, 11, 3), Decl(unionTypeInference.ts, 12, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) ->a1 : Symbol(a1, Decl(unionTypeInference.ts, 7, 3), Decl(unionTypeInference.ts, 8, 3)) - -var a4: any; ->a4 : Symbol(a4, Decl(unionTypeInference.ts, 13, 3), Decl(unionTypeInference.ts, 14, 3)) - -var a4 = f(undefined, "abc"); ->a4 : Symbol(a4, Decl(unionTypeInference.ts, 13, 3), Decl(unionTypeInference.ts, 14, 3)) ->f : Symbol(f, Decl(unionTypeInference.ts, 0, 0)) +declare const b: boolean; +>b : Symbol(b, Decl(unionTypeInference.ts, 0, 13)) + +declare const s: string; +>s : Symbol(s, Decl(unionTypeInference.ts, 1, 13)) + +declare const sn: string | number; +>sn : Symbol(sn, Decl(unionTypeInference.ts, 2, 13)) + +declare function f1(x: T, y: string | T): T; +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) +>x : Symbol(x, Decl(unionTypeInference.ts, 4, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) +>y : Symbol(y, Decl(unionTypeInference.ts, 4, 28)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 4, 20)) + +const a1 = f1(1, 2); // 1 | 2 +>a1 : Symbol(a1, Decl(unionTypeInference.ts, 6, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a2 = f1(1, "hello"); // 1 +>a2 : Symbol(a2, Decl(unionTypeInference.ts, 7, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a3 = f1(1, sn); // number +>a3 : Symbol(a3, Decl(unionTypeInference.ts, 8, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) +>sn : Symbol(sn, Decl(unionTypeInference.ts, 2, 13)) + +const a4 = f1(undefined, "abc"); // undefined +>a4 : Symbol(a4, Decl(unionTypeInference.ts, 9, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) >undefined : Symbol(undefined) -function g(value: [string, T]): T { ->g : Symbol(g, Decl(unionTypeInference.ts, 14, 29)) ->T : Symbol(T, Decl(unionTypeInference.ts, 16, 11)) ->value : Symbol(value, Decl(unionTypeInference.ts, 16, 14)) ->T : Symbol(T, Decl(unionTypeInference.ts, 16, 11)) ->T : Symbol(T, Decl(unionTypeInference.ts, 16, 11)) - - return value[1]; ->value : Symbol(value, Decl(unionTypeInference.ts, 16, 14)) ->1 : Symbol(1) +const a5 = f1("foo", "bar"); // "foo" +>a5 : Symbol(a5, Decl(unionTypeInference.ts, 10, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a6 = f1(true, false); // boolean +>a6 : Symbol(a6, Decl(unionTypeInference.ts, 11, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +const a7 = f1("hello", 1); // Error +>a7 : Symbol(a7, Decl(unionTypeInference.ts, 12, 5)) +>f1 : Symbol(f1, Decl(unionTypeInference.ts, 2, 34)) + +declare function f2(value: [string, T]): T; +>f2 : Symbol(f2, Decl(unionTypeInference.ts, 12, 26)) +>T : Symbol(T, Decl(unionTypeInference.ts, 14, 20)) +>value : Symbol(value, Decl(unionTypeInference.ts, 14, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 14, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 14, 20)) + +var b1 = f2(["string", true]); // boolean +>b1 : Symbol(b1, Decl(unionTypeInference.ts, 16, 3)) +>f2 : Symbol(f2, Decl(unionTypeInference.ts, 12, 26)) + +declare function f3(x: string | false | T): T; +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) +>T : Symbol(T, Decl(unionTypeInference.ts, 18, 20)) +>x : Symbol(x, Decl(unionTypeInference.ts, 18, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 18, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 18, 20)) + +const c1 = f3(5); // 5 +>c1 : Symbol(c1, Decl(unionTypeInference.ts, 20, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) + +const c2 = f3(sn); // number +>c2 : Symbol(c2, Decl(unionTypeInference.ts, 21, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) +>sn : Symbol(sn, Decl(unionTypeInference.ts, 2, 13)) + +const c3 = f3(true); // true +>c3 : Symbol(c3, Decl(unionTypeInference.ts, 22, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) + +const c4 = f3(b); // true +>c4 : Symbol(c4, Decl(unionTypeInference.ts, 23, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) +>b : Symbol(b, Decl(unionTypeInference.ts, 0, 13)) + +const c5 = f3("abc"); // never +>c5 : Symbol(c5, Decl(unionTypeInference.ts, 24, 5)) +>f3 : Symbol(f3, Decl(unionTypeInference.ts, 16, 30)) + +declare function f4(x: string & T): T; +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) +>x : Symbol(x, Decl(unionTypeInference.ts, 26, 23)) +>T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) +>T : Symbol(T, Decl(unionTypeInference.ts, 26, 20)) + +const d1 = f4("abc"); +>d1 : Symbol(d1, Decl(unionTypeInference.ts, 28, 5)) +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) + +const d2 = f4(s); +>d2 : Symbol(d2, Decl(unionTypeInference.ts, 29, 5)) +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) +>s : Symbol(s, Decl(unionTypeInference.ts, 1, 13)) + +const d3 = f4(42); // Error +>d3 : Symbol(d3, Decl(unionTypeInference.ts, 30, 5)) +>f4 : Symbol(f4, Decl(unionTypeInference.ts, 24, 21)) + +export interface Foo { +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>T : Symbol(T, Decl(unionTypeInference.ts, 32, 21)) + + then(f: (x: T) => U | Foo, g: U): Foo; +>then : Symbol(Foo.then, Decl(unionTypeInference.ts, 32, 25)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>f : Symbol(f, Decl(unionTypeInference.ts, 33, 12)) +>x : Symbol(x, Decl(unionTypeInference.ts, 33, 16)) +>T : Symbol(T, Decl(unionTypeInference.ts, 32, 21)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>g : Symbol(g, Decl(unionTypeInference.ts, 33, 36)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>U : Symbol(U, Decl(unionTypeInference.ts, 33, 9)) } - -var b1: boolean; ->b1 : Symbol(b1, Decl(unionTypeInference.ts, 20, 3), Decl(unionTypeInference.ts, 21, 3)) - -var b1 = g(["string", true]); ->b1 : Symbol(b1, Decl(unionTypeInference.ts, 20, 3), Decl(unionTypeInference.ts, 21, 3)) ->g : Symbol(g, Decl(unionTypeInference.ts, 14, 29)) - -function h(x: string|boolean|T): T { ->h : Symbol(h, Decl(unionTypeInference.ts, 21, 29)) ->T : Symbol(T, Decl(unionTypeInference.ts, 23, 11)) ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) ->T : Symbol(T, Decl(unionTypeInference.ts, 23, 11)) ->T : Symbol(T, Decl(unionTypeInference.ts, 23, 11)) - - return typeof x === "string" || typeof x === "boolean" ? undefined : x; ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) ->undefined : Symbol(undefined) ->x : Symbol(x, Decl(unionTypeInference.ts, 23, 14)) +export interface Bar { +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) +>T : Symbol(T, Decl(unionTypeInference.ts, 35, 21)) + + then(f: (x: T) => S | Bar, g: S): Bar; +>then : Symbol(Bar.then, Decl(unionTypeInference.ts, 35, 25)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>f : Symbol(f, Decl(unionTypeInference.ts, 36, 12)) +>x : Symbol(x, Decl(unionTypeInference.ts, 36, 16)) +>T : Symbol(T, Decl(unionTypeInference.ts, 35, 21)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>g : Symbol(g, Decl(unionTypeInference.ts, 36, 36)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) +>S : Symbol(S, Decl(unionTypeInference.ts, 36, 9)) } -var c1: number; ->c1 : Symbol(c1, Decl(unionTypeInference.ts, 27, 3), Decl(unionTypeInference.ts, 28, 3)) +function qux(p1: Foo, p2: Bar) { +>qux : Symbol(qux, Decl(unionTypeInference.ts, 37, 1)) +>p1 : Symbol(p1, Decl(unionTypeInference.ts, 39, 13)) +>Foo : Symbol(Foo, Decl(unionTypeInference.ts, 30, 18)) +>p2 : Symbol(p2, Decl(unionTypeInference.ts, 39, 27)) +>Bar : Symbol(Bar, Decl(unionTypeInference.ts, 34, 1)) -var c1 = h(5); ->c1 : Symbol(c1, Decl(unionTypeInference.ts, 27, 3), Decl(unionTypeInference.ts, 28, 3)) ->h : Symbol(h, Decl(unionTypeInference.ts, 21, 29)) - -var c2: string; ->c2 : Symbol(c2, Decl(unionTypeInference.ts, 29, 3), Decl(unionTypeInference.ts, 30, 3)) + p1 = p2; +>p1 : Symbol(p1, Decl(unionTypeInference.ts, 39, 13)) +>p2 : Symbol(p2, Decl(unionTypeInference.ts, 39, 27)) +} -var c2 = h("abc"); ->c2 : Symbol(c2, Decl(unionTypeInference.ts, 29, 3), Decl(unionTypeInference.ts, 30, 3)) ->h : Symbol(h, Decl(unionTypeInference.ts, 21, 29)) +// Repros from #32434 + +declare function foo(x: T | Promise): void; +>foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1)) +>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, --, --)) +>T : Symbol(T, Decl(unionTypeInference.ts, 45, 21)) + +declare let x: false | Promise; +>x : Symbol(x, Decl(unionTypeInference.ts, 46, 11)) +>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --)) + +foo(x); +>foo : Symbol(foo, Decl(unionTypeInference.ts, 41, 1)) +>x : Symbol(x, Decl(unionTypeInference.ts, 46, 11)) + +declare function bar(x: T, y: string | T): T; +>bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) +>x : Symbol(x, Decl(unionTypeInference.ts, 49, 24)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) +>y : Symbol(y, Decl(unionTypeInference.ts, 49, 29)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) +>T : Symbol(T, Decl(unionTypeInference.ts, 49, 21)) + +const y = bar(1, 2); +>y : Symbol(y, Decl(unionTypeInference.ts, 50, 5)) +>bar : Symbol(bar, Decl(unionTypeInference.ts, 47, 7)) diff --git a/tests/baselines/reference/unionTypeInference.types b/tests/baselines/reference/unionTypeInference.types index e712916c2ff55..304bbcd33ec51 100644 --- a/tests/baselines/reference/unionTypeInference.types +++ b/tests/baselines/reference/unionTypeInference.types @@ -1,113 +1,187 @@ === tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts === -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter +declare const b: boolean; +>b : boolean -function f(x: T, y: string|T): T { ->f : (x: T, y: string | T) => T ->x : T ->y : string | T +declare const s: string; +>s : string - return x; ->x : T -} +declare const sn: string | number; +>sn : string | number -var a1: number; ->a1 : number +declare function f1(x: T, y: string | T): T; +>f1 : (x: T, y: string | T) => T +>x : T +>y : string | T -var a1 = f(1, 2); ->a1 : number ->f(1, 2) : any ->f : (x: T, y: string | T) => T +const a1 = f1(1, 2); // 1 | 2 +>a1 : 1 | 2 +>f1(1, 2) : 1 | 2 +>f1 : (x: T, y: string | T) => T >1 : 1 >2 : 2 -var a2: number; ->a2 : number - -var a2 = f(1, "hello"); ->a2 : number ->f(1, "hello") : 1 ->f : (x: T, y: string | T) => T +const a2 = f1(1, "hello"); // 1 +>a2 : 1 +>f1(1, "hello") : 1 +>f1 : (x: T, y: string | T) => T >1 : 1 >"hello" : "hello" -var a3: number; ->a3 : number - -var a3 = f(1, a1 || "hello"); +const a3 = f1(1, sn); // number >a3 : number ->f(1, a1 || "hello") : number ->f : (x: T, y: string | T) => T +>f1(1, sn) : number +>f1 : (x: T, y: string | T) => T >1 : 1 ->a1 || "hello" : number | "hello" ->a1 : number ->"hello" : "hello" - -var a4: any; ->a4 : any +>sn : string | number -var a4 = f(undefined, "abc"); ->a4 : any ->f(undefined, "abc") : any ->f : (x: T, y: string | T) => T +const a4 = f1(undefined, "abc"); // undefined +>a4 : undefined +>f1(undefined, "abc") : undefined +>f1 : (x: T, y: string | T) => T >undefined : undefined >"abc" : "abc" -function g(value: [string, T]): T { ->g : (value: [string, T]) => T ->value : [string, T] +const a5 = f1("foo", "bar"); // "foo" +>a5 : "foo" +>f1("foo", "bar") : "foo" +>f1 : (x: T, y: string | T) => T +>"foo" : "foo" +>"bar" : "bar" + +const a6 = f1(true, false); // boolean +>a6 : boolean +>f1(true, false) : boolean +>f1 : (x: T, y: string | T) => T +>true : true +>false : false - return value[1]; ->value[1] : T ->value : [string, T] +const a7 = f1("hello", 1); // Error +>a7 : any +>f1("hello", 1) : any +>f1 : (x: T, y: string | T) => T +>"hello" : "hello" >1 : 1 -} -var b1: boolean; ->b1 : boolean +declare function f2(value: [string, T]): T; +>f2 : (value: [string, T]) => T +>value : [string, T] -var b1 = g(["string", true]); +var b1 = f2(["string", true]); // boolean >b1 : boolean ->g(["string", true]) : boolean ->g : (value: [string, T]) => T +>f2(["string", true]) : boolean +>f2 : (value: [string, T]) => T >["string", true] : [string, true] >"string" : "string" >true : true -function h(x: string|boolean|T): T { ->h : (x: string | boolean | T) => T ->x : string | boolean | T +declare function f3(x: string | false | T): T; +>f3 : (x: string | false | T) => T +>x : string | false | T +>false : false - return typeof x === "string" || typeof x === "boolean" ? undefined : x; ->typeof x === "string" || typeof x === "boolean" ? undefined : x : T ->typeof x === "string" || typeof x === "boolean" : boolean ->typeof x === "string" : boolean ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" ->x : string | boolean | T ->"string" : "string" ->typeof x === "boolean" : boolean ->typeof x : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" ->x : boolean | T ->"boolean" : "boolean" ->undefined : undefined +const c1 = f3(5); // 5 +>c1 : 5 +>f3(5) : 5 +>f3 : (x: string | false | T) => T +>5 : 5 + +const c2 = f3(sn); // number +>c2 : number +>f3(sn) : number +>f3 : (x: string | false | T) => T +>sn : string | number + +const c3 = f3(true); // true +>c3 : true +>f3(true) : true +>f3 : (x: string | false | T) => T +>true : true + +const c4 = f3(b); // true +>c4 : true +>f3(b) : true +>f3 : (x: string | false | T) => T +>b : boolean + +const c5 = f3("abc"); // never +>c5 : unknown +>f3("abc") : unknown +>f3 : (x: string | false | T) => T +>"abc" : "abc" + +declare function f4(x: string & T): T; +>f4 : (x: string & T) => T +>x : string & T + +const d1 = f4("abc"); +>d1 : "abc" +>f4("abc") : "abc" +>f4 : (x: string & T) => T +>"abc" : "abc" + +const d2 = f4(s); +>d2 : unknown +>f4(s) : unknown +>f4 : (x: string & T) => T +>s : string + +const d3 = f4(42); // Error +>d3 : any +>f4(42) : any +>f4 : (x: string & T) => T +>42 : 42 + +export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; +>then : (f: (x: T) => U | Foo, g: U) => Foo +>f : (x: T) => U | Foo >x : T +>g : U +} +export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; +>then : (f: (x: T) => S | Bar, g: S) => Bar +>f : (x: T) => S | Bar +>x : T +>g : S } -var c1: number; ->c1 : number +function qux(p1: Foo, p2: Bar) { +>qux : (p1: Foo, p2: Bar) => void +>p1 : Foo +>p2 : Bar -var c1 = h(5); ->c1 : number ->h(5) : 5 ->h : (x: string | boolean | T) => T ->5 : 5 + p1 = p2; +>p1 = p2 : Bar +>p1 : Foo +>p2 : Bar +} -var c2: string; ->c2 : string +// Repros from #32434 -var c2 = h("abc"); ->c2 : string ->h("abc") : "abc" ->h : (x: string | boolean | T) => T ->"abc" : "abc" +declare function foo(x: T | Promise): void; +>foo : (x: T | Promise) => void +>x : T | Promise + +declare let x: false | Promise; +>x : false | Promise +>false : false +>true : true + +foo(x); +>foo(x) : void +>foo : (x: T | Promise) => void +>x : false | Promise + +declare function bar(x: T, y: string | T): T; +>bar : (x: T, y: string | T) => T +>x : T +>y : string | T + +const y = bar(1, 2); +>y : 1 | 2 +>bar(1, 2) : 1 | 2 +>bar : (x: T, y: string | T) => T +>1 : 1 +>2 : 2 diff --git a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts index 39def70662252..4d9a3eae21ede 100644 --- a/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts +++ b/tests/cases/conformance/types/typeRelationships/typeInference/unionTypeInference.ts @@ -1,31 +1,53 @@ -// Verify that inferences made *to* a type parameter in a union type are secondary -// to inferences made directly to that type parameter +// @strict: true -function f(x: T, y: string|T): T { - return x; +declare const b: boolean; +declare const s: string; +declare const sn: string | number; + +declare function f1(x: T, y: string | T): T; + +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 + +declare function f2(value: [string, T]): T; + +var b1 = f2(["string", true]); // boolean + +declare function f3(x: string | false | T): T; + +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 + +declare function f4(x: string & T): T; + +const d1 = f4("abc"); +const d2 = f4(s); +const d3 = f4(42); // Error + +export interface Foo { + then(f: (x: T) => U | Foo, g: U): Foo; +} +export interface Bar { + then(f: (x: T) => S | Bar, g: S): Bar; } -var a1: number; -var a1 = f(1, 2); -var a2: number; -var a2 = f(1, "hello"); -var a3: number; -var a3 = f(1, a1 || "hello"); -var a4: any; -var a4 = f(undefined, "abc"); - -function g(value: [string, T]): T { - return value[1]; +function qux(p1: Foo, p2: Bar) { + p1 = p2; } -var b1: boolean; -var b1 = g(["string", true]); +// Repros from #32434 -function h(x: string|boolean|T): T { - return typeof x === "string" || typeof x === "boolean" ? undefined : x; -} +declare function foo(x: T | Promise): void; +declare let x: false | Promise; +foo(x); -var c1: number; -var c1 = h(5); -var c2: string; -var c2 = h("abc"); +declare function bar(x: T, y: string | T): T; +const y = bar(1, 2);