Change typeof
narrowing to narrow selected union members (#25243)
* For typeof narrow all union members prior to filtering * Revise narrowTypeByTypeof to both narrow unions and applicable union members * Add repros from issue
This commit is contained in:
parent
c62920ac81
commit
d8f736d319
|
@ -14963,30 +14963,34 @@ namespace ts {
|
|||
if (type.flags & TypeFlags.Any && literal.text === "function") {
|
||||
return type;
|
||||
}
|
||||
if (assumeTrue && !(type.flags & TypeFlags.Union)) {
|
||||
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
|
||||
return getUnionType([nonPrimitiveType, nullType]);
|
||||
}
|
||||
// We narrow a non-union type to an exact primitive type if the non-union type
|
||||
// is a supertype of that primitive type. For example, type 'any' can be narrowed
|
||||
// to one of the primitive types.
|
||||
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
|
||||
if (targetType) {
|
||||
if (isTypeSubtypeOf(targetType, type)) {
|
||||
return targetType;
|
||||
}
|
||||
if (type.flags & TypeFlags.Instantiable) {
|
||||
const constraint = getBaseConstraintOfType(type) || anyType;
|
||||
if (isTypeSubtypeOf(targetType, constraint)) {
|
||||
return getIntersectionType([type, targetType]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const facts = assumeTrue ?
|
||||
typeofEQFacts.get(literal.text) || TypeFacts.TypeofEQHostObject :
|
||||
typeofNEFacts.get(literal.text) || TypeFacts.TypeofNEHostObject;
|
||||
return getTypeWithFacts(type, facts);
|
||||
return getTypeWithFacts(assumeTrue ? mapType(type, narrowTypeForTypeof) : type, facts);
|
||||
|
||||
function narrowTypeForTypeof(type: Type) {
|
||||
if (assumeTrue && !(type.flags & TypeFlags.Union)) {
|
||||
if (type.flags & TypeFlags.Unknown && literal.text === "object") {
|
||||
return getUnionType([nonPrimitiveType, nullType]);
|
||||
}
|
||||
// We narrow a non-union type to an exact primitive type if the non-union type
|
||||
// is a supertype of that primitive type. For example, type 'any' can be narrowed
|
||||
// to one of the primitive types.
|
||||
const targetType = literal.text === "function" ? globalFunctionType : typeofTypesByName.get(literal.text);
|
||||
if (targetType) {
|
||||
if (isTypeSubtypeOf(targetType, type)) {
|
||||
return isTypeAny(type) ? targetType : getIntersectionType([type, targetType]); // Intersection to handle `string` being a subtype of `keyof T`
|
||||
}
|
||||
if (type.flags & TypeFlags.Instantiable) {
|
||||
const constraint = getBaseConstraintOfType(type) || anyType;
|
||||
if (isTypeSubtypeOf(targetType, constraint)) {
|
||||
return getIntersectionType([type, targetType]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return type;
|
||||
}
|
||||
}
|
||||
|
||||
function narrowTypeBySwitchOnDiscriminant(type: Type, switchStatement: SwitchStatement, clauseStart: number, clauseEnd: number) {
|
||||
|
|
|
@ -104,7 +104,7 @@ function c<T>(data: string | T): T {
|
|||
>JSON.parse : (text: string, reviver?: (key: any, value: any) => any) => any
|
||||
>JSON : JSON
|
||||
>parse : (text: string, reviver?: (key: any, value: any) => any) => any
|
||||
>data : string
|
||||
>data : string | (T & string)
|
||||
}
|
||||
else {
|
||||
return data;
|
||||
|
|
|
@ -58,9 +58,9 @@ export function css<S extends { [K in keyof S]: string }>(styles: S, ...classNam
|
|||
>"string" : "string"
|
||||
|
||||
return styles[arg];
|
||||
>styles[arg] : S[keyof S]
|
||||
>styles[arg] : S[keyof S & string]
|
||||
>styles : S
|
||||
>arg : keyof S
|
||||
>arg : keyof S & string
|
||||
}
|
||||
if (typeof arg == "object") {
|
||||
>typeof arg == "object" : boolean
|
||||
|
|
32
tests/baselines/reference/strictTypeofUnionNarrowing.js
Normal file
32
tests/baselines/reference/strictTypeofUnionNarrowing.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
//// [strictTypeofUnionNarrowing.ts]
|
||||
function stringify1(anything: { toString(): string } | undefined): string {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
function stringify2(anything: {} | undefined): string {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
function stringify3(anything: unknown | undefined): string { // should simplify to just `unknown` which should narrow fine
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
function stringify4(anything: { toString?(): string } | undefined): string {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
|
||||
//// [strictTypeofUnionNarrowing.js]
|
||||
"use strict";
|
||||
function stringify1(anything) {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
function stringify2(anything) {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
function stringify3(anything) {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
function stringify4(anything) {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
47
tests/baselines/reference/strictTypeofUnionNarrowing.symbols
Normal file
47
tests/baselines/reference/strictTypeofUnionNarrowing.symbols
Normal file
|
@ -0,0 +1,47 @@
|
|||
=== tests/cases/compiler/strictTypeofUnionNarrowing.ts ===
|
||||
function stringify1(anything: { toString(): string } | undefined): string {
|
||||
>stringify1 : Symbol(stringify1, Decl(strictTypeofUnionNarrowing.ts, 0, 0))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 0, 20))
|
||||
>toString : Symbol(toString, Decl(strictTypeofUnionNarrowing.ts, 0, 31))
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 0, 20))
|
||||
>anything.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 0, 20))
|
||||
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
|
||||
function stringify2(anything: {} | undefined): string {
|
||||
>stringify2 : Symbol(stringify2, Decl(strictTypeofUnionNarrowing.ts, 2, 1))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 4, 20))
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 4, 20))
|
||||
>anything.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 4, 20))
|
||||
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
|
||||
function stringify3(anything: unknown | undefined): string { // should simplify to just `unknown` which should narrow fine
|
||||
>stringify3 : Symbol(stringify3, Decl(strictTypeofUnionNarrowing.ts, 6, 1))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 8, 20))
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 8, 20))
|
||||
>anything.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 8, 20))
|
||||
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
|
||||
function stringify4(anything: { toString?(): string } | undefined): string {
|
||||
>stringify4 : Symbol(stringify4, Decl(strictTypeofUnionNarrowing.ts, 10, 1))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 12, 20))
|
||||
>toString : Symbol(toString, Decl(strictTypeofUnionNarrowing.ts, 12, 31))
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 12, 20))
|
||||
>anything.toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
>anything : Symbol(anything, Decl(strictTypeofUnionNarrowing.ts, 12, 20))
|
||||
>toUpperCase : Symbol(String.toUpperCase, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
|
71
tests/baselines/reference/strictTypeofUnionNarrowing.types
Normal file
71
tests/baselines/reference/strictTypeofUnionNarrowing.types
Normal file
|
@ -0,0 +1,71 @@
|
|||
=== tests/cases/compiler/strictTypeofUnionNarrowing.ts ===
|
||||
function stringify1(anything: { toString(): string } | undefined): string {
|
||||
>stringify1 : (anything: { toString(): string; } | undefined) => string
|
||||
>anything : { toString(): string; } | undefined
|
||||
>toString : () => string
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>typeof anything === "string" ? anything.toUpperCase() : "" : string
|
||||
>typeof anything === "string" : boolean
|
||||
>typeof anything : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>anything : { toString(): string; } | undefined
|
||||
>"string" : "string"
|
||||
>anything.toUpperCase() : string
|
||||
>anything.toUpperCase : () => string
|
||||
>anything : { toString(): string; } & string
|
||||
>toUpperCase : () => string
|
||||
>"" : ""
|
||||
}
|
||||
|
||||
function stringify2(anything: {} | undefined): string {
|
||||
>stringify2 : (anything: {} | undefined) => string
|
||||
>anything : {} | undefined
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>typeof anything === "string" ? anything.toUpperCase() : "" : string
|
||||
>typeof anything === "string" : boolean
|
||||
>typeof anything : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>anything : {} | undefined
|
||||
>"string" : "string"
|
||||
>anything.toUpperCase() : string
|
||||
>anything.toUpperCase : () => string
|
||||
>anything : string & {}
|
||||
>toUpperCase : () => string
|
||||
>"" : ""
|
||||
}
|
||||
|
||||
function stringify3(anything: unknown | undefined): string { // should simplify to just `unknown` which should narrow fine
|
||||
>stringify3 : (anything: unknown) => string
|
||||
>anything : unknown
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>typeof anything === "string" ? anything.toUpperCase() : "" : string
|
||||
>typeof anything === "string" : boolean
|
||||
>typeof anything : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>anything : unknown
|
||||
>"string" : "string"
|
||||
>anything.toUpperCase() : string
|
||||
>anything.toUpperCase : () => string
|
||||
>anything : string
|
||||
>toUpperCase : () => string
|
||||
>"" : ""
|
||||
}
|
||||
|
||||
function stringify4(anything: { toString?(): string } | undefined): string {
|
||||
>stringify4 : (anything: {} | undefined) => string
|
||||
>anything : {} | undefined
|
||||
>toString : (() => string) | undefined
|
||||
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
>typeof anything === "string" ? anything.toUpperCase() : "" : string
|
||||
>typeof anything === "string" : boolean
|
||||
>typeof anything : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>anything : {} | undefined
|
||||
>"string" : "string"
|
||||
>anything.toUpperCase() : string
|
||||
>anything.toUpperCase : () => string
|
||||
>anything : {} & string
|
||||
>toUpperCase : () => string
|
||||
>"" : ""
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@ if (typeof a === "number") {
|
|||
|
||||
let c: number = a;
|
||||
>c : number
|
||||
>a : number
|
||||
>a : number & {}
|
||||
}
|
||||
if (typeof a === "string") {
|
||||
>typeof a === "string" : boolean
|
||||
|
@ -24,7 +24,7 @@ if (typeof a === "string") {
|
|||
|
||||
let c: string = a;
|
||||
>c : string
|
||||
>a : string
|
||||
>a : string & {}
|
||||
}
|
||||
if (typeof a === "boolean") {
|
||||
>typeof a === "boolean" : boolean
|
||||
|
@ -34,7 +34,7 @@ if (typeof a === "boolean") {
|
|||
|
||||
let c: boolean = a;
|
||||
>c : boolean
|
||||
>a : boolean
|
||||
>a : (false & {}) | (true & {})
|
||||
}
|
||||
|
||||
if (typeof b === "number") {
|
||||
|
@ -45,7 +45,7 @@ if (typeof b === "number") {
|
|||
|
||||
let c: number = b;
|
||||
>c : number
|
||||
>b : number
|
||||
>b : { toString(): string; } & number
|
||||
}
|
||||
if (typeof b === "string") {
|
||||
>typeof b === "string" : boolean
|
||||
|
@ -55,7 +55,7 @@ if (typeof b === "string") {
|
|||
|
||||
let c: string = b;
|
||||
>c : string
|
||||
>b : string
|
||||
>b : { toString(): string; } & string
|
||||
}
|
||||
if (typeof b === "boolean") {
|
||||
>typeof b === "boolean" : boolean
|
||||
|
@ -65,6 +65,6 @@ if (typeof b === "boolean") {
|
|||
|
||||
let c: boolean = b;
|
||||
>c : boolean
|
||||
>b : boolean
|
||||
>b : ({ toString(): string; } & false) | ({ toString(): string; } & true)
|
||||
}
|
||||
|
||||
|
|
|
@ -134,7 +134,7 @@ function test5(a: boolean | void) {
|
|||
}
|
||||
else {
|
||||
a;
|
||||
>a : boolean | void
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,15 +151,15 @@ function test6(a: boolean | void) {
|
|||
if (typeof a === "boolean") {
|
||||
>typeof a === "boolean" : boolean
|
||||
>typeof a : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>a : boolean | void
|
||||
>a : undefined
|
||||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
>a : boolean
|
||||
>a : never
|
||||
}
|
||||
else {
|
||||
a;
|
||||
>a : void
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -184,7 +184,7 @@ function test7(a: boolean | void) {
|
|||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
>a : boolean | void
|
||||
>a : boolean
|
||||
}
|
||||
else {
|
||||
a;
|
||||
|
@ -212,7 +212,7 @@ function test8(a: boolean | void) {
|
|||
}
|
||||
else {
|
||||
a;
|
||||
>a : boolean | void
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +242,7 @@ function test9(a: boolean | number) {
|
|||
}
|
||||
else {
|
||||
a;
|
||||
>a : number | boolean
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -259,15 +259,15 @@ function test10(a: boolean | number) {
|
|||
if (typeof a === "boolean") {
|
||||
>typeof a === "boolean" : boolean
|
||||
>typeof a : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>a : number | boolean
|
||||
>a : undefined
|
||||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
>a : boolean
|
||||
>a : never
|
||||
}
|
||||
else {
|
||||
a;
|
||||
>a : number
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -292,7 +292,7 @@ function test11(a: boolean | number) {
|
|||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
>a : number | boolean
|
||||
>a : boolean
|
||||
}
|
||||
else {
|
||||
a;
|
||||
|
@ -320,7 +320,7 @@ function test12(a: boolean | number) {
|
|||
}
|
||||
else {
|
||||
a;
|
||||
>a : number | boolean
|
||||
>a : number
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -350,7 +350,7 @@ function test13(a: boolean | number | void) {
|
|||
}
|
||||
else {
|
||||
a;
|
||||
>a : number | boolean | void
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -367,15 +367,15 @@ function test14(a: boolean | number | void) {
|
|||
if (typeof a === "boolean") {
|
||||
>typeof a === "boolean" : boolean
|
||||
>typeof a : "string" | "number" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>a : number | boolean | void
|
||||
>a : undefined
|
||||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
>a : boolean
|
||||
>a : never
|
||||
}
|
||||
else {
|
||||
a;
|
||||
>a : number | void
|
||||
>a : undefined
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -400,7 +400,7 @@ function test15(a: boolean | number | void) {
|
|||
>"boolean" : "boolean"
|
||||
|
||||
a;
|
||||
>a : number | boolean | void
|
||||
>a : boolean
|
||||
}
|
||||
else {
|
||||
a;
|
||||
|
@ -428,7 +428,7 @@ function test16(a: boolean | number | void) {
|
|||
}
|
||||
else {
|
||||
a;
|
||||
>a : number | boolean | void
|
||||
>a : number
|
||||
}
|
||||
}
|
||||
|
||||
|
|
16
tests/cases/compiler/strictTypeofUnionNarrowing.ts
Normal file
16
tests/cases/compiler/strictTypeofUnionNarrowing.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
// @strict: true
|
||||
function stringify1(anything: { toString(): string } | undefined): string {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
function stringify2(anything: {} | undefined): string {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
function stringify3(anything: unknown | undefined): string { // should simplify to just `unknown` which should narrow fine
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
||||
|
||||
function stringify4(anything: { toString?(): string } | undefined): string {
|
||||
return typeof anything === "string" ? anything.toUpperCase() : "";
|
||||
}
|
Loading…
Reference in a new issue