Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
38 changes: 38 additions & 0 deletions tsc/internal/checker/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ type FlowType struct {
incomplete bool
}

type literalTypeKey struct {
flags TypeFlags
value any
}

func (ft *FlowType) isNil() bool {
return ft.t == nil
}
Expand Down Expand Up @@ -880,6 +885,11 @@ func (c *Checker) getNarrowedTypeWorker(t *Type, candidate *Type, assumeTrue boo
if t == candidate {
return candidate
}
if !checkDerived {
if narrowedType := c.tryNarrowLiteralUnion(t, candidate); narrowedType != nil {
return narrowedType
}
}
// We first attempt to filter the current type, narrowing constituents as appropriate and removing
// constituents that are unrelated to the candidate.
var keyPropertyName string
Expand Down Expand Up @@ -913,6 +923,17 @@ func (c *Checker) getNarrowedTypeWorker(t *Type, candidate *Type, assumeTrue boo
}
} else {
mapType = func(t *Type) *Type {
tLiteralFlags := t.flags & TypeFlagsLiteral
nLiteralFlags := n.flags & TypeFlagsLiteral
if tLiteralFlags != 0 && tLiteralFlags == nLiteralFlags && !(t.flags&TypeFlagsEnumLiteral != 0 && n.flags&TypeFlagsEnumLiteral != 0) {
if t.AsLiteralType().value == n.AsLiteralType().value {
if n.flags&TypeFlagsEnumLiteral != 0 {
return n
}
return t
}
return c.neverType
}
switch {
case c.isTypeStrictSubtypeOf(t, n):
return t
Expand Down Expand Up @@ -963,6 +984,23 @@ func (c *Checker) getNarrowedTypeWorker(t *Type, candidate *Type, assumeTrue boo
return c.getIntersectionType([]*Type{t, candidate})
}

func (c *Checker) tryNarrowLiteralUnion(t *Type, candidate *Type) *Type {
isNonEnumLiteral := func(t *Type) bool {
return t.flags&TypeFlagsLiteral != 0 && t.flags&TypeFlagsEnumLiteral == 0
}
if !everyType(t, isNonEnumLiteral) || !everyType(candidate, isNonEnumLiteral) {
return nil
}
candidateTypes := make(map[literalTypeKey]struct{})
forEachType(candidate, func(t *Type) {
candidateTypes[literalTypeKey{flags: t.flags & TypeFlagsLiteral, value: t.AsLiteralType().value}] = struct{}{}
})
return c.filterType(t, func(t *Type) bool {
_, ok := candidateTypes[literalTypeKey{flags: t.flags & TypeFlagsLiteral, value: t.AsLiteralType().value}]
return ok
})
}

func (c *Checker) getInstanceType(constructorType *Type) *Type {
prototypePropertyType := c.getTypeOfPropertyOfType(constructorType, "prototype")
if prototypePropertyType != nil && !IsTypeAny(prototypePropertyType) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//// [tests/cases/compiler/narrowLiteralUnionByTypePredicate.ts] ////

//// [narrowLiteralUnionByTypePredicate.ts]
type Source = "a" | "b" | "c" | "d" | 1 | 2 | 3 | 4;
type Candidate = "b" | "d" | 1 | 3;

declare const source: Source;
declare function isCandidate(value: Source): value is Candidate;

if (isCandidate(source)) {
source;
}

enum E {
A = "a",
B = "b",
}

declare const enumSource: "a" | "b";
declare function isEnumA(value: "a" | "b"): value is E.A;

if (isEnumA(enumSource)) {
const enumA: E.A = enumSource;
}


//// [narrowLiteralUnionByTypePredicate.js]
"use strict";
if (isCandidate(source)) {
source;
}
var E;
(function (E) {
E["A"] = "a";
E["B"] = "b";
})(E || (E = {}));
if (isEnumA(enumSource)) {
const enumA = enumSource;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//// [tests/cases/compiler/narrowLiteralUnionByTypePredicate.ts] ////

=== narrowLiteralUnionByTypePredicate.ts ===
type Source = "a" | "b" | "c" | "d" | 1 | 2 | 3 | 4;
>Source : Symbol(Source, Decl(narrowLiteralUnionByTypePredicate.ts, 0, 0))

type Candidate = "b" | "d" | 1 | 3;
>Candidate : Symbol(Candidate, Decl(narrowLiteralUnionByTypePredicate.ts, 0, 52))

declare const source: Source;
>source : Symbol(source, Decl(narrowLiteralUnionByTypePredicate.ts, 3, 13))
>Source : Symbol(Source, Decl(narrowLiteralUnionByTypePredicate.ts, 0, 0))

declare function isCandidate(value: Source): value is Candidate;
>isCandidate : Symbol(isCandidate, Decl(narrowLiteralUnionByTypePredicate.ts, 3, 29))
>value : Symbol(value, Decl(narrowLiteralUnionByTypePredicate.ts, 4, 29))
>Source : Symbol(Source, Decl(narrowLiteralUnionByTypePredicate.ts, 0, 0))
>value : Symbol(value, Decl(narrowLiteralUnionByTypePredicate.ts, 4, 29))
>Candidate : Symbol(Candidate, Decl(narrowLiteralUnionByTypePredicate.ts, 0, 52))

if (isCandidate(source)) {
>isCandidate : Symbol(isCandidate, Decl(narrowLiteralUnionByTypePredicate.ts, 3, 29))
>source : Symbol(source, Decl(narrowLiteralUnionByTypePredicate.ts, 3, 13))

source;
>source : Symbol(source, Decl(narrowLiteralUnionByTypePredicate.ts, 3, 13))
}

enum E {
>E : Symbol(E, Decl(narrowLiteralUnionByTypePredicate.ts, 8, 1))

A = "a",
>A : Symbol(E.A, Decl(narrowLiteralUnionByTypePredicate.ts, 10, 8))

B = "b",
>B : Symbol(E.B, Decl(narrowLiteralUnionByTypePredicate.ts, 11, 12))
}

declare const enumSource: "a" | "b";
>enumSource : Symbol(enumSource, Decl(narrowLiteralUnionByTypePredicate.ts, 15, 13))

declare function isEnumA(value: "a" | "b"): value is E.A;
>isEnumA : Symbol(isEnumA, Decl(narrowLiteralUnionByTypePredicate.ts, 15, 36))
>value : Symbol(value, Decl(narrowLiteralUnionByTypePredicate.ts, 16, 25))
>value : Symbol(value, Decl(narrowLiteralUnionByTypePredicate.ts, 16, 25))
>E : Symbol(E, Decl(narrowLiteralUnionByTypePredicate.ts, 8, 1))
>A : Symbol(E.A, Decl(narrowLiteralUnionByTypePredicate.ts, 10, 8))

if (isEnumA(enumSource)) {
>isEnumA : Symbol(isEnumA, Decl(narrowLiteralUnionByTypePredicate.ts, 15, 36))
>enumSource : Symbol(enumSource, Decl(narrowLiteralUnionByTypePredicate.ts, 15, 13))

const enumA: E.A = enumSource;
>enumA : Symbol(enumA, Decl(narrowLiteralUnionByTypePredicate.ts, 19, 9))
>E : Symbol(E, Decl(narrowLiteralUnionByTypePredicate.ts, 8, 1))
>A : Symbol(E.A, Decl(narrowLiteralUnionByTypePredicate.ts, 10, 8))
>enumSource : Symbol(enumSource, Decl(narrowLiteralUnionByTypePredicate.ts, 15, 13))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//// [tests/cases/compiler/narrowLiteralUnionByTypePredicate.ts] ////

=== narrowLiteralUnionByTypePredicate.ts ===
type Source = "a" | "b" | "c" | "d" | 1 | 2 | 3 | 4;
>Source : Source

type Candidate = "b" | "d" | 1 | 3;
>Candidate : Candidate

declare const source: Source;
>source : Source

declare function isCandidate(value: Source): value is Candidate;
>isCandidate : (value: Source) => value is Candidate
>value : Source

if (isCandidate(source)) {
>isCandidate(source) : boolean
>isCandidate : (value: Source) => value is Candidate
>source : Source

source;
>source : "b" | "d" | 1 | 3
}

enum E {
>E : E

A = "a",
>A : E.A
>"a" : "a"

B = "b",
>B : E.B
>"b" : "b"
}

declare const enumSource: "a" | "b";
>enumSource : "a" | "b"

declare function isEnumA(value: "a" | "b"): value is E.A;
>isEnumA : (value: "a" | "b") => value is E.A
>value : "a" | "b"
>E : any

if (isEnumA(enumSource)) {
>isEnumA(enumSource) : boolean
>isEnumA : (value: "a" | "b") => value is E.A
>enumSource : "a" | "b"

const enumA: E.A = enumSource;
>enumA : E.A
>E : any
>enumSource : E.A
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// @strict: true

type Source = "a" | "b" | "c" | "d" | 1 | 2 | 3 | 4;
type Candidate = "b" | "d" | 1 | 3;

declare const source: Source;
declare function isCandidate(value: Source): value is Candidate;

if (isCandidate(source)) {
source;
}

enum E {
A = "a",
B = "b",
}

declare const enumSource: "a" | "b";
declare function isEnumA(value: "a" | "b"): value is E.A;

if (isEnumA(enumSource)) {
const enumA: E.A = enumSource;
}