Skip to content

Commit d1b35c7

Browse files
authored
IsStringLiteral: Fix behaviour with tagged types (#1147)
1 parent a5e45d4 commit d1b35c7

File tree

2 files changed

+31
-7
lines changed

2 files changed

+31
-7
lines changed

source/is-literal.d.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import type {Primitive} from './primitive.d.ts';
22
import type {Numeric} from './numeric.d.ts';
3-
import type {IsNotFalse, IsPrimitive} from './internal/index.d.ts';
3+
import type {IfNotAnyOrNever, IsNotFalse, IsPrimitive} from './internal/index.d.ts';
44
import type {IsNever} from './is-never.d.ts';
5-
import type {If} from './if.js';
5+
import type {TagContainer, UnwrapTagged} from './tagged.js';
66

77
/**
88
Returns a boolean for whether the given type `T` is the specified `LiteralType`.
@@ -114,14 +114,18 @@ type L2 = Length<`${number}`>;
114114
@category Type Guard
115115
@category Utilities
116116
*/
117-
export type IsStringLiteral<T> = If<IsNever<T>, false,
117+
export type IsStringLiteral<S> = IfNotAnyOrNever<S,
118+
_IsStringLiteral<S extends TagContainer<any> ? UnwrapTagged<S> : S>,
119+
false, false>;
120+
121+
export type _IsStringLiteral<S> =
118122
// If `T` is an infinite string type (e.g., `on${string}`), `Record<T, never>` produces an index signature,
119123
// and since `{}` extends index signatures, the result becomes `false`.
120-
T extends string
121-
? {} extends Record<T, never>
124+
S extends string
125+
? {} extends Record<S, never>
122126
? false
123127
: true
124-
: false>;
128+
: false;
125129

126130
/**
127131
Returns a boolean for whether the given type is a `number` or `bigint` [literal type](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types).

test-d/is-literal.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import type {
66
IsBooleanLiteral,
77
IsSymbolLiteral,
88
Tagged,
9+
LiteralUnion,
910
} from '../index.d.ts';
1011

1112
const stringLiteral = '';
@@ -78,6 +79,14 @@ expectType<IsStringLiteral<Lowercase<'xyz'> | Capitalize<'abc'>>>(true);
7879
expectType<IsStringLiteral<Uppercase<string> | 'abc'>>({} as boolean);
7980
expectType<IsStringLiteral<Lowercase<string> | 'Abc'>>({} as boolean);
8081
expectType<IsStringLiteral<null | '1' | '2' | '3'>>({} as boolean);
82+
expectType<IsStringLiteral<1 | 2 | '3'>>({} as boolean);
83+
expectType<IsStringLiteral<'foo' | 'bar' | number>>({} as boolean);
84+
85+
// Types other than string return `false`
86+
expectType<IsStringLiteral<bigint>>(false);
87+
expectType<IsStringLiteral<1 | 2 | 3>>(false);
88+
expectType<IsStringLiteral<object>>(false);
89+
expectType<IsStringLiteral<false | undefined | null>>(false);
8190

8291
// Boundary types
8392
expectType<IsStringLiteral<any>>(false);
@@ -106,7 +115,18 @@ type A3 = IsBooleanLiteral;
106115
// @ts-expect-error
107116
type A4 = IsSymbolLiteral;
108117

109-
// Tagged types should be false
118+
// Tagged types
110119
expectType<IsStringLiteral<Tagged<string, 'Tag'>>>(false);
120+
expectType<IsStringLiteral<Tagged<Uppercase<string>, 'Tag'>>>(false);
121+
expectType<IsStringLiteral<Tagged<number, 'Tag'>>>(false);
122+
expectType<IsStringLiteral<Tagged<'foo' | 'bar', 'Tag'>>>(true);
123+
expectType<IsStringLiteral<Tagged<'foo' | 'bar' | `on${string}`, 'Tag'>>>({} as boolean);
124+
expectType<IsStringLiteral<Tagged<'1st' | '2nd' | '3rd' | number, 'Tag'>>>({} as boolean);
125+
126+
expectType<IsStringLiteral<Tagged<string, 'Tag'> | Tagged<number, 'Tag'>>>(false);
127+
expectType<IsStringLiteral<Tagged<'foo', 'Tag'> | Tagged<'bar', 'Tag'>>>(true);
128+
expectType<IsStringLiteral<Tagged<'foo' | 'bar', 'Tag'> | Tagged<number, 'Tag'>>>({} as boolean);
129+
expectType<IsStringLiteral<Tagged<'foo' | 'bar', 'Tag'> | number>>({} as boolean);
130+
111131
expectType<IsNumericLiteral<Tagged<number, 'Tag'>>>(false);
112132
expectType<IsBooleanLiteral<Tagged<boolean, 'Tag'>>>(false);

0 commit comments

Comments
 (0)