From 1f35684fa5dafef444c3c8514ab3058aa01026a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 21 Apr 2024 23:14:39 +0200 Subject: [PATCH 1/6] discriminate `NoInfer`ed union types --- src/compiler/checker.ts | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 0e45abf93737d..6cb72e72e4e7c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -28183,15 +28183,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { + const effectiveDeclaredType = isNoInferType(declaredType) ? (declaredType as SubstitutionType).baseType : declaredType; + const effectiveComputedType = isNoInferType(computedType) ? (computedType as SubstitutionType).baseType : computedType; // As long as the computed type is a subset of the declared type, we use the full declared type to detect // a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type // predicate narrowing, we use the actual computed type. - if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) { + if (effectiveDeclaredType.flags & TypeFlags.Union || effectiveComputedType.flags & TypeFlags.Union) { const access = getCandidateDiscriminantPropertyAccess(expr); if (access) { const name = getAccessedPropertyName(access); if (name) { - const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType; + const type = effectiveDeclaredType.flags & TypeFlags.Union && isTypeSubsetOf(effectiveComputedType, effectiveDeclaredType) ? effectiveDeclaredType : effectiveComputedType; if (isDiscriminantProperty(type, name)) { return access; } @@ -28221,18 +28223,19 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { - if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { - const keyPropertyName = getKeyPropertyName(type as UnionType); + const effectiveType = isNoInferType(type) ? (type as SubstitutionType).baseType : type; + if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && effectiveType.flags & TypeFlags.Union) { + const keyPropertyName = getKeyPropertyName(effectiveType as UnionType); if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { - const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); + const candidate = getConstituentTypeForKeyType(effectiveType as UnionType, getTypeOfExpression(value)); if (candidate) { return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : - isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : - type; + isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(effectiveType, candidate) : + effectiveType; } } } - return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); + return narrowTypeByDiscriminant(effectiveType, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { From e4a3a4ce7486a287af09ce40a9b5bcd36ed833a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 22 Apr 2024 21:44:50 +0200 Subject: [PATCH 2/6] unwrap `NoInfer` types early --- src/compiler/checker.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6cb72e72e4e7c..60d805ce6f494 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27681,6 +27681,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!flowNode) { return declaredType; } + declaredType = isNoInferType(declaredType) ? (declaredType as SubstitutionType).baseType : declaredType; + initialType = isNoInferType(initialType) ? (initialType as SubstitutionType).baseType : initialType; flowInvocationCount++; const sharedFlowStart = sharedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); @@ -28183,17 +28185,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getDiscriminantPropertyAccess(expr: Expression, computedType: Type) { - const effectiveDeclaredType = isNoInferType(declaredType) ? (declaredType as SubstitutionType).baseType : declaredType; - const effectiveComputedType = isNoInferType(computedType) ? (computedType as SubstitutionType).baseType : computedType; // As long as the computed type is a subset of the declared type, we use the full declared type to detect // a discriminant property. In cases where the computed type isn't a subset, e.g because of a preceding type // predicate narrowing, we use the actual computed type. - if (effectiveDeclaredType.flags & TypeFlags.Union || effectiveComputedType.flags & TypeFlags.Union) { + if (declaredType.flags & TypeFlags.Union || computedType.flags & TypeFlags.Union) { const access = getCandidateDiscriminantPropertyAccess(expr); if (access) { const name = getAccessedPropertyName(access); if (name) { - const type = effectiveDeclaredType.flags & TypeFlags.Union && isTypeSubsetOf(effectiveComputedType, effectiveDeclaredType) ? effectiveDeclaredType : effectiveComputedType; + const type = declaredType.flags & TypeFlags.Union && isTypeSubsetOf(computedType, declaredType) ? declaredType : computedType; if (isDiscriminantProperty(type, name)) { return access; } @@ -28223,19 +28223,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function narrowTypeByDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, operator: SyntaxKind, value: Expression, assumeTrue: boolean) { - const effectiveType = isNoInferType(type) ? (type as SubstitutionType).baseType : type; - if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && effectiveType.flags & TypeFlags.Union) { - const keyPropertyName = getKeyPropertyName(effectiveType as UnionType); + if ((operator === SyntaxKind.EqualsEqualsEqualsToken || operator === SyntaxKind.ExclamationEqualsEqualsToken) && type.flags & TypeFlags.Union) { + const keyPropertyName = getKeyPropertyName(type as UnionType); if (keyPropertyName && keyPropertyName === getAccessedPropertyName(access)) { - const candidate = getConstituentTypeForKeyType(effectiveType as UnionType, getTypeOfExpression(value)); + const candidate = getConstituentTypeForKeyType(type as UnionType, getTypeOfExpression(value)); if (candidate) { return operator === (assumeTrue ? SyntaxKind.EqualsEqualsEqualsToken : SyntaxKind.ExclamationEqualsEqualsToken) ? candidate : - isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(effectiveType, candidate) : - effectiveType; + isUnitType(getTypeOfPropertyOfType(candidate, keyPropertyName) || unknownType) ? removeType(type, candidate) : + type; } } } - return narrowTypeByDiscriminant(effectiveType, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); + return narrowTypeByDiscriminant(type, access, t => narrowTypeByEquality(t, operator, value, assumeTrue)); } function narrowTypeBySwitchOnDiscriminantProperty(type: Type, access: AccessExpression | BindingElement | ParameterDeclaration, data: FlowSwitchClauseData) { @@ -28835,6 +28834,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Narrow the given type based on the given expression having the assumed boolean value. The returned type // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { + type = isNoInferType(type) ? (type as SubstitutionType).baseType : type; // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` if ( isExpressionOfOptionalChainRoot(expr) || From 7b1ce0e82362c32b23982222890a93ec830b83b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 23 Apr 2024 09:55:24 +0200 Subject: [PATCH 3/6] add test case --- .../reference/narrowingNoInfer1.symbols | 63 ++++++++++++ .../reference/narrowingNoInfer1.types | 98 +++++++++++++++++++ tests/cases/compiler/narrowingNoInfer1.ts | 19 ++++ 3 files changed, 180 insertions(+) create mode 100644 tests/baselines/reference/narrowingNoInfer1.symbols create mode 100644 tests/baselines/reference/narrowingNoInfer1.types create mode 100644 tests/cases/compiler/narrowingNoInfer1.ts diff --git a/tests/baselines/reference/narrowingNoInfer1.symbols b/tests/baselines/reference/narrowingNoInfer1.symbols new file mode 100644 index 0000000000000..85147c29f7eb0 --- /dev/null +++ b/tests/baselines/reference/narrowingNoInfer1.symbols @@ -0,0 +1,63 @@ +//// [tests/cases/compiler/narrowingNoInfer1.ts] //// + +=== narrowingNoInfer1.ts === +// https://github.com/microsoft/TypeScript/issues/58266 + +type TaggedA = { _tag: "a" }; +>TaggedA : Symbol(TaggedA, Decl(narrowingNoInfer1.ts, 0, 0)) +>_tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 2, 16)) + +type TaggedB = { _tag: "b" }; +>TaggedB : Symbol(TaggedB, Decl(narrowingNoInfer1.ts, 2, 29)) +>_tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 3, 16)) + +type TaggedUnion = TaggedA | TaggedB; +>TaggedUnion : Symbol(TaggedUnion, Decl(narrowingNoInfer1.ts, 3, 29)) +>TaggedA : Symbol(TaggedA, Decl(narrowingNoInfer1.ts, 0, 0)) +>TaggedB : Symbol(TaggedB, Decl(narrowingNoInfer1.ts, 2, 29)) + +const m: { result: NoInfer }[] = []; +>m : Symbol(m, Decl(narrowingNoInfer1.ts, 7, 5)) +>result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>TaggedUnion : Symbol(TaggedUnion, Decl(narrowingNoInfer1.ts, 3, 29)) + +function map(items: readonly A[], f: (a: NoInfer) => B) { +>map : Symbol(map, Decl(narrowingNoInfer1.ts, 7, 49)) +>A : Symbol(A, Decl(narrowingNoInfer1.ts, 9, 13)) +>B : Symbol(B, Decl(narrowingNoInfer1.ts, 9, 15)) +>items : Symbol(items, Decl(narrowingNoInfer1.ts, 9, 19)) +>A : Symbol(A, Decl(narrowingNoInfer1.ts, 9, 13)) +>f : Symbol(f, Decl(narrowingNoInfer1.ts, 9, 39)) +>a : Symbol(a, Decl(narrowingNoInfer1.ts, 9, 44)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>A : Symbol(A, Decl(narrowingNoInfer1.ts, 9, 13)) +>B : Symbol(B, Decl(narrowingNoInfer1.ts, 9, 15)) + + return items.map(f); +>items.map : Symbol(ReadonlyArray.map, Decl(lib.es5.d.ts, --, --)) +>items : Symbol(items, Decl(narrowingNoInfer1.ts, 9, 19)) +>map : Symbol(ReadonlyArray.map, Decl(lib.es5.d.ts, --, --)) +>f : Symbol(f, Decl(narrowingNoInfer1.ts, 9, 39)) +} + +const something = map(m, (_) => +>something : Symbol(something, Decl(narrowingNoInfer1.ts, 13, 5)) +>map : Symbol(map, Decl(narrowingNoInfer1.ts, 7, 49)) +>m : Symbol(m, Decl(narrowingNoInfer1.ts, 7, 5)) +>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26)) + + _.result._tag === "a" ? { ..._, result: _.result } : null, +>_.result._tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 2, 16), Decl(narrowingNoInfer1.ts, 3, 16)) +>_.result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10)) +>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26)) +>result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10)) +>_tag : Symbol(_tag, Decl(narrowingNoInfer1.ts, 2, 16), Decl(narrowingNoInfer1.ts, 3, 16)) +>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26)) +>result : Symbol(result, Decl(narrowingNoInfer1.ts, 14, 33)) +>_.result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10)) +>_ : Symbol(_, Decl(narrowingNoInfer1.ts, 13, 26)) +>result : Symbol(result, Decl(narrowingNoInfer1.ts, 7, 10)) + +); + diff --git a/tests/baselines/reference/narrowingNoInfer1.types b/tests/baselines/reference/narrowingNoInfer1.types new file mode 100644 index 0000000000000..a70dc245f82c1 --- /dev/null +++ b/tests/baselines/reference/narrowingNoInfer1.types @@ -0,0 +1,98 @@ +//// [tests/cases/compiler/narrowingNoInfer1.ts] //// + +=== narrowingNoInfer1.ts === +// https://github.com/microsoft/TypeScript/issues/58266 + +type TaggedA = { _tag: "a" }; +>TaggedA : TaggedA +> : ^^^^^^^ +>_tag : "a" +> : ^^^ + +type TaggedB = { _tag: "b" }; +>TaggedB : TaggedB +> : ^^^^^^^ +>_tag : "b" +> : ^^^ + +type TaggedUnion = TaggedA | TaggedB; +>TaggedUnion : TaggedUnion +> : ^^^^^^^^^^^ + +const m: { result: NoInfer }[] = []; +>m : { result: NoInfer; }[] +> : ^^^^^^^^^^ ^^^^^ +>result : NoInfer +> : ^^^^^^^^^^^^^^^^^^^^ +>[] : never[] +> : ^^^^^^^ + +function map(items: readonly A[], f: (a: NoInfer) => B) { +>map : (items: readonly A[], f: (a: NoInfer) => B) => B[] +> : ^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^ +>items : readonly A[] +> : ^^^^^^^^^^^^ +>f : (a: NoInfer) => B +> : ^ ^^ ^^^^^ +>a : NoInfer +> : ^^^^^^^^^^ + + return items.map(f); +>items.map(f) : B[] +> : ^^^ +>items.map : (callbackfn: (value: A, index: number, array: readonly A[]) => U, thisArg?: any) => U[] +> : ^^^^ ^^^ ^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ +>items : readonly A[] +> : ^^^^^^^^^^^^ +>map : (callbackfn: (value: A, index: number, array: readonly A[]) => U, thisArg?: any) => U[] +> : ^^^^ ^^^ ^^^^^ ^^ ^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^ ^^^^^^^^ +>f : (a: NoInfer) => B +> : ^ ^^ ^^^^^^ +} + +const something = map(m, (_) => +>something : ({ result: TaggedA; } | null)[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>map(m, (_) => _.result._tag === "a" ? { ..._, result: _.result } : null,) : ({ result: TaggedA; } | null)[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>map : (items: readonly A[], f: (a: NoInfer) => B) => B[] +> : ^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^ +>m : { result: NoInfer; }[] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>(_) => _.result._tag === "a" ? { ..._, result: _.result } : null : (_: NoInfer<{ result: NoInfer; }>) => { result: TaggedA; } | null +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>_ : NoInfer<{ result: NoInfer; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + _.result._tag === "a" ? { ..._, result: _.result } : null, +>_.result._tag === "a" ? { ..._, result: _.result } : null : { result: TaggedA; } | null +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>_.result._tag === "a" : boolean +> : ^^^^^^^ +>_.result._tag : "a" | "b" +> : ^^^^^^^^^ +>_.result : TaggedUnion +> : ^^^^^^^^^^^ +>_ : { result: NoInfer; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>result : TaggedUnion +> : ^^^^^^^^^^^ +>_tag : "a" | "b" +> : ^^^^^^^^^ +>"a" : "a" +> : ^^^ +>{ ..._, result: _.result } : { result: TaggedA; } +> : ^^^^^^^^^^^^^^^^^^^^ +>_ : { result: NoInfer; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>result : TaggedA +> : ^^^^^^^ +>_.result : TaggedA +> : ^^^^^^^ +>_ : { result: NoInfer; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>result : TaggedA +> : ^^^^^^^ + +); + diff --git a/tests/cases/compiler/narrowingNoInfer1.ts b/tests/cases/compiler/narrowingNoInfer1.ts new file mode 100644 index 0000000000000..09e51d0a2ee8a --- /dev/null +++ b/tests/cases/compiler/narrowingNoInfer1.ts @@ -0,0 +1,19 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/58266 + +type TaggedA = { _tag: "a" }; +type TaggedB = { _tag: "b" }; + +type TaggedUnion = TaggedA | TaggedB; + +const m: { result: NoInfer }[] = []; + +function map(items: readonly A[], f: (a: NoInfer) => B) { + return items.map(f); +} + +const something = map(m, (_) => + _.result._tag === "a" ? { ..._, result: _.result } : null, +); From 6bd13aa4baa5e899437e33d75aa02670e1df6790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 23 Apr 2024 10:02:45 +0200 Subject: [PATCH 4/6] update baseline --- tests/baselines/reference/noInfer.types | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/baselines/reference/noInfer.types b/tests/baselines/reference/noInfer.types index 5c33a4fa725de..713fc849c0b3b 100644 --- a/tests/baselines/reference/noInfer.types +++ b/tests/baselines/reference/noInfer.types @@ -460,12 +460,12 @@ class OkClass { > : ^ return this._value; // ok ->this._value : NoInfer -> : ^^^^^^^^^^ +>this._value : T +> : ^ >this : this > : ^^^^ ->_value : NoInfer -> : ^^^^^^^^^^ +>_value : T +> : ^ } } class OkClass2 { From d1e40791e14a3c601a0cef0018d76c034a3ff90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 1 May 2024 23:30:36 +0200 Subject: [PATCH 5/6] Move `NoInfer` unwrapping to `getNarrowableTypeForReference` --- src/compiler/checker.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 83fb862f70205..0d615ef6115f2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -27698,8 +27698,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (!flowNode) { return declaredType; } - declaredType = isNoInferType(declaredType) ? (declaredType as SubstitutionType).baseType : declaredType; - initialType = isNoInferType(initialType) ? (initialType as SubstitutionType).baseType : initialType; flowInvocationCount++; const sharedFlowStart = sharedFlowCount; const evolvedType = getTypeFromFlowType(getTypeAtFlowNode(flowNode)); @@ -28851,7 +28849,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Narrow the given type based on the given expression having the assumed boolean value. The returned type // will be a subtype or the same type as the argument. function narrowType(type: Type, expr: Expression, assumeTrue: boolean): Type { - type = isNoInferType(type) ? (type as SubstitutionType).baseType : type; // for `a?.b`, we emulate a synthetic `a !== null && a !== undefined` condition for `a` if ( isExpressionOfOptionalChainRoot(expr) || @@ -29151,6 +29148,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function getNarrowableTypeForReference(type: Type, reference: Node, checkMode?: CheckMode) { + if (isNoInferType(type)) { + type = (type as SubstitutionType).baseType; + } // When the type of a reference is or contains an instantiable type with a union type constraint, and // when the reference is in a constraint position (where it is known we'll obtain the apparent type) or // has a contextual type containing no top-level instantiables (meaning constraints will determine From a3c58219da928c2eaa41d5e6b1b0a38d7e68f47a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Wed, 1 May 2024 23:42:04 +0200 Subject: [PATCH 6/6] add an extra test case --- .../reference/narrowingNoInfer1.symbols | 37 +++++++++++ .../reference/narrowingNoInfer1.types | 61 +++++++++++++++++++ tests/cases/compiler/narrowingNoInfer1.ts | 10 +++ 3 files changed, 108 insertions(+) diff --git a/tests/baselines/reference/narrowingNoInfer1.symbols b/tests/baselines/reference/narrowingNoInfer1.symbols index 85147c29f7eb0..a5f3ba9ab151f 100644 --- a/tests/baselines/reference/narrowingNoInfer1.symbols +++ b/tests/baselines/reference/narrowingNoInfer1.symbols @@ -61,3 +61,40 @@ const something = map(m, (_) => ); +declare function test2(a: T1, b: T2, cb: (thing: NoInfer | NoInfer) => void): void; +>test2 : Symbol(test2, Decl(narrowingNoInfer1.ts, 15, 2)) +>T1 : Symbol(T1, Decl(narrowingNoInfer1.ts, 17, 23)) +>T2 : Symbol(T2, Decl(narrowingNoInfer1.ts, 17, 26)) +>a : Symbol(a, Decl(narrowingNoInfer1.ts, 17, 31)) +>T1 : Symbol(T1, Decl(narrowingNoInfer1.ts, 17, 23)) +>b : Symbol(b, Decl(narrowingNoInfer1.ts, 17, 37)) +>T2 : Symbol(T2, Decl(narrowingNoInfer1.ts, 17, 26)) +>cb : Symbol(cb, Decl(narrowingNoInfer1.ts, 17, 44)) +>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 17, 50)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>T1 : Symbol(T1, Decl(narrowingNoInfer1.ts, 17, 23)) +>NoInfer : Symbol(NoInfer, Decl(lib.es5.d.ts, --, --)) +>T2 : Symbol(T2, Decl(narrowingNoInfer1.ts, 17, 26)) + +test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => { +>test2 : Symbol(test2, Decl(narrowingNoInfer1.ts, 15, 2)) +>type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 7)) +>const : Symbol(const) +>type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 31)) +>const : Symbol(const) +>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55)) + + if (thing.type === "a") { +>thing.type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 7), Decl(narrowingNoInfer1.ts, 19, 31)) +>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55)) +>type : Symbol(type, Decl(narrowingNoInfer1.ts, 19, 7), Decl(narrowingNoInfer1.ts, 19, 31)) + + thing; +>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55)) + + } else { + thing; +>thing : Symbol(thing, Decl(narrowingNoInfer1.ts, 19, 55)) + } +}); + diff --git a/tests/baselines/reference/narrowingNoInfer1.types b/tests/baselines/reference/narrowingNoInfer1.types index a70dc245f82c1..26b25d4da192c 100644 --- a/tests/baselines/reference/narrowingNoInfer1.types +++ b/tests/baselines/reference/narrowingNoInfer1.types @@ -96,3 +96,64 @@ const something = map(m, (_) => ); +declare function test2(a: T1, b: T2, cb: (thing: NoInfer | NoInfer) => void): void; +>test2 : (a: T1, b: T2, cb: (thing: NoInfer | NoInfer) => void) => void +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^ +>a : T1 +> : ^^ +>b : T2 +> : ^^ +>cb : (thing: NoInfer | NoInfer) => void +> : ^ ^^ ^^^^^ +>thing : NoInfer | NoInfer +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ + +test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => { +>test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => { if (thing.type === "a") { thing; } else { thing; }}) : void +> : ^^^^ +>test2 : (a: T1, b: T2, cb: (thing: NoInfer | NoInfer) => void) => void +> : ^ ^^ ^^ ^^ ^^ ^^ ^^ ^^ ^^^^^^^^^ +>{ type: 'a' as const } : { type: "a"; } +> : ^^^^^^^^^^^^^^ +>type : "a" +> : ^^^ +>'a' as const : "a" +> : ^^^ +>'a' : "a" +> : ^^^ +>{ type: 'b' as const } : { type: "b"; } +> : ^^^^^^^^^^^^^^ +>type : "b" +> : ^^^ +>'b' as const : "b" +> : ^^^ +>'b' : "b" +> : ^^^ +>(thing) => { if (thing.type === "a") { thing; } else { thing; }} : (thing: NoInfer<{ type: "a"; }> | NoInfer<{ type: "b"; }>) => void +> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>thing : NoInfer<{ type: "a"; }> | NoInfer<{ type: "b"; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + if (thing.type === "a") { +>thing.type === "a" : boolean +> : ^^^^^^^ +>thing.type : "a" | "b" +> : ^^^^^^^^^ +>thing : NoInfer<{ type: "a"; }> | NoInfer<{ type: "b"; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +>type : "a" | "b" +> : ^^^^^^^^^ +>"a" : "a" +> : ^^^ + + thing; +>thing : NoInfer<{ type: "a"; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^ + + } else { + thing; +>thing : NoInfer<{ type: "b"; }> +> : ^^^^^^^^^^^^^^^^^^^^^^^ + } +}); + diff --git a/tests/cases/compiler/narrowingNoInfer1.ts b/tests/cases/compiler/narrowingNoInfer1.ts index 09e51d0a2ee8a..97f56b2ac5b36 100644 --- a/tests/cases/compiler/narrowingNoInfer1.ts +++ b/tests/cases/compiler/narrowingNoInfer1.ts @@ -17,3 +17,13 @@ function map(items: readonly A[], f: (a: NoInfer) => B) { const something = map(m, (_) => _.result._tag === "a" ? { ..._, result: _.result } : null, ); + +declare function test2(a: T1, b: T2, cb: (thing: NoInfer | NoInfer) => void): void; + +test2({ type: 'a' as const }, { type: 'b' as const }, (thing) => { + if (thing.type === "a") { + thing; + } else { + thing; + } +});