type-fest icon indicating copy to clipboard operation
type-fest copied to clipboard

Add `Not<string, 'not me'>`

Open fregante opened this issue 2 years ago • 4 comments

I was looking for a way to implement “any string that doesn’t start with a slash” with template literals, then I realized that it can be achieved with a generic Not type:

type Unslashed = Not<string, `/${string}`>
const valid: Unslashed = 'home'
const invalid: Unslashed = '/home'

Is there anything like Not?

Edit: related:

  • https://github.com/microsoft/TypeScript/issues/49867

Upvote & Fund

  • We're using Polar.sh so you can upvote and help fund this issue.
  • The funding will be given to active contributors.
  • Thank you in advance for helping prioritize & fund our backlog.
Fund with Polar

fregante avatar Jul 06 '22 13:07 fregante

I think it could be useful.

If only TS had negated types: https://github.com/microsoft/TypeScript/pull/29317

sindresorhus avatar Jul 07 '22 21:07 sindresorhus

Seen in https://stackoverflow.com/questions/66354585/typescript-type-for-any-string-other-than-a-specific-string-literal

I can't make it work as a reusable type:

type Not<Yes, Not> = Yes extends Not ? never : Yes;
type NotHello = Not<string, 'hello'>;
const notHello: NotHello = 'hello';
// No error because `NotHello === string`

It's possible that TypeScript "forgets" the negation too early

https://www.typescriptlang.org/play?#code/C4TwDgpgBAcg9sAPATQgZwDSwQPigXilTSggA9gIA7AExPmCgH4oqIA3CAJygC4j0AbgBQoSNmAAJCABsZcAhMRpgXAJZUA5lgDkAC1nydOEQGM4VFawTS5cfg1vzF+w3B2CgA


The generic-based works but it's buggy with variables:

type Not<Yes, Not> = Yes extends Not ? never : Yes;
type NotHello<S extends string> = Not<S, 'hello'>;

export const join = <S extends string>(
	...parts: Array<NotHello<S> | number>
): string => parts.join(',');
join('hello'); // Error 🎉 
join('hello', 'world'); // Error 🎉 

let nonConstString = 'sup'
join('hello', nonConstString); // No error 😰 

https://www.typescriptlang.org/play?ts=4.6.4#code/C4TwDgpgBAcg9sAPATQgZwDSwQPigXilTSggA9gIA7AExPmCgH4oqIA3CAJygC4j0AbgCwAKFCRswABIQANnLiIAyqQrU6UNMC4BLKgHM8hBiqwByABbzF5nCNFjyYOF0YBjOFW1QAVnH0CKBU1SloSbT1DHAAKMQBIADpksABDNzR+AEEuLlSQRAZZBSVlPAAfVgBXAFsAI24cMQBKfkj9AwI8NIzE-30Y8wxzZod+qkHrEpHBKAB6OagAUVzXKEAeDcBI-agxccmbOCGocwB3VzkaGfnFla41rZ3HUTkIRiovAGEvbWUdDqDzGgqmBzLsAhMrAcju8qF9vMBflEDKNrthSKseIBeDcADHtQIA

fregante avatar Jul 08 '22 09:07 fregante

The generic-based works but it's buggy with variables

They needs to be as const or types will be widened.

Checkout the following code on TS Playground:

type Not<Yes, Not> = Yes extends Not ? never : Yes;
type NotHello<S extends string> = Not<S, 'hello'>;

export const join = <S extends string>(
	...parts: Array<NotHello<S> | number>
): string => parts.join(',');
join('hello'); // Error 🎉 
join('hello', 'world'); // Error 🎉 

let nonConstString = 'sup' as const
join('hello', nonConstString); // Error 🎉 

kidonng avatar Jul 08 '22 09:07 kidonng

Could microsoft/TypeScript#51865 help solve this issue?

tommy-mitchell avatar Feb 28 '23 21:02 tommy-mitchell