Skip to content

Commit

Permalink
Properly check whether union type contains only primitive types (#46645)
Browse files Browse the repository at this point in the history
* Properly check whether union type contains only primitive types

* Add regression test

* Remove 'export' modifier from test
  • Loading branch information
ahejlsberg authored Nov 2, 2021
1 parent 7f8bf0b commit 56f8107
Show file tree
Hide file tree
Showing 6 changed files with 81 additions and 2 deletions.
1 change: 1 addition & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14153,6 +14153,7 @@ namespace ts {
// We ignore 'never' types in unions
if (!(flags & TypeFlags.Never)) {
includes |= flags & TypeFlags.IncludesMask;
if (flags & TypeFlags.Instantiable) includes |= TypeFlags.IncludesInstantiable;
if (type === wildcardType) includes |= TypeFlags.IncludesWildcard;
if (!strictNullChecks && flags & TypeFlags.Nullable) {
if (!(getObjectFlags(type) & ObjectFlags.ContainsWideningType)) includes |= TypeFlags.IncludesNonWideningType;
Expand Down
6 changes: 4 additions & 2 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5187,8 +5187,6 @@ namespace ts {
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
/* @internal */
NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | Object | Intersection | Instantiable,
// The following flags are aggregated during union and intersection type construction
/* @internal */
IncludesMask = Any | Unknown | Primitive | Never | Object | Union | Intersection | NonPrimitive | TemplateLiteral,
Expand All @@ -5201,6 +5199,10 @@ namespace ts {
IncludesWildcard = IndexedAccess,
/* @internal */
IncludesEmptyObject = Conditional,
/* @internal */
IncludesInstantiable = Substitution,
/* @internal */
NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | Object | Intersection | IncludesInstantiable,
}

export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;
Expand Down
24 changes: 24 additions & 0 deletions tests/baselines/reference/primitiveUnionDetection.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//// [primitiveUnionDetection.ts]
// Repro from #46624

type Kind = "one" | "two" | "three";

declare function getInterfaceFromString<T extends Kind>(options?: { type?: T } & { type?: Kind }): T;

const result = getInterfaceFromString({ type: 'two' });


//// [primitiveUnionDetection.js]
"use strict";
// Repro from #46624
var result = getInterfaceFromString({ type: 'two' });


//// [primitiveUnionDetection.d.ts]
declare type Kind = "one" | "two" | "three";
declare function getInterfaceFromString<T extends Kind>(options?: {
type?: T;
} & {
type?: Kind;
}): T;
declare const result: "two";
22 changes: 22 additions & 0 deletions tests/baselines/reference/primitiveUnionDetection.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
=== tests/cases/compiler/primitiveUnionDetection.ts ===
// Repro from #46624

type Kind = "one" | "two" | "three";
>Kind : Symbol(Kind, Decl(primitiveUnionDetection.ts, 0, 0))

declare function getInterfaceFromString<T extends Kind>(options?: { type?: T } & { type?: Kind }): T;
>getInterfaceFromString : Symbol(getInterfaceFromString, Decl(primitiveUnionDetection.ts, 2, 36))
>T : Symbol(T, Decl(primitiveUnionDetection.ts, 4, 40))
>Kind : Symbol(Kind, Decl(primitiveUnionDetection.ts, 0, 0))
>options : Symbol(options, Decl(primitiveUnionDetection.ts, 4, 56))
>type : Symbol(type, Decl(primitiveUnionDetection.ts, 4, 67))
>T : Symbol(T, Decl(primitiveUnionDetection.ts, 4, 40))
>type : Symbol(type, Decl(primitiveUnionDetection.ts, 4, 82))
>Kind : Symbol(Kind, Decl(primitiveUnionDetection.ts, 0, 0))
>T : Symbol(T, Decl(primitiveUnionDetection.ts, 4, 40))

const result = getInterfaceFromString({ type: 'two' });
>result : Symbol(result, Decl(primitiveUnionDetection.ts, 6, 5))
>getInterfaceFromString : Symbol(getInterfaceFromString, Decl(primitiveUnionDetection.ts, 2, 36))
>type : Symbol(type, Decl(primitiveUnionDetection.ts, 6, 39))

20 changes: 20 additions & 0 deletions tests/baselines/reference/primitiveUnionDetection.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
=== tests/cases/compiler/primitiveUnionDetection.ts ===
// Repro from #46624

type Kind = "one" | "two" | "three";
>Kind : Kind

declare function getInterfaceFromString<T extends Kind>(options?: { type?: T } & { type?: Kind }): T;
>getInterfaceFromString : <T extends Kind>(options?: ({ type?: T | undefined; } & { type?: Kind | undefined; }) | undefined) => T
>options : ({ type?: T | undefined; } & { type?: Kind | undefined; }) | undefined
>type : T | undefined
>type : Kind | undefined

const result = getInterfaceFromString({ type: 'two' });
>result : "two"
>getInterfaceFromString({ type: 'two' }) : "two"
>getInterfaceFromString : <T extends Kind>(options?: ({ type?: T | undefined; } & { type?: Kind | undefined; }) | undefined) => T
>{ type: 'two' } : { type: "two"; }
>type : "two"
>'two' : "two"

10 changes: 10 additions & 0 deletions tests/cases/compiler/primitiveUnionDetection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @strict: true
// @declaration: true

// Repro from #46624

type Kind = "one" | "two" | "three";

declare function getInterfaceFromString<T extends Kind>(options?: { type?: T } & { type?: Kind }): T;

const result = getInterfaceFromString({ type: 'two' });

0 comments on commit 56f8107

Please sign in to comment.