mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 16:38:05 -06:00
Properly handle tagged primitives in control flow analysis (#43538)
* Ignore object types in intersections with primitive types * Add regression test * Also handle instantiable types constrained to object types * Add another test * Add ignoreObjects optional parameter to getTypeFacts
This commit is contained in:
parent
a4c683be12
commit
d41943eb4e
@ -21965,7 +21965,7 @@ namespace ts {
|
||||
resolved.members.get("bind" as __String) && isTypeSubtypeOf(type, globalFunctionType));
|
||||
}
|
||||
|
||||
function getTypeFacts(type: Type): TypeFacts {
|
||||
function getTypeFacts(type: Type, ignoreObjects = false): TypeFacts {
|
||||
const flags = type.flags;
|
||||
if (flags & TypeFlags.String) {
|
||||
return strictNullChecks ? TypeFacts.StringStrictFacts : TypeFacts.StringFacts;
|
||||
@ -22002,7 +22002,7 @@ namespace ts {
|
||||
(type === falseType || type === regularFalseType) ? TypeFacts.FalseStrictFacts : TypeFacts.TrueStrictFacts :
|
||||
(type === falseType || type === regularFalseType) ? TypeFacts.FalseFacts : TypeFacts.TrueFacts;
|
||||
}
|
||||
if (flags & TypeFlags.Object) {
|
||||
if (flags & TypeFlags.Object && !ignoreObjects) {
|
||||
return getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(<ObjectType>type) ?
|
||||
strictNullChecks ? TypeFacts.EmptyObjectStrictFacts : TypeFacts.EmptyObjectFacts :
|
||||
isFunctionObjectType(<ObjectType>type) ?
|
||||
@ -22025,14 +22025,17 @@ namespace ts {
|
||||
return TypeFacts.None;
|
||||
}
|
||||
if (flags & TypeFlags.Instantiable) {
|
||||
return !isPatternLiteralType(type) ? getTypeFacts(getBaseConstraintOfType(type) || unknownType) :
|
||||
return !isPatternLiteralType(type) ? getTypeFacts(getBaseConstraintOfType(type) || unknownType, ignoreObjects) :
|
||||
strictNullChecks ? TypeFacts.NonEmptyStringStrictFacts : TypeFacts.NonEmptyStringFacts;
|
||||
}
|
||||
if (flags & TypeFlags.Union) {
|
||||
return reduceLeft((<UnionType>type).types, (facts, t) => facts | getTypeFacts(t), TypeFacts.None);
|
||||
return reduceLeft((<UnionType>type).types, (facts, t) => facts | getTypeFacts(t, ignoreObjects), TypeFacts.None);
|
||||
}
|
||||
if (flags & TypeFlags.Intersection) {
|
||||
return reduceLeft((<UnionType>type).types, (facts, t) => facts & getTypeFacts(t), TypeFacts.All);
|
||||
// When an intersection contains a primitive type we ignore object type constituents as they are
|
||||
// presumably type tags. For example, in string & { __kind__: "name" } we ignore the object type.
|
||||
ignoreObjects ||= maybeTypeOfKind(type, TypeFlags.Primitive);
|
||||
return reduceLeft((<UnionType>type).types, (facts, t) => facts & getTypeFacts(t, ignoreObjects), TypeFacts.All);
|
||||
}
|
||||
return TypeFacts.All;
|
||||
}
|
||||
|
||||
42
tests/baselines/reference/taggedPrimitiveNarrowing.js
Normal file
42
tests/baselines/reference/taggedPrimitiveNarrowing.js
Normal file
@ -0,0 +1,42 @@
|
||||
//// [taggedPrimitiveNarrowing.ts]
|
||||
type Hash = string & { __hash: true };
|
||||
|
||||
function getHashLength(hash: Hash): number {
|
||||
if (typeof hash !== "string") {
|
||||
throw new Error("This doesn't look like a hash");
|
||||
}
|
||||
return hash.length;
|
||||
}
|
||||
|
||||
function getHashLength2<T extends { __tag__: unknown}>(hash: string & T): number {
|
||||
if (typeof hash !== "string") {
|
||||
throw new Error("This doesn't look like a hash");
|
||||
}
|
||||
return hash.length;
|
||||
}
|
||||
|
||||
|
||||
//// [taggedPrimitiveNarrowing.js]
|
||||
"use strict";
|
||||
function getHashLength(hash) {
|
||||
if (typeof hash !== "string") {
|
||||
throw new Error("This doesn't look like a hash");
|
||||
}
|
||||
return hash.length;
|
||||
}
|
||||
function getHashLength2(hash) {
|
||||
if (typeof hash !== "string") {
|
||||
throw new Error("This doesn't look like a hash");
|
||||
}
|
||||
return hash.length;
|
||||
}
|
||||
|
||||
|
||||
//// [taggedPrimitiveNarrowing.d.ts]
|
||||
declare type Hash = string & {
|
||||
__hash: true;
|
||||
};
|
||||
declare function getHashLength(hash: Hash): number;
|
||||
declare function getHashLength2<T extends {
|
||||
__tag__: unknown;
|
||||
}>(hash: string & T): number;
|
||||
41
tests/baselines/reference/taggedPrimitiveNarrowing.symbols
Normal file
41
tests/baselines/reference/taggedPrimitiveNarrowing.symbols
Normal file
@ -0,0 +1,41 @@
|
||||
=== tests/cases/compiler/taggedPrimitiveNarrowing.ts ===
|
||||
type Hash = string & { __hash: true };
|
||||
>Hash : Symbol(Hash, Decl(taggedPrimitiveNarrowing.ts, 0, 0))
|
||||
>__hash : Symbol(__hash, Decl(taggedPrimitiveNarrowing.ts, 0, 22))
|
||||
|
||||
function getHashLength(hash: Hash): number {
|
||||
>getHashLength : Symbol(getHashLength, Decl(taggedPrimitiveNarrowing.ts, 0, 38))
|
||||
>hash : Symbol(hash, Decl(taggedPrimitiveNarrowing.ts, 2, 23))
|
||||
>Hash : Symbol(Hash, Decl(taggedPrimitiveNarrowing.ts, 0, 0))
|
||||
|
||||
if (typeof hash !== "string") {
|
||||
>hash : Symbol(hash, Decl(taggedPrimitiveNarrowing.ts, 2, 23))
|
||||
|
||||
throw new Error("This doesn't look like a hash");
|
||||
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
return hash.length;
|
||||
>hash.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
|
||||
>hash : Symbol(hash, Decl(taggedPrimitiveNarrowing.ts, 2, 23))
|
||||
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
|
||||
function getHashLength2<T extends { __tag__: unknown}>(hash: string & T): number {
|
||||
>getHashLength2 : Symbol(getHashLength2, Decl(taggedPrimitiveNarrowing.ts, 7, 1))
|
||||
>T : Symbol(T, Decl(taggedPrimitiveNarrowing.ts, 9, 24))
|
||||
>__tag__ : Symbol(__tag__, Decl(taggedPrimitiveNarrowing.ts, 9, 35))
|
||||
>hash : Symbol(hash, Decl(taggedPrimitiveNarrowing.ts, 9, 55))
|
||||
>T : Symbol(T, Decl(taggedPrimitiveNarrowing.ts, 9, 24))
|
||||
|
||||
if (typeof hash !== "string") {
|
||||
>hash : Symbol(hash, Decl(taggedPrimitiveNarrowing.ts, 9, 55))
|
||||
|
||||
throw new Error("This doesn't look like a hash");
|
||||
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
return hash.length;
|
||||
>hash.length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
|
||||
>hash : Symbol(hash, Decl(taggedPrimitiveNarrowing.ts, 9, 55))
|
||||
>length : Symbol(String.length, Decl(lib.es5.d.ts, --, --))
|
||||
}
|
||||
|
||||
49
tests/baselines/reference/taggedPrimitiveNarrowing.types
Normal file
49
tests/baselines/reference/taggedPrimitiveNarrowing.types
Normal file
@ -0,0 +1,49 @@
|
||||
=== tests/cases/compiler/taggedPrimitiveNarrowing.ts ===
|
||||
type Hash = string & { __hash: true };
|
||||
>Hash : Hash
|
||||
>__hash : true
|
||||
>true : true
|
||||
|
||||
function getHashLength(hash: Hash): number {
|
||||
>getHashLength : (hash: Hash) => number
|
||||
>hash : Hash
|
||||
|
||||
if (typeof hash !== "string") {
|
||||
>typeof hash !== "string" : boolean
|
||||
>typeof hash : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>hash : Hash
|
||||
>"string" : "string"
|
||||
|
||||
throw new Error("This doesn't look like a hash");
|
||||
>new Error("This doesn't look like a hash") : Error
|
||||
>Error : ErrorConstructor
|
||||
>"This doesn't look like a hash" : "This doesn't look like a hash"
|
||||
}
|
||||
return hash.length;
|
||||
>hash.length : number
|
||||
>hash : Hash
|
||||
>length : number
|
||||
}
|
||||
|
||||
function getHashLength2<T extends { __tag__: unknown}>(hash: string & T): number {
|
||||
>getHashLength2 : <T extends { __tag__: unknown; }>(hash: string & T) => number
|
||||
>__tag__ : unknown
|
||||
>hash : string & T
|
||||
|
||||
if (typeof hash !== "string") {
|
||||
>typeof hash !== "string" : boolean
|
||||
>typeof hash : "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function"
|
||||
>hash : string & T
|
||||
>"string" : "string"
|
||||
|
||||
throw new Error("This doesn't look like a hash");
|
||||
>new Error("This doesn't look like a hash") : Error
|
||||
>Error : ErrorConstructor
|
||||
>"This doesn't look like a hash" : "This doesn't look like a hash"
|
||||
}
|
||||
return hash.length;
|
||||
>hash.length : number
|
||||
>hash : string & T
|
||||
>length : number
|
||||
}
|
||||
|
||||
18
tests/cases/compiler/taggedPrimitiveNarrowing.ts
Normal file
18
tests/cases/compiler/taggedPrimitiveNarrowing.ts
Normal file
@ -0,0 +1,18 @@
|
||||
// @strict: true
|
||||
// @declaration: true
|
||||
|
||||
type Hash = string & { __hash: true };
|
||||
|
||||
function getHashLength(hash: Hash): number {
|
||||
if (typeof hash !== "string") {
|
||||
throw new Error("This doesn't look like a hash");
|
||||
}
|
||||
return hash.length;
|
||||
}
|
||||
|
||||
function getHashLength2<T extends { __tag__: unknown}>(hash: string & T): number {
|
||||
if (typeof hash !== "string") {
|
||||
throw new Error("This doesn't look like a hash");
|
||||
}
|
||||
return hash.length;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user