Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 1 addition & 10 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24026,16 +24026,7 @@ namespace ts {

function getNarrowedType(type: Type, candidate: Type, assumeTrue: boolean, isRelated: (source: Type, target: Type) => boolean) {
if (!assumeTrue) {
return filterType(type, t => {
if (!isRelated(t, candidate)) {
return true;
}
const constraint = getBaseConstraintOfType(t);
if (constraint && constraint !== t) {
return !isRelated(constraint, candidate);
}
return false;
});
return filterType(type, t => !isRelated(t, candidate));
}
// If the current type is a union type, remove all constituents that couldn't be instances of
// the candidate type. If one or more constituents remain, return a union of those.
Expand Down
28 changes: 28 additions & 0 deletions tests/baselines/reference/genericCapturingFunctionNarrowing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//// [genericCapturingFunctionNarrowing.ts]
function needsToNarrowTheType<First extends { foo: string }, Second extends { bar: string }, SubFirst extends First, SubFirstMore extends First & {other: string}>(thing: First | SubFirst | SubFirstMore | Second) {
if (hasAFoo(thing)) {
console.log(thing.foo);
}
else {
// I would expect this to work because the type should be narrowed in this branch to `Second`
console.log(thing.bar); // Error: Property 'bar' does not exist on type 'First | Second'.
}

function hasAFoo(value: First | Second): value is First {
return "foo" in value;
}
}

//// [genericCapturingFunctionNarrowing.js]
function needsToNarrowTheType(thing) {
if (hasAFoo(thing)) {
console.log(thing.foo);
}
else {
// I would expect this to work because the type should be narrowed in this branch to `Second`
console.log(thing.bar); // Error: Property 'bar' does not exist on type 'First | Second'.
}
function hasAFoo(value) {
return "foo" in value;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
=== tests/cases/compiler/genericCapturingFunctionNarrowing.ts ===
function needsToNarrowTheType<First extends { foo: string }, Second extends { bar: string }, SubFirst extends First, SubFirstMore extends First & {other: string}>(thing: First | SubFirst | SubFirstMore | Second) {
>needsToNarrowTheType : Symbol(needsToNarrowTheType, Decl(genericCapturingFunctionNarrowing.ts, 0, 0))
>First : Symbol(First, Decl(genericCapturingFunctionNarrowing.ts, 0, 30))
>foo : Symbol(foo, Decl(genericCapturingFunctionNarrowing.ts, 0, 45))
>Second : Symbol(Second, Decl(genericCapturingFunctionNarrowing.ts, 0, 60))
>bar : Symbol(bar, Decl(genericCapturingFunctionNarrowing.ts, 0, 77))
>SubFirst : Symbol(SubFirst, Decl(genericCapturingFunctionNarrowing.ts, 0, 92))
>First : Symbol(First, Decl(genericCapturingFunctionNarrowing.ts, 0, 30))
>SubFirstMore : Symbol(SubFirstMore, Decl(genericCapturingFunctionNarrowing.ts, 0, 116))
>First : Symbol(First, Decl(genericCapturingFunctionNarrowing.ts, 0, 30))
>other : Symbol(other, Decl(genericCapturingFunctionNarrowing.ts, 0, 147))
>thing : Symbol(thing, Decl(genericCapturingFunctionNarrowing.ts, 0, 163))
>First : Symbol(First, Decl(genericCapturingFunctionNarrowing.ts, 0, 30))
>SubFirst : Symbol(SubFirst, Decl(genericCapturingFunctionNarrowing.ts, 0, 92))
>SubFirstMore : Symbol(SubFirstMore, Decl(genericCapturingFunctionNarrowing.ts, 0, 116))
>Second : Symbol(Second, Decl(genericCapturingFunctionNarrowing.ts, 0, 60))

if (hasAFoo(thing)) {
>hasAFoo : Symbol(hasAFoo, Decl(genericCapturingFunctionNarrowing.ts, 7, 5))
>thing : Symbol(thing, Decl(genericCapturingFunctionNarrowing.ts, 0, 163))

console.log(thing.foo);
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>thing.foo : Symbol(foo, Decl(genericCapturingFunctionNarrowing.ts, 0, 45))
>thing : Symbol(thing, Decl(genericCapturingFunctionNarrowing.ts, 0, 163))
>foo : Symbol(foo, Decl(genericCapturingFunctionNarrowing.ts, 0, 45))
}
else {
// I would expect this to work because the type should be narrowed in this branch to `Second`
console.log(thing.bar); // Error: Property 'bar' does not exist on type 'First | Second'.
>console.log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>console : Symbol(console, Decl(lib.dom.d.ts, --, --))
>log : Symbol(Console.log, Decl(lib.dom.d.ts, --, --))
>thing.bar : Symbol(bar, Decl(genericCapturingFunctionNarrowing.ts, 0, 77))
>thing : Symbol(thing, Decl(genericCapturingFunctionNarrowing.ts, 0, 163))
>bar : Symbol(bar, Decl(genericCapturingFunctionNarrowing.ts, 0, 77))
}

function hasAFoo(value: First | Second): value is First {
>hasAFoo : Symbol(hasAFoo, Decl(genericCapturingFunctionNarrowing.ts, 7, 5))
>value : Symbol(value, Decl(genericCapturingFunctionNarrowing.ts, 9, 21))
>First : Symbol(First, Decl(genericCapturingFunctionNarrowing.ts, 0, 30))
>Second : Symbol(Second, Decl(genericCapturingFunctionNarrowing.ts, 0, 60))
>value : Symbol(value, Decl(genericCapturingFunctionNarrowing.ts, 9, 21))
>First : Symbol(First, Decl(genericCapturingFunctionNarrowing.ts, 0, 30))

return "foo" in value;
>value : Symbol(value, Decl(genericCapturingFunctionNarrowing.ts, 9, 21))
}
}
44 changes: 44 additions & 0 deletions tests/baselines/reference/genericCapturingFunctionNarrowing.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
=== tests/cases/compiler/genericCapturingFunctionNarrowing.ts ===
function needsToNarrowTheType<First extends { foo: string }, Second extends { bar: string }, SubFirst extends First, SubFirstMore extends First & {other: string}>(thing: First | SubFirst | SubFirstMore | Second) {
>needsToNarrowTheType : <First extends { foo: string; }, Second extends { bar: string; }, SubFirst extends First, SubFirstMore extends First & { other: string; }>(thing: First | SubFirst | SubFirstMore | Second) => void
>foo : string
>bar : string
>other : string
>thing : First | Second | SubFirst | SubFirstMore

if (hasAFoo(thing)) {
>hasAFoo(thing) : boolean
>hasAFoo : (value: First | Second) => value is First
>thing : First | Second | SubFirst | SubFirstMore

console.log(thing.foo);
>console.log(thing.foo) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>thing.foo : string
>thing : First | SubFirst | SubFirstMore
>foo : string
}
else {
// I would expect this to work because the type should be narrowed in this branch to `Second`
console.log(thing.bar); // Error: Property 'bar' does not exist on type 'First | Second'.
>console.log(thing.bar) : void
>console.log : (...data: any[]) => void
>console : Console
>log : (...data: any[]) => void
>thing.bar : string
>thing : Second
>bar : string
}

function hasAFoo(value: First | Second): value is First {
>hasAFoo : (value: First | Second) => value is First
>value : First | Second

return "foo" in value;
>"foo" in value : boolean
>"foo" : "foo"
>value : First | Second
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
tests/cases/compiler/quickinfoTypeAtReturnPositionsInaccurate.ts(33,15): error TS2339: Property 'numExclusive' does not exist on type 'NumClass<number> | StrClass<string>'.
Property 'numExclusive' does not exist on type 'StrClass<string>'.
tests/cases/compiler/quickinfoTypeAtReturnPositionsInaccurate.ts(101,11): error TS2322: Type 'Program | T' is not assignable to type 'Program'.
Type 'T' is not assignable to type 'Program'.
Property 'state' is missing in type 'BuilderProgram' but required in type 'Program'.


==== tests/cases/compiler/quickinfoTypeAtReturnPositionsInaccurate.ts (2 errors) ====
==== tests/cases/compiler/quickinfoTypeAtReturnPositionsInaccurate.ts (1 errors) ====
class NumClass<T extends number> {
private value!: T;
public get(): T {
Expand Down Expand Up @@ -110,9 +107,4 @@ tests/cases/compiler/quickinfoTypeAtReturnPositionsInaccurate.ts(101,11): error
declare function isBuilderProgram<T extends BuilderProgram>(program: Program | T): program is T;
export function listFiles<T extends BuilderProgram>(program: Program | T) {
const x: Program = isBuilderProgram(program) ? program.getProgram() : program;
~
!!! error TS2322: Type 'Program | T' is not assignable to type 'Program'.
!!! error TS2322: Type 'T' is not assignable to type 'Program'.
!!! error TS2322: Property 'state' is missing in type 'BuilderProgram' but required in type 'Program'.
!!! related TS2728 tests/cases/compiler/quickinfoTypeAtReturnPositionsInaccurate.ts:97:5: 'state' is declared here.
}
Original file line number Diff line number Diff line change
Expand Up @@ -226,13 +226,13 @@ export function listFiles<T extends BuilderProgram>(program: Program | T) {

const x: Program = isBuilderProgram(program) ? program.getProgram() : program;
>x : Program
>isBuilderProgram(program) ? program.getProgram() : program : Program | T
>isBuilderProgram(program) ? program.getProgram() : program : Program
>isBuilderProgram(program) : boolean
>isBuilderProgram : <T extends BuilderProgram>(program: Program | T) => program is T
>program : Program | T
>program.getProgram() : Program
>program.getProgram : () => Program
>program : T
>getProgram : () => Program
>program : Program | T
>program : Program
}
13 changes: 13 additions & 0 deletions tests/cases/compiler/genericCapturingFunctionNarrowing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function needsToNarrowTheType<First extends { foo: string }, Second extends { bar: string }, SubFirst extends First, SubFirstMore extends First & {other: string}>(thing: First | SubFirst | SubFirstMore | Second) {
if (hasAFoo(thing)) {
console.log(thing.foo);
}
else {
// I would expect this to work because the type should be narrowed in this branch to `Second`
console.log(thing.bar); // Error: Property 'bar' does not exist on type 'First | Second'.
}

function hasAFoo(value: First | Second): value is First {
return "foo" in value;
}
}