From 211179f553cece171290ebfc96d64aa088aeec5a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 17 Jan 2023 17:26:42 -0800 Subject: [PATCH 01/11] Improvements to strictSubtypeRelation and getNarrowedType --- src/compiler/checker.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f133d503c6f4f..906adeec005fc 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19709,7 +19709,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isSimpleTypeRelatedTo(source: Type, target: Type, relation: Map, errorReporter?: ErrorReporter) { const s = source.flags; const t = target.flags; - if (t & TypeFlags.AnyOrUnknown || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Any || s & TypeFlags.Never || source === wildcardType) return true; + if (t & TypeFlags.Unknown && !(relation === strictSubtypeRelation && s & TypeFlags.Any)) return true; if (t & TypeFlags.Never) return false; if (s & TypeFlags.StringLike && t & TypeFlags.String) return true; if (s & TypeFlags.StringLiteral && s & TypeFlags.EnumLiteral && @@ -21324,9 +21325,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return Ternary.False; } } - // Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})` - // and not `{} <- fresh({}) <- {[idx: string]: any}` - else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { + // A fresh empty object type is never a subtype of a non-empty object type and an empty object type is never a subtype of + // a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx } and { [x: string: xxx] } <: {}. Without these + // rules, those types would be mutual subtypes. + else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source) || + (relation === strictSubtypeRelation && target.flags & TypeFlags.Object && !isEmptyObjectType(target) && isEmptyObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) { return Ternary.False; } // Even if relationship doesn't hold for unions, intersections, or generic type references, @@ -26903,21 +26906,22 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return getNarrowedType(type, targetType, assumeTrue, /*checkDerived*/ true); } - function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { + function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean): Type { const key = type.flags & TypeFlags.Union ? `N${getTypeId(type)},${getTypeId(candidate)},${(assumeTrue ? 1 : 0) | (checkDerived ? 2 : 0)}` : undefined; return getCachedType(key) ?? setCachedType(key, getNarrowedTypeWorker(type, candidate, assumeTrue, checkDerived)); } function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { - const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; if (!assumeTrue) { - return filterType(type, t => !isRelated(t, candidate)); + const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, checkDerived); + return filterType(type, t => !isTypeSubsetOf(t, trueType)); } if (type.flags & TypeFlags.AnyOrUnknown) { return candidate; } // We first attempt to filter the current type, narrowing constituents as appropriate and removing // constituents that are unrelated to the candidate. + const isRelated = checkDerived ? isTypeDerivedFrom : isTypeSubtypeOf; const keyPropertyName = type.flags & TypeFlags.Union ? getKeyPropertyName(type as UnionType) : undefined; const narrowedType = mapType(candidate, c => { // If a discriminant property is available, use that to reduce the type. @@ -26929,7 +26933,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // prototype object types. const directlyRelated = mapType(matching || type, checkDerived ? t => isTypeDerivedFrom(t, c) ? t : isTypeDerivedFrom(c, t) ? c : neverType : - t => isTypeSubtypeOf(c, t) ? c : isTypeSubtypeOf(t, c) ? t : neverType); + t => isTypeSubtypeOf(c, t) && !isTypeIdenticalTo(c, t) ? c : isTypeSubtypeOf(t, c) ? t : neverType); // If no constituents are directly related, create intersections for any generic constituents that // are related by constraint. return directlyRelated.flags & TypeFlags.Never ? From d44732a641e970ea8187553b7463ad572588996a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 17 Jan 2023 17:31:40 -0800 Subject: [PATCH 02/11] Accept new baselines --- .../reference/controlFlowBinaryOrExpression.symbols | 8 ++++---- .../reference/controlFlowBinaryOrExpression.types | 8 ++++---- ...controlFlowFavorAssertedTypeThroughTypePredicate.types | 8 ++++---- .../instanceofWithStructurallyIdenticalTypes.symbols | 4 ++-- .../instanceofWithStructurallyIdenticalTypes.types | 2 +- tests/baselines/reference/subtypesOfTypeParameter.types | 8 ++++---- .../reference/typePredicateStructuralMatch.symbols | 4 ++-- tests/baselines/reference/unionWithIndexSignature.types | 2 +- 8 files changed, 22 insertions(+), 22 deletions(-) diff --git a/tests/baselines/reference/controlFlowBinaryOrExpression.symbols b/tests/baselines/reference/controlFlowBinaryOrExpression.symbols index 3c2bfaa2f7cf0..5251973005fcd 100644 --- a/tests/baselines/reference/controlFlowBinaryOrExpression.symbols +++ b/tests/baselines/reference/controlFlowBinaryOrExpression.symbols @@ -64,9 +64,9 @@ if (isNodeList(sourceObj)) { >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) sourceObj.length; ->sourceObj.length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27)) +>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) ->length : Symbol(NodeList.length, Decl(controlFlowBinaryOrExpression.ts, 10, 27)) +>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) } if (isHTMLCollection(sourceObj)) { @@ -74,9 +74,9 @@ if (isHTMLCollection(sourceObj)) { >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) sourceObj.length; ->sourceObj.length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33)) +>sourceObj.length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) >sourceObj : Symbol(sourceObj, Decl(controlFlowBinaryOrExpression.ts, 23, 3)) ->length : Symbol(HTMLCollection.length, Decl(controlFlowBinaryOrExpression.ts, 14, 33)) +>length : Symbol(length, Decl(controlFlowBinaryOrExpression.ts, 10, 27), Decl(controlFlowBinaryOrExpression.ts, 14, 33)) } if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) { diff --git a/tests/baselines/reference/controlFlowBinaryOrExpression.types b/tests/baselines/reference/controlFlowBinaryOrExpression.types index 23e6034ce1cf0..9c188295a82f0 100644 --- a/tests/baselines/reference/controlFlowBinaryOrExpression.types +++ b/tests/baselines/reference/controlFlowBinaryOrExpression.types @@ -69,18 +69,18 @@ if (isNodeList(sourceObj)) { sourceObj.length; >sourceObj.length : number ->sourceObj : NodeList +>sourceObj : NodeList | HTMLCollection >length : number } if (isHTMLCollection(sourceObj)) { >isHTMLCollection(sourceObj) : boolean >isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection ->sourceObj : NodeList | { a: string; } +>sourceObj : EventTargetLike sourceObj.length; >sourceObj.length : number ->sourceObj : HTMLCollection +>sourceObj : NodeList | HTMLCollection >length : number } @@ -88,7 +88,7 @@ if (isNodeList(sourceObj) || isHTMLCollection(sourceObj)) { >isNodeList(sourceObj) || isHTMLCollection(sourceObj) : boolean >isNodeList(sourceObj) : boolean >isNodeList : (sourceObj: any) => sourceObj is NodeList ->sourceObj : HTMLCollection | { a: string; } +>sourceObj : EventTargetLike >isHTMLCollection(sourceObj) : boolean >isHTMLCollection : (sourceObj: any) => sourceObj is HTMLCollection >sourceObj : { a: string; } diff --git a/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types b/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types index 395da42f87632..f79e73f03e4d6 100644 --- a/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types +++ b/tests/baselines/reference/controlFlowFavorAssertedTypeThroughTypePredicate.types @@ -23,7 +23,7 @@ if (isObject1(obj1)) { } // check type after conditional block obj1; ->obj1 : Record +>obj1 : {} declare const obj2: {} | undefined; >obj2 : {} | undefined @@ -43,7 +43,7 @@ if (isObject1(obj2)) { } // check type after conditional block obj2; ->obj2 : Record | undefined +>obj2 : {} | undefined declare function isObject2(value: unknown): value is {}; >isObject2 : (value: unknown) => value is {} @@ -67,7 +67,7 @@ if (isObject2(obj3)) { } // check type after conditional block obj3; ->obj3 : {} +>obj3 : Record declare const obj4: Record | undefined; >obj4 : Record | undefined @@ -87,5 +87,5 @@ if (isObject2(obj4)) { } // check type after conditional block obj4; ->obj4 : {} | undefined +>obj4 : Record | undefined diff --git a/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.symbols b/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.symbols index 868d1c1fd784e..601699b93f716 100644 --- a/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.symbols +++ b/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.symbols @@ -95,9 +95,9 @@ function foo2(x: C1 | C2 | C3): string { >x : Symbol(x, Decl(instanceofWithStructurallyIdenticalTypes.ts, 23, 14)) return x.item; ->x.item : Symbol(C1.item, Decl(instanceofWithStructurallyIdenticalTypes.ts, 2, 10)) +>x.item : Symbol(item, Decl(instanceofWithStructurallyIdenticalTypes.ts, 2, 10), Decl(instanceofWithStructurallyIdenticalTypes.ts, 4, 10)) >x : Symbol(x, Decl(instanceofWithStructurallyIdenticalTypes.ts, 23, 14)) ->item : Symbol(C1.item, Decl(instanceofWithStructurallyIdenticalTypes.ts, 2, 10)) +>item : Symbol(item, Decl(instanceofWithStructurallyIdenticalTypes.ts, 2, 10), Decl(instanceofWithStructurallyIdenticalTypes.ts, 4, 10)) } else if (isC2(x)) { >isC2 : Symbol(isC2, Decl(instanceofWithStructurallyIdenticalTypes.ts, 19, 66)) diff --git a/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.types b/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.types index 6aeba6f1b733a..113c994c6e619 100644 --- a/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.types +++ b/tests/baselines/reference/instanceofWithStructurallyIdenticalTypes.types @@ -85,7 +85,7 @@ function foo2(x: C1 | C2 | C3): string { return x.item; >x.item : string ->x : C1 +>x : C1 | C3 >item : string } else if (isC2(x)) { diff --git a/tests/baselines/reference/subtypesOfTypeParameter.types b/tests/baselines/reference/subtypesOfTypeParameter.types index 02d4d527786b0..a79e1508a367f 100644 --- a/tests/baselines/reference/subtypesOfTypeParameter.types +++ b/tests/baselines/reference/subtypesOfTypeParameter.types @@ -393,16 +393,16 @@ function f2(x: T, y: U) { } var r19 = true ? new Object() : x; // BCT is Object ->r19 : Object ->true ? new Object() : x : Object +>r19 : Object | T +>true ? new Object() : x : Object | T >true : true >new Object() : Object >Object : ObjectConstructor >x : T var r19 = true ? x : new Object(); // BCT is Object ->r19 : Object ->true ? x : new Object() : Object +>r19 : Object | T +>true ? x : new Object() : Object | T >true : true >x : T >new Object() : Object diff --git a/tests/baselines/reference/typePredicateStructuralMatch.symbols b/tests/baselines/reference/typePredicateStructuralMatch.symbols index 75fea1027d2c3..5e4826ebbf2e8 100644 --- a/tests/baselines/reference/typePredicateStructuralMatch.symbols +++ b/tests/baselines/reference/typePredicateStructuralMatch.symbols @@ -51,9 +51,9 @@ function getResults1(value: Results | { data: Results }): Results { return isResponseInData(value) ? value.data : value; >isResponseInData : Symbol(isResponseInData, Decl(typePredicateStructuralMatch.ts, 9, 24)) >value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21)) ->value.data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 11, 63)) +>value.data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 15, 39)) >value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21)) ->data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 11, 63)) +>data : Symbol(data, Decl(typePredicateStructuralMatch.ts, 15, 39)) >value : Symbol(value, Decl(typePredicateStructuralMatch.ts, 15, 21)) } diff --git a/tests/baselines/reference/unionWithIndexSignature.types b/tests/baselines/reference/unionWithIndexSignature.types index 2bc7d0fbe5c9e..7aa5877e37a25 100644 --- a/tests/baselines/reference/unionWithIndexSignature.types +++ b/tests/baselines/reference/unionWithIndexSignature.types @@ -55,7 +55,7 @@ export function flatten(arr: T) { arr[1]; >arr[1] : number ->arr : Int32Array | Uint8Array +>arr : TypedArray >1 : 1 } } From 14985d407283e333d2ad960d9f4e3b3879556574 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 17 Jan 2023 17:48:12 -0800 Subject: [PATCH 03/11] Add tests --- .../reference/narrowingMutualSubtypes.js | 126 +++++++++++ .../reference/narrowingMutualSubtypes.symbols | 203 +++++++++++++++++ .../reference/narrowingMutualSubtypes.types | 205 ++++++++++++++++++ .../cases/compiler/narrowingMutualSubtypes.ts | 79 +++++++ 4 files changed, 613 insertions(+) create mode 100644 tests/baselines/reference/narrowingMutualSubtypes.js create mode 100644 tests/baselines/reference/narrowingMutualSubtypes.symbols create mode 100644 tests/baselines/reference/narrowingMutualSubtypes.types create mode 100644 tests/cases/compiler/narrowingMutualSubtypes.ts diff --git a/tests/baselines/reference/narrowingMutualSubtypes.js b/tests/baselines/reference/narrowingMutualSubtypes.js new file mode 100644 index 0000000000000..0d5f36d2f9551 --- /dev/null +++ b/tests/baselines/reference/narrowingMutualSubtypes.js @@ -0,0 +1,126 @@ +//// [narrowingMutualSubtypes.ts] +// Check that `any` is a strict supertype of `unknown` + +declare const ru1: { [x: string]: unknown }; +declare const ra1: { [x: string]: any }; + +const a1a = [ru1, ra1]; // { [x: string]: any }[] +const a1b = [ra1, ru1]; // { [x: string]: any }[] + +declare const ra2: { [x: string]: any }; +declare const ru2: { [x: string]: unknown }; + +const a2a = [ru2, ra2]; // { [x: string]: any }[] +const a2b = [ra2, ru2]; // { [x: string]: any }[] + +// Check that `{}` is strict supertype of any non-empty object + +const c3 = {}; +declare const r3: { [x: string]: unknown } + +const a3a = [c3, r3]; // {}[] +const a3b = [r3, c3]; // {}[] + +declare const r4: { [x: string]: unknown } +const c4 = {}; + +const a4a = [c4, r4]; // {}[] +const a4b = [r4, c4]; // {}[] + +// Check that narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; + +function gg(x: {}) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} + +declare function isObject2(value: unknown): value is {}; + +function gg2(x: Record) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; + +type Self = T extends unknown ? Identity : never; + +function is(value: T): value is Self { + return true; +} + +type Union = {a: number} | {b: number} | {c: number}; + +function example(x: Union) { + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + x; // Union +} + + +//// [narrowingMutualSubtypes.js] +"use strict"; +// Check that `any` is a strict supertype of `unknown` +var a1a = [ru1, ra1]; // { [x: string]: any }[] +var a1b = [ra1, ru1]; // { [x: string]: any }[] +var a2a = [ru2, ra2]; // { [x: string]: any }[] +var a2b = [ra2, ru2]; // { [x: string]: any }[] +// Check that `{}` is strict supertype of any non-empty object +var c3 = {}; +var a3a = [c3, r3]; // {}[] +var a3b = [r3, c3]; // {}[] +var c4 = {}; +var a4a = [c4, r4]; // {}[] +var a4b = [r4, c4]; // {}[] +function gg(x) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} +function gg2(x) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record +} +function is(value) { + return true; +} +function example(x) { + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + x; // Union +} diff --git a/tests/baselines/reference/narrowingMutualSubtypes.symbols b/tests/baselines/reference/narrowingMutualSubtypes.symbols new file mode 100644 index 0000000000000..9229c0b6e2946 --- /dev/null +++ b/tests/baselines/reference/narrowingMutualSubtypes.symbols @@ -0,0 +1,203 @@ +=== tests/cases/compiler/narrowingMutualSubtypes.ts === +// Check that `any` is a strict supertype of `unknown` + +declare const ru1: { [x: string]: unknown }; +>ru1 : Symbol(ru1, Decl(narrowingMutualSubtypes.ts, 2, 13)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 2, 22)) + +declare const ra1: { [x: string]: any }; +>ra1 : Symbol(ra1, Decl(narrowingMutualSubtypes.ts, 3, 13)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 3, 22)) + +const a1a = [ru1, ra1]; // { [x: string]: any }[] +>a1a : Symbol(a1a, Decl(narrowingMutualSubtypes.ts, 5, 5)) +>ru1 : Symbol(ru1, Decl(narrowingMutualSubtypes.ts, 2, 13)) +>ra1 : Symbol(ra1, Decl(narrowingMutualSubtypes.ts, 3, 13)) + +const a1b = [ra1, ru1]; // { [x: string]: any }[] +>a1b : Symbol(a1b, Decl(narrowingMutualSubtypes.ts, 6, 5)) +>ra1 : Symbol(ra1, Decl(narrowingMutualSubtypes.ts, 3, 13)) +>ru1 : Symbol(ru1, Decl(narrowingMutualSubtypes.ts, 2, 13)) + +declare const ra2: { [x: string]: any }; +>ra2 : Symbol(ra2, Decl(narrowingMutualSubtypes.ts, 8, 13)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 8, 22)) + +declare const ru2: { [x: string]: unknown }; +>ru2 : Symbol(ru2, Decl(narrowingMutualSubtypes.ts, 9, 13)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 9, 22)) + +const a2a = [ru2, ra2]; // { [x: string]: any }[] +>a2a : Symbol(a2a, Decl(narrowingMutualSubtypes.ts, 11, 5)) +>ru2 : Symbol(ru2, Decl(narrowingMutualSubtypes.ts, 9, 13)) +>ra2 : Symbol(ra2, Decl(narrowingMutualSubtypes.ts, 8, 13)) + +const a2b = [ra2, ru2]; // { [x: string]: any }[] +>a2b : Symbol(a2b, Decl(narrowingMutualSubtypes.ts, 12, 5)) +>ra2 : Symbol(ra2, Decl(narrowingMutualSubtypes.ts, 8, 13)) +>ru2 : Symbol(ru2, Decl(narrowingMutualSubtypes.ts, 9, 13)) + +// Check that `{}` is strict supertype of any non-empty object + +const c3 = {}; +>c3 : Symbol(c3, Decl(narrowingMutualSubtypes.ts, 16, 5)) + +declare const r3: { [x: string]: unknown } +>r3 : Symbol(r3, Decl(narrowingMutualSubtypes.ts, 17, 13)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 17, 21)) + +const a3a = [c3, r3]; // {}[] +>a3a : Symbol(a3a, Decl(narrowingMutualSubtypes.ts, 19, 5)) +>c3 : Symbol(c3, Decl(narrowingMutualSubtypes.ts, 16, 5)) +>r3 : Symbol(r3, Decl(narrowingMutualSubtypes.ts, 17, 13)) + +const a3b = [r3, c3]; // {}[] +>a3b : Symbol(a3b, Decl(narrowingMutualSubtypes.ts, 20, 5)) +>r3 : Symbol(r3, Decl(narrowingMutualSubtypes.ts, 17, 13)) +>c3 : Symbol(c3, Decl(narrowingMutualSubtypes.ts, 16, 5)) + +declare const r4: { [x: string]: unknown } +>r4 : Symbol(r4, Decl(narrowingMutualSubtypes.ts, 22, 13)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 22, 21)) + +const c4 = {}; +>c4 : Symbol(c4, Decl(narrowingMutualSubtypes.ts, 23, 5)) + +const a4a = [c4, r4]; // {}[] +>a4a : Symbol(a4a, Decl(narrowingMutualSubtypes.ts, 25, 5)) +>c4 : Symbol(c4, Decl(narrowingMutualSubtypes.ts, 23, 5)) +>r4 : Symbol(r4, Decl(narrowingMutualSubtypes.ts, 22, 13)) + +const a4b = [r4, c4]; // {}[] +>a4b : Symbol(a4b, Decl(narrowingMutualSubtypes.ts, 26, 5)) +>r4 : Symbol(r4, Decl(narrowingMutualSubtypes.ts, 22, 13)) +>c4 : Symbol(c4, Decl(narrowingMutualSubtypes.ts, 23, 5)) + +// Check that narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; +>isObject1 : Symbol(isObject1, Decl(narrowingMutualSubtypes.ts, 26, 21)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 30, 27)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 30, 27)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +function gg(x: {}) { +>gg : Symbol(gg, Decl(narrowingMutualSubtypes.ts, 30, 77)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) + + if (isObject1(x)) { +>isObject1 : Symbol(isObject1, Decl(narrowingMutualSubtypes.ts, 26, 21)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) + + x; // Record +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) + } + else { + x; // {} +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) + } + x; // {} +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 32, 12)) +} + +declare function isObject2(value: unknown): value is {}; +>isObject2 : Symbol(isObject2, Decl(narrowingMutualSubtypes.ts, 40, 1)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 42, 27)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 42, 27)) + +function gg2(x: Record) { +>gg2 : Symbol(gg2, Decl(narrowingMutualSubtypes.ts, 42, 56)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + if (isObject2(x)) { +>isObject2 : Symbol(isObject2, Decl(narrowingMutualSubtypes.ts, 40, 1)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) + + x; // {} +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) + } + else { + x; // Record +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) + } + x; // Record +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 44, 13)) +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; +>Identity : Symbol(Identity, Decl(narrowingMutualSubtypes.ts, 52, 1)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 56, 14)) +>K : Symbol(K, Decl(narrowingMutualSubtypes.ts, 56, 21)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 56, 14)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 56, 14)) +>K : Symbol(K, Decl(narrowingMutualSubtypes.ts, 56, 21)) + +type Self = T extends unknown ? Identity : never; +>Self : Symbol(Self, Decl(narrowingMutualSubtypes.ts, 56, 42)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 58, 10)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 58, 10)) +>Identity : Symbol(Identity, Decl(narrowingMutualSubtypes.ts, 52, 1)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 58, 10)) + +function is(value: T): value is Self { +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 60, 12)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 60, 15)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 60, 12)) +>value : Symbol(value, Decl(narrowingMutualSubtypes.ts, 60, 15)) +>Self : Symbol(Self, Decl(narrowingMutualSubtypes.ts, 56, 42)) +>T : Symbol(T, Decl(narrowingMutualSubtypes.ts, 60, 12)) + + return true; +} + +type Union = {a: number} | {b: number} | {c: number}; +>Union : Symbol(Union, Decl(narrowingMutualSubtypes.ts, 62, 1)) +>a : Symbol(a, Decl(narrowingMutualSubtypes.ts, 64, 15)) +>b : Symbol(b, Decl(narrowingMutualSubtypes.ts, 64, 29)) +>c : Symbol(c, Decl(narrowingMutualSubtypes.ts, 64, 43)) + +function example(x: Union) { +>example : Symbol(example, Decl(narrowingMutualSubtypes.ts, 64, 54)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +>Union : Symbol(Union, Decl(narrowingMutualSubtypes.ts, 62, 1)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(narrowingMutualSubtypes.ts, 58, 55)) +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) + + x; // Union +>x : Symbol(x, Decl(narrowingMutualSubtypes.ts, 66, 17)) +} + diff --git a/tests/baselines/reference/narrowingMutualSubtypes.types b/tests/baselines/reference/narrowingMutualSubtypes.types new file mode 100644 index 0000000000000..7cf817697aafb --- /dev/null +++ b/tests/baselines/reference/narrowingMutualSubtypes.types @@ -0,0 +1,205 @@ +=== tests/cases/compiler/narrowingMutualSubtypes.ts === +// Check that `any` is a strict supertype of `unknown` + +declare const ru1: { [x: string]: unknown }; +>ru1 : { [x: string]: unknown; } +>x : string + +declare const ra1: { [x: string]: any }; +>ra1 : { [x: string]: any; } +>x : string + +const a1a = [ru1, ra1]; // { [x: string]: any }[] +>a1a : { [x: string]: any; }[] +>[ru1, ra1] : { [x: string]: any; }[] +>ru1 : { [x: string]: unknown; } +>ra1 : { [x: string]: any; } + +const a1b = [ra1, ru1]; // { [x: string]: any }[] +>a1b : { [x: string]: any; }[] +>[ra1, ru1] : { [x: string]: any; }[] +>ra1 : { [x: string]: any; } +>ru1 : { [x: string]: unknown; } + +declare const ra2: { [x: string]: any }; +>ra2 : { [x: string]: any; } +>x : string + +declare const ru2: { [x: string]: unknown }; +>ru2 : { [x: string]: unknown; } +>x : string + +const a2a = [ru2, ra2]; // { [x: string]: any }[] +>a2a : { [x: string]: any; }[] +>[ru2, ra2] : { [x: string]: any; }[] +>ru2 : { [x: string]: unknown; } +>ra2 : { [x: string]: any; } + +const a2b = [ra2, ru2]; // { [x: string]: any }[] +>a2b : { [x: string]: any; }[] +>[ra2, ru2] : { [x: string]: any; }[] +>ra2 : { [x: string]: any; } +>ru2 : { [x: string]: unknown; } + +// Check that `{}` is strict supertype of any non-empty object + +const c3 = {}; +>c3 : {} +>{} : {} + +declare const r3: { [x: string]: unknown } +>r3 : { [x: string]: unknown; } +>x : string + +const a3a = [c3, r3]; // {}[] +>a3a : {}[] +>[c3, r3] : {}[] +>c3 : {} +>r3 : { [x: string]: unknown; } + +const a3b = [r3, c3]; // {}[] +>a3b : {}[] +>[r3, c3] : {}[] +>r3 : { [x: string]: unknown; } +>c3 : {} + +declare const r4: { [x: string]: unknown } +>r4 : { [x: string]: unknown; } +>x : string + +const c4 = {}; +>c4 : {} +>{} : {} + +const a4a = [c4, r4]; // {}[] +>a4a : {}[] +>[c4, r4] : {}[] +>c4 : {} +>r4 : { [x: string]: unknown; } + +const a4b = [r4, c4]; // {}[] +>a4b : {}[] +>[r4, c4] : {}[] +>r4 : { [x: string]: unknown; } +>c4 : {} + +// Check that narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; +>isObject1 : (value: unknown) => value is Record +>value : unknown + +function gg(x: {}) { +>gg : (x: {}) => void +>x : {} + + if (isObject1(x)) { +>isObject1(x) : boolean +>isObject1 : (value: unknown) => value is Record +>x : {} + + x; // Record +>x : Record + } + else { + x; // {} +>x : {} + } + x; // {} +>x : {} +} + +declare function isObject2(value: unknown): value is {}; +>isObject2 : (value: unknown) => value is {} +>value : unknown + +function gg2(x: Record) { +>gg2 : (x: Record) => void +>x : Record + + if (isObject2(x)) { +>isObject2(x) : boolean +>isObject2 : (value: unknown) => value is {} +>x : Record + + x; // {} +>x : {} + } + else { + x; // Record +>x : Record + } + x; // Record +>x : Record +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; +>Identity : Identity + +type Self = T extends unknown ? Identity : never; +>Self : Self + +function is(value: T): value is Self { +>is : (value: T) => value is Self +>value : T + + return true; +>true : true +} + +type Union = {a: number} | {b: number} | {c: number}; +>Union : { a: number; } | { b: number; } | { c: number; } +>a : number +>b : number +>c : number + +function example(x: Union) { +>example : (x: Union) => void +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Self +>x : Union + + x; // Union +>x : Union +} + diff --git a/tests/cases/compiler/narrowingMutualSubtypes.ts b/tests/cases/compiler/narrowingMutualSubtypes.ts new file mode 100644 index 0000000000000..1d666c256fa77 --- /dev/null +++ b/tests/cases/compiler/narrowingMutualSubtypes.ts @@ -0,0 +1,79 @@ +// @strict: true + +// Check that `any` is a strict supertype of `unknown` + +declare const ru1: { [x: string]: unknown }; +declare const ra1: { [x: string]: any }; + +const a1a = [ru1, ra1]; // { [x: string]: any }[] +const a1b = [ra1, ru1]; // { [x: string]: any }[] + +declare const ra2: { [x: string]: any }; +declare const ru2: { [x: string]: unknown }; + +const a2a = [ru2, ra2]; // { [x: string]: any }[] +const a2b = [ra2, ru2]; // { [x: string]: any }[] + +// Check that `{}` is strict supertype of any non-empty object + +const c3 = {}; +declare const r3: { [x: string]: unknown } + +const a3a = [c3, r3]; // {}[] +const a3b = [r3, c3]; // {}[] + +declare const r4: { [x: string]: unknown } +const c4 = {}; + +const a4a = [c4, r4]; // {}[] +const a4b = [r4, c4]; // {}[] + +// Check that narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; + +function gg(x: {}) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} + +declare function isObject2(value: unknown): value is {}; + +function gg2(x: Record) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; + +type Self = T extends unknown ? Identity : never; + +function is(value: T): value is Self { + return true; +} + +type Union = {a: number} | {b: number} | {c: number}; + +function example(x: Union) { + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + x; // Union +} From 95b1a3e290d0c2d9cd9a1dbd6cc0c5ee1c94bf75 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 18 Jan 2023 17:31:05 -0800 Subject: [PATCH 04/11] Keep object literal freshness in type of assignment expression --- 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 906adeec005fc..5323665f7be9b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -35889,7 +35889,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } else { checkAssignmentOperator(rightType); - return getRegularTypeOfObjectLiteral(rightType); + return rightType; } case SyntaxKind.CommaToken: if (!compilerOptions.allowUnreachableCode && isSideEffectFree(left) && !isIndirectCall(left.parent as BinaryExpression)) { From 1e429d79253fbd434c0788bcde7bb549ba5c2885 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 19 Jan 2023 07:12:41 -0800 Subject: [PATCH 05/11] Ensure (...args: any[]) => any is never subtype of other signatures --- src/compiler/checker.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5323665f7be9b..a4643498c6aaf 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1235,12 +1235,13 @@ export const enum CheckMode { /** @internal */ export const enum SignatureCheckMode { - None = 0, + None = 0, BivariantCallback = 1 << 0, - StrictCallback = 1 << 1, + StrictCallback = 1 << 1, IgnoreReturnTypes = 1 << 2, - StrictArity = 1 << 3, - Callback = BivariantCallback | StrictCallback, + StrictArity = 1 << 3, + StrictAnySignature = 1 << 4, + Callback = BivariantCallback | StrictCallback, } const enum IntersectionState { @@ -19454,6 +19455,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (isAnySignature(target)) { return Ternary.True; } + if (checkMode & SignatureCheckMode.StrictAnySignature && isAnySignature(source)) { + return Ternary.False; + } const targetCount = getParameterCount(target); const sourceHasMoreParameters = !hasEffectiveRestParameter(target) && @@ -21983,8 +21987,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * See signatureAssignableTo, compareSignaturesIdentical */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { + const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictAnySignature : + relation === strictSubtypeRelation ? SignatureCheckMode.StrictAnySignature | SignatureCheckMode.StrictArity : + SignatureCheckMode.None; return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, - relation === strictSubtypeRelation ? SignatureCheckMode.StrictArity : 0, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); + checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); } function signaturesIdenticalTo(source: Type, target: Type, kind: SignatureKind): Ternary { From 7d8fdc851326a4208f163df836cc02c87756da12 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 19 Jan 2023 07:35:15 -0800 Subject: [PATCH 06/11] Keep false branch logic for `instanceof` in getNarrowedType --- src/compiler/checker.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a4643498c6aaf..91c73332b82b0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -26920,7 +26920,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function getNarrowedTypeWorker(type: Type, candidate: Type, assumeTrue: boolean, checkDerived: boolean) { if (!assumeTrue) { - const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, checkDerived); + if (checkDerived) { + return filterType(type, t => !isTypeDerivedFrom(t, candidate)); + } + const trueType = getNarrowedType(type, candidate, /*assumeTrue*/ true, /*checkDerived*/ false); return filterType(type, t => !isTypeSubsetOf(t, trueType)); } if (type.flags & TypeFlags.AnyOrUnknown) { From d7a80c460c159011f69328f794e98756f974534a Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 19 Jan 2023 11:19:42 -0800 Subject: [PATCH 07/11] Broaden rule that ensures { [x: string]: xxx } <: {} and not vice-versa --- src/compiler/checker.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91c73332b82b0..1efd021a6ac31 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -21329,11 +21329,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return Ternary.False; } } - // A fresh empty object type is never a subtype of a non-empty object type and an empty object type is never a subtype of - // a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx } and { [x: string: xxx] } <: {}. Without these - // rules, those types would be mutual subtypes. - else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source) || - (relation === strictSubtypeRelation && target.flags & TypeFlags.Object && !isEmptyObjectType(target) && isEmptyObjectType(source) && !(getObjectFlags(source) & ObjectFlags.FreshLiteral))) { + // A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx } + // but not vice-versa. Without this rule, those types would be mutual subtypes. + else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) { return Ternary.False; } // Even if relationship doesn't hold for unions, intersections, or generic type references, @@ -22084,8 +22082,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (sourceInfo) { return indexInfoRelatedTo(sourceInfo, targetInfo, reportErrors); } - if (!(intersectionState & IntersectionState.Source) && isObjectTypeWithInferableIndex(source)) { - // Intersection constituents are never considered to have an inferred index signature + // Intersection constituents are never considered to have an inferred index signature. Also, in the strict subtype relation, + // only fresh object literals are considered to have inferred index signatures. This ensures { [x: string]: xxx } <: {} but + // not vice-versa. Without this rule, those types would be mutual strict subtypes. + if (!(intersectionState & IntersectionState.Source) && (relation !== strictSubtypeRelation || getObjectFlags(source) & ObjectFlags.FreshLiteral) && isObjectTypeWithInferableIndex(source)) { return membersRelatedToIndexInfo(source, targetInfo, reportErrors, intersectionState); } if (reportErrors) { From ed1ee1965434e673d278641863e68a199cc0c44d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 19 Jan 2023 11:21:02 -0800 Subject: [PATCH 08/11] Accept new baselines --- tests/baselines/reference/subtypesOfTypeParameter.types | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/subtypesOfTypeParameter.types b/tests/baselines/reference/subtypesOfTypeParameter.types index a79e1508a367f..02d4d527786b0 100644 --- a/tests/baselines/reference/subtypesOfTypeParameter.types +++ b/tests/baselines/reference/subtypesOfTypeParameter.types @@ -393,16 +393,16 @@ function f2(x: T, y: U) { } var r19 = true ? new Object() : x; // BCT is Object ->r19 : Object | T ->true ? new Object() : x : Object | T +>r19 : Object +>true ? new Object() : x : Object >true : true >new Object() : Object >Object : ObjectConstructor >x : T var r19 = true ? x : new Object(); // BCT is Object ->r19 : Object | T ->true ? x : new Object() : Object | T +>r19 : Object +>true ? x : new Object() : Object >true : true >x : T >new Object() : Object From 504149e903a6732a9dbf5c0813793cd378d76abe Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 20 Jan 2023 07:48:31 -0800 Subject: [PATCH 09/11] Add tests --- .../strictSubtypeAndNarrowing.errors.txt | 120 ++++++++ .../reference/strictSubtypeAndNarrowing.js | 170 +++++++++++ .../strictSubtypeAndNarrowing.symbols | 266 ++++++++++++++++ .../reference/strictSubtypeAndNarrowing.types | 288 ++++++++++++++++++ .../compiler/strictSubtypeAndNarrowing.ts | 103 +++++++ 5 files changed, 947 insertions(+) create mode 100644 tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt create mode 100644 tests/baselines/reference/strictSubtypeAndNarrowing.js create mode 100644 tests/baselines/reference/strictSubtypeAndNarrowing.symbols create mode 100644 tests/baselines/reference/strictSubtypeAndNarrowing.types create mode 100644 tests/cases/compiler/strictSubtypeAndNarrowing.ts diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt b/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt new file mode 100644 index 0000000000000..ac0086dc9ba5a --- /dev/null +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt @@ -0,0 +1,120 @@ +tests/cases/compiler/strictSubtypeAndNarrowing.ts(46,27): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. + Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. +tests/cases/compiler/strictSubtypeAndNarrowing.ts(47,27): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. + Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. +tests/cases/compiler/strictSubtypeAndNarrowing.ts(52,26): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. + Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + + +==== tests/cases/compiler/strictSubtypeAndNarrowing.ts (3 errors) ==== + // Check that `any` is a strict supertype of `unknown` + + declare const x11: { x: unknown }; + declare const x12: { x: any }; + + const a11 = [x11, x12]; + const a12 = [x12, x11]; + + declare const x21: { x: any }; + declare const x22: { x: unknown }; + + const a21 = [x22, x21]; + const a22 = [x21, x22]; + + // Strict subtype doesn't infer index signatures in non-fresh object types + + const x31 = { a: 1 }; + declare const x32: { [x: string]: unknown, a: number } + + const a31 = [x31, x32]; + const a32 = [x32, x31]; + + declare const x41: { [x: string]: unknown, a: number } + const x42 = { a: 1 }; + + const a41 = [x42, x41]; + const a42 = [x41, x42]; + + // (...args: any[]) => any is supertype of all other function types + + declare function isFunction(x: unknown): x is (...args: any[]) => any; + + function qqq(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined + } + + // Type of x = y is y with freshness preserved + + function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { + obj1 = obj2 = { x: 1, y: 2 }; + ~~~~ +!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + obj2 = obj1 = { x: 1, y: 2 }; + ~~~~ +!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + } + + function fx11(): { x?: number } { + let obj: { x?: number, y?: number }; + return obj = { x: 1, y: 2 }; + ~~~~ +!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + } + + // Narrowing preserves original type in false branch for non-identical mutual subtypes + + declare function isObject1(value: unknown): value is Record; + + function gg(x: {}) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} + } + + declare function isObject2(value: unknown): value is {}; + + function gg2(x: Record) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record + } + + // Repro from #50916 + + type Identity = {[K in keyof T]: T[K]}; + + function is(value: T): value is Identity { + return true; + } + + type Union = {a: number} | {b: number} | {c: number}; + + function example(x: Union) { + if (is(x)) { x } + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + x; // Union + } + \ No newline at end of file diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.js b/tests/baselines/reference/strictSubtypeAndNarrowing.js new file mode 100644 index 0000000000000..c75cb630cb5a4 --- /dev/null +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.js @@ -0,0 +1,170 @@ +//// [strictSubtypeAndNarrowing.ts] +// Check that `any` is a strict supertype of `unknown` + +declare const x11: { x: unknown }; +declare const x12: { x: any }; + +const a11 = [x11, x12]; +const a12 = [x12, x11]; + +declare const x21: { x: any }; +declare const x22: { x: unknown }; + +const a21 = [x22, x21]; +const a22 = [x21, x22]; + +// Strict subtype doesn't infer index signatures in non-fresh object types + +const x31 = { a: 1 }; +declare const x32: { [x: string]: unknown, a: number } + +const a31 = [x31, x32]; +const a32 = [x32, x31]; + +declare const x41: { [x: string]: unknown, a: number } +const x42 = { a: 1 }; + +const a41 = [x42, x41]; +const a42 = [x41, x42]; + +// (...args: any[]) => any is supertype of all other function types + +declare function isFunction(x: unknown): x is (...args: any[]) => any; + +function qqq(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined +} + +// Type of x = y is y with freshness preserved + +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { + obj1 = obj2 = { x: 1, y: 2 }; + obj2 = obj1 = { x: 1, y: 2 }; +} + +function fx11(): { x?: number } { + let obj: { x?: number, y?: number }; + return obj = { x: 1, y: 2 }; +} + +// Narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; + +function gg(x: {}) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} + +declare function isObject2(value: unknown): value is {}; + +function gg2(x: Record) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; + +function is(value: T): value is Identity { + return true; +} + +type Union = {a: number} | {b: number} | {c: number}; + +function example(x: Union) { + if (is(x)) { x } + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + x; // Union +} + + +//// [strictSubtypeAndNarrowing.js] +"use strict"; +// Check that `any` is a strict supertype of `unknown` +var a11 = [x11, x12]; +var a12 = [x12, x11]; +var a21 = [x22, x21]; +var a22 = [x21, x22]; +// Strict subtype doesn't infer index signatures in non-fresh object types +var x31 = { a: 1 }; +var a31 = [x31, x32]; +var a32 = [x32, x31]; +var x42 = { a: 1 }; +var a41 = [x42, x41]; +var a42 = [x41, x42]; +function qqq(f) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined +} +// Type of x = y is y with freshness preserved +function fx10(obj1, obj2) { + obj1 = obj2 = { x: 1, y: 2 }; + obj2 = obj1 = { x: 1, y: 2 }; +} +function fx11() { + var obj; + return obj = { x: 1, y: 2 }; +} +function gg(x) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} +function gg2(x) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record +} +function is(value) { + return true; +} +function example(x) { + if (is(x)) { + x; + } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + if (is(x)) { } + x; // Union +} diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.symbols b/tests/baselines/reference/strictSubtypeAndNarrowing.symbols new file mode 100644 index 0000000000000..a053ee1dcf86a --- /dev/null +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.symbols @@ -0,0 +1,266 @@ +=== tests/cases/compiler/strictSubtypeAndNarrowing.ts === +// Check that `any` is a strict supertype of `unknown` + +declare const x11: { x: unknown }; +>x11 : Symbol(x11, Decl(strictSubtypeAndNarrowing.ts, 2, 13)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 2, 20)) + +declare const x12: { x: any }; +>x12 : Symbol(x12, Decl(strictSubtypeAndNarrowing.ts, 3, 13)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 3, 20)) + +const a11 = [x11, x12]; +>a11 : Symbol(a11, Decl(strictSubtypeAndNarrowing.ts, 5, 5)) +>x11 : Symbol(x11, Decl(strictSubtypeAndNarrowing.ts, 2, 13)) +>x12 : Symbol(x12, Decl(strictSubtypeAndNarrowing.ts, 3, 13)) + +const a12 = [x12, x11]; +>a12 : Symbol(a12, Decl(strictSubtypeAndNarrowing.ts, 6, 5)) +>x12 : Symbol(x12, Decl(strictSubtypeAndNarrowing.ts, 3, 13)) +>x11 : Symbol(x11, Decl(strictSubtypeAndNarrowing.ts, 2, 13)) + +declare const x21: { x: any }; +>x21 : Symbol(x21, Decl(strictSubtypeAndNarrowing.ts, 8, 13)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 8, 20)) + +declare const x22: { x: unknown }; +>x22 : Symbol(x22, Decl(strictSubtypeAndNarrowing.ts, 9, 13)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 9, 20)) + +const a21 = [x22, x21]; +>a21 : Symbol(a21, Decl(strictSubtypeAndNarrowing.ts, 11, 5)) +>x22 : Symbol(x22, Decl(strictSubtypeAndNarrowing.ts, 9, 13)) +>x21 : Symbol(x21, Decl(strictSubtypeAndNarrowing.ts, 8, 13)) + +const a22 = [x21, x22]; +>a22 : Symbol(a22, Decl(strictSubtypeAndNarrowing.ts, 12, 5)) +>x21 : Symbol(x21, Decl(strictSubtypeAndNarrowing.ts, 8, 13)) +>x22 : Symbol(x22, Decl(strictSubtypeAndNarrowing.ts, 9, 13)) + +// Strict subtype doesn't infer index signatures in non-fresh object types + +const x31 = { a: 1 }; +>x31 : Symbol(x31, Decl(strictSubtypeAndNarrowing.ts, 16, 5)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 16, 13)) + +declare const x32: { [x: string]: unknown, a: number } +>x32 : Symbol(x32, Decl(strictSubtypeAndNarrowing.ts, 17, 13)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 17, 22)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 17, 42)) + +const a31 = [x31, x32]; +>a31 : Symbol(a31, Decl(strictSubtypeAndNarrowing.ts, 19, 5)) +>x31 : Symbol(x31, Decl(strictSubtypeAndNarrowing.ts, 16, 5)) +>x32 : Symbol(x32, Decl(strictSubtypeAndNarrowing.ts, 17, 13)) + +const a32 = [x32, x31]; +>a32 : Symbol(a32, Decl(strictSubtypeAndNarrowing.ts, 20, 5)) +>x32 : Symbol(x32, Decl(strictSubtypeAndNarrowing.ts, 17, 13)) +>x31 : Symbol(x31, Decl(strictSubtypeAndNarrowing.ts, 16, 5)) + +declare const x41: { [x: string]: unknown, a: number } +>x41 : Symbol(x41, Decl(strictSubtypeAndNarrowing.ts, 22, 13)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 22, 22)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 22, 42)) + +const x42 = { a: 1 }; +>x42 : Symbol(x42, Decl(strictSubtypeAndNarrowing.ts, 23, 5)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 23, 13)) + +const a41 = [x42, x41]; +>a41 : Symbol(a41, Decl(strictSubtypeAndNarrowing.ts, 25, 5)) +>x42 : Symbol(x42, Decl(strictSubtypeAndNarrowing.ts, 23, 5)) +>x41 : Symbol(x41, Decl(strictSubtypeAndNarrowing.ts, 22, 13)) + +const a42 = [x41, x42]; +>a42 : Symbol(a42, Decl(strictSubtypeAndNarrowing.ts, 26, 5)) +>x41 : Symbol(x41, Decl(strictSubtypeAndNarrowing.ts, 22, 13)) +>x42 : Symbol(x42, Decl(strictSubtypeAndNarrowing.ts, 23, 5)) + +// (...args: any[]) => any is supertype of all other function types + +declare function isFunction(x: unknown): x is (...args: any[]) => any; +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 30, 28)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 30, 28)) +>args : Symbol(args, Decl(strictSubtypeAndNarrowing.ts, 30, 47)) + +function qqq(f: (() => void) | undefined) { +>qqq : Symbol(qqq, Decl(strictSubtypeAndNarrowing.ts, 30, 70)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) + + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) + + f; // () => void +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) + } + else { + f; // undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) + } + f; // (() => void) | undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) +} + +// Type of x = y is y with freshness preserved + +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { +>fx10 : Symbol(fx10, Decl(strictSubtypeAndNarrowing.ts, 40, 1)) +>obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 44, 14)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 44, 21)) +>obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 44, 35)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 44, 43)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 44, 55)) + + obj1 = obj2 = { x: 1, y: 2 }; +>obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 44, 14)) +>obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 44, 35)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 45, 19)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 45, 25)) + + obj2 = obj1 = { x: 1, y: 2 }; +>obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 44, 35)) +>obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 44, 14)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 46, 19)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 46, 25)) +} + +function fx11(): { x?: number } { +>fx11 : Symbol(fx11, Decl(strictSubtypeAndNarrowing.ts, 47, 1)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 49, 18)) + + let obj: { x?: number, y?: number }; +>obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 50, 7)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 50, 14)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 50, 26)) + + return obj = { x: 1, y: 2 }; +>obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 50, 7)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 51, 18)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 51, 24)) +} + +// Narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; +>isObject1 : Symbol(isObject1, Decl(strictSubtypeAndNarrowing.ts, 52, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 56, 27)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 56, 27)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + +function gg(x: {}) { +>gg : Symbol(gg, Decl(strictSubtypeAndNarrowing.ts, 56, 77)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + + if (isObject1(x)) { +>isObject1 : Symbol(isObject1, Decl(strictSubtypeAndNarrowing.ts, 52, 1)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + + x; // Record +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + } + else { + x; // {} +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + } + x; // {} +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) +} + +declare function isObject2(value: unknown): value is {}; +>isObject2 : Symbol(isObject2, Decl(strictSubtypeAndNarrowing.ts, 66, 1)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 68, 27)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 68, 27)) + +function gg2(x: Record) { +>gg2 : Symbol(gg2, Decl(strictSubtypeAndNarrowing.ts, 68, 56)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + + if (isObject2(x)) { +>isObject2 : Symbol(isObject2, Decl(strictSubtypeAndNarrowing.ts, 66, 1)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + + x; // {} +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + } + else { + x; // Record +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + } + x; // Record +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; +>Identity : Symbol(Identity, Decl(strictSubtypeAndNarrowing.ts, 78, 1)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 82, 14)) +>K : Symbol(K, Decl(strictSubtypeAndNarrowing.ts, 82, 21)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 82, 14)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 82, 14)) +>K : Symbol(K, Decl(strictSubtypeAndNarrowing.ts, 82, 21)) + +function is(value: T): value is Identity { +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 84, 12)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 84, 15)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 84, 12)) +>value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 84, 15)) +>Identity : Symbol(Identity, Decl(strictSubtypeAndNarrowing.ts, 78, 1)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 84, 12)) + + return true; +} + +type Union = {a: number} | {b: number} | {c: number}; +>Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 86, 1)) +>a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 88, 15)) +>b : Symbol(b, Decl(strictSubtypeAndNarrowing.ts, 88, 29)) +>c : Symbol(c, Decl(strictSubtypeAndNarrowing.ts, 88, 43)) + +function example(x: Union) { +>example : Symbol(example, Decl(strictSubtypeAndNarrowing.ts, 88, 54)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) +>Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 86, 1)) + + if (is(x)) { x } +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + if (is(x)) {} +>is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + + x; // Union +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) +} + diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.types b/tests/baselines/reference/strictSubtypeAndNarrowing.types new file mode 100644 index 0000000000000..d7dd68fa760da --- /dev/null +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.types @@ -0,0 +1,288 @@ +=== tests/cases/compiler/strictSubtypeAndNarrowing.ts === +// Check that `any` is a strict supertype of `unknown` + +declare const x11: { x: unknown }; +>x11 : { x: unknown; } +>x : unknown + +declare const x12: { x: any }; +>x12 : { x: any; } +>x : any + +const a11 = [x11, x12]; +>a11 : { x: any; }[] +>[x11, x12] : { x: any; }[] +>x11 : { x: unknown; } +>x12 : { x: any; } + +const a12 = [x12, x11]; +>a12 : { x: any; }[] +>[x12, x11] : { x: any; }[] +>x12 : { x: any; } +>x11 : { x: unknown; } + +declare const x21: { x: any }; +>x21 : { x: any; } +>x : any + +declare const x22: { x: unknown }; +>x22 : { x: unknown; } +>x : unknown + +const a21 = [x22, x21]; +>a21 : { x: any; }[] +>[x22, x21] : { x: any; }[] +>x22 : { x: unknown; } +>x21 : { x: any; } + +const a22 = [x21, x22]; +>a22 : { x: any; }[] +>[x21, x22] : { x: any; }[] +>x21 : { x: any; } +>x22 : { x: unknown; } + +// Strict subtype doesn't infer index signatures in non-fresh object types + +const x31 = { a: 1 }; +>x31 : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +declare const x32: { [x: string]: unknown, a: number } +>x32 : { [x: string]: unknown; a: number; } +>x : string +>a : number + +const a31 = [x31, x32]; +>a31 : { a: number; }[] +>[x31, x32] : { a: number; }[] +>x31 : { a: number; } +>x32 : { [x: string]: unknown; a: number; } + +const a32 = [x32, x31]; +>a32 : { a: number; }[] +>[x32, x31] : { a: number; }[] +>x32 : { [x: string]: unknown; a: number; } +>x31 : { a: number; } + +declare const x41: { [x: string]: unknown, a: number } +>x41 : { [x: string]: unknown; a: number; } +>x : string +>a : number + +const x42 = { a: 1 }; +>x42 : { a: number; } +>{ a: 1 } : { a: number; } +>a : number +>1 : 1 + +const a41 = [x42, x41]; +>a41 : { a: number; }[] +>[x42, x41] : { a: number; }[] +>x42 : { a: number; } +>x41 : { [x: string]: unknown; a: number; } + +const a42 = [x41, x42]; +>a42 : { a: number; }[] +>[x41, x42] : { a: number; }[] +>x41 : { [x: string]: unknown; a: number; } +>x42 : { a: number; } + +// (...args: any[]) => any is supertype of all other function types + +declare function isFunction(x: unknown): x is (...args: any[]) => any; +>isFunction : (x: unknown) => x is (...args: any[]) => any +>x : unknown +>args : any[] + +function qqq(f: (() => void) | undefined) { +>qqq : (f: (() => void) | undefined) => void +>f : (() => void) | undefined + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is (...args: any[]) => any +>f : (() => void) | undefined + + f; // () => void +>f : () => void + } + else { + f; // undefined +>f : undefined + } + f; // (() => void) | undefined +>f : (() => void) | undefined +} + +// Type of x = y is y with freshness preserved + +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { +>fx10 : (obj1: { x?: number;}, obj2: { x?: number; y?: number;}) => void +>obj1 : { x?: number | undefined; } +>x : number | undefined +>obj2 : { x?: number | undefined; y?: number | undefined; } +>x : number | undefined +>y : number | undefined + + obj1 = obj2 = { x: 1, y: 2 }; +>obj1 = obj2 = { x: 1, y: 2 } : { x: number; y: number; } +>obj1 : { x?: number | undefined; } +>obj2 = { x: 1, y: 2 } : { x: number; y: number; } +>obj2 : { x?: number | undefined; y?: number | undefined; } +>{ x: 1, y: 2 } : { x: number; y: number; } +>x : number +>1 : 1 +>y : number +>2 : 2 + + obj2 = obj1 = { x: 1, y: 2 }; +>obj2 = obj1 = { x: 1, y: 2 } : { x: number; y: number; } +>obj2 : { x?: number | undefined; y?: number | undefined; } +>obj1 = { x: 1, y: 2 } : { x: number; y: number; } +>obj1 : { x?: number | undefined; } +>{ x: 1, y: 2 } : { x: number; y: number; } +>x : number +>1 : 1 +>y : number +>2 : 2 +} + +function fx11(): { x?: number } { +>fx11 : () => { x?: number;} +>x : number | undefined + + let obj: { x?: number, y?: number }; +>obj : { x?: number | undefined; y?: number | undefined; } +>x : number | undefined +>y : number | undefined + + return obj = { x: 1, y: 2 }; +>obj = { x: 1, y: 2 } : { x: number; y: number; } +>obj : { x?: number | undefined; y?: number | undefined; } +>{ x: 1, y: 2 } : { x: number; y: number; } +>x : number +>1 : 1 +>y : number +>2 : 2 +} + +// Narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; +>isObject1 : (value: unknown) => value is Record +>value : unknown + +function gg(x: {}) { +>gg : (x: {}) => void +>x : {} + + if (isObject1(x)) { +>isObject1(x) : boolean +>isObject1 : (value: unknown) => value is Record +>x : {} + + x; // Record +>x : Record + } + else { + x; // {} +>x : {} + } + x; // {} +>x : {} +} + +declare function isObject2(value: unknown): value is {}; +>isObject2 : (value: unknown) => value is {} +>value : unknown + +function gg2(x: Record) { +>gg2 : (x: Record) => void +>x : Record + + if (isObject2(x)) { +>isObject2(x) : boolean +>isObject2 : (value: unknown) => value is {} +>x : Record + + x; // {} +>x : {} + } + else { + x; // Record +>x : Record + } + x; // Record +>x : Record +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; +>Identity : Identity + +function is(value: T): value is Identity { +>is : (value: T) => value is Identity +>value : T + + return true; +>true : true +} + +type Union = {a: number} | {b: number} | {c: number}; +>Union : { a: number; } | { b: number; } | { c: number; } +>a : number +>b : number +>c : number + +function example(x: Union) { +>example : (x: Union) => void +>x : Union + + if (is(x)) { x } +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union +>x : { a: number; } | { b: number; } | { c: number; } + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + if (is(x)) {} +>is(x) : boolean +>is : (value: T) => value is Identity +>x : Union + + x; // Union +>x : Union +} + diff --git a/tests/cases/compiler/strictSubtypeAndNarrowing.ts b/tests/cases/compiler/strictSubtypeAndNarrowing.ts new file mode 100644 index 0000000000000..c1b58ae978767 --- /dev/null +++ b/tests/cases/compiler/strictSubtypeAndNarrowing.ts @@ -0,0 +1,103 @@ +// @strict: true + +// Check that `any` is a strict supertype of `unknown` + +declare const x11: { x: unknown }; +declare const x12: { x: any }; + +const a11 = [x11, x12]; +const a12 = [x12, x11]; + +declare const x21: { x: any }; +declare const x22: { x: unknown }; + +const a21 = [x22, x21]; +const a22 = [x21, x22]; + +// Strict subtype doesn't infer index signatures in non-fresh object types + +const x31 = { a: 1 }; +declare const x32: { [x: string]: unknown, a: number } + +const a31 = [x31, x32]; +const a32 = [x32, x31]; + +declare const x41: { [x: string]: unknown, a: number } +const x42 = { a: 1 }; + +const a41 = [x42, x41]; +const a42 = [x41, x42]; + +// (...args: any[]) => any is supertype of all other function types + +declare function isFunction(x: unknown): x is (...args: any[]) => any; + +function qqq(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined +} + +// Type of x = y is y with freshness preserved + +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { + obj1 = obj2 = { x: 1, y: 2 }; + obj2 = obj1 = { x: 1, y: 2 }; +} + +function fx11(): { x?: number } { + let obj: { x?: number, y?: number }; + return obj = { x: 1, y: 2 }; +} + +// Narrowing preserves original type in false branch for non-identical mutual subtypes + +declare function isObject1(value: unknown): value is Record; + +function gg(x: {}) { + if (isObject1(x)) { + x; // Record + } + else { + x; // {} + } + x; // {} +} + +declare function isObject2(value: unknown): value is {}; + +function gg2(x: Record) { + if (isObject2(x)) { + x; // {} + } + else { + x; // Record + } + x; // Record +} + +// Repro from #50916 + +type Identity = {[K in keyof T]: T[K]}; + +function is(value: T): value is Identity { + return true; +} + +type Union = {a: number} | {b: number} | {c: number}; + +function example(x: Union) { + if (is(x)) { x } + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + if (is(x)) {} + x; // Union +} From cfaa7ef2b2727444a0c4a65428652a2f1a206655 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Sat, 21 Jan 2023 08:50:16 -0800 Subject: [PATCH 10/11] Allow combinations of `any`, `never`, and `unknown` in top signatures --- src/compiler/checker.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1efd021a6ac31..2a384a33bdcf2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1240,7 +1240,7 @@ export const enum SignatureCheckMode { StrictCallback = 1 << 1, IgnoreReturnTypes = 1 << 2, StrictArity = 1 << 3, - StrictAnySignature = 1 << 4, + StrictTopSignature = 1 << 4, Callback = BivariantCallback | StrictCallback, } @@ -19428,12 +19428,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { type ErrorReporter = (message: DiagnosticMessage, arg0?: string, arg1?: string) => void; /** - * Returns true if `s` is `(...args: any[]) => any` or `(this: any, ...args: any[]) => any` + * Returns true if `s` is `(...args: A) => R` where `A` is `any`, `any[]`, `never`, or `never[]`, and `R` is `any` or `unknown`. */ - function isAnySignature(s: Signature) { - return !s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && - signatureHasRestParameter(s) && (getTypeOfParameter(s.parameters[0]) === anyArrayType || isTypeAny(getTypeOfParameter(s.parameters[0]))) && - isTypeAny(getReturnTypeOfSignature(s)); + function isTopSignature(s: Signature) { + if (!s.typeParameters && (!s.thisParameter || isTypeAny(getTypeOfParameter(s.thisParameter))) && s.parameters.length === 1 && signatureHasRestParameter(s)) { + const paramType = getTypeOfParameter(s.parameters[0]); + const restType = isArrayType(paramType) ? getTypeArguments(paramType)[0] : paramType; + return !!(restType.flags & (TypeFlags.Any | TypeFlags.Never) && getReturnTypeOfSignature(s).flags & TypeFlags.AnyOrUnknown); + } + return false; } /** @@ -19452,10 +19455,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return Ternary.True; } - if (isAnySignature(target)) { + if (!(checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source)) && isTopSignature(target)) { return Ternary.True; } - if (checkMode & SignatureCheckMode.StrictAnySignature && isAnySignature(source)) { + if (checkMode & SignatureCheckMode.StrictTopSignature && isTopSignature(source) && !isTopSignature(target)) { return Ternary.False; } @@ -21985,8 +21988,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { * See signatureAssignableTo, compareSignaturesIdentical */ function signatureRelatedTo(source: Signature, target: Signature, erase: boolean, reportErrors: boolean, incompatibleReporter: (source: Type, target: Type) => void): Ternary { - const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictAnySignature : - relation === strictSubtypeRelation ? SignatureCheckMode.StrictAnySignature | SignatureCheckMode.StrictArity : + const checkMode = relation === subtypeRelation ? SignatureCheckMode.StrictTopSignature : + relation === strictSubtypeRelation ? SignatureCheckMode.StrictTopSignature | SignatureCheckMode.StrictArity : SignatureCheckMode.None; return compareSignaturesRelated(erase ? getErasedSignature(source) : source, erase ? getErasedSignature(target) : target, checkMode, reportErrors, reportError, incompatibleReporter, isRelatedToWorker, reportUnreliableMapper); From caf42f0882dbf03f720e22b4c7a23861b45e6eb3 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 14 Feb 2023 11:41:18 -0800 Subject: [PATCH 11/11] Add more tests and remove duplicates --- .../strictSubtypeAndNarrowing.errors.txt | 137 +++++--- .../reference/strictSubtypeAndNarrowing.js | 206 +++++++---- .../strictSubtypeAndNarrowing.symbols | 323 ++++++++++-------- .../reference/strictSubtypeAndNarrowing.types | 292 +++++++++------- .../compiler/strictSubtypeAndNarrowing.ts | 113 +++--- 5 files changed, 637 insertions(+), 434 deletions(-) diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt b/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt index ac0086dc9ba5a..1dd63fe6e0f77 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.errors.txt @@ -1,8 +1,8 @@ -tests/cases/compiler/strictSubtypeAndNarrowing.ts(46,27): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +tests/cases/compiler/strictSubtypeAndNarrowing.ts(123,27): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. -tests/cases/compiler/strictSubtypeAndNarrowing.ts(47,27): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +tests/cases/compiler/strictSubtypeAndNarrowing.ts(124,27): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. -tests/cases/compiler/strictSubtypeAndNarrowing.ts(52,26): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +tests/cases/compiler/strictSubtypeAndNarrowing.ts(129,26): error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. @@ -35,12 +35,19 @@ tests/cases/compiler/strictSubtypeAndNarrowing.ts(52,26): error TS2322: Type '{ const a41 = [x42, x41]; const a42 = [x41, x42]; - // (...args: any[]) => any is supertype of all other function types + // (...args: A) => R, where A is any, any[], never, or never[] and R is any or unknown, is supertype of all function types. - declare function isFunction(x: unknown): x is (...args: any[]) => any; + declare function isFunction(x: unknown): x is T; - function qqq(f: (() => void) | undefined) { - if (isFunction(f)) { + type A = (...args: any) => unknown; + type B = (...args: any[]) => unknown; + type C = (...args: never) => unknown; + type D = (...args: never[]) => unknown; + + type FnTypes = A | B | C | D; + + function fx1(f: (() => void) | undefined) { + if (isFunction(f)) { f; // () => void } else { @@ -49,72 +56,94 @@ tests/cases/compiler/strictSubtypeAndNarrowing.ts(52,26): error TS2322: Type '{ f; // (() => void) | undefined } - // Type of x = y is y with freshness preserved - - function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { - obj1 = obj2 = { x: 1, y: 2 }; - ~~~~ -!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. -!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. - obj2 = obj1 = { x: 1, y: 2 }; - ~~~~ -!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. -!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + function fx2(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } - function fx11(): { x?: number } { - let obj: { x?: number, y?: number }; - return obj = { x: 1, y: 2 }; - ~~~~ -!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. -!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + function fx3(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } - // Narrowing preserves original type in false branch for non-identical mutual subtypes - - declare function isObject1(value: unknown): value is Record; + function fx4(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined + } - function gg(x: {}) { - if (isObject1(x)) { - x; // Record + function checkA(f: FnTypes) { + if (isFunction(f)) { + f; // A | B } else { - x; // {} + f; // C | D } - x; // {} + f; // FnTypes } - declare function isObject2(value: unknown): value is {}; + function checkB(f: FnTypes) { + if (isFunction(f)) { + f; // A | B + } + else { + f; // C | D + } + f; // FnTypes + } - function gg2(x: Record) { - if (isObject2(x)) { - x; // {} + function checkC(f: FnTypes) { + if (isFunction(f)) { + f; // FnTypes } else { - x; // Record + f; // never } - x; // Record + f; // FnTypes } - // Repro from #50916 + function checkD(f: FnTypes) { + if (isFunction(f)) { + f; // FnTypes + } + else { + f; // never + } + f; // FnTypes + } - type Identity = {[K in keyof T]: T[K]}; + // Type of x = y is y with freshness preserved - function is(value: T): value is Identity { - return true; + function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { + obj1 = obj2 = { x: 1, y: 2 }; + ~~~~ +!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. + obj2 = obj1 = { x: 1, y: 2 }; + ~~~~ +!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. } - type Union = {a: number} | {b: number} | {c: number}; - - function example(x: Union) { - if (is(x)) { x } - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - x; // Union + function fx11(): { x?: number } { + let obj: { x?: number, y?: number }; + return obj = { x: 1, y: 2 }; + ~~~~ +!!! error TS2322: Type '{ x: number; y: number; }' is not assignable to type '{ x?: number | undefined; }'. +!!! error TS2322: Object literal may only specify known properties, and 'y' does not exist in type '{ x?: number | undefined; }'. } \ No newline at end of file diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.js b/tests/baselines/reference/strictSubtypeAndNarrowing.js index c75cb630cb5a4..3e135c3af6fc1 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.js +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.js @@ -27,12 +27,19 @@ const x42 = { a: 1 }; const a41 = [x42, x41]; const a42 = [x41, x42]; -// (...args: any[]) => any is supertype of all other function types +// (...args: A) => R, where A is any, any[], never, or never[] and R is any or unknown, is supertype of all function types. -declare function isFunction(x: unknown): x is (...args: any[]) => any; +declare function isFunction(x: unknown): x is T; -function qqq(f: (() => void) | undefined) { - if (isFunction(f)) { +type A = (...args: any) => unknown; +type B = (...args: any[]) => unknown; +type C = (...args: never) => unknown; +type D = (...args: never[]) => unknown; + +type FnTypes = A | B | C | D; + +function fx1(f: (() => void) | undefined) { + if (isFunction(f)) { f; // () => void } else { @@ -41,64 +48,86 @@ function qqq(f: (() => void) | undefined) { f; // (() => void) | undefined } -// Type of x = y is y with freshness preserved - -function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { - obj1 = obj2 = { x: 1, y: 2 }; - obj2 = obj1 = { x: 1, y: 2 }; +function fx2(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } -function fx11(): { x?: number } { - let obj: { x?: number, y?: number }; - return obj = { x: 1, y: 2 }; +function fx3(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } -// Narrowing preserves original type in false branch for non-identical mutual subtypes - -declare function isObject1(value: unknown): value is Record; +function fx4(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined +} -function gg(x: {}) { - if (isObject1(x)) { - x; // Record +function checkA(f: FnTypes) { + if (isFunction(f)) { + f; // A | B } else { - x; // {} + f; // C | D } - x; // {} + f; // FnTypes } -declare function isObject2(value: unknown): value is {}; +function checkB(f: FnTypes) { + if (isFunction(f)) { + f; // A | B + } + else { + f; // C | D + } + f; // FnTypes +} -function gg2(x: Record) { - if (isObject2(x)) { - x; // {} +function checkC(f: FnTypes) { + if (isFunction(f)) { + f; // FnTypes } else { - x; // Record + f; // never } - x; // Record + f; // FnTypes } -// Repro from #50916 +function checkD(f: FnTypes) { + if (isFunction(f)) { + f; // FnTypes + } + else { + f; // never + } + f; // FnTypes +} -type Identity = {[K in keyof T]: T[K]}; +// Type of x = y is y with freshness preserved -function is(value: T): value is Identity { - return true; +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { + obj1 = obj2 = { x: 1, y: 2 }; + obj2 = obj1 = { x: 1, y: 2 }; } -type Union = {a: number} | {b: number} | {c: number}; - -function example(x: Union) { - if (is(x)) { x } - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - x; // Union +function fx11(): { x?: number } { + let obj: { x?: number, y?: number }; + return obj = { x: 1, y: 2 }; } @@ -116,7 +145,7 @@ var a32 = [x32, x31]; var x42 = { a: 1 }; var a41 = [x42, x41]; var a42 = [x41, x42]; -function qqq(f) { +function fx1(f) { if (isFunction(f)) { f; // () => void } @@ -125,46 +154,75 @@ function qqq(f) { } f; // (() => void) | undefined } -// Type of x = y is y with freshness preserved -function fx10(obj1, obj2) { - obj1 = obj2 = { x: 1, y: 2 }; - obj2 = obj1 = { x: 1, y: 2 }; +function fx2(f) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } -function fx11() { - var obj; - return obj = { x: 1, y: 2 }; +function fx3(f) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined +} +function fx4(f) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } -function gg(x) { - if (isObject1(x)) { - x; // Record +function checkA(f) { + if (isFunction(f)) { + f; // A | B } else { - x; // {} + f; // C | D } - x; // {} + f; // FnTypes } -function gg2(x) { - if (isObject2(x)) { - x; // {} +function checkB(f) { + if (isFunction(f)) { + f; // A | B } else { - x; // Record + f; // C | D } - x; // Record + f; // FnTypes } -function is(value) { - return true; +function checkC(f) { + if (isFunction(f)) { + f; // FnTypes + } + else { + f; // never + } + f; // FnTypes } -function example(x) { - if (is(x)) { - x; - } - if (is(x)) { } - if (is(x)) { } - if (is(x)) { } - if (is(x)) { } - if (is(x)) { } - if (is(x)) { } - if (is(x)) { } - x; // Union +function checkD(f) { + if (isFunction(f)) { + f; // FnTypes + } + else { + f; // never + } + f; // FnTypes +} +// Type of x = y is y with freshness preserved +function fx10(obj1, obj2) { + obj1 = obj2 = { x: 1, y: 2 }; + obj2 = obj1 = { x: 1, y: 2 }; +} +function fx11() { + var obj; + return obj = { x: 1, y: 2 }; } diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.symbols b/tests/baselines/reference/strictSubtypeAndNarrowing.symbols index a053ee1dcf86a..2bdf3b7777cae 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.symbols +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.symbols @@ -77,190 +77,237 @@ const a42 = [x41, x42]; >x41 : Symbol(x41, Decl(strictSubtypeAndNarrowing.ts, 22, 13)) >x42 : Symbol(x42, Decl(strictSubtypeAndNarrowing.ts, 23, 5)) -// (...args: any[]) => any is supertype of all other function types +// (...args: A) => R, where A is any, any[], never, or never[] and R is any or unknown, is supertype of all function types. -declare function isFunction(x: unknown): x is (...args: any[]) => any; +declare function isFunction(x: unknown): x is T; >isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 30, 28)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 30, 28)) ->args : Symbol(args, Decl(strictSubtypeAndNarrowing.ts, 30, 47)) - -function qqq(f: (() => void) | undefined) { ->qqq : Symbol(qqq, Decl(strictSubtypeAndNarrowing.ts, 30, 70)) ->f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) - - if (isFunction(f)) { +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 30, 28)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 30, 31)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 30, 31)) +>T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 30, 28)) + +type A = (...args: any) => unknown; +>A : Symbol(A, Decl(strictSubtypeAndNarrowing.ts, 30, 51)) +>args : Symbol(args, Decl(strictSubtypeAndNarrowing.ts, 32, 10)) + +type B = (...args: any[]) => unknown; +>B : Symbol(B, Decl(strictSubtypeAndNarrowing.ts, 32, 35)) +>args : Symbol(args, Decl(strictSubtypeAndNarrowing.ts, 33, 10)) + +type C = (...args: never) => unknown; +>C : Symbol(C, Decl(strictSubtypeAndNarrowing.ts, 33, 37)) +>args : Symbol(args, Decl(strictSubtypeAndNarrowing.ts, 34, 10)) + +type D = (...args: never[]) => unknown; +>D : Symbol(D, Decl(strictSubtypeAndNarrowing.ts, 34, 37)) +>args : Symbol(args, Decl(strictSubtypeAndNarrowing.ts, 35, 10)) + +type FnTypes = A | B | C | D; +>FnTypes : Symbol(FnTypes, Decl(strictSubtypeAndNarrowing.ts, 35, 39)) +>A : Symbol(A, Decl(strictSubtypeAndNarrowing.ts, 30, 51)) +>B : Symbol(B, Decl(strictSubtypeAndNarrowing.ts, 32, 35)) +>C : Symbol(C, Decl(strictSubtypeAndNarrowing.ts, 33, 37)) +>D : Symbol(D, Decl(strictSubtypeAndNarrowing.ts, 34, 37)) + +function fx1(f: (() => void) | undefined) { +>fx1 : Symbol(fx1, Decl(strictSubtypeAndNarrowing.ts, 37, 29)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 39, 13)) + + if (isFunction(f)) { >isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) ->f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) +>A : Symbol(A, Decl(strictSubtypeAndNarrowing.ts, 30, 51)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 39, 13)) f; // () => void ->f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 39, 13)) } else { f; // undefined ->f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 39, 13)) } f; // (() => void) | undefined ->f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 32, 13)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 39, 13)) } -// Type of x = y is y with freshness preserved +function fx2(f: (() => void) | undefined) { +>fx2 : Symbol(fx2, Decl(strictSubtypeAndNarrowing.ts, 47, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 49, 13)) -function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { ->fx10 : Symbol(fx10, Decl(strictSubtypeAndNarrowing.ts, 40, 1)) ->obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 44, 14)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 44, 21)) ->obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 44, 35)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 44, 43)) ->y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 44, 55)) - - obj1 = obj2 = { x: 1, y: 2 }; ->obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 44, 14)) ->obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 44, 35)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 45, 19)) ->y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 45, 25)) + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>B : Symbol(B, Decl(strictSubtypeAndNarrowing.ts, 32, 35)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 49, 13)) - obj2 = obj1 = { x: 1, y: 2 }; ->obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 44, 35)) ->obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 44, 14)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 46, 19)) ->y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 46, 25)) + f; // () => void +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 49, 13)) + } + else { + f; // undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 49, 13)) + } + f; // (() => void) | undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 49, 13)) } -function fx11(): { x?: number } { ->fx11 : Symbol(fx11, Decl(strictSubtypeAndNarrowing.ts, 47, 1)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 49, 18)) +function fx3(f: (() => void) | undefined) { +>fx3 : Symbol(fx3, Decl(strictSubtypeAndNarrowing.ts, 57, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 59, 13)) - let obj: { x?: number, y?: number }; ->obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 50, 7)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 50, 14)) ->y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 50, 26)) + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>C : Symbol(C, Decl(strictSubtypeAndNarrowing.ts, 33, 37)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 59, 13)) - return obj = { x: 1, y: 2 }; ->obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 50, 7)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 51, 18)) ->y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 51, 24)) + f; // () => void +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 59, 13)) + } + else { + f; // undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 59, 13)) + } + f; // (() => void) | undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 59, 13)) } -// Narrowing preserves original type in false branch for non-identical mutual subtypes +function fx4(f: (() => void) | undefined) { +>fx4 : Symbol(fx4, Decl(strictSubtypeAndNarrowing.ts, 67, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 69, 13)) -declare function isObject1(value: unknown): value is Record; ->isObject1 : Symbol(isObject1, Decl(strictSubtypeAndNarrowing.ts, 52, 1)) ->value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 56, 27)) ->value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 56, 27)) ->Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>D : Symbol(D, Decl(strictSubtypeAndNarrowing.ts, 34, 37)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 69, 13)) + + f; // () => void +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 69, 13)) + } + else { + f; // undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 69, 13)) + } + f; // (() => void) | undefined +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 69, 13)) +} -function gg(x: {}) { ->gg : Symbol(gg, Decl(strictSubtypeAndNarrowing.ts, 56, 77)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) +function checkA(f: FnTypes) { +>checkA : Symbol(checkA, Decl(strictSubtypeAndNarrowing.ts, 77, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 79, 16)) +>FnTypes : Symbol(FnTypes, Decl(strictSubtypeAndNarrowing.ts, 35, 39)) - if (isObject1(x)) { ->isObject1 : Symbol(isObject1, Decl(strictSubtypeAndNarrowing.ts, 52, 1)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>A : Symbol(A, Decl(strictSubtypeAndNarrowing.ts, 30, 51)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 79, 16)) - x; // Record ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + f; // A | B +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 79, 16)) } else { - x; // {} ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + f; // C | D +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 79, 16)) } - x; // {} ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 58, 12)) + f; // FnTypes +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 79, 16)) } -declare function isObject2(value: unknown): value is {}; ->isObject2 : Symbol(isObject2, Decl(strictSubtypeAndNarrowing.ts, 66, 1)) ->value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 68, 27)) ->value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 68, 27)) - -function gg2(x: Record) { ->gg2 : Symbol(gg2, Decl(strictSubtypeAndNarrowing.ts, 68, 56)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) ->Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +function checkB(f: FnTypes) { +>checkB : Symbol(checkB, Decl(strictSubtypeAndNarrowing.ts, 87, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 89, 16)) +>FnTypes : Symbol(FnTypes, Decl(strictSubtypeAndNarrowing.ts, 35, 39)) - if (isObject2(x)) { ->isObject2 : Symbol(isObject2, Decl(strictSubtypeAndNarrowing.ts, 66, 1)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>B : Symbol(B, Decl(strictSubtypeAndNarrowing.ts, 32, 35)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 89, 16)) - x; // {} ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + f; // A | B +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 89, 16)) } else { - x; // Record ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + f; // C | D +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 89, 16)) } - x; // Record ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 70, 13)) + f; // FnTypes +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 89, 16)) } -// Repro from #50916 - -type Identity = {[K in keyof T]: T[K]}; ->Identity : Symbol(Identity, Decl(strictSubtypeAndNarrowing.ts, 78, 1)) ->T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 82, 14)) ->K : Symbol(K, Decl(strictSubtypeAndNarrowing.ts, 82, 21)) ->T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 82, 14)) ->T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 82, 14)) ->K : Symbol(K, Decl(strictSubtypeAndNarrowing.ts, 82, 21)) - -function is(value: T): value is Identity { ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 84, 12)) ->value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 84, 15)) ->T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 84, 12)) ->value : Symbol(value, Decl(strictSubtypeAndNarrowing.ts, 84, 15)) ->Identity : Symbol(Identity, Decl(strictSubtypeAndNarrowing.ts, 78, 1)) ->T : Symbol(T, Decl(strictSubtypeAndNarrowing.ts, 84, 12)) - - return true; -} +function checkC(f: FnTypes) { +>checkC : Symbol(checkC, Decl(strictSubtypeAndNarrowing.ts, 97, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 99, 16)) +>FnTypes : Symbol(FnTypes, Decl(strictSubtypeAndNarrowing.ts, 35, 39)) + + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>C : Symbol(C, Decl(strictSubtypeAndNarrowing.ts, 33, 37)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 99, 16)) -type Union = {a: number} | {b: number} | {c: number}; ->Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 86, 1)) ->a : Symbol(a, Decl(strictSubtypeAndNarrowing.ts, 88, 15)) ->b : Symbol(b, Decl(strictSubtypeAndNarrowing.ts, 88, 29)) ->c : Symbol(c, Decl(strictSubtypeAndNarrowing.ts, 88, 43)) + f; // FnTypes +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 99, 16)) + } + else { + f; // never +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 99, 16)) + } + f; // FnTypes +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 99, 16)) +} -function example(x: Union) { ->example : Symbol(example, Decl(strictSubtypeAndNarrowing.ts, 88, 54)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) ->Union : Symbol(Union, Decl(strictSubtypeAndNarrowing.ts, 86, 1)) +function checkD(f: FnTypes) { +>checkD : Symbol(checkD, Decl(strictSubtypeAndNarrowing.ts, 107, 1)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 109, 16)) +>FnTypes : Symbol(FnTypes, Decl(strictSubtypeAndNarrowing.ts, 35, 39)) - if (is(x)) { x } ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + if (isFunction(f)) { +>isFunction : Symbol(isFunction, Decl(strictSubtypeAndNarrowing.ts, 26, 23)) +>C : Symbol(C, Decl(strictSubtypeAndNarrowing.ts, 33, 37)) +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 109, 16)) - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + f; // FnTypes +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 109, 16)) + } + else { + f; // never +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 109, 16)) + } + f; // FnTypes +>f : Symbol(f, Decl(strictSubtypeAndNarrowing.ts, 109, 16)) +} - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) +// Type of x = y is y with freshness preserved - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { +>fx10 : Symbol(fx10, Decl(strictSubtypeAndNarrowing.ts, 117, 1)) +>obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 121, 14)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 121, 21)) +>obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 121, 35)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 121, 43)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 121, 55)) - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + obj1 = obj2 = { x: 1, y: 2 }; +>obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 121, 14)) +>obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 121, 35)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 122, 19)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 122, 25)) - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + obj2 = obj1 = { x: 1, y: 2 }; +>obj2 : Symbol(obj2, Decl(strictSubtypeAndNarrowing.ts, 121, 35)) +>obj1 : Symbol(obj1, Decl(strictSubtypeAndNarrowing.ts, 121, 14)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 123, 19)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 123, 25)) +} - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) +function fx11(): { x?: number } { +>fx11 : Symbol(fx11, Decl(strictSubtypeAndNarrowing.ts, 124, 1)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 126, 18)) - if (is(x)) {} ->is : Symbol(is, Decl(strictSubtypeAndNarrowing.ts, 82, 42)) ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + let obj: { x?: number, y?: number }; +>obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 127, 7)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 127, 14)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 127, 26)) - x; // Union ->x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 90, 17)) + return obj = { x: 1, y: 2 }; +>obj : Symbol(obj, Decl(strictSubtypeAndNarrowing.ts, 127, 7)) +>x : Symbol(x, Decl(strictSubtypeAndNarrowing.ts, 128, 18)) +>y : Symbol(y, Decl(strictSubtypeAndNarrowing.ts, 128, 24)) } diff --git a/tests/baselines/reference/strictSubtypeAndNarrowing.types b/tests/baselines/reference/strictSubtypeAndNarrowing.types index d7dd68fa760da..8f2e6a2cd8c83 100644 --- a/tests/baselines/reference/strictSubtypeAndNarrowing.types +++ b/tests/baselines/reference/strictSubtypeAndNarrowing.types @@ -89,20 +89,98 @@ const a42 = [x41, x42]; >x41 : { [x: string]: unknown; a: number; } >x42 : { a: number; } -// (...args: any[]) => any is supertype of all other function types +// (...args: A) => R, where A is any, any[], never, or never[] and R is any or unknown, is supertype of all function types. -declare function isFunction(x: unknown): x is (...args: any[]) => any; ->isFunction : (x: unknown) => x is (...args: any[]) => any +declare function isFunction(x: unknown): x is T; +>isFunction : (x: unknown) => x is T >x : unknown + +type A = (...args: any) => unknown; +>A : (...args: any) => unknown +>args : any + +type B = (...args: any[]) => unknown; +>B : (...args: any[]) => unknown >args : any[] -function qqq(f: (() => void) | undefined) { ->qqq : (f: (() => void) | undefined) => void +type C = (...args: never) => unknown; +>C : (...args: never) => unknown +>args : never + +type D = (...args: never[]) => unknown; +>D : (...args: never[]) => unknown +>args : never[] + +type FnTypes = A | B | C | D; +>FnTypes : A | B | C | D + +function fx1(f: (() => void) | undefined) { +>fx1 : (f: (() => void) | undefined) => void +>f : (() => void) | undefined + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : (() => void) | undefined + + f; // () => void +>f : () => void + } + else { + f; // undefined +>f : undefined + } + f; // (() => void) | undefined +>f : (() => void) | undefined +} + +function fx2(f: (() => void) | undefined) { +>fx2 : (f: (() => void) | undefined) => void +>f : (() => void) | undefined + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : (() => void) | undefined + + f; // () => void +>f : () => void + } + else { + f; // undefined +>f : undefined + } + f; // (() => void) | undefined +>f : (() => void) | undefined +} + +function fx3(f: (() => void) | undefined) { +>fx3 : (f: (() => void) | undefined) => void +>f : (() => void) | undefined + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : (() => void) | undefined + + f; // () => void +>f : () => void + } + else { + f; // undefined +>f : undefined + } + f; // (() => void) | undefined +>f : (() => void) | undefined +} + +function fx4(f: (() => void) | undefined) { +>fx4 : (f: (() => void) | undefined) => void >f : (() => void) | undefined - if (isFunction(f)) { ->isFunction(f) : boolean ->isFunction : (x: unknown) => x is (...args: any[]) => any + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T >f : (() => void) | undefined f; // () => void @@ -116,6 +194,86 @@ function qqq(f: (() => void) | undefined) { >f : (() => void) | undefined } +function checkA(f: FnTypes) { +>checkA : (f: FnTypes) => void +>f : FnTypes + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : FnTypes + + f; // A | B +>f : A | B + } + else { + f; // C | D +>f : C | D + } + f; // FnTypes +>f : FnTypes +} + +function checkB(f: FnTypes) { +>checkB : (f: FnTypes) => void +>f : FnTypes + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : FnTypes + + f; // A | B +>f : A | B + } + else { + f; // C | D +>f : C | D + } + f; // FnTypes +>f : FnTypes +} + +function checkC(f: FnTypes) { +>checkC : (f: FnTypes) => void +>f : FnTypes + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : FnTypes + + f; // FnTypes +>f : FnTypes + } + else { + f; // never +>f : never + } + f; // FnTypes +>f : FnTypes +} + +function checkD(f: FnTypes) { +>checkD : (f: FnTypes) => void +>f : FnTypes + + if (isFunction(f)) { +>isFunction(f) : boolean +>isFunction : (x: unknown) => x is T +>f : FnTypes + + f; // FnTypes +>f : FnTypes + } + else { + f; // never +>f : never + } + f; // FnTypes +>f : FnTypes +} + // Type of x = y is y with freshness preserved function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { @@ -168,121 +326,3 @@ function fx11(): { x?: number } { >2 : 2 } -// Narrowing preserves original type in false branch for non-identical mutual subtypes - -declare function isObject1(value: unknown): value is Record; ->isObject1 : (value: unknown) => value is Record ->value : unknown - -function gg(x: {}) { ->gg : (x: {}) => void ->x : {} - - if (isObject1(x)) { ->isObject1(x) : boolean ->isObject1 : (value: unknown) => value is Record ->x : {} - - x; // Record ->x : Record - } - else { - x; // {} ->x : {} - } - x; // {} ->x : {} -} - -declare function isObject2(value: unknown): value is {}; ->isObject2 : (value: unknown) => value is {} ->value : unknown - -function gg2(x: Record) { ->gg2 : (x: Record) => void ->x : Record - - if (isObject2(x)) { ->isObject2(x) : boolean ->isObject2 : (value: unknown) => value is {} ->x : Record - - x; // {} ->x : {} - } - else { - x; // Record ->x : Record - } - x; // Record ->x : Record -} - -// Repro from #50916 - -type Identity = {[K in keyof T]: T[K]}; ->Identity : Identity - -function is(value: T): value is Identity { ->is : (value: T) => value is Identity ->value : T - - return true; ->true : true -} - -type Union = {a: number} | {b: number} | {c: number}; ->Union : { a: number; } | { b: number; } | { c: number; } ->a : number ->b : number ->c : number - -function example(x: Union) { ->example : (x: Union) => void ->x : Union - - if (is(x)) { x } ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union ->x : { a: number; } | { b: number; } | { c: number; } - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - if (is(x)) {} ->is(x) : boolean ->is : (value: T) => value is Identity ->x : Union - - x; // Union ->x : Union -} - diff --git a/tests/cases/compiler/strictSubtypeAndNarrowing.ts b/tests/cases/compiler/strictSubtypeAndNarrowing.ts index c1b58ae978767..6239072f97efe 100644 --- a/tests/cases/compiler/strictSubtypeAndNarrowing.ts +++ b/tests/cases/compiler/strictSubtypeAndNarrowing.ts @@ -28,12 +28,19 @@ const x42 = { a: 1 }; const a41 = [x42, x41]; const a42 = [x41, x42]; -// (...args: any[]) => any is supertype of all other function types +// (...args: A) => R, where A is any, any[], never, or never[] and R is any or unknown, is supertype of all function types. -declare function isFunction(x: unknown): x is (...args: any[]) => any; +declare function isFunction(x: unknown): x is T; -function qqq(f: (() => void) | undefined) { - if (isFunction(f)) { +type A = (...args: any) => unknown; +type B = (...args: any[]) => unknown; +type C = (...args: never) => unknown; +type D = (...args: never[]) => unknown; + +type FnTypes = A | B | C | D; + +function fx1(f: (() => void) | undefined) { + if (isFunction(f)) { f; // () => void } else { @@ -42,62 +49,84 @@ function qqq(f: (() => void) | undefined) { f; // (() => void) | undefined } -// Type of x = y is y with freshness preserved - -function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { - obj1 = obj2 = { x: 1, y: 2 }; - obj2 = obj1 = { x: 1, y: 2 }; +function fx2(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } -function fx11(): { x?: number } { - let obj: { x?: number, y?: number }; - return obj = { x: 1, y: 2 }; +function fx3(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined } -// Narrowing preserves original type in false branch for non-identical mutual subtypes - -declare function isObject1(value: unknown): value is Record; +function fx4(f: (() => void) | undefined) { + if (isFunction(f)) { + f; // () => void + } + else { + f; // undefined + } + f; // (() => void) | undefined +} -function gg(x: {}) { - if (isObject1(x)) { - x; // Record +function checkA(f: FnTypes) { + if (isFunction(f)) { + f; // A | B } else { - x; // {} + f; // C | D } - x; // {} + f; // FnTypes } -declare function isObject2(value: unknown): value is {}; +function checkB(f: FnTypes) { + if (isFunction(f)) { + f; // A | B + } + else { + f; // C | D + } + f; // FnTypes +} -function gg2(x: Record) { - if (isObject2(x)) { - x; // {} +function checkC(f: FnTypes) { + if (isFunction(f)) { + f; // FnTypes } else { - x; // Record + f; // never } - x; // Record + f; // FnTypes } -// Repro from #50916 +function checkD(f: FnTypes) { + if (isFunction(f)) { + f; // FnTypes + } + else { + f; // never + } + f; // FnTypes +} -type Identity = {[K in keyof T]: T[K]}; +// Type of x = y is y with freshness preserved -function is(value: T): value is Identity { - return true; +function fx10(obj1: { x?: number }, obj2: { x?: number, y?: number }) { + obj1 = obj2 = { x: 1, y: 2 }; + obj2 = obj1 = { x: 1, y: 2 }; } -type Union = {a: number} | {b: number} | {c: number}; - -function example(x: Union) { - if (is(x)) { x } - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - if (is(x)) {} - x; // Union +function fx11(): { x?: number } { + let obj: { x?: number, y?: number }; + return obj = { x: 1, y: 2 }; }